Merge branch 'develop' into notification-read-api

This commit is contained in:
tamaina 2021-08-20 00:46:55 +09:00 committed by GitHub
commit 9bc4609fd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1669 changed files with 41713 additions and 5838 deletions

View file

@ -1,13 +0,0 @@
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}

7
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,7 @@
contact_links:
- name: 👪 Misskey Forum
url: https://forum.misskey.io/
about: Ask questions and share knowledge
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey

View file

@ -1,13 +1,40 @@
## Summary <!-- お読みください
PRありがとうございます PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
- fix / refactor / feat / enhance / perf / chore
- また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
- `npm run test`、`npm run lint`でぞれぞれ実施可能です
- UIに変更がある場合はスクリーンショットを本文内に添付してください。
ご協力ありがとうございます🤗
-->
<!-- README
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
- fix / refactor / feat / enhance / perf / chore
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
- You can run it with `npm run test` and `npm run lint`.
- If this PR includes UI changes, please attach a screenshot in the text.
Thanks for your cooperation 🤗
-->
<!-- # What
- <!-- このPRで何をしたのか どう変わるのか? -->
- * Please describe your changes here * <!-- What did you do with this PR? How will it change things? -->
-
- If you are going to resolve some issue, please add this context. # Why
- Resolve #ISSUE_NUMBER <!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
- <!-- Why do you do it? What are your intentions? What is the problem? -->
- If you are going to fix some bug issue, please add this context.
- Fix #ISSUE_NUMBER # Additional info (optional)
- <!-- テスト観点など -->
--> <!-- Test perspective, etc -->

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View file

@ -35,6 +35,8 @@ jobs:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Check yarn.lock
run: git diff --exit-code yarn.lock
- name: Copy Configure - name: Copy Configure
run: cp .circleci/misskey/*.yml .config run: cp .circleci/misskey/*.yml .config
- name: Build - name: Build

View file

@ -1 +1 @@
v16.2.0 v16.6.2

View file

@ -1 +1,65 @@
see [releases](https://github.com/misskey-dev/misskey/releases) <!--
## 12.x.x (unreleased)
### Improvements
### Bugfixes
-->
## 12.x.x (unreleased)
### Improvements
- 依存関係の更新
### Bugfixes
- チャンネルを作成しているとアカウントを削除できないのを修正
## 12.88.0 (2021/08/17)
### Features
- ノートの翻訳機能を追加
- 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。
- Misskey更新時にダイアログを表示するように
- ジョブキューウィジェットに警報音を鳴らす設定を追加
### Improvements
- ブロックの挙動を改修
- ブロックされたユーザーがブロックしたユーザーに対してアクション出来ないようになりました。詳細はドキュメントをご確認ください。
- UIデザインの調整
- データベースのインデックスを最適化
- Proxy使用時にKeep-Aliveをサポート
- DNSキャッシュでネガティブキャッシュをサポート
- 依存関係の更新
### Bugfixes
- タッチ操作でウィンドウを閉じることができない問題を修正
- Renoteされた時刻が投稿された時刻のように表示される問題を修正
- コントロールパネルでファイルを削除した際の表示を修正
- ActivityPub: 長いユーザーの名前や自己紹介の対応
## 12.87.0 (2021/08/12)
### Improvements
- 絵文字オートコンプリートで一文字目は最近使った絵文字をサジェストするように
- 絵文字オートコンプリートのパフォーマンスを改善
- about-misskeyページにドキュメントへのリンクを追加
- Docker: Node.jsを16.6.2に
- 依存関係の更新
- 翻訳の更新
### Bugfixes
- Misskey更新時、テーマキャッシュの影響でスタイルがおかしくなる問題を修正
## 12.86.0 (2021/08/11)
### Improvements
- ドキュメントの更新
- ドキュメントにchangelogを追加
- ぼかし効果のオプションを追加
- Vueを3.2.1に更新
- UIの調整
### Bugfixes
- ハッシュタグ入力が空のときに#が付くのを修正
- フォローリクエストのEメール通知を修正

View file

@ -35,6 +35,11 @@ If your language is not listed in Crowdin, please open an issue.
## Test ## Test
* Test codes are located in [`/test`](/test). * Test codes are located in [`/test`](/test).
### Run specify test
```
npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT="./test/tsconfig.json" npx mocha test/foo.ts --require ts-node/register
```
## Continuous integration ## Continuous integration
Misskey uses GitHub Actions for executing automated tests. Misskey uses GitHub Actions for executing automated tests.
Configuration files are located in [`/.github/workflows`](/.github/workflows). Configuration files are located in [`/.github/workflows`](/.github/workflows).

View file

@ -1,4 +1,4 @@
FROM node:16.2.0-alpine3.13 AS base FROM node:16.6.2-alpine3.13 AS base
ENV NODE_ENV=production ENV NODE_ENV=production
@ -18,9 +18,7 @@ RUN apk add --no-cache \
nasm \ nasm \
pkgconfig \ pkgconfig \
python3 \ python3 \
zlib-dev \ zlib-dev
vips-dev \
vips
COPY package.json yarn.lock .yarnrc ./ COPY package.json yarn.lock .yarnrc ./
RUN yarn install RUN yarn install
@ -31,8 +29,7 @@ FROM base AS runner
RUN apk add --no-cache \ RUN apk add --no-cache \
ffmpeg \ ffmpeg \
tini \ tini
vips
ENTRYPOINT ["/sbin/tini", "--"] ENTRYPOINT ["/sbin/tini", "--"]

Binary file not shown.

View file

@ -2,6 +2,6 @@ files:
- source: /locales/ja-JP.yml - source: /locales/ja-JP.yml
translation: /locales/%locale%.yml translation: /locales/%locale%.yml
update_option: update_as_unapproved update_option: update_as_unapproved
- source: /src/docs/ja-JP/*.md - source: /src/docs/ja-JP/**/*.md
translation: /src/docs/%locale%/%original_file_name% translation: /src/docs/%locale%/**/%original_file_name%
update_option: update_as_unapproved update_option: update_as_unapproved

3
cypress.json Normal file
View file

@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost"
}

View file

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View file

@ -0,0 +1,69 @@
describe('Basic', () => {
before(() => {
cy.request('POST', '/api/reset-db');
});
beforeEach(() => {
cy.reload(true);
});
it('successfully loads', () => {
cy.visit('/');
});
it('setup instance', () => {
cy.visit('/');
cy.get('[data-cy-admin-username] input').type('admin');
cy.get('[data-cy-admin-password] input').type('admin1234');
cy.get('[data-cy-admin-ok]').click();
});
it('signup', () => {
cy.visit('/');
cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-username] input').type('alice');
cy.get('[data-cy-signup-password] input').type('alice1234');
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').click();
});
it('signin', () => {
cy.visit('/');
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice');
// Enterキーでサインインできるかの確認も兼ねる
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
});
it('note', () => {
cy.visit('/');
//#region TODO: この辺はUI操作ではなくAPI操作でログインする
cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice');
// Enterキーでサインインできるかの確認も兼ねる
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
//#endregion
cy.get('[data-cy-open-post-form]').click();
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
cy.get('[data-cy-open-post-form-submit]').click();
// TODO: 投稿した文字列が画面内にあるか(=タイムラインに流れてきたか)のテスト
});
});

22
cypress/plugins/index.js Normal file
View file

@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View file

@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

20
cypress/support/index.js Normal file
View file

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View file

@ -1,3 +1,13 @@
/*
import * as fs from 'fs';
if (fs.existsSync('./built')) {
import('./built/index.js').then(built => built());
} else {
console.log('Built code is not found. Probably an error occurred during a build or you just forgot to build.');
}
*/
const fs = require('fs'); const fs = require('fs');
if (fs.existsSync('./built')) { if (fs.existsSync('./built')) {

View file

@ -427,9 +427,13 @@ inUse: "مستخدم"
info: "عن" info: "عن"
user: "المستخدمون" user: "المستخدمون"
administration: "إدارة " administration: "إدارة "
postToGallery: "انشر في المعرض"
gallery: "المعرض"
expiration: "ينتهي استطلاع الرأي في" expiration: "ينتهي استطلاع الرأي في"
middle: "متوسط" middle: "متوسط"
global: "الشامل" global: "الشامل"
_docs:
admin: "إدارة "
_email: _email:
_follow: _follow:
title: "يتابعك" title: "يتابعك"

View file

@ -111,6 +111,7 @@ editWidgets: "Upravit widget"
editWidgetsExit: "Hotovo" editWidgetsExit: "Hotovo"
customEmojis: "Vlastní emoji" customEmojis: "Vlastní emoji"
emoji: "Emoji" emoji: "Emoji"
emojis: "Emoji"
emojiName: "Jméno emoji" emojiName: "Jméno emoji"
emojiUrl: "URL obrázku" emojiUrl: "URL obrázku"
addEmoji: "Přidat emoji" addEmoji: "Přidat emoji"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,19 @@
--- ---
_lang_: "Esperanto" _lang_: "Esperanto"
headlineMisskey: "Reto ligiĝas per notoj" headlineMisskey: "Reto ligata per notoj"
introMisskey: "Bonvenon! Miskejo estas malferma kodaの分散型マイクロブログサービスです。\nBonvolu Krei「noto」、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「 reaktigoj 」機能で、皆のnotojに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentraliza mikrobloga servo.\nKreu \"noto\"n por diskonigi tion ke nun okazas, aŭ por dissendu pri vi📡\nPer la funkcio \"reago\" vi ankaŭ povas rapide esprimi vian senton pri ĉies noto👍\nVolu esplori nova mondo🚀"
monthAndDay: "{day}-a/{month}" monthAndDay: "{day}-a/{month}"
search: "Serĉi" search: "Serĉi"
notifications: "Sciigoj" notifications: "Sciigoj"
username: "Uzantonomo" username: "Uzantnomo"
password: "Pasvorto" password: "Pasvorto"
forgotPassword: "Ĉu vi forgesis pasvorton?" forgotPassword: "Ĉu vi forgesis pasvorton?"
fetchingAsApObject: "Informpetado de fediverso..." fetchingAsApObject: "Informpetado de Fediverso..."
ok: "Okej" ok: "Akcepteble"
gotIt: "Mi konprenas!" gotIt: "Mi komprenas"
cancel: "Nuligi" cancel: "Nuligi"
enterUsername: "Entajpu uzantonomon" enterUsername: "Entajpu uzantnomon"
renotedBy: "Renotigojn faras {user}" renotedBy: "Renoto farita de {user}"
noNotes: "Neniu noto!" noNotes: "Neniu noto!"
noNotifications: "Vi ne havas sciigojn." noNotifications: "Vi ne havas sciigojn."
instance: "Ekzemplo" instance: "Ekzemplo"
@ -22,7 +22,8 @@ basicSettings: "Ĝeneralaj agordoj"
otherSettings: "Aliaj agordoj" otherSettings: "Aliaj agordoj"
openInWindow: "Malfermi en nova fenestro" openInWindow: "Malfermi en nova fenestro"
profile: "Profilo" profile: "Profilo"
timeline: "Tempolinio" timeline: "Templinio"
noAccountDescription: "Tiu uzanto ankoraŭ ne skribis biografieton"
login: "Ensaluti" login: "Ensaluti"
loggingIn: "Ensalutado..." loggingIn: "Ensalutado..."
logout: "Elsaluti" logout: "Elsaluti"
@ -42,31 +43,33 @@ unpin: "Depingli"
copyContent: "Kopii enhavon" copyContent: "Kopii enhavon"
copyLink: "Kopii ligilon" copyLink: "Kopii ligilon"
delete: "Forviŝi" delete: "Forviŝi"
deleteAndEdit: "Forviŝi kaj redakti" deleteAndEdit: "Forigi kaj redakti"
deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forviŝi la noton? La reaktigoj, renotigoj, kaj respondoj ankaŭ forigiĝos." deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forigi kaj redakti la noton? Ankaŭ ĉiuj reagoj, renotoj, kaj respondoj al ĝi foriĝos."
addToList: "Aldoni al listo" addToList: "Aldoni al la listo"
sendMessage: "Sendi mesaĝon" sendMessage: "Sendi mesaĝon"
copyUsername: "Kopii uzantonomon" copyUsername: "Kopii uzantnomon"
searchUser: "Serĉi uzanton" searchUser: "Serĉi uzanton"
reply: "Respondi" reply: "Respondi"
loadMore: "Vidu plu" loadMore: "Vidu pli"
showMore: "Vidi plu" showMore: "Vidi pli"
youGotNewFollower: "Vi estas eksekvita." youGotNewFollower: "sksekvis vin"
receiveFollowRequest: "Peto de sekvado estas ricevita"
followRequestAccepted: "La peto de sekvado akceptita"
mention: "Mencioj" mention: "Mencioj"
mentions: "Mencioj" mentions: "Al vi"
directNotes: "Senperaj notoj" directNotes: "Rektaj notoj"
importAndExport: "Importaĵo / Eksportaĵo" importAndExport: "Importi/eksporti"
import: "Importi" import: "Importi"
export: "Eksporti" export: "Eksporti"
files: "Dosieroj" files: "Dosieroj"
download: "Elŝuti" download: "Elŝuti"
driveFileDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la dosieron \"{name}\"? La notoj kun la aldonaĵo ankaŭ forviŝiĝos." driveFileDeleteConfirm: "Ĉu vi certas, ke vi volas forviŝi la dosieron \"{name}\"? Ankaŭ notoj kiu enhavas ĝin forviŝiĝos."
unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}?" unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}'(o)n?"
lists: "Listoj" lists: "Listoj"
noLists: "Neniu listo" noLists: "Neniu listo"
note: "Elsendi noto" note: "Elsendi noto"
notes: "Notoj" notes: "Notoj"
following: "Sekvi" following: "Sekvatoj"
followers: "Sekvantoj" followers: "Sekvantoj"
followsYou: "Sekvas vin" followsYou: "Sekvas vin"
createList: "Kreii liston" createList: "Kreii liston"
@ -76,13 +79,15 @@ retry: "Reprovi"
enterListName: "Entajpu nomon de la listo" enterListName: "Entajpu nomon de la listo"
privacy: "Privateco" privacy: "Privateco"
follow: "Sekvi" follow: "Sekvi"
followRequest: "Peti eksekvi" followRequest: "Peti de sekvado"
followRequests: "Eksekvopetoj" followRequests: "Petoj de sekvado"
unfollow: "Ne plu sekvi" unfollow: "Malsekvi"
renote: "Renotici" enterEmoji: "Entajpu emoĵion"
unrenote: "Forigi renotici" renote: "Fari renoton"
cantRenote: "Tiu noto estas renototebla." unrenote: "Malfari renoton"
cantReRenote: "Renotigo ne estas renotigebla." renoted: "Renoton fariĝis."
cantRenote: "Tiu noto ne estas renototebla."
cantReRenote: "Oni ne povas fari renoton kiu enhavas renoto."
quote: "Citi" quote: "Citi"
pinnedNote: "Pinglita noto" pinnedNote: "Pinglita noto"
pinned: "Alpingli sur la profilo" pinned: "Alpingli sur la profilo"
@ -91,7 +96,7 @@ clickToShow: "Klaku por malkaŝu"
sensitive: "Enhavo ne estas deca por laborejo (NSFW)" sensitive: "Enhavo ne estas deca por laborejo (NSFW)"
add: "Aldoni" add: "Aldoni"
reaction: "Reagoj" reaction: "Reagoj"
enterFileName: "Entajpu dosiernomon" enterFileName: "Entajpu nomon de dosiero"
mute: "Silentigi" mute: "Silentigi"
unmute: "Malsilentigi" unmute: "Malsilentigi"
block: "Bloki" block: "Bloki"
@ -103,201 +108,548 @@ unblockConfirm: "Ĉu vi certas ke vi volas malbloki la uzanton?"
suspendConfirm: "Ĉu vi certas ke vi volas frostigi la uzanton?" suspendConfirm: "Ĉu vi certas ke vi volas frostigi la uzanton?"
unsuspendConfirm: "Ĉu vi certas ke vi volas fandi la uzanton?" unsuspendConfirm: "Ĉu vi certas ke vi volas fandi la uzanton?"
selectList: "Elekti liston" selectList: "Elekti liston"
emojiUrl: "Retadreso de la emoĵio" selectAntenna: "Elekti antenon"
selectWidget: "Elekti enestraĵon"
editWidgets: "Redakti fenestraĵon"
editWidgetsExit: "Fini la redaktadon"
customEmojis: "Personecigitaj emoĵioj"
emoji: "Emoĵio"
emojis: "Emoĵio"
emojiName: "Nomo de emoĵio"
emojiUrl: "URL de la emoĵio"
addEmoji: "Aldoni emoĵion"
settingGuide: "Rekomendaj agordoj"
cacheRemoteFiles: "Havi staplon por transaj dosieroj"
flagAsBot: "Tiu uzanto estas roboto" flagAsBot: "Tiu uzanto estas roboto"
flagAsCat: "Tiu uzanto estas kato" flagAsCat: "Tiu uzanto estas kato"
addAccount: "Aldoni konton" addAccount: "Aldoni konton"
showOnRemote: "Vidi sur la transa ekzemplo" showOnRemote: "Vidi sur la fora ekzemplo"
general: "Ĝenerala" general: "Ĝenerala"
wallpaper: "Ekranfonoj"
setWallpaper: "Apliki ekranfonon"
removeWallpaper: "Forviŝi ekranfonon. "
searchWith: "Serĉi: {q}" searchWith: "Serĉi: {q}"
youHaveNoLists: "Vi ne havas listojn." youHaveNoLists: "Vi ne havas listojn."
followConfirm: "Ĉu vi certas, ke vi volas sekvi {name}'n?" followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?"
selectUser: "Elekti uzanton" selectUser: "Elekti uzanton"
annotation: "Komentarioj" annotation: "Komentarioj"
federation: "Fediverso" federation: "Kunfederaĵo"
instances: "Ekzemplo" instances: "Ekzemplo"
perHour: "Po horo"
perDay: "Po tago"
blockThisInstance: "Bloki tiu ekzemplo" blockThisInstance: "Bloki tiu ekzemplo"
version: "Versio"
withNFiles: "{n} dosiero(j)"
disk: "Diskilo" disk: "Diskilo"
blockedInstances: "Blokitaj ekzemploj" instanceInfo: "Informo pri la ekzemplo"
muteAndBlock: "Silentitaj / Blokitaj" clearCachedFiles: "Malplenigi la staplon"
mutedUsers: "Silentigitaj uzantoj" clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?"
blockedUsers: "Blokitaj uzantoj" blockedInstances: "Blokataj ekzemploj"
muteAndBlock: "Silentigatoj kaj blokatoj"
mutedUsers: "Silentigataj uzantoj"
blockedUsers: "Blokataj uzantoj"
noUsers: "Sen uzantoj" noUsers: "Sen uzantoj"
editProfile: "Redakti profilon" editProfile: "Redakti profilon"
noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?" noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?"
pinLimitExceeded: "Vi ne plu povas alpingli noton." pinLimitExceeded: "Vi ne plu povas alpingli noton."
processing: "Traktado..."
noCustomEmojis: "Neniu emoĵio" noCustomEmojis: "Neniu emoĵio"
federating: "Konfederado" federating: "Kunfederado"
blocked: "Blokita" blocked: "Blokata"
all: "Ĉiuj"
subscribing: "Abonita" subscribing: "Abonita"
publishing: "Dissendado"
notResponding: "Alvokato ne disponeblas" notResponding: "Alvokato ne disponeblas"
instanceFollowing: "Sekvi ekzemplon" instanceFollowing: "Sekvatoj el la ekzemplo"
instanceFollowers: "Sekvantoj de la ekzemplo" instanceFollowers: "Sekvantoj el la ekzemplo"
instanceUsers: "Uzantoj de la ekzemplo" instanceUsers: "Uzantoj de la ekzemplo"
changePassword: "Ŝanĝi pasvorton" changePassword: "Ŝanĝi pasvorton"
security: "Sekureco"
currentPassword: "Aktuala pasvorto" currentPassword: "Aktuala pasvorto"
newPassword: "Nova pasvorto" newPassword: "Nova pasvorto"
newPasswordRetype: "Reentajpu la novan pasvorton" newPasswordRetype: "Reentajpu la novan pasvorton"
attachFile: "Aldoni dosieron" attachFile: "Aldoni dosieron"
more: "Plu!" more: "Plu!"
usernameOrUserId: "Uzantonomo aŭ ID de uzanto" featured: "Maksimumi"
usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto"
noSuchUser: "Neniuj uzantoj trovitaj." noSuchUser: "Neniuj uzantoj trovitaj."
remove: "Forviŝi" lookup: "Informpeti"
announcements: "Novaĵoj"
imageUrl: "URL de bildo"
remove: "Forigi"
removed: "Forviŝis" removed: "Forviŝis"
removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?" removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"'(o)n?"
deleteAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?" deleteAreYouSure: "Ĉu vi certas ke vi volas forviŝi \"{x}\"'(o)n?"
messaging: "Babilejoj" saved: "Konservita"
messaging: "Retbabili"
upload: "Alŝuti" upload: "Alŝuti"
fromDrive: "De la diskilo" fromDrive: "De la disko"
fromUrl: "De retadreso" fromUrl: "De URL"
uploadFromUrl: "Aldoni de retadreso" uploadFromUrl: "Alŝuti de URL"
uploadFromUrlDescription: "Retadreso de la dosiero kiun vi volu alŝuti" uploadFromUrlDescription: "URL de la dosiero kiun vi volas alŝuti"
games: "Ludoj sur Miskejo" explore: "Esplori"
games: "Ludoj sur Misskey"
messageRead: "Legita" messageRead: "Legita"
startMessaging: "Komenci babiladon" startMessaging: "Komenci babiladon"
tos: "Kondiĉoj de Uzado" nUsersRead: "Legita de {n} homoj"
tos: "Kondiĉoj de uzado"
start: "Komenciĝi" start: "Komenciĝi"
home: "Ĉefpaĝo" home: "Hejmo"
drive: "Diskilo" remoteUserCaution: "Ĉi tiu Infomoj estas ne tute ekzakta pro distanca uzanto."
activity: "Aktiveco"
images: "Bildoj"
birthday: "Naskiĝdato"
registeredDate: "Registriĝdato"
location: "Loko"
theme: "Koloraro"
light: "Luma"
dark: "Malluma"
drive: "Disko"
fileName: "Dosiernomo" fileName: "Dosiernomo"
selectFile: "Elekti dosieron" selectFile: "Elekti dosieron"
selectFiles: "Elekti dosieron" selectFiles: "Elekti dosieron"
renameFile: "Renomigi dosieron" selectFolder: "Elekti dosierujon"
selectFolders: "Elekti dosierujon"
renameFile: "Alinomi la dosieron"
folderName: "Nomo de la dosierujo"
createFolder: "Krei dosierujon"
renameFolder: "Alinomi la dosierujon"
deleteFolder: "Forviŝi dosierujon" deleteFolder: "Forviŝi dosierujon"
addFile: "Aldoni dosieron" addFile: "Aldoni dosieron"
emptyDrive: "La diskilo enhavas neniun." emptyDrive: "La disko malplenas"
emptyFolder: "La dosierujo malplenas"
unableToDelete: "Ne forigebla" unableToDelete: "Ne forigebla"
inputNewFileName: "Entajpu nova dosiernomon" inputNewFileName: "Entajpu nova nomon de la dosiero"
hasChildFilesOrFolders: "La dosierujo estas neforviŝebla pro tio, ke ĝi enhavas dosieron." inputNewFolderName: "Entajpu nova nomon de la dosierujo"
copyUrl: "Kopii retadreson" hasChildFilesOrFolders: "La dosierujo ne estas forviŝebla, ĉar ĝi ne malplenas."
copyUrl: "Kopii URL"
rename: "Alinomi"
avatar: "Ikono"
banner: "Standardo"
nsfw: "Enhavo ne estas deca por laborejo (NSFW)" nsfw: "Enhavo ne estas deca por laborejo (NSFW)"
reload: "Reŝargi"
watch: "Observi"
unwatch: "Malobservi"
accept: "Permesi"
normal: "Normala"
instanceName: "Nomo de la ekzemplo" instanceName: "Nomo de la ekzemplo"
maintainerName: "Nomo de la administranto"
maintainerEmail: "Retpoŝto de la administranto"
tosUrl: "URL de kondiĉoj de uzado"
thisYear: "Ĉi-jare"
thisMonth: "Ĉi-monate"
today: "Hodiaŭ"
dayX: "{day}-a"
monthX: "{month}"
yearX: "La jaro {year}"
pages: "Paĝoj"
connectService: "Konekti" connectService: "Konekti"
disconnectService: "Farkonektiĝi" disconnectService: "Farkonektiĝi"
driveCapacityPerLocalAccount: "Volumo po unu loka-uzanto" enableGlobalTimeline: "Ebligi mallokan templinion"
driveCapacityPerRemoteAccount: "Volumo po unu transa uzanto" registration: "Registri"
driveCapacityPerLocalAccount: "Volumo de disko po unu loka uzanto"
driveCapacityPerRemoteAccount: "Volumo de disko po unu transa uzanto"
iconUrl: "URL de la ikono (retpaĝsimbolo, ktp)"
bannerUrl: "URL de standardo"
backgroundImageUrl: "URL de fona bildo"
basicInfo: "Baza informo"
pinnedUsers: "Alpinglita uzanto" pinnedUsers: "Alpinglita uzanto"
pinnedPages: "Alpinglitaj paĝoj"
pinnedNotes: "Pinglita noto" pinnedNotes: "Pinglita noto"
antennas: "Antenoj"
name: "Nomo"
withFileAntenna: "Nur kun aldonaĵo" withFileAntenna: "Nur kun aldonaĵo"
withReplies: "Inkluzive respondoj"
notesAndReplies: "Kun respondoj" notesAndReplies: "Kun respondoj"
withFiles: "Kun aldonaĵo" withFiles: "Kun aldonaĵo"
silenceConfirm: "Ĉu vi certas ke vi volas silentigi la uzanton?" silence: "Mutigi"
unsilenceConfirm: "Ĉu vi certas ke vi volas malsilentigi la uzanton?" silenceConfirm: "Ĉu vi certas ke vi volas mutigi la uzanton?"
unsilence: "Malmutigi"
unsilenceConfirm: "Ĉu vi certas ke vi volas malmutigi la uzanton?"
recentlyUpdatedUsers: "Uzantoj kiu lastatempe faris noton"
recentlyRegisteredUsers: "Nove aniĝintaj uzantoj"
popularTags: "Popularaj kradvortoj"
userList: "Listoj" userList: "Listoj"
aboutMisskey: "Pri Miskejo" about: "Informoj"
aboutMisskey: "Pri Misskey"
administrator: "Administranto"
moderator: "Moderigisto"
securityKey: "Sekureca ŝlosilo"
securityKeyName: "Nomo de la ŝlosilo"
lastUsed: "Plej malnove uzita"
passwordLessLogin: "Ensaluti sen pasvorto" passwordLessLogin: "Ensaluti sen pasvorto"
resetPassword: "Restarigi pasvorton" resetPassword: "Restarigi pasvorton"
newPasswordIs: "La nova pasvorto estas {password}." newPasswordIs: "La nova pasvorto estas {password}."
share: "Diskonigi"
notFound: "Ne trovita"
cacheClear: "Malplenigi staplon"
help: "Manlibro de uzado"
inputMessageHere: "Entajpu masaĝo tie ĉi" inputMessageHere: "Entajpu masaĝo tie ĉi"
close: "Fermi"
group: "Grupo"
groups: "Grupoj"
createGroup: "Krei grupon"
groupName: "Grupa nomo"
members: "Membroj"
messagingWithUser: "Mesaĝado kun uzanto"
messagingWithGroup: "Mesaĝado kun grupo"
title: "Titolo"
text: "Teksto"
enable: "Ebligi"
next: "Sekve"
noteOf: "Noto de {user}" noteOf: "Noto de {user}"
noMessagesYet: "Neniu mesaĝo"
newMessageExists: "Vi ricevis novan mesaĝon." newMessageExists: "Vi ricevis novan mesaĝon."
onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo." onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo."
uiLanguage: "Lingvo de la interfaco" invitationCode: "Kodo de invito"
noFollowRequests: "Vi ne havas eksekvopetojn." or: "Aŭ"
language: "Lingvo"
uiLanguage: "Lingvo de la fasado"
aboutX: "Pri {x}"
useOsNativeEmojis: "Oni uzas la emoĵioj de la denaska sistemo"
youHaveNoGroups: "Neniuj grupoj"
category: "Kategorio"
tags: "Etikedoj"
createAccount: "Krei konton"
existingAccount: "Ekzista konto"
fontSize: "Tipara grando"
noFollowRequests: "Vi ne havas peto de sekvado"
openImageInNewTab: "Fermi la bildon en nova tablo"
dashboard: "Stirpanelo"
local: "Loka" local: "Loka"
remote: "Transa" remote: "Transa"
total: "Entute"
clientSettings: "Agordoj de kliento"
accountSettings: "Agordoj de Konto"
numberOfDays: "Nombro de tagoj"
hideThisNote: "Kaŝi tiun noton" hideThisNote: "Kaŝi tiun noton"
deleteAllFiles: "Forvisi ĉiujn dosierojn" objectStorageBaseUrl: "Baza URL"
objectStorageRegion: "Regiono"
objectStorageUseSSL: "Oni uzas SSL"
serverLogs: "Servila protokolo"
deleteAll: "Forviŝi ĉiujn"
sounds: "Sonoj"
listen: "Aŭdi"
none: "Neniu"
showInPage: "Vidi en paĝo"
deleteAllFiles: "Forviŝi ĉiujn dosierojn"
deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?" deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?"
userSilenced: "Tiu uzanto estas mutigata."
menu: "Menuo"
deletedNote: "Forviŝita noto"
invisibleNote: "Malpublika noto" invisibleNote: "Malpublika noto"
poll: "Balotujo"
useCw: "Kaŝi enhavo"
themeEditor: "Redaktilo de koloraroj"
author: "Aŭtoro"
plugins: "Kromaĵoj"
deck: "Kartaro"
medium: "Meza"
small: "Malgranda"
edit: "Redakti"
emailServer: "Retpoŝta servilo" emailServer: "Retpoŝta servilo"
email: "Retpoŝto" email: "Retpoŝto"
emailAddress: "Retpoŝtadreso" emailAddress: "Retpoŝta adreso"
smtpUser: "Uzantonomo" smtpConfig: "Agordoj de la servilo SMTP"
smtpUser: "Uzantnomo"
smtpPass: "Pasvorto" smtpPass: "Pasvorto"
wordMute: "Silentigo de vortoj"
userSaysSomething: "{name} parolis ion" userSaysSomething: "{name} parolis ion"
display: "Vidi"
copy: "Kopii"
database: "Datumbazo" database: "Datumbazo"
channel: "Kanalo" channel: "Kanalo"
fileIdOrUrl: "Dosirero ID aŭ retadreso" create: "Krei"
notificationSetting: "Agordoj de sciigoj"
useGlobalSetting: "Oni uzas malloka agordo"
fileIdOrUrl: "Dosiera identigilo aŭ URL"
abuseReports: "Signali"
reportAbuse: "Signali"
reportAbuseOf: "Signali {name}'(o)n"
send: "Sendi" send: "Sendi"
i18nInfo: "Tradukojn de Misskey en diversaj lingvoj faras volontuloj. Vi povus kunlabori en tradukado sur {link}, se vi volus." openInNewTab: "Malfermi en nova langeto"
driveFilesCount: "Numero de dosieroj en la diskilo" editTheseSettingsMayBreakAccount: "Redakti tiujn agordojn estas eble damaĝi konton."
onlineUsersCount: "{n} uzanto(j) estas surkonektita" public: "Publika"
i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}."
accountInfo: "Kontaj Informoj"
notesCount: "Numero de notoj"
repliesCount: "Numero de respondoj senditaj"
renotesCount: "Numero de renotoj kiun vi sendis"
repliedCount: "Numero de respondoj ricevitaj"
renotedCount: "Numero de renotoj kiun vi ricevis"
followingCount: "Numero de sekvatoj"
followersCount: "Numero de sekvantoj"
sentReactionsCount: "Numero de sentitaj reagoj"
receivedReactionsCount: "Numero de ricevitaj reagoj"
yes: "Jes"
no: "Ne"
driveFilesCount: "Numero de dosieroj sur la disko"
notSet: "Ne elektita"
noteFavoritesCount: "Numero de la preferataj notoj"
contact: "Kontakto"
makeExplorable: "Videbligi konton sur la paĝo \"Esplori\""
duplicate: "Duobligi"
left: "Maldekstra"
center: "Centra"
showTitlebar: "Montri titola stango"
clearCache: "Malplenigi staplon"
onlineUsersCount: "{n} uzanto(j) estas surlinea"
nUsers: "{n} uzanto(j)" nUsers: "{n} uzanto(j)"
nNotes: "{n} notoj"
myTheme: "Miaj koloraroj"
backgroundColor: "Fona koloro"
textColor: "Teksto"
saveAs: "Konservi kiel…"
value: "Valoro"
createdAt: "Kreita je"
updatedAt: "Laste ĝisdatigita"
deleteConfirm: "Ĉu certas forviŝi?"
closeAccount: "Forigi konton"
currentVersion: "Nuna versio"
latestVersion: "Plej nova versio"
youAreRunningUpToDateClient: "Vi uzas la plej novan version de via kliento."
newVersionOfClientAvailable: "Nova versio de via kliento estas disponebla."
inUse: "Uzata"
editCode: "Redakti kodon"
emailNotification: "Sciigoj per retpoŝto" emailNotification: "Sciigoj per retpoŝto"
publish: "Publikigi" publish: "Publikigi"
inChannelSearch: "Serĉi en kanalo" inChannelSearch: "Serĉi en kanalo"
useReactionPickerForContextMenu: "Oni malfermas reago-elektilon per dekstro-kliki"
typingUsers: "{users} estas entajpanta(j)..." typingUsers: "{users} estas entajpanta(j)..."
info: "Informoj"
unknown: "Nekonata"
online: "Surkonektita" online: "Surkonektita"
offline: "Forkonektita" offline: "Forkonektita"
instanceBlocking: "Ekzempla blokado" instanceBlocking: "Blokado de ekzemplo"
selectAccount: "Elekti konton"
user: "Uzanto" user: "Uzanto"
accounts: "Kontoj"
high: "Alta"
middle: "Meza"
low: "Malalta"
customCss: "Uzantula CSS"
global: "Malloka"
sent: "Sendi"
received: "Ricevita"
searchResult: "Serĉorezultoj"
hashtags: "Kradvorto"
learnMore: "Lernu pli"
translate: "Traduki"
translatedFrom: "Tradukita el {x}"
_docs:
continueReading: "Legi plu"
features: "Funkcioj"
_gallery: _gallery:
liked: "Ŝatitaj notoj" liked: "Ŝatitaj notoj"
like: "Ŝati"
_email: _email:
_follow: _follow:
title: "Vi estas eksekvita." title: "Vi estas eksekvita"
_receiveFollowRequest: _receiveFollowRequest:
title: "Vi ricevis eksekvopeton." title: "Vi ricevis peton de sekvado"
_registry:
key: "Ŝlosilo"
keys: "Ŝlosiloj"
domain: "Nomregno"
createKey: "Krei ŝlosilon"
_aboutMisskey: _aboutMisskey:
about: "Misskey estas malferma koda programo evoluigata far syuilo ekde la 2014." about: "Misskey estas malfermitkoda programo evoluigata de syuilo ekde la 2014."
contributors: "Precipaj kontribuantoj"
allContributors: "Ĉiuj kontribuintoj"
source: "Fontkodo" source: "Fontkodo"
translation: "Traduki Misskey'on" translation: "Traduki Misskey'on"
patrons: "Mecenatoj"
_mfm: _mfm:
mention: "Mencioj" mention: "Mencioj"
url: "Retadreso" hashtag: "Kradvorto"
blockCode: "Kodo (Ujo)" url: "URL"
blockMath: "Formulo (Ujo)" link: "Ligilo"
bold: "Grasa"
small: "Malgrande"
center: "Centrigi"
inlineCode: "Kodo (en linio)"
blockCode: "Kodo (bloko)"
inlineMath: "Formulo (en linio)"
blockMath: "Formulo (bloko)"
quote: "Citi" quote: "Citi"
emoji: "Personecigitaj emoĵioj"
search: "Serĉi" search: "Serĉi"
flip: "Inversa"
x2: "Granda"
x3: "Grandega"
x4: "Pli grandega"
_reversi:
total: "Entute"
_instanceTicker: _instanceTicker:
none: "Ne montri" none: "Ne montri"
remote: "Montri al transaj uzantoj" remote: "Montri al transaj uzantoj"
always: "Ĉiam montri"
_channel: _channel:
create: "Krei kanalon" create: "Krei kanalon"
edit: "Redakti kanalon" edit: "Redakti kanalon"
following: "Sekvaton" following: "Sekvante"
usersCount: "{n} partoprenanto(j)"
_menuDisplay:
hide: "Kaŝi"
_wordMute:
muteWords: "Kaŝigitaj vortoj"
mutedNotes: "Silentigataj notoj"
_theme: _theme:
code: "Kodo de koloraro"
darken: "Malbrileco"
lighten: "Brileco"
keys: keys:
bg: "Fono"
navBg: "Fono de flanka stango"
hashtag: "Kradvorto"
mention: "Mencioj" mention: "Mencioj"
renote: "Renotici" renote: "Fari renoton"
buttonBg: "Fono de butono"
driveFolderBg: "Fono de dosierujo de la disko"
_sfx: _sfx:
note: "Nova noto" note: "Nova noto"
noteMy: "Mia noto"
notification: "Sciigoj" notification: "Sciigoj"
chat: "Babilejoj" chat: "Retbabili"
channel: "Kanala sciigoj" chatBg: "Retbabili (BG)"
antenna: "Ricevo de anteno"
channel: "Sciigoj de kanalo"
_ago:
future: "Futuro"
justNow: "Ĵus"
secondsAgo: "Antaŭ {n} sekundoj"
minutesAgo: "Antaŭ {n} minutoj"
hoursAgo: "Antaŭ {n} horo(j)"
daysAgo: "Antaŭ {n} tagoj"
weeksAgo: "Antaŭ {n} semajnoj"
monthsAgo: "Antaŭ {n} monatoj"
yearsAgo: "Antaŭ {n} jaroj"
_time:
second: "sek"
minute: "min"
hour: "hor"
day: "Tago"
_tutorial: _tutorial:
title: "Uzado de Miskejo" title: "Uzado de Misskey"
step1_1: "Bonvenon."
step7_2: "Se vi volas scii pli pri Misskey, rigardu la fakon {help}."
step7_3: "Do, bonvolu amuziĝi Misskey'on🚀"
_permissions: _permissions:
"read:blocks": "Vidi la listo de la uzantoj kiun vi blokis." "read:blocks": "Vidi la liston de uzantoj kiun vi blokas"
"read:drive": "Vidi dosierojn en la diskilo" "write:blocks": "Redakti vian liston de blokataj uzantoj"
"read:channels": "Legi kanalon" "read:drive": "Operacio por legi la informon de dosiero en via disko de Misskey"
"write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via disko de Misskey"
"read:favorites": "Vidi vian liston de preferatoj"
"read:following": "Vidi tiun kiun vi sekvas"
"write:following": "Sekvi aŭ malsekvi alian uzanton"
"read:messaging": "Vidi vian retbabiladon"
"read:mutes": "Vidi vian liston de silentigoj"
"write:mutes": "Redakti vian liston de silentigoj"
"write:notes": "Krei / Forviŝi noton"
"read:notifications": "Vidi sciigojn"
"read:reactions": "Vidi reagojn"
"write:reactions": "Redakti viajn reagojn"
"read:pages": "Vidi via paĝojn"
"read:page-likes": "Vidi ŝatojn de paĝo"
"read:channels": "Vidi kanalojn"
_antennaSources:
all: "Ĉiuj notoj"
homeTimeline: "Notoj far uzantoj, kiujn vi sekvas"
_weekday:
sunday: "dimanĉo"
monday: "lundo"
tuesday: "mardo"
wednesday: "merkredo"
thursday: "ĵaŭdo"
friday: "vendredo"
saturday: "sabato"
_widgets: _widgets:
notifications: "Sciigoj" notifications: "Sciigoj"
timeline: "Tempolinio" timeline: "Templinio"
federation: "Fediverso" clock: "Horloĝo"
activity: "Aktiveco"
federation: "Kunfederaĵo"
slideshow: "Bildoprezento"
button: "Butono"
onlineUsers: "Surkonektita uzanto" onlineUsers: "Surkonektita uzanto"
_cw: _cw:
show: "Vidu plu" show: "Vidu pli"
files: "{count} dosiero(j)" files: "{count} dosiero(j)"
_poll:
choiceN: "Balotilo {n}"
noMore: "Oni ne povas aldoni pli."
infinite: "Neniam"
deadlineTime: "hor"
votesCount: "{n} balotiloj"
vote: "Baloti"
closed: "Oni jam balotis ĝin"
_visibility: _visibility:
publicDescription: "Via noto aperiĝos sur konfederacia tempolinio" publicDescription: "Via noto aperiĝos sur la konfederacia templinio"
home: "Ĉefpaĝo" home: "Hejmo"
homeDescription: "Elsendi nur sur hejma tempolinio" homeDescription: "Elsendi nur sur la hejmtemplinio"
followers: "Sekvantoj" followers: "Sekvantoj"
followersDescription: "Elsendi nur al sekvantoj de mi" followersDescription: "Nur al sekvantoj al mi"
localOnly: "Nur loka" localOnly: "Nur loka"
localOnlyDescription: "Nelegabla al transaj uzantoj" localOnlyDescription: "Ne montri al transaj uzantoj"
_postForm: _postForm:
channelPlaceholder: "Elsendi sur la kanalo" replyPlaceholder: "Respondi al tiu noto..."
quotePlaceholder: "Citado tiun noton..."
channelPlaceholder: "Sendi sur la kanalo"
_profile: _profile:
username: "Uzantonomo" name: "Nomo"
username: "Uzantnomo"
metadataEdit: "Redakti kromaj informoj"
changeAvatar: "Ŝanĝi profilbildon"
changeBanner: "Ŝanĝi standardon"
_exportOrImport: _exportOrImport:
followingList: "Sekvi" allNotes: "Ĉiuj notoj"
muteList: "Silentigi" followingList: "Sekvataj"
muteList: "Silentigoj"
blockingList: "Blokado" blockingList: "Blokado"
userLists: "Listoj" userLists: "Listoj"
_charts:
federationInstancesTotal: "Tuta numero de kunfederantaj ekzemploj"
filesTotal: "Tuta numero de dosieroj"
_timelines: _timelines:
home: "Hejmo" home: "Hejmo"
local: "Loka" local: "Loka"
social: "Hejmo kaj loka" social: "Sociala"
global: "Malloka"
_rooms: _rooms:
translate: "Movi"
chooseImage: "Elekti bildon"
_furnitures: _furnitures:
server: "Servilo" server: "Servilo"
moon: "La luno"
_pages: _pages:
editPage: "Redakti paĝon"
deleted: "La paĝo estas forigita."
editThisPage: "Redakti la paĝon"
viewPage: "Vidi via paĝojn"
my: "Miaj paĝoj"
featured: "Ravaĵoj"
content: "Blokado de paĝo" content: "Blokado de paĝo"
url: "Retadreso de la paĝo" url: "URL de paĝo"
chooseBlock: "Aldoni blokado" alignCenter: "Centrigi"
chooseBlock: "Aldoni blokon"
blocks:
image: "Bildo"
button: "Butono"
_post:
canvasId: "Kanvasa identigilo"
_numberInput:
text: "Titolo"
_canvas:
id: "Kanvasa identigilo"
_note:
id: "Identigilo de noto"
_counter:
text: "Titolo"
_button:
text: "Titolo"
_action:
_pushEvent:
event: "Nomo de la evento"
script: script:
categories: categories:
list: "Listoj" list: "Listoj"
@ -317,21 +669,28 @@ _pages:
arg1: "Listoj" arg1: "Listoj"
types: types:
array: "Listoj" array: "Listoj"
stringArray: "List de teksto"
_notification: _notification:
fileUploaded: "La dosiero sukcese alŝutiĝis." fileUploaded: "La dosiero sukcese alŝutiĝis."
youWereFollowed: "Vi estas eksekvita." youGotPoll: "{name} balotis"
youReceivedFollowRequest: "Vi ricevis eksekvopeton." youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi."
yourFollowRequestAccepted: "Via eksekvopeto estas akceptita." youGotMessagingMessageFromGroup: "Retbabilan mesaĝon oni sendis al la grupo {name}"
youWereFollowed: "sksekvis vin"
youReceivedFollowRequest: "Vi ricevis peton de sekvado"
yourFollowRequestAccepted: "Via peto por eksekvu estas akceptita."
_types: _types:
follow: "Sekvi" follow: "Sekvatoj"
mention: "Mencioj" mention: "Mencioj"
renote: "Renotici" renote: "Fari renoton"
quote: "Citi" quote: "Citi"
reaction: "Reagoj" reaction: "Reagoj"
receiveFollowRequest: "Eksekvopeto ricevita" receiveFollowRequest: "Ricevita peton de sekvado"
followRequestAccepted: "Peto por eksekvu akceptita"
_deck: _deck:
profile: "Agordaro"
_columns: _columns:
notifications: "Sciigoj" notifications: "Sciigoj"
tl: "Tempolinio" tl: "Templinio"
antenna: "Antenoj"
list: "Listoj" list: "Listoj"
mentions: "Mencioj" mentions: "Al vi"

View file

@ -127,6 +127,7 @@ editWidgets: "Editar widgets"
editWidgetsExit: "Terminar edición" editWidgetsExit: "Terminar edición"
customEmojis: "Emojis personalizados" customEmojis: "Emojis personalizados"
emoji: "Emoji" emoji: "Emoji"
emojis: "Emoji"
emojiName: "Nombre del emoji" emojiName: "Nombre del emoji"
emojiUrl: "URL de la imágen del emoji" emojiUrl: "URL de la imágen del emoji"
addEmoji: "Agregar emoji" addEmoji: "Agregar emoji"
@ -665,6 +666,10 @@ administration: "Administrar"
expiration: "Termina el" expiration: "Termina el"
middle: "Mediano" middle: "Mediano"
global: "Global" global: "Global"
sent: "Enviar"
hashtags: "Hashtag"
_docs:
admin: "Administrar"
_ad: _ad:
back: "Deseleccionar" back: "Deseleccionar"
_gallery: _gallery:

View file

@ -91,11 +91,11 @@ followRequests: "Demandes dabonnement"
unfollow: "Se désabonner" unfollow: "Se désabonner"
followRequestPending: "Demande d'abonnement en attente de confirmation" followRequestPending: "Demande d'abonnement en attente de confirmation"
enterEmoji: "Insérer un émoji" enterEmoji: "Insérer un émoji"
renote: "Partager" renote: "Renoter"
unrenote: "Annuler le partage" unrenote: "Annuler la Renote"
renoted: "Republié !" renoted: "Renoté !"
cantRenote: "Ce message ne peut pas être republié." cantRenote: "Ce message ne peut pas être renoté."
cantReRenote: "Impossible de repartager un partage." cantReRenote: "Impossible de renoter une Renote."
quote: "Citer" quote: "Citer"
pinnedNote: "Note épinglée" pinnedNote: "Note épinglée"
pinned: "Épingler sur le profil" pinned: "Épingler sur le profil"
@ -128,6 +128,7 @@ editWidgets: "Modifier les widgets"
editWidgetsExit: "Valider les modifications" editWidgetsExit: "Valider les modifications"
customEmojis: "Émojis personnalisés" customEmojis: "Émojis personnalisés"
emoji: "Émoji" emoji: "Émoji"
emojis: "Émoji"
emojiName: "Nom de lémoji" emojiName: "Nom de lémoji"
emojiUrl: "URL de lémoji" emojiUrl: "URL de lémoji"
addEmoji: "Ajouter un émoji" addEmoji: "Ajouter un émoji"
@ -528,6 +529,7 @@ removeAllFollowing: "Retenir tous les abonnements"
removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si linstance nexiste plus." removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si linstance nexiste plus."
userSuspended: "Cet·te utilisateur·rice a été suspendu·e." userSuspended: "Cet·te utilisateur·rice a été suspendu·e."
userSilenced: "Cette utilisateur·trice a été mis·e en sourdine." userSilenced: "Cette utilisateur·trice a été mis·e en sourdine."
menu: "Menu"
divider: "Séparateur" divider: "Séparateur"
addItem: "Ajouter un élément" addItem: "Ajouter un élément"
rooms: "Chambre" rooms: "Chambre"
@ -636,9 +638,9 @@ manageAccessTokens: "Gérer les jetons d'accès"
accountInfo: " Informations du compte " accountInfo: " Informations du compte "
notesCount: "Nombre de notes" notesCount: "Nombre de notes"
repliesCount: "Nombre de réponses envoyées" repliesCount: "Nombre de réponses envoyées"
renotesCount: "Nombre de notes repartagées" renotesCount: "Nombre de notes que vous avez renotées"
repliedCount: "Nombre de réponses reçues" repliedCount: "Nombre de réponses reçues"
renotedCount: "Nombre de Renotes" renotedCount: "Nombre de vos notes renotées"
followingCount: "Nombre de comptes suivis" followingCount: "Nombre de comptes suivis"
followersCount: "Nombre d'abonnés" followersCount: "Nombre d'abonnés"
sentReactionsCount: "Nombre de réactions envoyées" sentReactionsCount: "Nombre de réactions envoyées"
@ -760,7 +762,27 @@ middle: "Moyen"
low: "Basse" low: "Basse"
emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail." emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail."
ratio: "Ratio" ratio: "Ratio"
customCss: "CSS personnalisé"
customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exactement ce que vous faites. Une configuration inadaptée peut empêcher le client de s'exécuter normalement."
global: "Global" global: "Global"
squareAvatars: "Avatars carrés"
sent: "Envoyer"
searchResult: "Résultats de la recherche"
hashtags: "Hashtags"
troubleshooting: "Résolution de problèmes"
useBlurEffect: "Utiliser des effets de flou dans l'interface"
learnMore: "Plus d'informations"
misskeyUpdated: "Misskey a été mis à jour !"
whatIsNew: "Voir les derniers changements"
translate: "Traduire"
translatedFrom: "Traduit depuis {x}"
_docs:
continueReading: "Lire plus"
features: "Fonctionnalités"
generalTopics: "Sujets généraux"
advancedTopics: "Sujets avancés"
admin: "Gestion"
translateWarn: "Ceci est une traduction dont le contenu peut différer du texte original."
_ad: _ad:
back: "Retour" back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
@ -859,6 +881,8 @@ _mfm:
blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant avec le curseur." blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant avec le curseur."
font: "Police de caractères" font: "Police de caractères"
fontDescription: "Il est possible de choisir la police." fontDescription: "Il est possible de choisir la police."
rainbow: "Arc-en-ciel"
rainbowDescription: "Permet d'afficher le contenu en couleurs arc-en-ciel."
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Réglages de la partie" gameSettings: "Réglages de la partie"
@ -911,6 +935,9 @@ _channel:
usersCount: "{n} Participant·e·s" usersCount: "{n} Participant·e·s"
notesCount: "{n} Notes" notesCount: "{n} Notes"
_menuDisplay: _menuDisplay:
sideFull: "Latéral"
sideIcon: "Latéral (icônes)"
top: "Haut de page"
hide: "Masquer" hide: "Masquer"
_wordMute: _wordMute:
muteWords: "Mots à filtrer" muteWords: "Mots à filtrer"
@ -969,7 +996,7 @@ _theme:
hashtag: "Hashtags" hashtag: "Hashtags"
mention: "Mentionner" mention: "Mentionner"
mentionMe: "Mentions (Moi)" mentionMe: "Mentions (Moi)"
renote: "Partager" renote: "Renoter"
modalBg: "Modal d'arrière-plan" modalBg: "Modal d'arrière-plan"
divider: "Séparateur" divider: "Séparateur"
scrollbarHandle: "Poignée de la barre de navigation" scrollbarHandle: "Poignée de la barre de navigation"
@ -1590,11 +1617,11 @@ _notification:
youWereInvitedToGroup: "Invité·e au groupe" youWereInvitedToGroup: "Invité·e au groupe"
_types: _types:
all: "Toutes" all: "Toutes"
follow: "Abonnements" follow: "Nouvel·le abonné·e"
mention: "Mentions" mention: "Mentions"
reply: "Réponses" reply: "Réponses"
renote: "Partager" renote: "Renotes"
quote: "Citer" quote: "Citations"
reaction: "Réactions" reaction: "Réactions"
pollVote: "Votes dans des sondages" pollVote: "Votes dans des sondages"
receiveFollowRequest: "Demande d'abonnement reçue" receiveFollowRequest: "Demande d'abonnement reçue"

View file

@ -128,6 +128,7 @@ editWidgets: "Sunting gawit"
editWidgetsExit: "Selesai" editWidgetsExit: "Selesai"
customEmojis: "Emoji kustom" customEmojis: "Emoji kustom"
emoji: "Emoji" emoji: "Emoji"
emojis: "Emoji"
emojiName: "Nama emoji" emojiName: "Nama emoji"
emojiUrl: "URL Emoji" emojiUrl: "URL Emoji"
addEmoji: "Tambahkan emoji" addEmoji: "Tambahkan emoji"
@ -528,6 +529,7 @@ removeAllFollowing: "Tahan semua mengikuti"
removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi." removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi."
userSuspended: "Pengguna ini telah dibekukan." userSuspended: "Pengguna ini telah dibekukan."
userSilenced: "Pengguna ini telah dibungkam." userSilenced: "Pengguna ini telah dibungkam."
menu: "Menu"
divider: "Pembagi" divider: "Pembagi"
addItem: "Tambahkan item" addItem: "Tambahkan item"
rooms: "Ruang" rooms: "Ruang"
@ -760,7 +762,26 @@ middle: "Sedang"
low: "Rendah" low: "Rendah"
emailNotConfiguredWarning: "Alamat surel tidak disetel." emailNotConfiguredWarning: "Alamat surel tidak disetel."
ratio: "Rasio" ratio: "Rasio"
customCss: "Custom CSS"
customCssWarn: "Pengaturan ini seharusnya digunakan jika kamu tahu cara kerjanya. Memasukkan nilai yang tidak tepat dapat menyebabkan klien tidak berfungsi semestinya."
global: "Global" global: "Global"
squareAvatars: "Tampilkan avatar sebagai persegi"
sent: "Kirim"
received: "Diterima"
searchResult: "Hasil Penelusuran"
hashtags: "Tagar"
troubleshooting: "Penyelesaian Masalah"
useBlurEffect: "Gunakan efek blur pada antarmuka"
learnMore: "Pelajari lebih lanjut"
misskeyUpdated: "Misskey telah dimutakhirkan!"
whatIsNew: "Lihat perubahan pemutakhiran"
_docs:
continueReading: "Baca lebih lanjut"
features: "Fitur"
generalTopics: "Topik umum"
advancedTopics: "Topik tingkat lanjut"
admin: "Manajemen"
translateWarn: "Ini merupakan dokumen terjemahan. Konten di dalamnya kemungkinan dapat berbeda dari yang aslinya."
_ad: _ad:
back: "Kembali" back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
@ -859,6 +880,8 @@ _mfm:
blurDescription: "Konten dapat diburamkan dengan efek ini. Konten dapat ditampilkan dengan jelas dengan melayangkan kursor tetikus di atasnya." blurDescription: "Konten dapat diburamkan dengan efek ini. Konten dapat ditampilkan dengan jelas dengan melayangkan kursor tetikus di atasnya."
font: "Font" font: "Font"
fontDescription: "Setel font yang ditampilkan untuk konten." fontDescription: "Setel font yang ditampilkan untuk konten."
rainbow: "Pelangi"
rainbowDescription: "Membuat konten muncul dalam warna pelangi."
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Pengaturan permainan" gameSettings: "Pengaturan permainan"
@ -911,6 +934,9 @@ _channel:
usersCount: "{n} Partisipan" usersCount: "{n} Partisipan"
notesCount: "terdapat {n} catatan" notesCount: "terdapat {n} catatan"
_menuDisplay: _menuDisplay:
sideFull: "Horisontal"
sideIcon: "Horisontal (Ikon)"
top: "Atas"
hide: "Sembunyikan" hide: "Sembunyikan"
_wordMute: _wordMute:
muteWords: "Kata yang dibisukan" muteWords: "Kata yang dibisukan"

View file

@ -127,6 +127,7 @@ editWidgets: "Modifica i widget"
editWidgetsExit: "Modifica fine" editWidgetsExit: "Modifica fine"
customEmojis: "Emoji personalizzati" customEmojis: "Emoji personalizzati"
emoji: "Emoji" emoji: "Emoji"
emojis: "Emoji"
emojiName: "Nome dell'emoji" emojiName: "Nome dell'emoji"
emojiUrl: "URL dell'emoji" emojiUrl: "URL dell'emoji"
addEmoji: "Aggiungi un emoji" addEmoji: "Aggiungi un emoji"
@ -741,6 +742,13 @@ low: "Bassa"
emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail." emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail."
ratio: "Rapporto" ratio: "Rapporto"
global: "Federata" global: "Federata"
sent: "Inviare"
hashtags: "Hashtag"
troubleshooting: "Risoluzione problemi"
_docs:
continueReading: "Leggi di più"
features: "Funzionalità"
admin: "Gestione"
_ad: _ad:
back: "Indietro" back: "Indietro"
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso" reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
@ -799,6 +807,7 @@ _mfm:
blur: "Sfocatura" blur: "Sfocatura"
font: "Tipo di carattere" font: "Tipo di carattere"
fontDescription: "Puoi scegliere il tipo di carattere per il contenuto." fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
rainbow: "Arcobaleno"
_reversi: _reversi:
reversi: "Reversi" reversi: "Reversi"
gameSettings: "Impostazioni di gioco" gameSettings: "Impostazioni di gioco"
@ -1386,12 +1395,12 @@ _notification:
youWereInvitedToGroup: "Invitat@ al gruppo" youWereInvitedToGroup: "Invitat@ al gruppo"
_types: _types:
all: "Tutto" all: "Tutto"
follow: "Follows" follow: "Nuovə follower"
mention: "Menzioni" mention: "Menzioni"
reply: "Rispondi" reply: "Risposte"
renote: "Rinota" renote: "Rinota"
quote: "Cita" quote: "Cita"
reaction: "Reazione" reaction: "Reazioni"
pollVote: "Voti ricevuti" pollVote: "Voti ricevuti"
receiveFollowRequest: "Richiesta di follow ricevuta" receiveFollowRequest: "Richiesta di follow ricevuta"
followRequestAccepted: "Richiesta di follow accettata" followRequestAccepted: "Richiesta di follow accettata"

View file

@ -128,6 +128,7 @@ editWidgets: "ウィジェットを編集"
editWidgetsExit: "編集を終了" editWidgetsExit: "編集を終了"
customEmojis: "カスタム絵文字" customEmojis: "カスタム絵文字"
emoji: "絵文字" emoji: "絵文字"
emojis: "絵文字"
emojiName: "絵文字名" emojiName: "絵文字名"
emojiUrl: "絵文字画像URL" emojiUrl: "絵文字画像URL"
addEmoji: "絵文字を追加" addEmoji: "絵文字を追加"
@ -765,6 +766,25 @@ customCss: "カスタムCSS"
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。" customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
global: "グローバル" global: "グローバル"
squareAvatars: "アイコンを四角形で表示" squareAvatars: "アイコンを四角形で表示"
sent: "送信"
received: "受信"
searchResult: "検索結果"
hashtags: "ハッシュタグ"
troubleshooting: "トラブルシューティング"
useBlurEffect: "UIにぼかし効果を使用"
learnMore: "詳しく"
misskeyUpdated: "Misskeyが更新されました"
whatIsNew: "更新情報を見る"
translate: "翻訳"
translatedFrom: "{x}から翻訳"
_docs:
continueReading: "続きを読む"
features: "機能"
generalTopics: "一般的なトピック"
advancedTopics: "高度なトピック"
admin: "管理"
translateWarn: "このドキュメントは翻訳されたものです。オリジナルとは内容が異なる場合があります。"
_ad: _ad:
back: "戻る" back: "戻る"
@ -872,6 +892,8 @@ _mfm:
blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。" blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。"
font: "フォント" font: "フォント"
fontDescription: "内容のフォントを指定することができます。" fontDescription: "内容のフォントを指定することができます。"
rainbow: "レインボー"
rainbowDescription: "内容をレインボーにします。"
_reversi: _reversi:
reversi: "リバーシ" reversi: "リバーシ"

View file

@ -7,6 +7,7 @@ search: "探す"
notifications: "通知" notifications: "通知"
username: "ユーザー名" username: "ユーザー名"
password: "パスワード" password: "パスワード"
forgotPassword: "パスワード忘れてん"
fetchingAsApObject: "今ちと連合に照会しとるで" fetchingAsApObject: "今ちと連合に照会しとるで"
ok: "OKや" ok: "OKや"
gotIt: "ほい" gotIt: "ほい"
@ -127,6 +128,7 @@ editWidgets: "ウィジェットをいじる"
editWidgetsExit: "編集終ったで" editWidgetsExit: "編集終ったで"
customEmojis: "カスタム絵文字" customEmojis: "カスタム絵文字"
emoji: "絵文字" emoji: "絵文字"
emojis: "絵文字"
emojiName: "絵文字名" emojiName: "絵文字名"
emojiUrl: "絵文字画像URL" emojiUrl: "絵文字画像URL"
addEmoji: "絵文字を追加" addEmoji: "絵文字を追加"
@ -138,6 +140,7 @@ flagAsBotDescription: "もしこのアカウントがプログラムによって
flagAsCat: "Catやで" flagAsCat: "Catやで"
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?" flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?"
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
addAccount: "アカウントを追加"
loginFailed: "ログインに失敗してしもうた…" loginFailed: "ログインに失敗してしもうた…"
showOnRemote: "リモートで見る" showOnRemote: "リモートで見る"
general: "全般" general: "全般"
@ -277,6 +280,7 @@ emptyDrive: "ドライブにはなんも残っとらん"
emptyFolder: "ふぉろだーにはなんも残っとらん" emptyFolder: "ふぉろだーにはなんも残っとらん"
unableToDelete: "消そうおもってんけどな、あかんかったわ" unableToDelete: "消そうおもってんけどな、あかんかったわ"
inputNewFileName: "今度のファイル名は何にするん?" inputNewFileName: "今度のファイル名は何にするん?"
inputNewDescription: "新しいキャプションを入力しましょ"
inputNewFolderName: "今度のフォルダ名は何にするん?" inputNewFolderName: "今度のフォルダ名は何にするん?"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん" hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん"
@ -647,6 +651,10 @@ high: "高い"
middle: "中" middle: "中"
low: "低い" low: "低い"
global: "グローバル" global: "グローバル"
sent: "送信"
hashtags: "ハッシュタグ"
_docs:
admin: "管理"
_ad: _ad:
back: "戻る" back: "戻る"
_gallery: _gallery:

View file

@ -7,7 +7,9 @@ username: "Isem n umseqdac"
password: "Awal uffir" password: "Awal uffir"
ok: "IH" ok: "IH"
settings: "Iɣewwaṛen" settings: "Iɣewwaṛen"
otherSettings: "Iɣewwaren nniḍen"
profile: "Amaɣnu" profile: "Amaɣnu"
signup: "Jerred"
save: "Sekles" save: "Sekles"
delete: "Kkes" delete: "Kkes"
addToList: "Rnu ɣer tebdart" addToList: "Rnu ɣer tebdart"
@ -27,15 +29,31 @@ followers: "Imeḍfaṛen"
followsYou: "Yeṭṭafaṛ-ik·em-id" followsYou: "Yeṭṭafaṛ-ik·em-id"
createList: "Snulfu-d tabdart" createList: "Snulfu-d tabdart"
enterListName: "Isem n tebdart" enterListName: "Isem n tebdart"
privacy: "Tabaḍnit"
follow: "Ḍfeṛ" follow: "Ḍfeṛ"
you: "Kečči·mmi" you: "Kečči·mmi"
selectList: "Fren tabdart" selectList: "Fren tabdart"
youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart" youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart"
security: "Taɣellist"
remove: "Kkes" remove: "Kkes"
userList: "Tibdarin" userList: "Tibdarin"
securityKey: "Tasarutt n tɣellist"
securityKeyName: "Isem n tsarutt"
signinRequired: "Ttxil jerred"
signinWith: "Tuqqna s {x}"
tapSecurityKey: "Sekcem tasarutt-ik·im n tɣellist"
uiLanguage: "Tutlayt n wegrudem" uiLanguage: "Tutlayt n wegrudem"
accountSettings: "Iɣewwaṛen n umiḍan"
plugins: "Izegrar"
email: "Imayl"
emailAddress: "Tansa imayl"
smtpUser: "Isem n umseqdac" smtpUser: "Isem n umseqdac"
smtpPass: "Awal uffir" smtpPass: "Awal uffir"
other: "Wiyyaḍ"
accountInfo: "Talɣut n umiḍan"
emailNotification: "Ilɣa imayl"
selectAccount: "Fren amiḍan"
accounts: "Imiḍan"
_email: _email:
_follow: _follow:
title: "Yeṭṭafaṛ-ik·em-id" title: "Yeṭṭafaṛ-ik·em-id"
@ -48,6 +66,8 @@ _theme:
mention: "Bder" mention: "Bder"
_sfx: _sfx:
notification: "Ilɣuyen" notification: "Ilɣuyen"
_permissions:
"write:account": "Ẓreg talɣut n umiḍan-ik·im"
_widgets: _widgets:
notifications: "Ilɣuyen" notifications: "Ilɣuyen"
_cw: _cw:

View file

@ -128,6 +128,7 @@ editWidgets: "위젯 편집"
editWidgetsExit: "편집 종료" editWidgetsExit: "편집 종료"
customEmojis: "커스텀 이모지" customEmojis: "커스텀 이모지"
emoji: "이모지" emoji: "이모지"
emojis: "이모지"
emojiName: "이모지 이름" emojiName: "이모지 이름"
emojiUrl: "이모지 URL" emojiUrl: "이모지 URL"
addEmoji: "이모지 추가" addEmoji: "이모지 추가"
@ -528,6 +529,7 @@ removeAllFollowing: "모든 팔로잉 해제"
removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요." removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요."
userSuspended: "이 계정은 정지된 상태입니다." userSuspended: "이 계정은 정지된 상태입니다."
userSilenced: "이 계정은 사일런스된 상태입니다." userSilenced: "이 계정은 사일런스된 상태입니다."
menu: "메뉴"
divider: "구분선" divider: "구분선"
addItem: "항목 추가" addItem: "항목 추가"
rooms: "방" rooms: "방"
@ -760,7 +762,28 @@ middle: "보통"
low: "낮음" low: "낮음"
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
ratio: "비율" ratio: "비율"
customCss: "CSS 사용자화"
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다."
global: "글로벌" global: "글로벌"
squareAvatars: "프로필 아이콘을 사각형으로 표시"
sent: "전송"
received: "수신"
searchResult: "검색 결과"
hashtags: "해시태그"
troubleshooting: "문제 해결"
useBlurEffect: "UI에 흐림 효과 사용"
learnMore: "자세히"
misskeyUpdated: "Misskey가 업데이트 되었습니다!"
whatIsNew: "패치 정보 보기"
translate: "번역"
translatedFrom: "{x}에서 번역"
_docs:
continueReading: "계속 읽기"
features: "기능"
generalTopics: "일반 주제"
advancedTopics: "심화 주제"
admin: "관리"
translateWarn: "이 문서는 번역되었기 때문에 원본과는 내용이 다를 수 있습니다."
_ad: _ad:
back: "뒤로" back: "뒤로"
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
@ -859,6 +882,8 @@ _mfm:
blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다." blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다."
font: "폰트" font: "폰트"
fontDescription: "내용의 글꼴을 지정할 수 있습니다." fontDescription: "내용의 글꼴을 지정할 수 있습니다."
rainbow: "무지개"
rainbowDescription: "내용을 무지개로 표시합니다."
_reversi: _reversi:
reversi: "리버시" reversi: "리버시"
gameSettings: "대국 설정" gameSettings: "대국 설정"
@ -911,6 +936,9 @@ _channel:
usersCount: "{n}명 참여 중" usersCount: "{n}명 참여 중"
notesCount: "{n}노트" notesCount: "{n}노트"
_menuDisplay: _menuDisplay:
sideFull: "가로"
sideIcon: "가로(아이콘)"
top: "상단"
hide: "숨기기" hide: "숨기기"
_wordMute: _wordMute:
muteWords: "뮤트할 단어" muteWords: "뮤트할 단어"

View file

@ -128,6 +128,7 @@ editWidgets: "Edytuj widżet"
editWidgetsExit: "Gotowe" editWidgetsExit: "Gotowe"
customEmojis: "Niestandardowe emoji" customEmojis: "Niestandardowe emoji"
emoji: "Emoji" emoji: "Emoji"
emojis: "Emoji"
emojiName: "Nazwa emoji" emojiName: "Nazwa emoji"
emojiUrl: "Adres URL emoji" emojiUrl: "Adres URL emoji"
addEmoji: "Dodaj emoji" addEmoji: "Dodaj emoji"
@ -735,6 +736,10 @@ low: "Niski"
emailNotConfiguredWarning: "Nie podano adresu e-mail" emailNotConfiguredWarning: "Nie podano adresu e-mail"
ratio: "Stosunek" ratio: "Stosunek"
global: "Globalna" global: "Globalna"
sent: "Wyślij"
hashtags: "Hashtag"
_docs:
admin: "Zarządzanie"
_ad: _ad:
back: "Wróć" back: "Wróć"
reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej" reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej"

View file

@ -128,6 +128,7 @@ editWidgets: "Редактировать виджеты"
editWidgetsExit: "Готово" editWidgetsExit: "Готово"
customEmojis: "Эмодзи пользователя" customEmojis: "Эмодзи пользователя"
emoji: "Эмодзи" emoji: "Эмодзи"
emojis: "Эмодзи"
emojiName: "Название эмодзи" emojiName: "Название эмодзи"
emojiUrl: "URL эмодзи" emojiUrl: "URL эмодзи"
addEmoji: "Добавить эмодзи" addEmoji: "Добавить эмодзи"
@ -528,6 +529,7 @@ removeAllFollowing: "Удалить всех подписчиков"
removeAllFollowingDescription: "Отменить все подписки с домена {host}? Пожалуйста, применяйте это действие, если инстанс больше не существует." removeAllFollowingDescription: "Отменить все подписки с домена {host}? Пожалуйста, применяйте это действие, если инстанс больше не существует."
userSuspended: "Эта учётная запись заморожена" userSuspended: "Эта учётная запись заморожена"
userSilenced: "Этот пользователь был заглушен" userSilenced: "Этот пользователь был заглушен"
menu: "Меню"
divider: "Линия-разделитель" divider: "Линия-разделитель"
addItem: "Добавить элемент" addItem: "Добавить элемент"
rooms: "Комната" rooms: "Комната"
@ -760,7 +762,26 @@ middle: "Средне"
low: "Низкий" low: "Низкий"
emailNotConfiguredWarning: "Не указан адрес электронной почты" emailNotConfiguredWarning: "Не указан адрес электронной почты"
ratio: "Соотношение" ratio: "Соотношение"
customCss: "Индивидуальный CSS"
customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас."
global: "Всеобщая" global: "Всеобщая"
squareAvatars: "Квадратные аватарки"
sent: "Отправить"
received: "Получено"
searchResult: "Результаты поиска"
hashtags: "Хэштег"
troubleshooting: "Разрешение проблем"
useBlurEffect: "Размытие в интерфейсе"
learnMore: "Подробнее"
misskeyUpdated: "Misskey обновился!"
whatIsNew: "Что новенького?"
_docs:
continueReading: "Читать подробнее"
features: "Возможности"
generalTopics: "Основные темы"
advancedTopics: "Дополнительные темы"
admin: "Управление"
translateWarn: "Это перевод документа. Он может неточно отражать содержимое оригинала."
_ad: _ad:
back: "Выход" back: "Выход"
reduceFrequencyOfThisAd: "Реже показывать эту рекламу" reduceFrequencyOfThisAd: "Реже показывать эту рекламу"
@ -859,6 +880,8 @@ _mfm:
blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость." blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость."
font: "Шрифт" font: "Шрифт"
fontDescription: "Так можно писать произвольным шрифтом." fontDescription: "Так можно писать произвольным шрифтом."
rainbow: "Радуга"
rainbowDescription: "Заставлять содержимое отображаться в цветах радуги."
_reversi: _reversi:
reversi: "Реверси" reversi: "Реверси"
gameSettings: "Настройки игры" gameSettings: "Настройки игры"
@ -911,6 +934,9 @@ _channel:
usersCount: "Участников: {n}" usersCount: "Участников: {n}"
notesCount: "Заметок: {n}" notesCount: "Заметок: {n}"
_menuDisplay: _menuDisplay:
sideFull: "Сторона"
sideIcon: "Сторона (иконки)"
top: "Вверх"
hide: "Спрятать" hide: "Спрятать"
_wordMute: _wordMute:
muteWords: "Скрыть слово" muteWords: "Скрыть слово"

View file

@ -127,6 +127,7 @@ editWidgets: "Редагувати віджети"
editWidgetsExit: "Готово" editWidgetsExit: "Готово"
customEmojis: "Кастомні емоджі" customEmojis: "Кастомні емоджі"
emoji: "Емоджі" emoji: "Емоджі"
emojis: "Емоджі"
emojiName: "Назва емоджі" emojiName: "Назва емоджі"
emojiUrl: "URL емодзі" emojiUrl: "URL емодзі"
addEmoji: "Додати емодзі" addEmoji: "Додати емодзі"
@ -689,6 +690,10 @@ administration: "Управління"
expiration: "Опитування закінчується" expiration: "Опитування закінчується"
middle: "Середній" middle: "Середній"
global: "Глобальна" global: "Глобальна"
sent: "Відправити"
hashtags: "Хештеґ"
_docs:
admin: "Управління"
_ad: _ad:
back: "Назад" back: "Назад"
_gallery: _gallery:

View file

@ -128,6 +128,7 @@ editWidgets: "编辑小工具"
editWidgetsExit: "完成编辑" editWidgetsExit: "完成编辑"
customEmojis: "自定义表情符号" customEmojis: "自定义表情符号"
emoji: "表情符号" emoji: "表情符号"
emojis: "表情符号"
emojiName: "表情符号名称" emojiName: "表情符号名称"
emojiUrl: "表情符号地址" emojiUrl: "表情符号地址"
addEmoji: "添加表情符号" addEmoji: "添加表情符号"
@ -765,6 +766,24 @@ customCss: "自定义 CSS"
customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!" customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!"
global: "全局" global: "全局"
squareAvatars: "显示方形头像图标" squareAvatars: "显示方形头像图标"
sent: "发送"
received: "收取"
searchResult: "搜索结果"
hashtags: "话题标签"
troubleshooting: "故障排除"
useBlurEffect: "在UI上使用模糊效果"
learnMore: "更多信息"
misskeyUpdated: "Misskey更新完成"
whatIsNew: "显示更新信息"
translate: "翻译"
translatedFrom: "从 {x} 翻译"
_docs:
continueReading: "继续阅读"
features: "特性"
generalTopics: "通常提示"
advancedTopics: "进阶提示"
admin: "管理"
translateWarn: "本文档是翻译后的文档。内容可能与原文有所不同。"
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "减少此广告的频率" reduceFrequencyOfThisAd: "减少此广告的频率"
@ -863,6 +882,8 @@ _mfm:
blurDescription: "产生模糊效果。将鼠标指针放在上面即可将内容显示出来。" blurDescription: "产生模糊效果。将鼠标指针放在上面即可将内容显示出来。"
font: "字体" font: "字体"
fontDescription: "可以设置内容所使用的字体。" fontDescription: "可以设置内容所使用的字体。"
rainbow: "彩虹"
rainbowDescription: "用彩虹色来显示内容。"
_reversi: _reversi:
reversi: "黑白棋" reversi: "黑白棋"
gameSettings: "对局设置" gameSettings: "对局设置"

View file

@ -128,6 +128,7 @@ editWidgets: "編輯小工具"
editWidgetsExit: "完成" editWidgetsExit: "完成"
customEmojis: "自訂表情符號" customEmojis: "自訂表情符號"
emoji: "表情符號" emoji: "表情符號"
emojis: "表情符號"
emojiName: "表情符號名稱" emojiName: "表情符號名稱"
emojiUrl: "表情符號URL" emojiUrl: "表情符號URL"
addEmoji: "加入表情符號" addEmoji: "加入表情符號"
@ -751,6 +752,10 @@ low: "低"
emailNotConfiguredWarning: "沒有設定電子郵件地址" emailNotConfiguredWarning: "沒有設定電子郵件地址"
ratio: "%" ratio: "%"
global: "公開" global: "公開"
sent: "發送"
hashtags: "#tag"
_docs:
admin: "管理"
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "降低此廣告的頻率 " reduceFrequencyOfThisAd: "降低此廣告的頻率 "

View file

@ -0,0 +1,182 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class chartReindex1629004542760 implements MigrationInterface {
name = 'chartReindex1629004542760'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE FROM "__chart__active_users" a USING "__chart__active_users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__drive" a USING "__chart__drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__federation" a USING "__chart__federation" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__hashtag" a USING "__chart__hashtag" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__instance" a USING "__chart__instance" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__network" a USING "__chart__network" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__notes" a USING "__chart__notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__per_user_drive" a USING "__chart__per_user_drive" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__per_user_following" a USING "__chart__per_user_following" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__per_user_notes" a USING "__chart__per_user_notes" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__per_user_reaction" a USING "__chart__per_user_reaction" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__test_grouped" a USING "__chart__test_grouped" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__test_unique" a USING "__chart__test_unique" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DELETE FROM "__chart__users" a USING "__chart__users" b WHERE a.id < b.id AND ((a.group IS NULL AND b.group IS NULL) OR a.group = b.group) AND a.date = b.date;`);
await queryRunner.query(`DROP INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc"`);
await queryRunner.query(`DROP INDEX "IDX_00ed5f86db1f7efafb1978bf21"`);
await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
await queryRunner.query(`DROP INDEX "IDX_13565815f618a1ff53886c5b28"`);
await queryRunner.query(`DROP INDEX "IDX_7a170f67425e62a8fabb76c872"`);
await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
await queryRunner.query(`DROP INDEX "IDX_36cb699c49580d4e6c2e6159f9"`);
await queryRunner.query(`DROP INDEX "IDX_76e87c7bfc5d925fcbba405d84"`);
await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
await queryRunner.query(`DROP INDEX "IDX_07747a1038c05f532a718fe1de"`);
await queryRunner.query(`DROP INDEX "IDX_99a7d2faaef84a6f728d714ad6"`);
await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
await queryRunner.query(`DROP INDEX "IDX_6b8f34a1a64b06014b6fb66824"`);
await queryRunner.query(`DROP INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63"`);
await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
await queryRunner.query(`DROP INDEX "IDX_a1efd3e0048a5f2793a47360dc"`);
await queryRunner.query(`DROP INDEX "IDX_7b5da130992ec9df96712d4290"`);
await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
await queryRunner.query(`DROP INDEX "IDX_42eb716a37d381cdf566192b2b"`);
await queryRunner.query(`DROP INDEX "IDX_7036f2957151588b813185c794"`);
await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
await queryRunner.query(`DROP INDEX "IDX_5f86db6492274e07c1a3cdf286"`);
await queryRunner.query(`DROP INDEX "IDX_e496ca8096d28f6b9b509264dc"`);
await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
await queryRunner.query(`DROP INDEX "IDX_7af07790712aa3438ff6773f3b"`);
await queryRunner.query(`DROP INDEX "IDX_4b3593098b6edc9c5afe36b18b"`);
await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
await queryRunner.query(`DROP INDEX "IDX_84234bd1abb873f07329681c83"`);
await queryRunner.query(`DROP INDEX "IDX_55bf20f366979f2436de99206b"`);
await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
await queryRunner.query(`DROP INDEX "IDX_f7bf4c62059764c2c2bb40fdab"`);
await queryRunner.query(`DROP INDEX "IDX_8cf3156fd7a6b15c43459c6e3b"`);
await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
await queryRunner.query(`DROP INDEX "IDX_0c641990ecf47d2545df4edb75"`);
await queryRunner.query(`DROP INDEX "IDX_234dff3c0b56a6150b95431ab9"`);
await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
await queryRunner.query(`DROP INDEX "IDX_437bab3c6061d90f6bb65fd2cc"`);
await queryRunner.query(`DROP INDEX "IDX_bbfa573a8181018851ed0b6357"`);
await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
await queryRunner.query(`DROP INDEX "IDX_b070a906db04b44c67c6c2144d"`);
await queryRunner.query(`DROP INDEX "IDX_d41cce6aee1a50bfc062038f9b"`);
await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
await queryRunner.query(`DROP INDEX "IDX_845254b3eaf708ae8a6cac3026"`);
await queryRunner.query(`DROP INDEX "IDX_ed9b95919c672a13008e9487ee"`);
await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_60c5c6e7e538c09aa274ecd1cf" ON "__chart__active_users" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d" ON "__chart__drive" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_eddfed8fb40305a04c6f941050" ON "__chart__federation" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_53a3604b939e2b479eb2cfaac8" ON "__chart__hashtag" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_8111b817b9818c04d7eb8475b1" ON "__chart__instance" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2082327b2699ce924fa654afc5" ON "__chart__network" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e60c358aaced5aab8900a4af31" ON "__chart__notes" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a9a806d466b314f253a1a611c4" ON "__chart__per_user_drive" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dabbb38a51ab86ee3cab291326" ON "__chart__per_user_following" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_583a157ed0cf0ed1b5ec2a833f" ON "__chart__per_user_notes" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3b7697a96f522d0478972e6d6f" ON "__chart__per_user_reaction" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_da522b4008a9f5d7743b87ad55" ON "__chart__test_grouped" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_16effb2e888f6763673b579f80" ON "__chart__test_unique" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_dab383a36f3c9db4a0c9b02cf3" ON "__chart__test" ("date") WHERE "group" IS NULL`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_66feba81e1795d176d06c0b1e6" ON "__chart__users" ("date") WHERE "group" IS NULL`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_66feba81e1795d176d06c0b1e6"`);
await queryRunner.query(`DROP INDEX "IDX_337e9599f278bd7537fe30876f"`);
await queryRunner.query(`DROP INDEX "IDX_dab383a36f3c9db4a0c9b02cf3"`);
await queryRunner.query(`DROP INDEX "IDX_a319e5dbf47e8a17497623beae"`);
await queryRunner.query(`DROP INDEX "IDX_16effb2e888f6763673b579f80"`);
await queryRunner.query(`DROP INDEX "IDX_a0cd75442dd10d0643a17c4a49"`);
await queryRunner.query(`DROP INDEX "IDX_da522b4008a9f5d7743b87ad55"`);
await queryRunner.query(`DROP INDEX "IDX_b14489029e4b3aaf4bba5fb524"`);
await queryRunner.query(`DROP INDEX "IDX_3b7697a96f522d0478972e6d6f"`);
await queryRunner.query(`DROP INDEX "IDX_229a41ad465f9205f1f5703291"`);
await queryRunner.query(`DROP INDEX "IDX_583a157ed0cf0ed1b5ec2a833f"`);
await queryRunner.query(`DROP INDEX "IDX_5048e9daccbbbc6d567bb142d3"`);
await queryRunner.query(`DROP INDEX "IDX_dabbb38a51ab86ee3cab291326"`);
await queryRunner.query(`DROP INDEX "IDX_b77d4dd9562c3a899d9a286fcd"`);
await queryRunner.query(`DROP INDEX "IDX_a9a806d466b314f253a1a611c4"`);
await queryRunner.query(`DROP INDEX "IDX_30bf67687f483ace115c5ca642"`);
await queryRunner.query(`DROP INDEX "IDX_e60c358aaced5aab8900a4af31"`);
await queryRunner.query(`DROP INDEX "IDX_f09d543e3acb16c5976bdb31fa"`);
await queryRunner.query(`DROP INDEX "IDX_2082327b2699ce924fa654afc5"`);
await queryRunner.query(`DROP INDEX "IDX_0a905b992fecd2b5c3fb98759e"`);
await queryRunner.query(`DROP INDEX "IDX_8111b817b9818c04d7eb8475b1"`);
await queryRunner.query(`DROP INDEX "IDX_39ee857ab2f23493037c6b6631"`);
await queryRunner.query(`DROP INDEX "IDX_53a3604b939e2b479eb2cfaac8"`);
await queryRunner.query(`DROP INDEX "IDX_25a97c02003338124b2b75fdbc"`);
await queryRunner.query(`DROP INDEX "IDX_eddfed8fb40305a04c6f941050"`);
await queryRunner.query(`DROP INDEX "IDX_dd907becf76104e4b656659e6b"`);
await queryRunner.query(`DROP INDEX "IDX_ceab80a6729f8e2e6f5b8a1a3d"`);
await queryRunner.query(`DROP INDEX "IDX_3313d7288855ec105b5bbf6c21"`);
await queryRunner.query(`DROP INDEX "IDX_60c5c6e7e538c09aa274ecd1cf"`);
await queryRunner.query(`DROP INDEX "IDX_9a3ed15a30ab7e3a37702e6e08"`);
await queryRunner.query(`DROP INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5"`);
await queryRunner.query(`DROP INDEX "IDX_f22169eb10657bded6d875ac8f"`);
await queryRunner.query(`DROP INDEX "IDX_c8cc87bd0f2f4487d17c651fbf"`);
await queryRunner.query(`DROP INDEX "IDX_754499f9b2642336433769518d"`);
await queryRunner.query(`DROP INDEX "IDX_315c779174fe8247ab324f036e"`);
await queryRunner.query(`DROP INDEX "IDX_c5d46cbfda48b1c33ed852e21b"`);
await queryRunner.query(`CREATE INDEX "IDX_337e9599f278bd7537fe30876f" ON "__chart__users" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_ed9b95919c672a13008e9487ee" ON "__chart__users" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_845254b3eaf708ae8a6cac3026" ON "__chart__users" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_a319e5dbf47e8a17497623beae" ON "__chart__test" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_d41cce6aee1a50bfc062038f9b" ON "__chart__test" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_b070a906db04b44c67c6c2144d" ON "__chart__test" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_a0cd75442dd10d0643a17c4a49" ON "__chart__test_unique" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_bbfa573a8181018851ed0b6357" ON "__chart__test_unique" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_437bab3c6061d90f6bb65fd2cc" ON "__chart__test_unique" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_b14489029e4b3aaf4bba5fb524" ON "__chart__test_grouped" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_234dff3c0b56a6150b95431ab9" ON "__chart__test_grouped" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_0c641990ecf47d2545df4edb75" ON "__chart__test_grouped" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_229a41ad465f9205f1f5703291" ON "__chart__per_user_reaction" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_8cf3156fd7a6b15c43459c6e3b" ON "__chart__per_user_reaction" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_f7bf4c62059764c2c2bb40fdab" ON "__chart__per_user_reaction" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_5048e9daccbbbc6d567bb142d3" ON "__chart__per_user_notes" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_55bf20f366979f2436de99206b" ON "__chart__per_user_notes" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_84234bd1abb873f07329681c83" ON "__chart__per_user_notes" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_b77d4dd9562c3a899d9a286fcd" ON "__chart__per_user_following" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_4b3593098b6edc9c5afe36b18b" ON "__chart__per_user_following" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_7af07790712aa3438ff6773f3b" ON "__chart__per_user_following" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_30bf67687f483ace115c5ca642" ON "__chart__per_user_drive" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_e496ca8096d28f6b9b509264dc" ON "__chart__per_user_drive" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_5f86db6492274e07c1a3cdf286" ON "__chart__per_user_drive" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_f09d543e3acb16c5976bdb31fa" ON "__chart__notes" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_7036f2957151588b813185c794" ON "__chart__notes" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_42eb716a37d381cdf566192b2b" ON "__chart__notes" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_0a905b992fecd2b5c3fb98759e" ON "__chart__network" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_7b5da130992ec9df96712d4290" ON "__chart__network" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_a1efd3e0048a5f2793a47360dc" ON "__chart__network" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_39ee857ab2f23493037c6b6631" ON "__chart__instance" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_da8a46ba84ca1d8bb5a29bfb63" ON "__chart__instance" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_6b8f34a1a64b06014b6fb66824" ON "__chart__instance" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_25a97c02003338124b2b75fdbc" ON "__chart__hashtag" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_99a7d2faaef84a6f728d714ad6" ON "__chart__hashtag" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_07747a1038c05f532a718fe1de" ON "__chart__hashtag" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_dd907becf76104e4b656659e6b" ON "__chart__federation" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_76e87c7bfc5d925fcbba405d84" ON "__chart__federation" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_36cb699c49580d4e6c2e6159f9" ON "__chart__federation" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_3313d7288855ec105b5bbf6c21" ON "__chart__drive" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_7a170f67425e62a8fabb76c872" ON "__chart__drive" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_13565815f618a1ff53886c5b28" ON "__chart__drive" ("date") `);
await queryRunner.query(`CREATE INDEX "IDX_9a3ed15a30ab7e3a37702e6e08" ON "__chart__active_users" ("date", "group") `);
await queryRunner.query(`CREATE INDEX "IDX_00ed5f86db1f7efafb1978bf21" ON "__chart__active_users" ("group") `);
await queryRunner.query(`CREATE INDEX "IDX_0ad37b7ef50f4ddc84363d7ccc" ON "__chart__active_users" ("date") `);
}
}

View file

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class deeplIntegration1629024377804 implements MigrationInterface {
name = 'deeplIntegration1629024377804'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" ADD "deeplAuthKey" character varying(128)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplAuthKey"`);
}
}

View file

@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class fixChannelUserId1629288472000 implements MigrationInterface {
name = 'fixChannelUserId1629288472000'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" DROP NOT NULL;`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "channel" ALTER COLUMN "userId" SET NOT NULL;`);
}
}

View file

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.84.3", "version": "12.88.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -10,7 +10,8 @@
"main": "./index.js", "main": "./index.js",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./index.js", "start": "node --experimental-json-modules ./index.js",
"start:test": "cross-env NODE_ENV=test node --experimental-json-modules ./index.js",
"init": "npm run migrate", "init": "npm run migrate",
"ormconfig": "node ./built/ormconfig.js", "ormconfig": "node ./built/ormconfig.js",
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run", "migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
@ -26,6 +27,9 @@
"clean": "gulp clean", "clean": "gulp clean",
"cleanall": "gulp cleanall", "cleanall": "gulp cleanall",
"lint": "tslint 'src/**/*.ts'", "lint": "tslint 'src/**/*.ts'",
"cy:open": "cypress open",
"cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost cy:run",
"test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"format": "gulp format" "format": "gulp format"
}, },
@ -34,7 +38,6 @@
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"dependencies": { "dependencies": {
"@babel/plugin-transform-runtime": "7.14.5",
"@elastic/elasticsearch": "7.11.0", "@elastic/elasticsearch": "7.11.0",
"@koa/cors": "3.1.0", "@koa/cors": "3.1.0",
"@koa/multer": "3.0.0", "@koa/multer": "3.0.0",
@ -44,7 +47,7 @@
"@sinonjs/fake-timers": "7.1.2", "@sinonjs/fake-timers": "7.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.2", "@types/bull": "3.15.3",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/dateformat": "3.0.1", "@types/dateformat": "3.0.1",
"@types/escape-regexp": "0.0.0", "@types/escape-regexp": "0.0.0",
@ -57,8 +60,8 @@
"@types/jsonld": "1.5.6", "@types/jsonld": "1.5.6",
"@types/katex": "0.11.1", "@types/katex": "0.11.1",
"@types/koa": "2.13.4", "@types/koa": "2.13.4",
"@types/koa-bodyparser": "4.3.2", "@types/koa-bodyparser": "4.3.3",
"@types/koa-cors": "0.0.1", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.1", "@types/koa-logger": "3.1.1",
"@types/koa-mount": "4.0.0", "@types/koa-mount": "4.0.0",
@ -68,10 +71,10 @@
"@types/koa__multer": "2.0.3", "@types/koa__multer": "2.0.3",
"@types/koa__router": "8.0.7", "@types/koa__router": "8.0.7",
"@types/markdown-it": "12.0.3", "@types/markdown-it": "12.0.3",
"@types/matter-js": "0.17.3", "@types/matter-js": "0.17.5",
"@types/mocha": "8.2.3", "@types/mocha": "8.2.3",
"@types/node": "16.3.3", "@types/node": "16.6.2",
"@types/node-fetch": "2.5.11", "@types/node-fetch": "2.5.12",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -86,9 +89,9 @@
"@types/redis": "2.8.31", "@types/redis": "2.8.31",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.1", "@types/rimraf": "3.0.2",
"@types/seedrandom": "2.4.28", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.28.4", "@types/sharp": "0.28.5",
"@types/sinonjs__fake-timers": "6.0.3", "@types/sinonjs__fake-timers": "6.0.3",
"@types/speakeasy": "2.0.6", "@types/speakeasy": "2.0.6",
"@types/throttle-debounce": "2.1.0", "@types/throttle-debounce": "2.1.0",
@ -98,40 +101,40 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/webpack": "5.28.0", "@types/webpack": "5.28.0",
"@types/webpack-stream": "3.2.12", "@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.3", "@types/websocket": "1.0.4",
"@types/ws": "7.4.6", "@types/ws": "7.4.7",
"@typescript-eslint/parser": "4.28.3", "@typescript-eslint/parser": "4.29.2",
"@vue/compiler-sfc": "3.1.5", "@vue/compiler-sfc": "3.2.4",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"apexcharts": "3.27.2", "apexcharts": "3.27.3",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.4", "autosize": "4.0.4",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.948.0", "aws-sdk": "2.966.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.3", "blurhash": "1.1.4",
"broadcast-channel": "3.7.0", "broadcast-channel": "4.2.0",
"bull": "3.26.0", "bull": "3.28.1",
"cacheable-lookup": "6.0.0",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "7.0.6", "cbor": "8.0.0",
"chalk": "4.1.1", "chalk": "4.1.2",
"chart.js": "2.9.4", "chart.js": "2.9.4",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"commander": "7.2.0", "commander": "8.1.0",
"concurrently": "6.2.0", "compare-versions": "3.6.0",
"concurrently": "6.2.1",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"core-js": "3.15.2",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "6.0.0", "css-loader": "6.2.0",
"cssnano": "5.0.6", "cssnano": "5.0.8",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"diskusage": "1.1.3",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "7.30.0", "eslint": "7.32.0",
"eslint-plugin-vue": "7.13.0", "eslint-plugin-vue": "7.16.0",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "16.5.1", "file-type": "16.5.3",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"glob": "7.1.7", "glob": "7.1.7",
"got": "11.8.2", "got": "11.8.2",
@ -141,22 +144,18 @@
"gulp-replace": "1.1.3", "gulp-replace": "1.1.3",
"gulp-terser": "2.0.1", "gulp-terser": "2.0.1",
"gulp-tslint": "8.1.4", "gulp-tslint": "8.1.4",
"hard-source-webpack-plugin": "0.13.1", "hpagent": "0.1.2",
"html-minifier": "4.0.0",
"http-proxy-agent": "4.0.1",
"http-signature": "1.3.5", "http-signature": "1.3.5",
"https-proxy-agent": "5.0.0", "idb-keyval": "5.1.3",
"idb-keyval": "5.0.6",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.3.1", "is-svg": "4.3.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "16.6.0", "jsdom": "16.7.0",
"json5": "2.2.0", "json5": "2.2.0",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "5.2.0", "jsonld": "5.2.0",
"jsrsasign": "8.0.20", "jsrsasign": "8.0.20",
"katex": "0.13.11", "katex": "0.13.13",
"koa": "2.13.1", "koa": "2.13.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.3.0",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
@ -167,30 +166,26 @@
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.1", "koa-views": "7.0.1",
"langmap": "0.0.16", "langmap": "0.0.16",
"lookup-dns-cache": "2.1.0", "markdown-it": "12.2.0",
"markdown-it": "12.1.0",
"markdown-it-anchor": "7.1.0", "markdown-it-anchor": "7.1.0",
"matter-js": "0.17.1", "matter-js": "0.17.1",
"mfm-js": "0.19.0", "mfm-js": "0.19.0",
"misskey-js": "0.0.6", "misskey-js": "0.0.6",
"mocha": "8.4.0", "mocha": "8.4.0",
"moji": "0.5.1",
"ms": "2.1.3", "ms": "2.1.3",
"multer": "1.4.2", "multer": "1.4.3",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"nodemailer": "6.6.3", "nodemailer": "6.6.3",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.6.0", "pg": "8.7.1",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.3.5", "postcss": "8.3.6",
"postcss-loader": "6.1.1", "postcss-loader": "6.1.1",
"prismjs": "1.24.1", "prismjs": "1.24.1",
"probe-image-size": "7.2.1", "probe-image-size": "7.2.1",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "3.0.2", "pug": "3.0.2",
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.3.2", "pureimage": "0.3.2",
@ -198,67 +193,62 @@
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.16.0", "re2": "1.16.0",
"reconnecting-websocket": "4.4.0",
"redis": "3.1.2", "redis": "3.1.2",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"regenerator-runtime": "0.13.7",
"rename": "1.0.4", "rename": "1.0.4",
"request-stats": "3.0.0", "request-stats": "3.0.0",
"require-all": "3.0.0", "require-all": "3.0.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.35.2", "sass": "1.38.0",
"sass-loader": "12.1.0", "sass-loader": "12.1.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.28.3", "sharp": "0.29.0",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "3.1.0", "style-loader": "3.2.1",
"summaly": "2.4.0", "summaly": "2.4.1",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.7.7", "systeminformation": "5.8.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.117.1", "three": "0.117.1",
"throttle-debounce": "3.0.1", "throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "9.2.3", "ts-loader": "9.2.5",
"ts-node": "10.1.0", "ts-node": "10.2.1",
"tsc-alias": "1.3.7", "tsc-alias": "1.3.9",
"tsconfig-paths": "3.10.1", "tsconfig-paths": "3.10.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"twemoji-parser": "13.1.0", "twemoji-parser": "13.1.0",
"typeorm": "0.2.32", "typeorm": "0.2.37",
"typescript": "4.3.5", "typescript": "4.3.5",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
"vanilla-tilt": "1.7.0", "vue": "3.2.4",
"vue": "3.1.5", "vue-loader": "16.5.0",
"vue-color": "2.8.1",
"vue-json-pretty": "1.8.1",
"vue-loader": "16.3.1",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.5", "vue-router": "4.0.5",
"vue-style-loader": "4.1.3", "vue-style-loader": "4.1.3",
"vue-svg-loader": "0.17.0-beta.2", "vue-svg-loader": "0.17.0-beta.2",
"vuedraggable": "4.0.1", "vuedraggable": "4.0.1",
"web-push": "3.4.5", "web-push": "3.4.5",
"webpack": "5.45.1", "webpack": "5.51.0",
"webpack-cli": "4.7.2", "webpack-cli": "4.8.0",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "7.5.3", "ws": "8.2.0",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.44", "@redocly/openapi-core": "1.0.0-beta.44",
"@types/chai": "4.2.16",
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.17",
"chai": "4.3.4", "cross-env": "7.0.3",
"cross-env": "7.0.3" "cypress": "8.3.0",
"start-server-and-test": "1.13.1"
} }
} }

View file

@ -1,7 +0,0 @@
declare module 'is-root' {
function isRoot(): boolean;
namespace isRoot {} // Hack
export = isRoot;
}

View file

@ -1,9 +0,0 @@
declare module 'lookup-dns-cache' {
import { LookupOneOptions, LookupAllOptions, LookupOptions, LookupAddress } from 'dns';
function lookup(hostname: string, family: number, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
function lookup(hostname: string, options: LookupOneOptions, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
function lookup(hostname: string, options: LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void): void;
function lookup(hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family: number) => void): void;
function lookup(hostname: string, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void): void;
}

View file

@ -1,24 +1,23 @@
import { Command } from 'commander'; import { Command } from 'commander';
import config from '@/config'; import config from '@/config/index';
const program = new Command(); const program = new Command();
program program.version(config.version);
.version(config.version) program.option('--no-daemons', 'Disable daemon processes (for debbuging)');
.option('--no-daemons', 'Disable daemon processes (for debbuging)') program.option('--disable-clustering', 'Disable clustering');
.option('--disable-clustering', 'Disable clustering') program.option('--only-server', 'Run server only (without job queue processing)');
.option('--only-server', 'Run server only (without job queue processing)') program.option('--only-queue', 'Pocessing job queue only (without server)');
.option('--only-queue', 'Pocessing job queue only (without server)') program.option('--quiet', 'Suppress all logs');
.option('--quiet', 'Suppress all logs') program.option('--verbose', 'Enable all logs');
.option('--verbose', 'Enable all logs') program.option('--with-log-time', 'Include timestamp for each logs');
.option('--with-log-time', 'Include timestamp for each logs') program.option('--slow', 'Delay all requests (for debbuging)');
.option('--slow', 'Delay all requests (for debbuging)') program.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.');
.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.') program.parse(process.argv);
.parse(process.argv);
if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true; if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true;
if (process.env.NODE_ENV === 'test') program.disableClustering = true; if (process.env.NODE_ENV === 'test') program.disableClustering = true;
if (process.env.NODE_ENV === 'test') program.quiet = true; //if (process.env.NODE_ENV === 'test') program.quiet = true;
if (process.env.NODE_ENV === 'test') program.noDaemons = true; if (process.env.NODE_ENV === 'test') program.noDaemons = true;
export { program }; export { program };

View file

@ -2,7 +2,7 @@ import * as cluster from 'cluster';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import Xev from 'xev'; import Xev from 'xev';
import Logger from '../services/logger'; import Logger from '@/services/logger';
import { program } from '../argv'; import { program } from '../argv';
// for typeorm // for typeorm

View file

@ -2,17 +2,16 @@ import * as os from 'os';
import * as cluster from 'cluster'; import * as cluster from 'cluster';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import * as portscanner from 'portscanner'; import * as portscanner from 'portscanner';
import * as isRoot from 'is-root';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import Logger from '../services/logger'; import Logger from '@/services/logger';
import loadConfig from '@/config/load'; import loadConfig from '@/config/load';
import { Config } from '@/config/types'; import { Config } from '@/config/types';
import { lessThan } from '../prelude/array'; import { lessThan } from '@/prelude/array';
import { program } from '../argv'; import { program } from '../argv';
import { showMachineInfo } from '@/misc/show-machine-info'; import { showMachineInfo } from '@/misc/show-machine-info';
import { initDb } from '../db/postgre'; import { initDb } from '../db/postgre';
const meta = require('../meta.json'); import * as meta from '../meta.json';
const logger = new Logger('core', 'cyan'); const logger = new Logger('core', 'cyan');
const bootLogger = logger.createSubLogger('boot', 'magenta', false); const bootLogger = logger.createSubLogger('boot', 'magenta', false);
@ -39,6 +38,11 @@ function greet() {
bootLogger.info(`Misskey v${meta.version}`, null, true); bootLogger.info(`Misskey v${meta.version}`, null, true);
} }
function isRoot() {
// maybe process.getuid will be undefined under not POSIX environment (e.g. Windows)
return process.getuid != null && process.getuid() === 0;
}
/** /**
* Init master process * Init master process
*/ */

View file

@ -10,9 +10,9 @@
</template> </template>
<div class="dpvffvvy _monolithic_"> <div class="dpvffvvy _monolithic_">
<div class="_section"> <div class="_section">
<MkTextarea v-model:value="comment"> <MkTextarea v-model="comment">
<span>{{ $ts.details }}</span> <template #label>{{ $ts.details }}</template>
<template #desc>{{ $ts.fillAbuseReportDescription }}</template> <template #caption>{{ $ts.fillAbuseReportDescription }}</template>
</MkTextarea> </MkTextarea>
</div> </div>
<div class="_section"> <div class="_section">

View file

@ -35,6 +35,7 @@ import { twemojiSvgBase } from '@/misc/twemoji-base';
import { getStaticImageUrl } from '@client/scripts/get-static-image-url'; import { getStaticImageUrl } from '@client/scripts/get-static-image-url';
import { acct } from '@client/filters/user'; import { acct } from '@client/filters/user';
import * as os from '@client/os'; import * as os from '@client/os';
import { instance } from '@client/instance';
type EmojiDef = { type EmojiDef = {
emoji: string; emoji: string;
@ -75,6 +76,36 @@ for (const x of lib) {
emjdb.sort((a, b) => a.name.length - b.name.length); emjdb.sort((a, b) => a.name.length - b.name.length);
//#region Construct Emoji DB
const customEmojis = instance.emojis;
const emojiDefinitions: EmojiDef[] = [];
for (const x of customEmojis) {
emojiDefinitions.push({
name: x.name,
emoji: `:${x.name}:`,
url: x.url,
isCustomEmoji: true
});
if (x.aliases) {
for (const alias of x.aliases) {
emojiDefinitions.push({
name: alias,
aliasOf: x.name,
emoji: `:${x.name}:`,
url: x.url,
isCustomEmoji: true
});
}
}
}
emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
//#endregion
export default defineComponent({ export default defineComponent({
props: { props: {
type: { type: {
@ -124,7 +155,6 @@ export default defineComponent({
emojis: [], emojis: [],
items: [], items: [],
select: -1, select: -1,
emojiDb: [] as EmojiDef[]
} }
}, },
@ -144,36 +174,6 @@ export default defineComponent({
mounted() { mounted() {
this.setPosition(); this.setPosition();
//#region Construct Emoji DB
const customEmojis = this.$instance.emojis;
const emojiDefinitions: EmojiDef[] = [];
for (const x of customEmojis) {
emojiDefinitions.push({
name: x.name,
emoji: `:${x.name}:`,
url: x.url,
isCustomEmoji: true
});
if (x.aliases) {
for (const alias of x.aliases) {
emojiDefinitions.push({
name: alias,
aliasOf: x.name,
emoji: `:${x.name}:`,
url: x.url,
isCustomEmoji: true
});
}
}
}
emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
this.emojiDb = markRaw(emojiDefinitions.concat(emjdb));
//#endregion
this.textarea.addEventListener('keydown', this.onKeydown); this.textarea.addEventListener('keydown', this.onKeydown);
for (const el of Array.from(document.querySelectorAll('body *'))) { for (const el of Array.from(document.querySelectorAll('body *'))) {
@ -203,6 +203,13 @@ export default defineComponent({
complete(type, value) { complete(type, value) {
this.$emit('done', { type, value }); this.$emit('done', { type, value });
this.$emit('closed'); this.$emit('closed');
if (type === 'emoji') {
let recents = this.$store.state.recentlyUsedEmojis;
recents = recents.filter((e: any) => e !== value);
recents.unshift(value);
this.$store.set('recentlyUsedEmojis', recents.splice(0, 32));
}
}, },
setPosition() { setPosition() {
@ -281,29 +288,26 @@ export default defineComponent({
} }
} else if (this.type == 'emoji') { } else if (this.type == 'emoji') {
if (this.q == null || this.q == '') { if (this.q == null || this.q == '') {
this.emojis = this.emojiDb.filter(x => x.isCustomEmoji && !x.aliasOf).sort((a, b) => { // 使
var textA = a.name.toUpperCase(); this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null);
var textB = b.name.toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
});
return; return;
} }
const matched = []; const matched = [];
const max = 30; const max = 30;
this.emojiDb.some(x => { emojiDb.some(x => {
if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
return matched.length == max; return matched.length == max;
}); });
if (matched.length < max) { if (matched.length < max) {
this.emojiDb.some(x => { emojiDb.some(x => {
if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
return matched.length == max; return matched.length == max;
}); });
} }
if (matched.length < max) { if (matched.length < max) {
this.emojiDb.some(x => { emojiDb.some(x => {
if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x);
return matched.length == max; return matched.length == max;
}); });

View file

@ -48,15 +48,7 @@ export default defineComponent({
render() { render() {
if (this.items.length === 0) return; if (this.items.length === 0) return;
return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? { const renderChildren = () => this.items.map((item, i) => {
class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
name: 'list',
tag: 'div',
'data-direction': this.direction,
'data-reversed': this.reversed ? 'true' : 'false',
} : {
class: 'sqadhkmv' + (this.noGap ? ' noGap _block' : ''),
}, this.items.map((item, i) => {
const el = this.$slots.default({ const el = this.$slots.default({
item: item item: item
})[0]; })[0];
@ -98,7 +90,19 @@ export default defineComponent({
return el; return el;
} }
} }
})); });
return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? {
class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''),
name: 'list',
tag: 'div',
'data-direction': this.direction,
'data-reversed': this.reversed ? 'true' : 'false',
} : {
class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''),
}, {
default: renderChildren
});
}, },
}); });
</script> </script>

View file

@ -14,8 +14,8 @@
</div> </div>
<header v-if="title"><Mfm :text="title"/></header> <header v-if="title"><Mfm :text="title"/></header>
<div class="body" v-if="text"><Mfm :text="text"/></div> <div class="body" v-if="text"><Mfm :text="text"/></div>
<MkInput v-if="input" v-model:value="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput> <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput>
<MkSelect v-if="select" v-model:value="selectedValue" autofocus> <MkSelect v-if="select" v-model="selectedValue" autofocus>
<template v-if="select.items"> <template v-if="select.items">
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> <option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
</template> </template>

View file

@ -114,7 +114,7 @@ export default defineComponent({
if (this.selectMode) { if (this.selectMode) {
this.$emit('chosen', this.file); this.$emit('chosen', this.file);
} else { } else {
os.modalMenu(this.getMenu(), ev.currentTarget || ev.target); os.popupMenu(this.getMenu(), ev.currentTarget || ev.target);
} }
}, },

View file

@ -10,6 +10,7 @@
<span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span> <span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span>
<span class="folder current" v-if="folder != null">{{ folder.name }}</span> <span class="folder current" v-if="folder != null">{{ folder.name }}</span>
</div> </div>
<button @click="showMenu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
</nav> </nav>
<div class="main" :class="{ uploading: uploadings.length > 0, fetching }" <div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
ref="main" ref="main"
@ -627,8 +628,12 @@ export default defineComponent({
}]; }];
}, },
onContextmenu(e) { showMenu(ev) {
os.contextMenu(this.getMenu(), e); os.popupMenu(this.getMenu(), ev.currentTarget || ev.target);
},
onContextmenu(ev) {
os.contextMenu(this.getMenu(), ev);
}, },
} }
}); });
@ -641,7 +646,7 @@ export default defineComponent({
height: 100%; height: 100%;
> nav { > nav {
display: block; display: flex;
z-index: 2; z-index: 2;
width: 100%; width: 100%;
padding: 0 8px; padding: 0 8px;
@ -696,6 +701,10 @@ export default defineComponent({
} }
} }
} }
> .menu {
margin-left: auto;
}
} }
> .main { > .main {

View file

@ -1,17 +1,17 @@
<template> <template>
<MkModal ref="modal" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> <MkPopup ref="popup" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.popup.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')" #default="{point}">
<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/> <MkEmojiPicker class="ryghynhb _popup _shadow" :class="{ pointer: point === 'top' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/>
</MkModal> </MkPopup>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, markRaw } from 'vue'; import { defineComponent, markRaw } from 'vue';
import MkModal from '@client/components/ui/modal.vue'; import MkPopup from '@client/components/ui/popup.vue';
import MkEmojiPicker from '@client/components/emoji-picker.vue'; import MkEmojiPicker from '@client/components/emoji-picker.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
MkModal, MkPopup,
MkEmojiPicker, MkEmojiPicker,
}, },
@ -33,7 +33,7 @@ export default defineComponent({
}, },
}, },
emits: ['done', 'closed'], emits: ['done', 'close', 'closed'],
data() { data() {
return { return {
@ -44,7 +44,7 @@ export default defineComponent({
methods: { methods: {
chosen(emoji: any) { chosen(emoji: any) {
this.$emit('done', emoji); this.$emit('done', emoji);
this.$refs.modal.close(); this.$refs.popup.close();
}, },
opening() { opening() {
@ -56,145 +56,20 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.omfetrab { .ryghynhb {
$pad: 8px; &.pointer {
--eachSize: 40px; &:before {
--size: 8px;
display: flex; content: '';
flex-direction: column; display: block;
contain: content;
&.big {
--eachSize: 44px;
}
&.w1 {
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
}
&.w2 {
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
}
&.w3 {
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
}
&.h1 {
--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
}
&.h2 {
--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
}
&.h3 {
--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
}
> .search {
width: 100%;
padding: 12px;
box-sizing: border-box;
font-size: 1em;
outline: none;
border: none;
background: transparent;
color: var(--fg);
&:not(.filled) {
order: 1;
z-index: 2;
box-shadow: 0px -1px 0 0px var(--divider);
}
}
> .emojis {
height: var(--height);
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
> .index {
min-height: var(--height);
position: relative;
border-bottom: solid 0.5px var(--divider);
> .arrow {
position: absolute; position: absolute;
bottom: 0; top: calc(0px - (var(--size) * 2));
left: 0; left: 0;
width: 100%; right: 0;
padding: 16px 0; width: 0;
text-align: center; margin: auto;
opacity: 0.5; border: solid var(--size) transparent;
pointer-events: none; border-bottom-color: var(--popup);
}
}
section {
> header {
position: sticky;
top: 0;
left: 0;
z-index: 1;
padding: 8px;
font-size: 12px;
}
> div {
padding: $pad;
> button {
position: relative;
padding: 0;
width: var(--eachSize);
height: var(--eachSize);
border-radius: 4px;
&:focus {
outline: solid 2px var(--focus);
z-index: 1;
}
&:hover {
background: rgba(0, 0, 0, 0.05);
}
&:active {
background: var(--accent);
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
}
> * {
font-size: 24px;
height: 1.25em;
vertical-align: -.25em;
pointer-events: none;
}
}
}
&.result {
border-bottom: solid 0.5px var(--divider);
&:empty {
display: none;
}
}
&.unicode {
min-height: 384px;
}
&.custom {
min-height: 64px;
}
} }
} }
} }

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]"> <div class="omfetrab" :class="['w' + width, 'h' + height, { big }]">
<input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> <input ref="search" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
<div class="emojis" ref="emojis"> <div class="emojis" ref="emojis">
<section class="result"> <section class="result">
@ -346,7 +346,6 @@ export default defineComponent({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
contain: content;
&.big { &.big {
--eachSize: 44px; --eachSize: 44px;

View file

@ -9,14 +9,14 @@
<form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail"> <form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail">
<div class="_section"> <div class="_section">
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
<span>{{ $ts.username }}</span> <template #label>{{ $ts.username }}</template>
<template #prefix>@</template> <template #prefix>@</template>
</MkInput> </MkInput>
<MkInput v-model:value="email" type="email" spellcheck="false" required> <MkInput v-model="email" type="email" spellcheck="false" required>
<span>{{ $ts.emailAddress }}</span> <template #label>{{ $ts.emailAddress }}</template>
<template #desc>{{ $ts._forgotPassword.enterEmail }}</template> <template #caption>{{ $ts._forgotPassword.enterEmail }}</template>
</MkInput> </MkInput>
<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> <MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton>

View file

@ -32,8 +32,8 @@
margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1); margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1);
padding: 8px calc(var(--formContentHMargin) + var(--formXPadding)) 8px calc(var(--formContentHMargin) + var(--formXPadding)); padding: 8px calc(var(--formContentHMargin) + var(--formXPadding)) 8px calc(var(--formContentHMargin) + var(--formXPadding));
background: var(--X17); background: var(--X17);
-webkit-backdrop-filter: blur(10px); -webkit-backdrop-filter: var(--blur, blur(10px));
backdrop-filter: blur(10px); backdrop-filter: var(--blur, blur(10px));
} }
._themeChanging_ ._formLabel { ._themeChanging_ ._formLabel {

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="yxspomdl" :class="{ inline, colored }"> <div class="yxspomdl" :class="{ inline, colored, mini }">
<div class="ring"></div> <div class="ring"></div>
</div> </div>
</template> </template>
@ -18,7 +18,12 @@ export default defineComponent({
type: Boolean, type: Boolean,
required: false, required: false,
default: true default: true
} },
mini: {
type: Boolean,
required: false,
default: false
},
} }
}); });
</script> </script>
@ -38,6 +43,8 @@ export default defineComponent({
text-align: center; text-align: center;
cursor: wait; cursor: wait;
--size: 48px;
&.colored { &.colored {
color: var(--accent); color: var(--accent);
} }
@ -45,19 +52,12 @@ export default defineComponent({
&.inline { &.inline {
display: inline; display: inline;
padding: 0; padding: 0;
--size: 32px;
> .ring:after {
width: 32px;
height: 32px;
} }
> .ring { &.mini {
&:before, padding: 16px;
&:after { --size: 32px;
width: 32px;
height: 32px;
}
}
} }
> .ring { > .ring {
@ -70,8 +70,8 @@ export default defineComponent({
content: " "; content: " ";
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
width: 48px; width: var(--size);
height: 48px; height: var(--size);
border-radius: 50%; border-radius: 50%;
border: solid 4px; border: solid 4px;
} }

View file

@ -117,6 +117,11 @@ export default defineComponent({
75% { transform: scale3d(1.05, 0.95, 1); } 75% { transform: scale3d(1.05, 0.95, 1); }
to { transform: scale3d(1, 1, 1); } to { transform: scale3d(1, 1, 1); }
} }
@keyframes mfm-rainbow {
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="zbcjwnqg" style="margin-top: -8px;"> <div class="zbcjwnqg" style="margin-top: -8px;">
<div class="selects" style="display: flex;"> <div class="selects" style="display: flex;">
<MkSelect v-model:value="chartSrc" style="margin: 0; flex: 1;"> <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
<optgroup :label="$ts.federation"> <optgroup :label="$ts.federation">
<option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option> <option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option>
<option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option> <option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option>
@ -24,7 +24,7 @@
<option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option> <option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option>
</optgroup> </optgroup>
</MkSelect> </MkSelect>
<MkSelect v-model:value="chartSpan" style="margin: 0;"> <MkSelect v-model="chartSpan" style="margin: 0;">
<option value="hour">{{ $ts.perHour }}</option> <option value="hour">{{ $ts.perHour }}</option>
<option value="day">{{ $ts.perDay }}</option> <option value="day">{{ $ts.perDay }}</option>
</MkSelect> </MkSelect>

View file

@ -165,6 +165,10 @@ export default defineComponent({
class: '_mfm_blur_', class: '_mfm_blur_',
}, genEl(token.children)); }, genEl(token.children));
} }
case 'rainbow': {
style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
break;
}
} }
if (style == null) { if (style == null) {
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']); return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);

View file

@ -1,13 +1,10 @@
<template> <template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> <div class="hrmcaedk _window _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header" @contextmenu="onContextmenu"> <div class="header" @contextmenu="onContextmenu">
<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
<span class="title"> <span class="title">
<XHeader :info="pageInfo" :with-back="false"/> <XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="$refs.modal.close()"/>
</span> </span>
<button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button>
</div> </div>
<div class="body _flat_"> <div class="body _flat_">
<keep-alive> <keep-alive>
@ -177,35 +174,19 @@ export default defineComponent({
flex-shrink: 0; flex-shrink: 0;
box-shadow: 0px 1px var(--divider); box-shadow: 0px 1px var(--divider);
> button {
height: $height;
width: $height;
@media (max-width: 500px) {
height: $height-narrow;
width: $height-narrow;
}
}
> .title { > .title {
flex: 1; flex: 1;
line-height: $height; height: $height;
padding-left: 32px;
font-weight: bold; font-weight: bold;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
pointer-events: none;
@media (max-width: 500px) { @media (max-width: 500px) {
line-height: $height-narrow; height: $height-narrow;
padding-left: 16px; padding-left: 16px;
} }
} }
> button + .title {
padding-left: 0;
}
} }
> .body { > .body {

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="note _block" class="lxwezrsl _block"
v-if="!muted" v-if="!muted"
v-show="!isDeleted" v-show="!isDeleted"
:tabindex="!isDeleted ? '-1' : null" :tabindex="!isDeleted ? '-1' : null"
@ -67,6 +67,13 @@
<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<a class="rp" v-if="appearNote.renote != null">RN:</a> <a class="rp" v-if="appearNote.renote != null">RN:</a>
<div class="translation" v-if="translating || translation">
<MkLoading v-if="translating" mini/>
<div class="translated" v-else>
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}:</b>
{{ translation.text }}
</div>
</div>
</div> </div>
<div class="files" v-if="appearNote.files.length > 0"> <div class="files" v-if="appearNote.files.length > 0">
<XMediaList :media-list="appearNote.files"/> <XMediaList :media-list="appearNote.files"/>
@ -79,8 +86,8 @@
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="info"> <div class="info">
<span class="mobile" v-if="note.viaMobile"><i class="fas fa-mobile-alt"></i></span> <span class="mobile" v-if="appearNote.viaMobile"><i class="fas fa-mobile-alt"></i></span>
<MkTime class="created-at" :time="note.createdAt" mode="detail"/> <MkTime class="created-at" :time="appearNote.createdAt" mode="detail"/>
</div> </div>
<XReactionsViewer :note="appearNote" ref="reactionsViewer"/> <XReactionsViewer :note="appearNote" ref="reactionsViewer"/>
<button @click="reply()" class="button _button"> <button @click="reply()" class="button _button">
@ -178,6 +185,8 @@ export default defineComponent({
showContent: false, showContent: false,
isDeleted: false, isDeleted: false,
muted: false, muted: false,
translation: null,
translating: false,
}; };
}, },
@ -454,7 +463,7 @@ export default defineComponent({
renote(viaKeyboard = false) { renote(viaKeyboard = false) {
pleaseLogin(); pleaseLogin();
this.blur(); this.blur();
os.modalMenu([{ os.popupMenu([{
text: this.$ts.renote, text: this.$ts.renote,
icon: 'fas fa-retweet', icon: 'fas fa-retweet',
action: () => { action: () => {
@ -619,6 +628,11 @@ export default defineComponent({
text: this.$ts.share, text: this.$ts.share,
action: this.share action: this.share
}, },
this.$instance.translatorAvailable ? {
icon: 'fas fa-language',
text: this.$ts.translate,
action: this.translate
} : undefined,
null, null,
statePromise.then(state => state.isFavorited ? { statePromise.then(state => state.isFavorited ? {
icon: 'fas fa-star', icon: 'fas fa-star',
@ -743,14 +757,14 @@ export default defineComponent({
}, },
menu(viaKeyboard = false) { menu(viaKeyboard = false) {
os.modalMenu(this.getMenu(), this.$refs.menuButton, { os.popupMenu(this.getMenu(), this.$refs.menuButton, {
viaKeyboard viaKeyboard
}).then(this.focus); }).then(this.focus);
}, },
showRenoteMenu(viaKeyboard = false) { showRenoteMenu(viaKeyboard = false) {
if (!this.isMyRenote) return; if (!this.isMyRenote) return;
os.modalMenu([{ os.popupMenu([{
text: this.$ts.unrenote, text: this.$ts.unrenote,
icon: 'fas fa-trash-alt', icon: 'fas fa-trash-alt',
danger: true, danger: true,
@ -794,7 +808,7 @@ export default defineComponent({
async clip() { async clip() {
const clips = await os.api('clips/list'); const clips = await os.api('clips/list');
os.modalMenu([{ os.popupMenu([{
icon: 'fas fa-plus', icon: 'fas fa-plus',
text: this.$ts.createNew, text: this.$ts.createNew,
action: async () => { action: async () => {
@ -852,6 +866,17 @@ export default defineComponent({
}); });
}, },
async translate() {
if (this.translation != null) return;
this.translating = true;
const res = await os.api('notes/translate', {
noteId: this.appearNote.id,
targetLang: localStorage.getItem('lang') || navigator.language,
});
this.translating = false;
this.translation = res;
},
focus() { focus() {
this.$el.focus(); this.$el.focus();
}, },
@ -874,7 +899,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.note { .lxwezrsl {
position: relative; position: relative;
transition: box-shadow 0.1s ease; transition: box-shadow 0.1s ease;
overflow: hidden; overflow: hidden;
@ -1050,6 +1075,13 @@ export default defineComponent({
font-style: oblique; font-style: oblique;
color: var(--renote); color: var(--renote);
} }
> .translation {
border: solid 0.5px var(--divider);
border-radius: var(--radius);
padding: 12px;
margin-top: 8px;
}
} }
> .url-preview { > .url-preview {

View file

@ -24,8 +24,8 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import notePage from '../filters/note'; import notePage from '@client/filters/note';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
import * as os from '@client/os'; import * as os from '@client/os';
export default defineComponent({ export default defineComponent({

View file

@ -51,6 +51,13 @@
<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<a class="rp" v-if="appearNote.renote != null">RN:</a> <a class="rp" v-if="appearNote.renote != null">RN:</a>
<div class="translation" v-if="translating || translation">
<MkLoading v-if="translating" mini/>
<div class="translated" v-else>
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}:</b>
{{ translation.text }}
</div>
</div>
</div> </div>
<div class="files" v-if="appearNote.files.length > 0"> <div class="files" v-if="appearNote.files.length > 0">
<XMediaList :media-list="appearNote.files"/> <XMediaList :media-list="appearNote.files"/>
@ -164,6 +171,8 @@ export default defineComponent({
collapsed: false, collapsed: false,
isDeleted: false, isDeleted: false,
muted: false, muted: false,
translation: null,
translating: false,
}; };
}, },
@ -429,7 +438,7 @@ export default defineComponent({
renote(viaKeyboard = false) { renote(viaKeyboard = false) {
pleaseLogin(); pleaseLogin();
this.blur(); this.blur();
os.modalMenu([{ os.popupMenu([{
text: this.$ts.renote, text: this.$ts.renote,
icon: 'fas fa-retweet', icon: 'fas fa-retweet',
action: () => { action: () => {
@ -594,6 +603,11 @@ export default defineComponent({
text: this.$ts.share, text: this.$ts.share,
action: this.share action: this.share
}, },
this.$instance.translatorAvailable ? {
icon: 'fas fa-language',
text: this.$ts.translate,
action: this.translate
} : undefined,
null, null,
statePromise.then(state => state.isFavorited ? { statePromise.then(state => state.isFavorited ? {
icon: 'fas fa-star', icon: 'fas fa-star',
@ -718,14 +732,14 @@ export default defineComponent({
}, },
menu(viaKeyboard = false) { menu(viaKeyboard = false) {
os.modalMenu(this.getMenu(), this.$refs.menuButton, { os.popupMenu(this.getMenu(), this.$refs.menuButton, {
viaKeyboard viaKeyboard
}).then(this.focus); }).then(this.focus);
}, },
showRenoteMenu(viaKeyboard = false) { showRenoteMenu(viaKeyboard = false) {
if (!this.isMyRenote) return; if (!this.isMyRenote) return;
os.modalMenu([{ os.popupMenu([{
text: this.$ts.unrenote, text: this.$ts.unrenote,
icon: 'fas fa-trash-alt', icon: 'fas fa-trash-alt',
danger: true, danger: true,
@ -769,7 +783,7 @@ export default defineComponent({
async clip() { async clip() {
const clips = await os.api('clips/list'); const clips = await os.api('clips/list');
os.modalMenu([{ os.popupMenu([{
icon: 'fas fa-plus', icon: 'fas fa-plus',
text: this.$ts.createNew, text: this.$ts.createNew,
action: async () => { action: async () => {
@ -827,6 +841,17 @@ export default defineComponent({
}); });
}, },
async translate() {
if (this.translation != null) return;
this.translating = true;
const res = await os.api('notes/translate', {
noteId: this.appearNote.id,
targetLang: localStorage.getItem('lang') || navigator.language,
});
this.translating = false;
this.translation = res;
},
focus() { focus() {
this.$el.focus(); this.$el.focus();
}, },
@ -1053,6 +1078,13 @@ export default defineComponent({
font-style: oblique; font-style: oblique;
color: var(--renote); color: var(--renote);
} }
> .translation {
border: solid 0.5px var(--divider);
border-radius: var(--radius);
padding: 12px;
margin-top: 8px;
}
} }
> .url-preview { > .url-preview {

View file

@ -9,7 +9,7 @@
<div>{{ $ts.noNotes }}</div> <div>{{ $ts.noNotes }}</div>
</div> </div>
<div v-else> <div v-else class="giivymft" :class="{ noGap }">
<div v-show="more && reversed" style="margin-bottom: var(--margin);"> <div v-show="more && reversed" style="margin-bottom: var(--margin);">
<MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
@ -17,8 +17,8 @@
</MkButton> </MkButton>
</div> </div>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true"> <XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true" class="notes">
<XNote :note="note" class="_block" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/> <XNote class="qtqtichx" :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
</XList> </XList>
<div v-show="more && !reversed" style="margin-top: var(--margin);"> <div v-show="more && !reversed" style="margin-top: var(--margin);">
@ -108,4 +108,21 @@ export default defineComponent({
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
} }
.giivymft {
&.noGap {
> .notes {
background: var(--panel);
}
}
&:not(.noGap) {
> .notes {
.qtqtichx {
background: var(--panel);
border-radius: var(--radius);
}
}
}
}
</style> </style>

View file

@ -11,16 +11,16 @@
<template #header>{{ $ts.notificationSetting }}</template> <template #header>{{ $ts.notificationSetting }}</template>
<div class="_monolithic_"> <div class="_monolithic_">
<div v-if="showGlobalToggle" class="_section"> <div v-if="showGlobalToggle" class="_section">
<MkSwitch v-model:value="useGlobalSetting"> <MkSwitch v-model="useGlobalSetting">
{{ $ts.useGlobalSetting }} {{ $ts.useGlobalSetting }}
<template #desc>{{ $ts.useGlobalSettingDesc }}</template> <template #caption>{{ $ts.useGlobalSettingDesc }}</template>
</MkSwitch> </MkSwitch>
</div> </div>
<div v-if="!useGlobalSetting" class="_section"> <div v-if="!useGlobalSetting" class="_section">
<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo> <MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo>
<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> <MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> <MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
<MkSwitch v-for="type in notificationTypes" :key="type" v-model:value="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch> <MkSwitch v-for="type in notificationTypes" :key="type" v-model="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch>
</div> </div>
</div> </div>
</XModalWindow> </XModalWindow>

View file

@ -62,8 +62,8 @@ import { defineComponent, markRaw } from 'vue';
import { getNoteSummary } from '@/misc/get-note-summary'; import { getNoteSummary } from '@/misc/get-note-summary';
import XReactionIcon from './reaction-icon.vue'; import XReactionIcon from './reaction-icon.vue';
import MkFollowButton from './follow-button.vue'; import MkFollowButton from './follow-button.vue';
import notePage from '../filters/note'; import notePage from '@client/filters/note';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
import { i18n } from '@client/i18n'; import { i18n } from '@client/i18n';
import * as os from '@client/os'; import * as os from '@client/os';

View file

@ -16,7 +16,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { userName } from '../filters/user'; import { userName } from '@client/filters/user';
import * as os from '@client/os'; import * as os from '@client/os';
export default defineComponent({ export default defineComponent({

View file

@ -3,16 +3,12 @@
:initial-width="500" :initial-width="500"
:initial-height="500" :initial-height="500"
:can-resize="true" :can-resize="true"
:close-right="true" :close-button="false"
:contextmenu="contextmenu" :contextmenu="contextmenu"
@closed="$emit('closed')" @closed="$emit('closed')"
> >
<template #header> <template #header>
<XHeader :info="pageInfo" :with-back="false"/> <XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()"/>
</template>
<template #buttons>
<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
</template> </template>
<div class="yrolvcoq _flat_"> <div class="yrolvcoq _flat_">
<component :is="component" v-bind="props" :ref="changePage"/> <component :is="component" v-bind="props" :ref="changePage"/>
@ -139,6 +135,10 @@ export default defineComponent({
this.navigate(this.history.pop(), false); this.navigate(this.history.pop(), false);
}, },
close() {
this.$refs.window.close();
},
expand() { expand() {
this.$router.push(this.path); this.$router.push(this.path);
this.$refs.window.close(); this.$refs.window.close();
@ -155,6 +155,5 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.yrolvcoq { .yrolvcoq {
min-height: 100%; min-height: 100%;
background: var(--bg);
} }
</style> </style>

View file

@ -1,6 +1,8 @@
<template> <template>
<div> <div>
<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="number">{{ hpml.interpolate(block.text) }}</MkInput> <MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="number">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div> </div>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="ngbfujlo"> <div class="ngbfujlo">
<MkTextarea :value="text" readonly style="margin: 0;"></MkTextarea> <MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
<MkButton class="button" primary @click="post()" :disabled="posting || posted"> <MkButton class="button" primary @click="post()" :disabled="posting || posted">
<i v-if="posted" class="fas fa-check"></i> <i v-if="posted" class="fas fa-check"></i>
<i v-else class="fas fa-paper-plane"></i> <i v-else class="fas fa-paper-plane"></i>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="hkcxmtwj"> <div class="hkcxmtwj">
<MkSwitch :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch> <MkSwitch :model-value="value" @update:modelValue="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
</div> </div>
</template> </template>

View file

@ -1,6 +1,8 @@
<template> <template>
<div> <div>
<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="text">{{ hpml.interpolate(block.text) }}</MkInput> <MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="text">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div> </div>
</template> </template>

View file

@ -1,6 +1,8 @@
<template> <template>
<div> <div>
<MkTextarea :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkTextarea> <MkTextarea :model-value="value" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkTextarea>
</div> </div>
</template> </template>

View file

@ -1,5 +1,5 @@
<template> <template>
<MkTextarea :value="text" readonly></MkTextarea> <MkTextarea :model-value="text" readonly></MkTextarea>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -5,8 +5,7 @@
</p> </p>
<ul ref="choices"> <ul ref="choices">
<li v-for="(choice, i) in choices" :key="i"> <li v-for="(choice, i) in choices" :key="i">
<MkInput class="input" :value="choice" @update:value="onInput(i, $event)"> <MkInput class="input" :model-value="choice" @update:modelValue="onInput(i, $event)" :placeholder="$t('_poll.choiceN', { n: i + 1 })">
<span>{{ $t('_poll.choiceN', { n: i + 1 }) }}</span>
</MkInput> </MkInput>
<button @click="remove(i)" class="_button"> <button @click="remove(i)" class="_button">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
@ -16,27 +15,27 @@
<MkButton class="add" v-if="choices.length < 10" @click="add">{{ $ts.add }}</MkButton> <MkButton class="add" v-if="choices.length < 10" @click="add">{{ $ts.add }}</MkButton>
<MkButton class="add" v-else disabled>{{ $ts._poll.noMore }}</MkButton> <MkButton class="add" v-else disabled>{{ $ts._poll.noMore }}</MkButton>
<section> <section>
<MkSwitch v-model:value="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
<div> <div>
<MkSelect v-model:value="expiration"> <MkSelect v-model="expiration">
<template #label>{{ $ts._poll.expiration }}</template> <template #label>{{ $ts._poll.expiration }}</template>
<option value="infinite">{{ $ts._poll.infinite }}</option> <option value="infinite">{{ $ts._poll.infinite }}</option>
<option value="at">{{ $ts._poll.at }}</option> <option value="at">{{ $ts._poll.at }}</option>
<option value="after">{{ $ts._poll.after }}</option> <option value="after">{{ $ts._poll.after }}</option>
</MkSelect> </MkSelect>
<section v-if="expiration === 'at'"> <section v-if="expiration === 'at'">
<MkInput v-model:value="atDate" type="date" class="input"> <MkInput v-model="atDate" type="date" class="input">
<span>{{ $ts._poll.deadlineDate }}</span> <template #label>{{ $ts._poll.deadlineDate }}</template>
</MkInput> </MkInput>
<MkInput v-model:value="atTime" type="time" class="input"> <MkInput v-model="atTime" type="time" class="input">
<span>{{ $ts._poll.deadlineTime }}</span> <template #label>{{ $ts._poll.deadlineTime }}</template>
</MkInput> </MkInput>
</section> </section>
<section v-if="expiration === 'after'"> <section v-if="expiration === 'after'">
<MkInput v-model:value="after" type="number" class="input"> <MkInput v-model="after" type="number" class="input">
<span>{{ $ts._poll.duration }}</span> <template #label>{{ $ts._poll.duration }}</template>
</MkInput> </MkInput>
<MkSelect v-model:value="unit"> <MkSelect v-model="unit">
<option value="second">{{ $ts._time.second }}</option> <option value="second">{{ $ts._time.second }}</option>
<option value="minute">{{ $ts._time.minute }}</option> <option value="minute">{{ $ts._time.minute }}</option>
<option value="hour">{{ $ts._time.hour }}</option> <option value="hour">{{ $ts._time.hour }}</option>

View file

@ -112,7 +112,7 @@ export default defineComponent({
showFileMenu(file, ev: MouseEvent) { showFileMenu(file, ev: MouseEvent) {
if (this.menu) return; if (this.menu) return;
this.menu = os.modalMenu([{ this.menu = os.popupMenu([{
text: this.$ts.renameFile, text: this.$ts.renameFile,
icon: 'fas fa-i-cursor', icon: 'fas fa-i-cursor',
action: () => { this.rename(file) } action: () => { this.rename(file) }

View file

@ -17,7 +17,7 @@
<span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span>
<span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span>
</button> </button>
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> <button class="submit _buttonPrimary" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button>
</div> </div>
</header> </header>
<div class="form" :class="{ fixed }"> <div class="form" :class="{ fixed }">
@ -36,7 +36,8 @@
</div> </div>
<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> <MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> <input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown">
<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" /> <textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" data-cy-post-form-text/>
<input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags">
<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
<footer> <footer>
@ -44,9 +45,13 @@
<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button> <button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button>
<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button> <button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button>
<button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button> <button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button>
<button class="_button" @click="withHashtags = !withHashtags" :class="{ active: withHashtags }" v-tooltip="$ts.hashtags"><i class="fas fa-hashtag"></i></button>
<button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button> <button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button>
<button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button> <button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button>
</footer> </footer>
<datalist id="hashtags">
<option v-for="hashtag in recentHashtags" :value="hashtag" :key="hashtag"/>
</datalist>
</div> </div>
</div> </div>
</template> </template>
@ -67,10 +72,11 @@ import { Autocomplete } from '@client/scripts/autocomplete';
import { noteVisibilities } from '../../types'; import { noteVisibilities } from '../../types';
import * as os from '@client/os'; import * as os from '@client/os';
import { selectFile } from '@client/scripts/select-file'; import { selectFile } from '@client/scripts/select-file';
import { notePostInterruptors, postFormActions } from '@client/store'; import { defaultStore, notePostInterruptors, postFormActions } from '@client/store';
import { isMobile } from '@client/scripts/is-mobile'; import { isMobile } from '@client/scripts/is-mobile';
import { throttle } from 'throttle-debounce'; import { throttle } from 'throttle-debounce';
import MkInfo from '@client/components/ui/info.vue'; import MkInfo from '@client/components/ui/info.vue';
import { defaultStore } from '@client/store';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -212,7 +218,10 @@ export default defineComponent({
max(): number { max(): number {
return this.$instance ? this.$instance.maxNoteTextLength : 1000; return this.$instance ? this.$instance.maxNoteTextLength : 1000;
} },
withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'),
hashtags: defaultStore.makeGetterSetter('postFormHashtags'),
}, },
watch: { watch: {
@ -303,6 +312,7 @@ export default defineComponent({
// TODO: detach when unmount // TODO: detach when unmount
new Autocomplete(this.$refs.text, this, { model: 'text' }); new Autocomplete(this.$refs.text, this, { model: 'text' });
new Autocomplete(this.$refs.cw, this, { model: 'cw' }); new Autocomplete(this.$refs.cw, this, { model: 'cw' });
new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' });
this.$nextTick(() => { this.$nextTick(() => {
// 稿 // 稿
@ -605,6 +615,11 @@ export default defineComponent({
viaMobile: isMobile viaMobile: isMobile
}; };
if (this.withHashtags && this.hashtags && this.hashtags.trim() !== '') {
const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
data.text = data.text ? `${data.text} ${hashtags}` : hashtags;
}
// plugin // plugin
if (notePostInterruptors.length > 0) { if (notePostInterruptors.length > 0) {
for (const interruptor of notePostInterruptors) { for (const interruptor of notePostInterruptors) {
@ -618,8 +633,8 @@ export default defineComponent({
this.$nextTick(() => { this.$nextTick(() => {
this.deleteDraft(); this.deleteDraft();
this.$emit('posted'); this.$emit('posted');
if (this.text && this.text != '') { if (data.text && data.text != '') {
const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
} }
@ -649,7 +664,7 @@ export default defineComponent({
}, },
showActions(ev) { showActions(ev) {
os.modalMenu(postFormActions.map(action => ({ os.popupMenu(postFormActions.map(action => ({
text: action.title, text: action.title,
action: () => { action: () => {
action.handler({ action.handler({
@ -785,6 +800,7 @@ export default defineComponent({
} }
> .cw, > .cw,
> .hashtags,
> .text { > .text {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
@ -813,6 +829,13 @@ export default defineComponent({
border-bottom: solid 0.5px var(--divider); border-bottom: solid 0.5px var(--divider);
} }
> .hashtags {
z-index: 1;
padding-top: 8px;
padding-bottom: 8px;
border-top: solid 0.5px var(--divider);
}
> .text { > .text {
max-width: 100%; max-width: 100%;
min-width: 100%; min-width: 100%;
@ -872,6 +895,7 @@ export default defineComponent({
} }
> .cw, > .cw,
> .hashtags,
> .text { > .text {
padding: 0 16px; padding: 0 16px;
} }

View file

@ -170,7 +170,7 @@ export default defineComponent({
} }
> span { > span {
color: #fff; color: var(--fgOnAccent);
} }
} }

View file

@ -1,10 +1,10 @@
<template> <template>
<div class="_card"> <div class="_card">
<div class="_content"> <div class="_content">
<MkInput v-model:value="text"> <MkInput v-model="text">
<span>Text</span> <template #label>Text</template>
</MkInput> </MkInput>
<MkSwitch v-model:value="flag"> <MkSwitch v-model="flag">
<span>Switch is now {{ flag ? 'on' : 'off' }}</span> <span>Switch is now {{ flag ? 'on' : 'off' }}</span>
</MkSwitch> </MkSwitch>
<div style="margin: 32px 0;"> <div style="margin: 32px 0;">
@ -93,7 +93,7 @@ export default defineComponent({
}, },
async openMenu(ev) { async openMenu(ev) {
os.modalMenu([{ os.popupMenu([{
type: 'label', type: 'label',
text: 'Fruits' text: 'Fruits'
}, { }, {

View file

@ -3,15 +3,13 @@
<div class="auth _section"> <div class="auth _section">
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> <div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
<div class="normal-signin" v-if="!totpLogin"> <div class="normal-signin" v-if="!totpLogin">
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange"> <MkInput v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username>
<span>{{ $ts.username }}</span>
<template #prefix>@</template> <template #prefix>@</template>
<template #suffix>@{{ host }}</template> <template #suffix>@{{ host }}</template>
</MkInput> </MkInput>
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> <MkInput v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password>
<span>{{ $ts.password }}</span>
<template #prefix><i class="fas fa-lock"></i></template> <template #prefix><i class="fas fa-lock"></i></template>
<template #desc><button class="_textButton" @click="resetPassword">{{ $ts.forgotPassword }}</button></template> <template #caption><button class="_textButton" @click="resetPassword" type="button">{{ $ts.forgotPassword }}</button></template>
</MkInput> </MkInput>
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> <MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
</div> </div>
@ -27,12 +25,12 @@
</div> </div>
<div class="twofa-group totp-group"> <div class="twofa-group totp-group">
<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p> <p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> <MkInput v-model="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
<span>{{ $ts.password }}</span> <template #label>{{ $ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template> <template #prefix><i class="fas fa-lock"></i></template>
</MkInput> </MkInput>
<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
<span>{{ $ts.token }}</span> <template #label>{{ $ts.token }}</template>
<template #prefix><i class="fas fa-gavel"></i></template> <template #prefix><i class="fas fa-gavel"></i></template>
</MkInput> </MkInput>
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>

View file

@ -1,39 +1,39 @@
<template> <template>
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<template v-if="meta"> <template v-if="meta">
<MkInput v-if="meta.disableRegistration" v-model:value="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> <MkInput v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<span>{{ $ts.invitationCode }}</span> <template #label>{{ $ts.invitationCode }}</template>
<template #prefix><i class="fas fa-key"></i></template> <template #prefix><i class="fas fa-key"></i></template>
</MkInput> </MkInput>
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:value="onChangeUsername"> <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username>
<span>{{ $ts.username }}</span> <template #label>{{ $ts.username }}</template>
<template #prefix>@</template> <template #prefix>@</template>
<template #suffix>@{{ host }}</template> <template #suffix>@{{ host }}</template>
<template #desc> <template #caption>
<span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> <span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
<span v-if="usernameState == 'ok'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> <span v-if="usernameState == 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
<span v-if="usernameState == 'unavailable'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> <span v-if="usernameState == 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
<span v-if="usernameState == 'error'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> <span v-if="usernameState == 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> <span v-if="usernameState == 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
<span v-if="usernameState == 'min-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> <span v-if="usernameState == 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
<span v-if="usernameState == 'max-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> <span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
</template> </template>
</MkInput> </MkInput>
<MkInput v-model:value="password" type="password" :autocomplete="Math.random()" required @update:value="onChangePassword"> <MkInput v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password>
<span>{{ $ts.password }}</span> <template #label>{{ $ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template> <template #prefix><i class="fas fa-lock"></i></template>
<template #desc> <template #caption>
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</p> <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</p> <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</p> <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
</template> </template>
</MkInput> </MkInput>
<MkInput v-model:value="retypedPassword" type="password" :autocomplete="Math.random()" required @update:value="onChangePasswordRetype"> <MkInput v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype>
<span>{{ $ts.password }} ({{ $ts.retype }})</span> <template #label>{{ $ts.password }} ({{ $ts.retype }})</template>
<template #prefix><i class="fas fa-lock"></i></template> <template #prefix><i class="fas fa-lock"></i></template>
<template #desc> <template #caption>
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</p> <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</p> <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
</template> </template>
</MkInput> </MkInput>
<label v-if="meta.tosUrl" class="tou"> <label v-if="meta.tosUrl" class="tou">
@ -46,7 +46,7 @@
</label> </label>
<captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/> <captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/>
<captcha v-if="meta.enableRecaptcha" class="captcha" provider="recaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/> <captcha v-if="meta.enableRecaptcha" class="captcha" provider="recaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/>
<MkButton type="submit" :disabled="shouldDisableSubmitting" primary>{{ $ts.start }}</MkButton> <MkButton type="submit" :disabled="shouldDisableSubmitting" primary data-cy-signup-submit>{{ $ts.start }}</MkButton>
</template> </template>
</form> </form>
</template> </template>

View file

@ -14,13 +14,15 @@
<MkInfo warn>{{ information }}</MkInfo> <MkInfo warn>{{ information }}</MkInfo>
</div> </div>
<div class="_section"> <div class="_section">
<MkInput v-model:value="name">{{ $ts.name }}</MkInput> <MkInput v-model="name">
<template #label>{{ $ts.name }}</template>
</MkInput>
</div> </div>
<div class="_section"> <div class="_section">
<div style="margin-bottom: 16px;"><b>{{ $ts.permission }}</b></div> <div style="margin-bottom: 16px;"><b>{{ $ts.permission }}</b></div>
<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> <MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> <MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton>
<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model:value="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch> <MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch>
</div> </div>
</XModalWindow> </XModalWindow>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<component class="bghgjjyj _button" <component class="bghgjjyj _button"
:is="link ? 'a' : 'button'" :is="link ? 'MkA' : 'button'"
:class="{ inline, primary, danger, full }" :class="{ inline, primary, danger, full }"
:type="type" :type="type"
@click="$emit('click', $event)" @click="$emit('click', $event)"
@ -115,6 +115,7 @@ export default defineComponent({
z-index: 1; // box-shadow z-index: 1; // box-shadow
display: block; display: block;
min-width: 100px; min-width: 100px;
width: max-content;
padding: 8px 14px; padding: 8px 14px;
text-align: center; text-align: center;
font-weight: normal; font-weight: normal;
@ -125,6 +126,8 @@ export default defineComponent({
background: var(--buttonBg); background: var(--buttonBg);
border-radius: 999px; border-radius: 999px;
overflow: hidden; overflow: hidden;
box-sizing: border-box;
transition: background 0.1s ease;
&:not(:disabled):hover { &:not(:disabled):hover {
background: var(--buttonHoverBg); background: var(--buttonHoverBg);
@ -140,7 +143,7 @@ export default defineComponent({
&.primary { &.primary {
font-weight: bold; font-weight: bold;
color: #fff !important; color: var(--fgOnAccent) !important;
background: var(--accent); background: var(--accent);
&:not(:disabled):hover { &:not(:disabled):hover {
@ -174,17 +177,8 @@ export default defineComponent({
} }
&:focus { &:focus {
&:after { outline: solid 2px var(--focus);
content: ""; outline-offset: 2px;
pointer-events: none;
position: absolute;
top: -5px;
right: -5px;
bottom: -5px;
left: -5px;
border: 2px solid var(--accentAlpha03);
border-radius: 10px;
}
} }
&.inline + .bghgjjyj { &.inline + .bghgjjyj {

View file

@ -99,9 +99,12 @@ export default defineComponent({
z-index: 10; z-index: 10;
position: sticky; position: sticky;
top: var(--stickyTop, 0px); top: var(--stickyTop, 0px);
background: var(--panel);
/* TODO panel
background: var(--X17); background: var(--X17);
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: blur(20px); backdrop-filter: var(--blur, blur(20px));
*/
> .title { > .title {
margin: 0; margin: 0;

View file

@ -1,32 +1,9 @@
<template> <template>
<div class="juejbjww" :class="{ focused, filled, inline, disabled }"> <div class="matxzzsk">
<div class="icon" ref="icon"><slot name="icon"></slot></div> <div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input"> <div class="input" :class="{ inline, disabled, focused }">
<span class="label" ref="labelEl"><slot></slot></span>
<span class="title" ref="title">
<slot name="title"></slot>
<span class="warning" v-if="invalid"><i class="fas fa-exclamation-circle"></i>{{ $refs.input.validationMessage }}</span>
</span>
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<input v-if="debounce" ref="inputEl" <input ref="inputEl"
v-debounce="500"
:type="type"
v-model.lazy="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:step="step"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
:list="id"
>
<input v-else ref="inputEl"
:type="type" :type="type"
v-model="v" v-model="v"
:disabled="disabled" :disabled="disabled"
@ -48,23 +25,25 @@
</datalist> </datalist>
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> <div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
</div> </div>
<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button> <div class="caption"><slot name="caption"></slot></div>
<div class="desc _caption"><slot name="desc"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import debounce from 'v-debounce'; import MkButton from './button.vue';
import * as os from '@client/os'; import { debounce } from 'throttle-debounce';
export default defineComponent({ export default defineComponent({
directives: { components: {
debounce MkButton,
}, },
props: { props: {
value: { modelValue: {
required: false required: true
}, },
type: { type: {
type: String, type: String,
@ -104,9 +83,6 @@ export default defineComponent({
step: { step: {
required: false required: false
}, },
debounce: {
required: false
},
datalist: { datalist: {
type: Array, type: Array,
required: false, required: false,
@ -116,15 +92,23 @@ export default defineComponent({
required: false, required: false,
default: false default: false
}, },
save: { debounce: {
type: Function, type: Boolean,
required: false, required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
}, },
}, },
emits: ['change', 'keydown', 'enter'],
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) { setup(props, context) {
const { value, type, autofocus } = toRefs(props); const { modelValue, type, autofocus } = toRefs(props);
const v = ref(value.value); const v = ref(modelValue.value);
const id = Math.random().toString(); // TODO: uuid? const id = Math.random().toString(); // TODO: uuid?
const focused = ref(false); const focused = ref(false);
const changed = ref(false); const changed = ref(false);
@ -133,7 +117,6 @@ export default defineComponent({
const inputEl = ref(null); const inputEl = ref(null);
const prefixEl = ref(null); const prefixEl = ref(null);
const suffixEl = ref(null); const suffixEl = ref(null);
const labelEl = ref(null);
const focus = () => inputEl.value.focus(); const focus = () => inputEl.value.focus();
const onInput = (ev) => { const onInput = (ev) => {
@ -148,15 +131,28 @@ export default defineComponent({
} }
}; };
watch(value, newValue => { const updated = () => {
changed.value = false;
if (type?.value === 'number') {
context.emit('update:modelValue', parseFloat(v.value));
} else {
context.emit('update:modelValue', v.value);
}
};
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
v.value = newValue; v.value = newValue;
}); });
watch(v, newValue => { watch(v, newValue => {
if (type?.value === 'number') { if (!props.manualSave) {
context.emit('update:value', parseFloat(newValue)); if (props.debounce) {
debouncedUpdated();
} else { } else {
context.emit('update:value', newValue); updated();
}
} }
invalid.value = inputEl.value.validity.badInput; invalid.value = inputEl.value.validity.badInput;
@ -172,7 +168,6 @@ export default defineComponent({
// 0 // 0
const clock = setInterval(() => { const clock = setInterval(() => {
if (prefixEl.value) { if (prefixEl.value) {
labelEl.value.style.left = (prefixEl.value.offsetLeft + prefixEl.value.offsetWidth) + 'px';
if (prefixEl.value.offsetWidth) { if (prefixEl.value.offsetWidth) {
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
} }
@ -200,148 +195,78 @@ export default defineComponent({
inputEl, inputEl,
prefixEl, prefixEl,
suffixEl, suffixEl,
labelEl,
focus, focus,
onInput, onInput,
onKeydown, onKeydown,
updated,
}; };
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.juejbjww { .matxzzsk {
position: relative; margin: 1.5em 0;
margin: 32px 0;
&:not(.inline):first-child { > .label {
margin-top: 8px; font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
} }
&:not(.inline):last-child { > .caption {
margin-bottom: 8px; font-size: 0.8em;
} padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
> .icon { &:empty {
position: absolute; display: none;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:not(:empty) + .input {
margin-left: 28px;
} }
} }
> .input { > .input {
$height: 42px;
position: relative; position: relative;
&:before {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: var(--inputBorder);
}
&:after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent);
opacity: 0;
transform: scaleX(0.12);
transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
will-change: border opacity transform;
}
> .label {
position: absolute;
z-index: 1;
top: 0;
left: 0;
pointer-events: none;
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
transition-duration: 0.3s;
font-size: 1em;
line-height: 32px;
color: var(--inputLabel);
pointer-events: none;
//will-change transform
transform-origin: top left;
transform: scale(1);
}
> .title {
position: absolute;
z-index: 1;
top: -17px;
left: 0 !important;
pointer-events: none;
font-size: 1em;
line-height: 32px;
color: var(--inputLabel);
pointer-events: none;
//will-change transform
transform-origin: top left;
transform: scale(.75);
white-space: nowrap;
width: 133%;
overflow: hidden;
text-overflow: ellipsis;
> .warning {
margin-left: 0.5em;
color: var(--infoWarnFg);
> svg {
margin-right: 0.1em;
}
}
}
> input { > input {
$height: 32px; appearance: none;
-webkit-appearance: none;
display: block; display: block;
height: $height; height: $height;
width: 100%; width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0 12px;
font: inherit; font: inherit;
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
line-height: $height; color: var(--fg);
color: var(--inputText); background: var(--panel);
background: transparent; border: solid 1px var(--inputBorder);
border: none; border-radius: 6px;
border-radius: 0;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
box-sizing: border-box; box-sizing: border-box;
transition: border-color 0.1s ease-out;
&[type='file'] { &:hover {
display: none; border-color: var(--inputBorderHover);
} }
} }
> .prefix, > .prefix,
> .suffix { > .suffix {
display: block; display: flex;
align-items: center;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: 0; top: 0;
padding: 0 12px;
font-size: 1em; font-size: 1em;
line-height: 32px; height: $height;
color: var(--inputLabel);
pointer-events: none; pointer-events: none;
&:empty { &:empty {
@ -360,54 +285,12 @@ export default defineComponent({
> .prefix { > .prefix {
left: 0; left: 0;
padding-right: 4px; padding-right: 6px;
} }
> .suffix { > .suffix {
right: 0; right: 0;
padding-left: 4px; padding-left: 6px;
}
}
> .save {
margin: 6px 0 0 0;
font-size: 0.8em;
}
> .desc {
margin: 6px 0 0 0;
&:empty {
display: none;
}
* {
margin: 0;
}
}
&.focused {
> .input {
&:after {
opacity: 1;
transform: scaleX(1);
}
> .label {
color: var(--accent);
}
}
}
&.focused,
&.filled {
> .input {
> .label {
top: -17px;
left: 0 !important;
transform: scale(0.75);
}
}
} }
&.inline { &.inline {
@ -415,6 +298,13 @@ export default defineComponent({
margin: 0; margin: 0;
} }
&.focused {
> input {
border-color: var(--accent);
//box-shadow: 0 0 0 4px var(--focus);
}
}
&.disabled { &.disabled {
opacity: 0.7; opacity: 0.7;
@ -422,5 +312,6 @@ export default defineComponent({
cursor: not-allowed !important; cursor: not-allowed !important;
} }
} }
}
} }
</style> </style>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="rrevdjwt" :class="{ left: align === 'left' }" <div class="rrevdjwt" :class="{ left: align === 'left', pointer: point === 'top' }"
ref="items" ref="items"
@contextmenu.self="e => e.preventDefault()" @contextmenu.self="e => e.preventDefault()"
v-hotkey="keymap" v-hotkey="keymap"
@ -58,7 +58,11 @@ export default defineComponent({
align: { align: {
type: String, type: String,
requried: false requried: false
} },
point: {
type: String,
requried: false
},
}, },
emits: ['close'], emits: ['close'],
data() { data() {
@ -137,6 +141,22 @@ export default defineComponent({
.rrevdjwt { .rrevdjwt {
padding: 8px 0; padding: 8px 0;
&.pointer {
&:before {
--size: 8px;
content: '';
display: block;
position: absolute;
top: calc(0px - (var(--size) * 2));
left: 0;
right: 0;
width: 0;
margin: auto;
border: solid var(--size) transparent;
border-bottom-color: var(--popup);
}
}
&.left { &.left {
> .item { > .item {
text-align: left; text-align: left;
@ -171,13 +191,13 @@ export default defineComponent({
} }
&:hover { &:hover {
color: #fff; color: var(--fgOnAccent);
background: var(--accent); background: var(--accent);
text-decoration: none; text-decoration: none;
} }
&:active { &:active {
color: #fff; color: var(--fgOnAccent);
background: var(--accentDarken); background: var(--accentDarken);
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }"> <div class="ebkgoccj _window _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header"> <div class="header">
<button class="_button" v-if="withOkButton" @click="$emit('close')"><i class="fas fa-times"></i></button> <button class="_button" v-if="withOkButton" @click="$emit('close')"><i class="fas fa-times"></i></button>
<span class="title"> <span class="title">

View file

@ -1,19 +1,20 @@
<template> <template>
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> <MkPopup ref="popup" :src="src" @closed="$emit('closed')" #default="{point}">
<MkMenu :items="items" :align="align" @close="$refs.modal.close()" class="_popup"/> <MkMenu :items="items" :align="align" :point="point" @close="$refs.popup.close()" class="_popup _shadow"/>
</MkModal> </MkPopup>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import MkModal from './modal.vue'; import MkPopup from './popup.vue';
import MkMenu from './menu.vue'; import MkMenu from './menu.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
MkModal, MkPopup,
MkMenu, MkMenu,
}, },
props: { props: {
items: { items: {
type: Array, type: Array,
@ -31,17 +32,7 @@ export default defineComponent({
required: false required: false
}, },
}, },
emits: ['closed'],
computed: { emits: ['close', 'closed'],
keymap(): any {
return {
'esc': () => this.$refs.modal.close(),
};
},
},
}); });
</script> </script>
<style lang="scss" scoped>
</style>

View file

@ -0,0 +1,216 @@
<template>
<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
<div v-show="manualShowing != null ? manualShowing : showing" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" ref="content" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<slot :point="point"></slot>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
const position = window.getComputedStyle(el).getPropertyValue('position');
if (position === 'fixed') {
return el;
} else {
return getFixedContainer(el.parentElement);
}
}
export default defineComponent({
props: {
manualShowing: {
type: Boolean,
required: false,
default: null,
},
srcCenter: {
type: Boolean,
required: false
},
src: {
type: Object as PropType<HTMLElement>,
required: false,
},
position: {
required: false
},
front: {
type: Boolean,
required: false,
default: false,
}
},
emits: ['opening', 'click', 'esc', 'close', 'closed'],
data() {
return {
showing: true,
fixed: false,
transformOrigin: 'center',
contentClicking: false,
point: null,
};
},
mounted() {
this.$watch('src', () => {
if (this.src) {
this.src.style.pointerEvents = 'none';
}
this.fixed = getFixedContainer(this.src) != null;
this.$nextTick(() => {
this.align();
});
}, { immediate: true });
this.$nextTick(() => {
const popover = this.$refs.content as any;
new ResizeObserver((entries, observer) => {
this.align();
}).observe(popover);
});
document.addEventListener('mousedown', this.onDocumentClick, { passive: true });
},
beforeUnmount() {
document.removeEventListener('mousedown', this.onDocumentClick);
},
methods: {
align() {
if (this.src == null) return;
const popover = this.$refs.content as any;
if (popover == null) return;
const rect = this.src.getBoundingClientRect();
const width = popover.offsetWidth;
const height = popover.offsetHeight;
let left;
let top;
if (this.srcCenter) {
const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + (this.src.offsetHeight / 2);
left = (x - (width / 2));
top = (y - (height / 2));
} else {
const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + this.src.offsetHeight;
left = (x - (width / 2));
top = y;
}
if (this.fixed) {
if (left + width > window.innerWidth) {
left = window.innerWidth - width;
}
if (top + height > window.innerHeight) {
top = window.innerHeight - height;
}
} else {
if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset - 1;
}
if (top + height - window.pageYOffset > window.innerHeight) {
top = window.innerHeight - height + window.pageYOffset - 1;
}
}
if (top < 0) {
top = 0;
}
if (left < 0) {
left = 0;
}
if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) {
this.point = 'top';
this.transformOrigin = 'center top';
} else {
this.point = null;
this.transformOrigin = 'center';
}
popover.style.left = left + 'px';
popover.style.top = top + 'px';
},
childRendered() {
//
const content = this.$refs.content.children[0];
content.addEventListener('mousedown', e => {
this.contentClicking = true;
window.addEventListener('mouseup', e => {
// click mouseup
setTimeout(() => {
this.contentClicking = false;
}, 100);
}, { passive: true, once: true });
}, { passive: true });
},
close() {
if (this.src) this.src.style.pointerEvents = 'auto';
this.showing = false;
this.$emit('close');
},
onClosed() {
this.$emit('closed');
},
onDocumentClick(ev) {
const flyoutElement = this.$refs.content;
let targetElement = ev.target;
do {
if (targetElement === flyoutElement) {
return;
}
targetElement = targetElement.parentNode;
} while (targetElement);
this.close();
}
}
});
</script>
<style lang="scss" scoped>
.popup-menu-enter-active {
transform-origin: var(--transformOrigin);
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
}
.popup-menu-leave-active {
transform-origin: var(--transformOrigin);
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), transform 0.2s cubic-bezier(0.4, 0, 1, 1) !important;
}
.popup-menu-enter-from, .popup-menu-leave-to {
pointer-events: none;
opacity: 0;
transform: scale(0.9);
}
.ccczpooj {
position: absolute;
z-index: 10000;
&.fixed {
position: fixed;
}
&.front {
z-index: 20000;
}
}
</style>

View file

@ -1,185 +1,218 @@
<template> <template>
<div class="eiipwacr" :class="{ focused, disabled, filled, inline }"> <div class="vblkjoeq">
<div class="icon" ref="icon"><slot name="icon"></slot></div> <div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" @click="focus"> <div class="input" :class="{ inline, disabled, focused }">
<span class="label" ref="label"><slot name="label"></slot></span> <div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div> <select ref="inputEl"
<select ref="input"
v-model="v" v-model="v"
:required="required"
:disabled="disabled" :disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
@focus="focused = true" @focus="focused = true"
@blur="focused = false" @blur="focused = false"
@input="onInput"
> >
<slot></slot> <slot></slot>
</select> </select>
<div class="suffix"> <div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div>
<slot name="suffix">
<i class="fas fa-chevron-down"></i>
</slot>
</div> </div>
</div> <div class="caption"><slot name="caption"></slot></div>
<div class="text"><slot name="text"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from './button.vue';
export default defineComponent({ export default defineComponent({
components: {
MkButton,
},
props: { props: {
value: { modelValue: {
required: false required: true
}, },
required: { required: {
type: Boolean, type: Boolean,
required: false required: false
}, },
readonly: {
type: Boolean,
required: false
},
disabled: { disabled: {
type: Boolean, type: Boolean,
required: false required: false
}, },
placeholder: {
type: String,
required: false
},
autofocus: {
type: Boolean,
required: false,
default: false
},
inline: { inline: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false default: false
}, },
manualSave: {
type: Boolean,
required: false,
default: false
}, },
data() { },
emits: ['change', 'update:modelValue'],
setup(props, context) {
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const prefixEl = ref(null);
const suffixEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
//
// 0
const clock = setInterval(() => {
if (prefixEl.value) {
if (prefixEl.value.offsetWidth) {
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
}
}
if (suffixEl.value) {
if (suffixEl.value.offsetWidth) {
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
}
}
}, 100);
onUnmounted(() => {
clearInterval(clock);
});
});
});
return { return {
focused: false, v,
focused,
invalid,
changed,
filled,
inputEl,
prefixEl,
suffixEl,
focus,
onInput,
updated,
}; };
}, },
computed: {
v: {
get() {
return this.value;
},
set(v) {
this.$emit('update:value', v);
}
},
filled(): boolean {
return true;
}
},
mounted() {
if (this.$refs.prefix) {
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
}
},
methods: {
focus() {
this.$refs.input.focus();
}
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.eiipwacr { .vblkjoeq {
position: relative; margin: 1.5em 0;
margin: 32px 0;
&:not(.inline):first-child { > .label {
margin-top: 8px; font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
} }
&:not(.inline):last-child { > .caption {
margin-bottom: 8px; font-size: 0.8em;
} padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
> .icon { &:empty {
position: absolute; display: none;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:not(:empty) + .input {
margin-left: 28px;
} }
} }
> .input { > .input {
display: flex; $height: 42px;
position: relative; position: relative;
&:before {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: var(--inputBorder);
}
&:after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent);
opacity: 0;
transform: scaleX(0.12);
transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
will-change: border opacity transform;
}
> .label {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
transition-duration: 0.3s;
font-size: 1em;
line-height: 32px;
pointer-events: none;
//will-change transform
transform-origin: top left;
transform: scale(1);
}
> select { > select {
appearance: none;
-webkit-appearance: none;
display: block; display: block;
flex: 1; height: $height;
width: 100%; width: 100%;
padding: 0; margin: 0;
padding: 0 12px;
font: inherit; font: inherit;
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
height: 32px; color: var(--fg);
background: none; background: var(--panel);
border: none; border: solid 1px var(--inputBorder);
border-radius: 0; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
appearance: none; box-sizing: border-box;
-webkit-appearance: none; cursor: pointer;
color: var(--fg); transition: border-color 0.1s ease-out;
option, &:hover {
optgroup { border-color: var(--inputBorderHover);
color: var(--fg);
background: var(--bg);
} }
} }
> .prefix, > .prefix,
> .suffix { > .suffix {
display: block; display: flex;
align-self: center; align-items: center;
justify-self: center; position: absolute;
z-index: 1;
top: 0;
padding: 0 12px;
font-size: 1em; font-size: 1em;
line-height: 32px; height: $height;
color: var(--inputLabel);
pointer-events: none; pointer-events: none;
&:empty { &:empty {
@ -187,53 +220,41 @@ export default defineComponent({
} }
> * { > * {
display: block; display: inline-block;
min-width: 16px; min-width: 16px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
} }
> .prefix { > .prefix {
padding-right: 4px; left: 0;
padding-right: 6px;
} }
> .suffix { > .suffix {
padding-left: 4px; right: 0;
} padding-left: 6px;
} }
> .text { &.inline {
margin: 6px 0; display: inline-block;
font-size: 0.8em;
&:empty {
display: none;
}
* {
margin: 0; margin: 0;
} }
}
&.focused { &.focused {
> .input { > select {
&:after { border-color: var(--accent);
opacity: 1;
transform: scaleX(1);
}
> .label {
color: var(--accent);
}
} }
} }
&.focused, &.disabled {
&.filled { opacity: 0.7;
> .input {
> .label { &, * {
top: -17px; cursor: not-allowed !important;
left: 0 !important;
transform: scale(0.75);
} }
} }
} }

View file

@ -18,7 +18,7 @@
</span> </span>
<span class="label"> <span class="label">
<span><slot></slot></span> <span><slot></slot></span>
<p><slot name="desc"></slot></p> <p><slot name="caption"></slot></p>
</span> </span>
</div> </div>
</template> </template>
@ -28,7 +28,7 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
value: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false
}, },
@ -39,13 +39,13 @@ export default defineComponent({
}, },
computed: { computed: {
checked(): boolean { checked(): boolean {
return this.value; return this.modelValue;
} }
}, },
methods: { methods: {
toggle() { toggle() {
if (this.disabled) return; if (this.disabled) return;
this.$emit('update:value', !this.checked); this.$emit('update:modelValue', !this.checked);
} }
} }
}); });
@ -136,7 +136,7 @@ export default defineComponent({
> p { > p {
margin: 0; margin: 0;
opacity: 0.7; color: var(--fgTransparentWeak);
font-size: 90%; font-size: 90%;
} }
} }

View file

@ -1,30 +1,45 @@
<template> <template>
<div class="adhpbeos" :class="{ focused, filled, tall, pre }"> <div class="adhpbeos">
<div class="input"> <div class="label" @click="focus"><slot name="label"></slot></div>
<span class="label" ref="label"><slot></slot></span> <div class="input" :class="{ disabled, focused, tall, pre }">
<textarea ref="input" :class="{ code, _monospace: code }" <textarea ref="inputEl"
:value="value" :class="{ code, _monospace: code }"
v-model="v"
:disabled="disabled"
:required="required" :required="required"
:readonly="readonly" :readonly="readonly"
:placeholder="placeholder"
:pattern="pattern" :pattern="pattern"
:autocomplete="autocomplete" :autocomplete="autocomplete"
:spellcheck="!code" :spellcheck="spellcheck"
@input="onInput"
@focus="focused = true" @focus="focused = true"
@blur="focused = false" @blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
></textarea> ></textarea>
</div> </div>
<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button> <div class="caption"><slot name="caption"></slot></div>
<div class="desc _caption"><slot name="desc"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from './button.vue';
import { debounce } from 'throttle-debounce';
export default defineComponent({ export default defineComponent({
components: {
MkButton,
},
props: { props: {
value: { modelValue: {
required: true
},
type: {
type: String,
required: false required: false
}, },
required: { required: {
@ -35,14 +50,29 @@ export default defineComponent({
type: Boolean, type: Boolean,
required: false required: false
}, },
disabled: {
type: Boolean,
required: false
},
pattern: { pattern: {
type: String, type: String,
required: false required: false
}, },
autocomplete: { placeholder: {
type: String, type: String,
required: false required: false
}, },
autofocus: {
type: Boolean,
required: false,
default: false
},
autocomplete: {
required: false
},
spellcheck: {
required: false
},
code: { code: {
type: Boolean, type: Boolean,
required: false required: false
@ -57,169 +87,164 @@ export default defineComponent({
required: false, required: false,
default: false default: false
}, },
save: { debounce: {
type: Function, type: Boolean,
required: false, required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
}, },
}, },
data() {
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) {
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const onKeydown = (ev: KeyboardEvent) => {
context.emit('keydown', ev);
if (ev.code === 'Enter') {
context.emit('enter');
}
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
if (props.debounce) {
debouncedUpdated();
} else {
updated();
}
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
});
});
return { return {
focused: false, v,
changed: false, focused,
} invalid,
changed,
filled,
inputEl,
focus,
onInput,
onKeydown,
updated,
};
}, },
computed: {
filled(): boolean {
return this.value != '' && this.value != null;
}
},
methods: {
focus() {
this.$refs.input.focus();
},
onInput(ev) {
this.changed = true;
this.$emit('update:value', ev.target.value);
}
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.adhpbeos { .adhpbeos {
margin: 42px 0 32px 0; margin: 1.5em 0;
position: relative;
&:first-child { > .label {
margin-top: 16px; font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
} }
&:last-child { > .caption {
margin-bottom: 0; font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
} }
> .input { > .input {
position: relative; position: relative;
&:before {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: none;
border: solid 1px var(--inputBorder);
border-radius: 3px;
pointer-events: none;
}
&:after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: none;
border: solid 2px var(--accent);
border-radius: 3px;
opacity: 0;
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
> .label {
position: absolute;
top: 6px;
left: 12px;
pointer-events: none;
transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
transition-duration: 0.3s;
font-size: 1em;
line-height: 32px;
pointer-events: none;
//will-change transform
transform-origin: top left;
transform: scale(1);
}
> textarea { > textarea {
appearance: none;
-webkit-appearance: none;
display: block; display: block;
width: 100%; width: 100%;
min-width: 100%; min-width: 100%;
max-width: 100%; max-width: 100%;
min-height: 130px; min-height: 130px;
margin: 0;
padding: 12px; padding: 12px;
box-sizing: border-box;
font: inherit; font: inherit;
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
background: transparent; color: var(--fg);
border: none; background: var(--panel);
border-radius: 0; border: solid 1px var(--inputBorder);
border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
color: var(--fg); box-sizing: border-box;
transition: border-color 0.1s ease-out;
&.code { &:hover {
tab-size: 2; border-color: var(--inputBorderHover);
}
}
}
> .save {
margin: 6px 0 0 0;
font-size: 0.8em;
}
> .desc {
margin: 6px 0 0 0;
&:empty {
display: none;
}
* {
margin: 0;
} }
} }
&.focused { &.focused {
> .input { > textarea {
&:after { border-color: var(--accent);
opacity: 1;
}
> .label {
color: var(--accent);
}
} }
} }
&.focused, &.disabled {
&.filled { opacity: 0.7;
> .input {
> .label { &, * {
top: -24px; cursor: not-allowed !important;
left: 0 !important;
transform: scale(0.75);
}
} }
} }
&.tall { &.tall {
> .input {
> textarea { > textarea {
min-height: 200px; min-height: 200px;
} }
} }
}
&.pre { &.pre {
> .input {
> textarea { > textarea {
white-space: pre; white-space: pre;
} }

View file

@ -1,17 +1,13 @@
<template> <template>
<transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')"> <transition :name="$store.state.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
<div class="ebkgocck" :class="{ front }" v-if="showing"> <div class="ebkgocck" :class="{ front }" v-if="showing">
<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> <div class="body _window _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu"> <div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu">
<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> <button v-if="closeButton" class="_button" @click="close()"><i class="fas fa-times"></i></button>
<button v-else class="_button" @click="close()"><i class="fas fa-times"></i></button>
<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> <span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
<slot name="header"></slot> <slot name="header"></slot>
</span> </span>
<button v-if="closeRight" class="_button" @click="close()"><i class="fas fa-times"></i></button>
<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
</div> </div>
<div class="body" v-if="padding"> <div class="body" v-if="padding">
<div class="_section"> <div class="_section">
@ -86,10 +82,10 @@ export default defineComponent({
required: false, required: false,
default: false, default: false,
}, },
closeRight: { closeButton: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: true,
}, },
mini: { mini: {
type: Boolean, type: Boolean,
@ -420,6 +416,7 @@ export default defineComponent({
flex-shrink: 0; flex-shrink: 0;
user-select: none; user-select: none;
height: var(--height); height: var(--height);
border-bottom: solid 1px var(--divider);
> ::v-deep(button) { > ::v-deep(button) {
height: var(--height); height: var(--height);

View file

@ -0,0 +1,58 @@
<template>
<MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')">
<div class="ewlycnyt">
<div class="title">{{ $ts.misskeyUpdated }}</div>
<div class="version">{{ version }}🚀</div>
<MkButton full @click="whatIsNew">{{ $ts.whatIsNew }}</MkButton>
<MkButton primary full @click="$refs.modal.close()">{{ $ts.gotIt }}</MkButton>
</div>
</MkModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkModal from '@client/components/ui/modal.vue';
import MkButton from '@client/components/ui/button.vue';
import { version } from '@client/config';
export default defineComponent({
components: {
MkModal,
MkButton,
},
data() {
return {
version: version,
};
},
methods: {
whatIsNew() {
this.$refs.modal.close();
this.$router.push('/docs/general/changelog');
}
}
});
</script>
<style lang="scss" scoped>
.ewlycnyt {
position: relative;
padding: 32px;
min-width: 320px;
max-width: 480px;
box-sizing: border-box;
text-align: center;
background: var(--panel);
border-radius: var(--radius);
> .title {
font-weight: bold;
}
> .version {
margin: 1em 0;
}
}
</style>

View file

@ -31,7 +31,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { parseAcct } from '@/misc/acct'; import { parseAcct } from '@/misc/acct';
import MkFollowButton from './follow-button.vue'; import MkFollowButton from './follow-button.vue';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
export default defineComponent({ export default defineComponent({
components: { components: {

View file

@ -18,7 +18,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import paging from '@client/scripts/paging'; import paging from '@client/scripts/paging';
import MkUserInfo from './user-info.vue'; import MkUserInfo from './user-info.vue';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
export default defineComponent({ export default defineComponent({
components: { components: {

View file

@ -35,7 +35,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { parseAcct } from '@/misc/acct'; import { parseAcct } from '@/misc/acct';
import MkFollowButton from './follow-button.vue'; import MkFollowButton from './follow-button.vue';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
import * as os from '@client/os'; import * as os from '@client/os';
export default defineComponent({ export default defineComponent({

View file

@ -10,9 +10,15 @@
<template #header>{{ $ts.selectUser }}</template> <template #header>{{ $ts.selectUser }}</template>
<div class="tbhwbxda _monolithic_"> <div class="tbhwbxda _monolithic_">
<div class="_section"> <div class="_section">
<div class="inputs"> <div class="_inputSplit _inputNoTopMargin _inputNoBottomMargin">
<MkInput v-model:value="username" class="input" @update:value="search" ref="username"><span>{{ $ts.username }}</span><template #prefix>@</template></MkInput> <MkInput v-model="username" class="input" @update:modelValue="search" ref="username">
<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $ts.host }}</span><template #prefix>@</template></MkInput> <template #label>{{ $ts.username }}</template>
<template #prefix>@</template>
</MkInput>
<MkInput v-model="host" class="input" @update:modelValue="search">
<template #label>{{ $ts.host }}</template>
<template #prefix>@</template>
</MkInput>
</div> </div>
</div> </div>
<div class="_section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }"> <div class="_section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }">
@ -138,14 +144,6 @@ export default defineComponent({
padding: 0; padding: 0;
} }
> .inputs {
> .input {
display: inline-block;
width: 50%;
margin: 0;
}
}
> .users { > .users {
flex: 1; flex: 1;
overflow: auto; overflow: auto;

View file

@ -28,7 +28,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import paging from '@client/scripts/paging'; import paging from '@client/scripts/paging';
import { userPage } from '../filters/user'; import { userPage } from '@client/filters/user';
export default defineComponent({ export default defineComponent({
mixins: [ mixins: [

View file

@ -2,7 +2,7 @@
<div class="vjoppmmu"> <div class="vjoppmmu">
<template v-if="edit"> <template v-if="edit">
<header> <header>
<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)">
<template #label>{{ $ts.selectWidget }}</template> <template #label>{{ $ts.selectWidget }}</template>
<option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> <option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option>
</MkSelect> </MkSelect>
@ -18,12 +18,12 @@
<div class="customize-container"> <div class="customize-container">
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button>
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button>
<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="updateWidget(element.id, $event)"/> <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" @updateProps="updateWidget(element.id, $event)"/>
</div> </div>
</template> </template>
</XDraggable> </XDraggable>
</template> </template>
<component v-else class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="updateWidget(widget.id, $event)"/> <component v-else class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" @updateProps="updateWidget(widget.id, $event)"/>
</div> </div>
</template> </template>

Some files were not shown because too many files have changed in this diff Show more