Merge branch 'develop' into migrate-accounts-to-idb

This commit is contained in:
tamaina 2021-08-16 13:29:55 +09:00
commit 1ba6423aab
1094 changed files with 39163 additions and 3503 deletions

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 -->

View file

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

View file

@ -1 +1,47 @@
see [releases](https://github.com/misskey-dev/misskey/releases) <!--
## 12.x.x (unreleased)
### Improvements
### Bugfixes
-->
## 12.x.x (unreleased)
### Improvements
- ノートの翻訳機能を追加
- 有効にするには、サーバー管理者がDeepLの無料アカウントを登録し、取得した認証キーを「インスタンス設定 > その他 > DeepL Auth Key」に設定する必要があります。
- Misskey更新時にダイアログを表示するように
- ジョブキューウィジェットに警報音を鳴らす設定を追加
- UIデザインの調整
- データベースのインデックスを最適化
### Bugfixes
- 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

@ -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

@ -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 ligiĝanta per notoj"
introMisskey: "Bonvenon! Miskejo estas malferma kodaの分散型マイクロブログサービスです。\nBonvolu Krei「noto」、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「 reaktigoj 」機能で、皆のnotojに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" introMisskey: "Bonvenon! Misskey estas malfermitkoda malcentriza mikrobloga servo.\nKreu \"noto\"n por kunhavu 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 konprenas!"
cancel: "Nuligi" cancel: "Nuligi"
enterUsername: "Entajpu uzantonomon" enterUsername: "Entajpu uzantnomon"
renotedBy: "Renotigojn faras {user}" renotedBy: "Renoton faras {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,33 +43,34 @@ 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? Ĉiuj reagoj, renotoj, kaj respondoj ankaŭ 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: "Vin eksekvis"
receiveFollowRequest: "Eksekvopeton riceviĝis."
followRequestAccepted: "La eksekvopeto akceptiĝis."
mention: "Mencioj" mention: "Mencioj"
mentions: "Mencioj" mentions: "Al vi"
directNotes: "Senperaj notoj"
importAndExport: "Importaĵo / Eksportaĵo" importAndExport: "Importaĵo / Eksportaĵo"
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: "Vin sekvas "
createList: "Kreii liston" createList: "Kreii liston"
error: "Eraro" error: "Eraro"
somethingHappened: "Problemo okazis." somethingHappened: "Problemo okazis."
@ -76,13 +78,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 akcepti de vi eksekvi"
followRequests: "Eksekvopetoj" followRequests: "Eksekvopetoj"
unfollow: "Ne plu sekvi" unfollow: "Ne plu sekvi"
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"
@ -103,36 +107,53 @@ 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 bildo de 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"
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: "Konfederacio"
instances: "Ekzemplo" instances: "Ekzemplo"
perHour: "Po horo"
perDay: "Po tago"
blockThisInstance: "Bloki tiu ekzemplo" blockThisInstance: "Bloki tiu ekzemplo"
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."
noCustomEmojis: "Neniu emoĵio" noCustomEmojis: "Neniu emoĵio"
federating: "Konfederado" federating: "Konfederado"
blocked: "Blokita" blocked: "Blokata"
subscribing: "Abonita" subscribing: "Abonita"
notResponding: "Alvokato ne disponeblas" notResponding: "Alvokato ne disponeblas"
instanceFollowing: "Sekvi ekzemplon" instanceFollowing: "Sekvatoj sur 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"
currentPassword: "Aktuala pasvorto" currentPassword: "Aktuala pasvorto"
@ -140,164 +161,332 @@ 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" usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto"
noSuchUser: "Neniuj uzantoj trovitaj." noSuchUser: "Neniuj uzantoj trovitaj."
remove: "Forviŝi" 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" messaging: "Retbabili"
upload: "Alŝuti" upload: "Alŝuti"
fromDrive: "De la diskilo" fromDrive: "De la diskingo"
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 volu alŝuti"
games: "Ludoj sur Miskejo" games: "Ludoj sur Misskey"
messageRead: "Legita" messageRead: "Legita"
startMessaging: "Komenci babiladon" startMessaging: "Komenci babiladon"
tos: "Kondiĉoj de Uzado" 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."
images: "Bildoj"
birthday: "Naskiĝtago"
registeredDate: "Registriĝdato"
drive: "Diskingo"
fileName: "Dosiernomo" fileName: "Dosiernomo"
selectFile: "Elekti dosieron" selectFile: "Elekti dosieron"
selectFiles: "Elekti dosieron" selectFiles: "Elekti dosieron"
renameFile: "Renomigi dosieron" renameFile: "Alinomi la dosieron"
folderName: "Nomo de la dosierujo"
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 diskingo malplenas."
unableToDelete: "Ne forigebla" unableToDelete: "Ne forigebla"
inputNewFileName: "Entajpu nova dosiernomon" inputNewFileName: "Entajpu nova dosiernomon"
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"
nsfw: "Enhavo ne estas deca por laborejo (NSFW)" nsfw: "Enhavo ne estas deca por laborejo (NSFW)"
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}"
connectService: "Konekti" connectService: "Konekti"
disconnectService: "Farkonektiĝi" disconnectService: "Farkonektiĝi"
driveCapacityPerLocalAccount: "Volumo po unu loka-uzanto" driveCapacityPerLocalAccount: "Volumo de miskej-diskingo po unu loka uzanto"
driveCapacityPerRemoteAccount: "Volumo po unu transa uzanto" driveCapacityPerRemoteAccount: "Volumo de miskej-diskingo po unu transa uzanto"
iconUrl: "URL de la ikono (retpaĝsimbolo, ktp.)"
pinnedUsers: "Alpinglita uzanto" pinnedUsers: "Alpinglita uzanto"
pinnedNotes: "Pinglita noto" pinnedNotes: "Pinglita noto"
name: "Nomo"
withFileAntenna: "Nur kun aldonaĵo" withFileAntenna: "Nur kun aldonaĵo"
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" aboutMisskey: "Pri Misskey"
securityKeyName: "Nomo de la ŝlosilo"
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}."
cacheClear: "Malplenigi staplon"
help: "Manlibro de uzado"
inputMessageHere: "Entajpu masaĝo tie ĉi" inputMessageHere: "Entajpu masaĝo tie ĉi"
groupName: "Grupa nomo"
messagingWithUser: "Mesaĝado kun uzanto"
messagingWithGroup: "Mesaĝi kun grupo"
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."
invitationCode: "Kodo de invito"
uiLanguage: "Lingvo de la interfaco" uiLanguage: "Lingvo de la interfaco"
tags: "Etikedoj"
createAccount: "Krei konton"
existingAccount: "Ekzista konto"
noFollowRequests: "Vi ne havas eksekvopetojn." noFollowRequests: "Vi ne havas eksekvopetojn."
openImageInNewTab: "Fermi la bildon en nova tablo"
local: "Loka" local: "Loka"
remote: "Transa" remote: "Transa"
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"
deleteAll: "Forviŝi ĉiujn"
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."
deletedNote: "Forviŝita noto"
invisibleNote: "Malpublika noto" invisibleNote: "Malpublika noto"
poll: "Balotujo"
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" smtpUser: "Uzantnomo"
smtpPass: "Pasvorto" smtpPass: "Pasvorto"
wordMute: "Silentigo de vortoj"
userSaysSomething: "{name} parolis ion" userSaysSomething: "{name} parolis ion"
display: "Vidi"
database: "Datumbazo" database: "Datumbazo"
channel: "Kanalo" channel: "Kanalo"
fileIdOrUrl: "Dosirero ID aŭ retadreso" 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" i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado ĉe {link}."
followingCount: "Numero de sekvatoj"
followersCount: "Numero de sekvantoj"
yes: "Jes"
no: "Ne"
driveFilesCount: "Numero de dosieroj en la diskingo"
noteFavoritesCount: "Numero de la preferataj notoj"
makeExplorable: "Igi videbla konto sur la paĝo \"Esplorado\""
showTitlebar: "Montri titolobredon"
clearCache: "Malplenigi staplon"
onlineUsersCount: "{n} uzanto(j) estas surlinea"
nUsers: "{n} uzanto(j)" nUsers: "{n} uzanto(j)"
saveAs: "Konservi kiel…"
createdAt: "Kreita je"
updatedAt: "Laste ĝisdatigita"
deleteConfirm: "Ĉu certas forviŝi?"
closeAccount: "Forigi konton"
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: "Malfermu reago-elektilon per dekstro-kliki"
typingUsers: "{users} estas entajpanta(j)..." typingUsers: "{users} estas entajpanta(j)..."
online: "Surkonektita" online: "Surkonektita"
offline: "Forkonektita" offline: "Forkonektita"
instanceBlocking: "Ekzempla blokado" instanceBlocking: "Blokado de ekzemplo"
selectAccount: "Elekti konton"
user: "Uzanto" user: "Uzanto"
accounts: "Kontoj"
global: "Konfederacia"
sent: "Sendi"
hashtags: "Kradvorto"
_gallery: _gallery:
liked: "Ŝatitaj notoj" liked: "Ŝatitaj notoj"
_email: _email:
_follow: _follow:
title: "Vi estas eksekvita." title: "Vin eksekvis"
_receiveFollowRequest: _receiveFollowRequest:
title: "Vi ricevis eksekvopeton." title: "Vi ricevis eksekvopeton."
_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)" 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"
_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: "Sekvata"
_menuDisplay:
hide: "Kaŝi"
_wordMute:
muteWords: "Silentigataj vortoj"
mutedNotes: "Silentigataj notoj"
_theme: _theme:
code: "Kodo de koloraro"
keys: keys:
hashtag: "Kradvorto"
mention: "Mencioj" mention: "Mencioj"
renote: "Renotici" renote: "Fari renoton"
_sfx: _sfx:
note: "Nova noto" note: "Nova noto"
noteMy: "Mia noto"
notification: "Sciigoj" notification: "Sciigoj"
chat: "Babilejoj" chat: "Retbabilejo"
channel: "Kanala sciigoj" chatBg: "Retbabilejo (BG)"
antenna: "Ricevo de anteno"
channel: "Sciigoj de kanalo"
_ago:
secondsAgo: "Antaŭ {n} sekundoj"
minutesAgo: "Antaŭ {n} minutoj"
hoursAgo: "Antaŭ {n} horoj"
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 volus scii pli pri Miskejon, volu rigardi la fakon {help}."
step7_3: "Do, bonvolu amuziĝi Miskejon🚀"
_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 diskingo de Miskejo"
"write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via diskingo de Miskejo"
"read:favorites": "Vidi vian liston de preferatoj"
"read:following": "Vidi tion kion vi sekvas"
"write:following": "Sekvi kaj/aŭ malsekvi alian uzanton"
"read:messaging": "Vidi via retbabilado"
"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:
homeTimeline: "Notoj far uzantoj sekvataj de vi"
_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"
federation: "Konfederacio"
slideshow: "Bildoprezento"
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 plu povas aldoni."
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: "Elsendi 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: "Respondado 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"
_exportOrImport: _exportOrImport:
followingList: "Sekvi" followingList: "Sekvataj"
muteList: "Silentigi" muteList: "Silentigoj"
blockingList: "Blokado" blockingList: "Blokado"
userLists: "Listoj" userLists: "Listoj"
_timelines: _timelines:
home: "Hejmo" home: "Hejmo"
local: "Loka" local: "Loka"
social: "Hejmo kaj loka" social: "Sociala"
global: "Konfederacia"
_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"
content: "Blokado de paĝo" content: "Blokado de paĝo"
url: "Retadreso de la paĝo" url: "URL de paĝo"
chooseBlock: "Aldoni blokado" chooseBlock: "Aldoni blokon"
blocks:
image: "Bildoj"
_post:
canvasId: "Kanvasa identigilo"
_canvas:
id: "Kanvasa identigilo"
_note:
id: "Identigilo de noto"
_button:
_action:
_pushEvent:
event: "Nomo de la evento"
script: script:
categories: categories:
list: "Listoj" list: "Listoj"
@ -317,21 +506,26 @@ _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"
youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi."
youWereFollowed: "Vin eksekvis"
youReceivedFollowRequest: "Vi ricevis eksekvopeton." youReceivedFollowRequest: "Vi ricevis eksekvopeton."
yourFollowRequestAccepted: "Via eksekvopeto estas akceptita." yourFollowRequestAccepted: "Via eksekvopeto 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: "Eksekvopeto ricevita"
followRequestAccepted: "Eksekvopeto akceptiĝis."
_deck: _deck:
profile: "Agordaro"
_columns: _columns:
notifications: "Sciigoj" notifications: "Sciigoj"
tl: "Tempolinio" tl: "Templinio"
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

@ -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"
@ -760,7 +762,19 @@ 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"
hashtags: "Hashtags"
troubleshooting: "Résolution de problèmes"
_docs:
continueReading: "Lire plus"
features: "Fonctionnalités"
generalTopics: "Sujets généraux"
advancedTopics: "Sujets avancés"
admin: "Gestion"
_ad: _ad:
back: "Retour" back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
@ -859,6 +873,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 +927,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"
@ -1590,11 +1609,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,14 @@ 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"
hashtags: "Tagar"
_docs:
admin: "Manajemen"
_ad: _ad:
back: "Kembali" back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
@ -911,6 +920,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

@ -127,6 +127,7 @@ editWidgets: "ウィジェットをいじる"
editWidgetsExit: "編集終ったで" editWidgetsExit: "編集終ったで"
customEmojis: "カスタム絵文字" customEmojis: "カスタム絵文字"
emoji: "絵文字" emoji: "絵文字"
emojis: "絵文字"
emojiName: "絵文字名" emojiName: "絵文字名"
emojiUrl: "絵文字画像URL" emojiUrl: "絵文字画像URL"
addEmoji: "絵文字を追加" addEmoji: "絵文字を追加"
@ -647,6 +648,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,23 @@ middle: "보통"
low: "낮음" low: "낮음"
emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다."
ratio: "비율" ratio: "비율"
customCss: "CSS 사용자화"
customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다."
global: "글로벌" global: "글로벌"
squareAvatars: "프로필 아이콘을 사각형으로 표시"
sent: "전송"
received: "수신"
searchResult: "검색 결과"
hashtags: "해시태그"
troubleshooting: "트러블 슈팅"
useBlurEffect: "UI에 흐림 효과 사용"
_docs:
continueReading: "계속 읽기"
features: "기능"
generalTopics: "일반 주제"
advancedTopics: "심화 주제"
admin: "관리"
translateWarn: "이 문서는 번역되었기 때문에 원본과는 내용이 다를 수 있습니다."
_ad: _ad:
back: "뒤로" back: "뒤로"
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
@ -859,6 +877,8 @@ _mfm:
blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다." blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다."
font: "폰트" font: "폰트"
fontDescription: "내용의 글꼴을 지정할 수 있습니다." fontDescription: "내용의 글꼴을 지정할 수 있습니다."
rainbow: "무지개"
rainbowDescription: "내용을 무지개로 표시합니다."
_reversi: _reversi:
reversi: "리버시" reversi: "리버시"
gameSettings: "대국 설정" gameSettings: "대국 설정"
@ -911,6 +931,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,23 @@ middle: "Средне"
low: "Низкий" low: "Низкий"
emailNotConfiguredWarning: "Не указан адрес электронной почты" emailNotConfiguredWarning: "Не указан адрес электронной почты"
ratio: "Соотношение" ratio: "Соотношение"
customCss: "Индивидуальный CSS"
customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас."
global: "Всеобщая" global: "Всеобщая"
squareAvatars: "Квадратные аватарки"
sent: "Отправить"
received: "Получено"
searchResult: "Результаты поиска"
hashtags: "Хэштег"
troubleshooting: "Разрешение проблем"
useBlurEffect: "Размытие в интерфейсе"
_docs:
continueReading: "Читать подробнее"
features: "Возможности"
generalTopics: "Основные темы"
advancedTopics: "Дополнительные темы"
admin: "Управление"
translateWarn: "Это перевод документа. Он может неточно отражать содержимое оригинала."
_ad: _ad:
back: "Выход" back: "Выход"
reduceFrequencyOfThisAd: "Реже показывать эту рекламу" reduceFrequencyOfThisAd: "Реже показывать эту рекламу"
@ -859,6 +877,8 @@ _mfm:
blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость." blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость."
font: "Шрифт" font: "Шрифт"
fontDescription: "Так можно писать произвольным шрифтом." fontDescription: "Так можно писать произвольным шрифтом."
rainbow: "Радуга"
rainbowDescription: "Заставлять содержимое отображаться в цветах радуги."
_reversi: _reversi:
reversi: "Реверси" reversi: "Реверси"
gameSettings: "Настройки игры" gameSettings: "Настройки игры"
@ -911,6 +931,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,20 @@ customCss: "自定义 CSS"
customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!" customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!"
global: "全局" global: "全局"
squareAvatars: "显示方形头像图标" squareAvatars: "显示方形头像图标"
sent: "发送"
received: "收取"
searchResult: "搜索结果"
hashtags: "话题标签"
troubleshooting: "故障排除"
useBlurEffect: "在UI上使用模糊效果"
learnMore: "更多信息"
_docs:
continueReading: "继续阅读"
features: "特性"
generalTopics: "通常提示"
advancedTopics: "进阶提示"
admin: "管理"
translateWarn: "本文档是翻译后的文档。内容可能与原文有所不同。"
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "减少此广告的频率" reduceFrequencyOfThisAd: "减少此广告的频率"
@ -863,6 +878,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,168 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class chartReindex1629004542760 implements MigrationInterface {
name = 'chartReindex1629004542760'
public async up(queryRunner: QueryRunner): Promise<void> {
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

@ -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.87.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -11,6 +11,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./index.js", "start": "node ./index.js",
"start:test": "cross-env NODE_ENV=test node ./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"
}, },
@ -44,7 +48,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 +61,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 +72,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.0",
"@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",
@ -88,7 +92,7 @@
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.1", "@types/rimraf": "3.0.1",
"@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 +102,41 @@
"@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.1",
"@vue/compiler-sfc": "3.1.5", "@vue/compiler-sfc": "3.2.2",
"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.3",
"broadcast-channel": "3.7.0", "broadcast-channel": "3.7.0",
"bull": "3.26.0", "bull": "3.26.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": "7.2.0",
"compare-versions": "3.6.0",
"concurrently": "6.2.0", "concurrently": "6.2.0",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"core-js": "3.15.2", "core-js": "3.16.1",
"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.7",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"diskusage": "1.1.3", "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",
@ -151,12 +156,12 @@
"is-root": "2.1.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",
@ -168,7 +173,7 @@
"koa-views": "7.0.1", "koa-views": "7.0.1",
"langmap": "0.0.16", "langmap": "0.0.16",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"markdown-it": "12.1.0", "markdown-it": "12.2.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",
@ -176,7 +181,7 @@
"mocha": "8.4.0", "mocha": "8.4.0",
"moji": "0.5.1", "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",
@ -185,7 +190,7 @@
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.6.0", "pg": "8.6.0",
"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",
@ -202,32 +207,32 @@
"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", "regenerator-runtime": "0.13.9",
"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.37.5",
"sass-loader": "12.1.0", "sass-loader": "12.1.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.28.3", "sharp": "0.28.3",
"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.0",
"tsc-alias": "1.3.7", "tsc-alias": "1.3.8",
"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",
@ -237,21 +242,21 @@
"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", "vanilla-tilt": "1.7.1",
"vue": "3.1.5", "vue": "3.2.2",
"vue-color": "2.8.1", "vue-color": "2.8.1",
"vue-json-pretty": "1.8.1", "vue-json-pretty": "1.8.1",
"vue-loader": "16.3.1", "vue-loader": "16.5.0",
"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.50.0",
"webpack-cli": "4.7.2", "webpack-cli": "4.7.2",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "7.5.3", "ws": "8.1.0",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
@ -259,6 +264,8 @@
"@types/chai": "4.2.16", "@types/chai": "4.2.16",
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.17",
"chai": "4.3.4", "chai": "4.3.4",
"cross-env": "7.0.3" "cross-env": "7.0.3",
"cypress": "8.2.0",
"start-server-and-test": "1.13.1"
} }
} }

View file

@ -18,7 +18,7 @@ program
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

@ -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; position: absolute;
top: calc(0px - (var(--size) * 2));
&.big { left: 0;
--eachSize: 44px; right: 0;
} width: 0;
margin: auto;
&.w1 { border: solid var(--size) transparent;
width: calc((var(--eachSize) * 5) + (#{$pad} * 2)); border-bottom-color: var(--popup);
}
&.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;
bottom: 0;
left: 0;
width: 100%;
padding: 16px 0;
text-align: center;
opacity: 0.5;
pointer-events: none;
}
}
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 { &.mini {
width: 32px; padding: 16px;
height: 32px; --size: 32px;
}
> .ring {
&:before,
&:after {
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

@ -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"/>
@ -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();
}, },
@ -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">
<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 :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,10 @@ export default defineComponent({
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
} }
.giivymft {
> .notes {
background: var(--panel);
}
}
</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) {
} else { debouncedUpdated();
context.emit('update:value', newValue); } else {
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,67 +285,33 @@ 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;
} }
* { &.inline {
display: inline-block;
margin: 0; margin: 0;
} }
}
&.focused { &.focused {
> .input { > input {
&:after { border-color: var(--accent);
opacity: 1; //box-shadow: 0 0 0 4px var(--focus);
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);
} }
} }
} }
&.inline {
display: inline-block;
margin: 0;
}
&.disabled {
opacity: 0.7;
&, * {
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' : ''" :duration="$store.state.animation ? 300 : 0" 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="text"><slot name="text"></slot></div> <div class="caption"><slot name="caption"></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 {
margin: 6px 0;
font-size: 0.8em;
&:empty {
display: none;
} }
* { &.inline {
display: inline-block;
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 { &.focused {
margin: 6px 0 0 0; > textarea {
font-size: 0.8em; border-color: var(--accent);
}
> .desc {
margin: 6px 0 0 0;
&:empty {
display: none;
}
* {
margin: 0;
}
}
&.focused {
> .input {
&:after {
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>

View file

@ -16,6 +16,7 @@ if (localStorage.getItem('accounts') != null) {
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing'; import { Integrations } from '@sentry/tracing';
import { computed, createApp, watch, markRaw } from 'vue'; import { computed, createApp, watch, markRaw } from 'vue';
import compareVersions from 'compare-versions';
import widgets from '@client/widgets'; import widgets from '@client/widgets';
import directives from '@client/directives'; import directives from '@client/directives';
@ -25,7 +26,7 @@ import { router } from '@client/router';
import { applyTheme } from '@client/scripts/theme'; import { applyTheme } from '@client/scripts/theme';
import { isDeviceDarkmode } from '@client/scripts/is-device-darkmode'; import { isDeviceDarkmode } from '@client/scripts/is-device-darkmode';
import { i18n } from '@client/i18n'; import { i18n } from '@client/i18n';
import { stream, dialog, post } from '@client/os'; import { stream, dialog, post, popup } from '@client/os';
import * as sound from '@client/scripts/sound'; import * as sound from '@client/scripts/sound';
import { $i, refreshAccount, login, updateAccount, signout } from '@client/account'; import { $i, refreshAccount, login, updateAccount, signout } from '@client/account';
import { defaultStore, ColdDeviceStorage } from '@client/store'; import { defaultStore, ColdDeviceStorage } from '@client/store';
@ -207,6 +208,23 @@ if (splash) {
splash.style.pointerEvents = 'none'; splash.style.pointerEvents = 'none';
} }
// クライアントが更新されたか?
const lastVersion = localStorage.getItem('lastVersion');
if (lastVersion !== version) {
localStorage.setItem('lastVersion', version);
// テーマリビルドするため
localStorage.removeItem('theme');
try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
popup(import('@client/components/updated.vue'), {}, {}, 'closed');
}
} catch (e) {
}
}
// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため)
watch(defaultStore.reactiveState.darkMode, (darkMode) => { watch(defaultStore.reactiveState.darkMode, (darkMode) => {
applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
}, { immediate: localStorage.theme == null }); }, { immediate: localStorage.theme == null });
@ -252,6 +270,14 @@ watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true }); }, { immediate: true });
watch(defaultStore.reactiveState.useBlurEffect, v => {
if (v) {
document.documentElement.style.removeProperty('--blur');
} else {
document.documentElement.style.setProperty('--blur', 'none');
}
}, { immediate: true });
let reloadDialogShowing = false; let reloadDialogShowing = false;
stream.on('_disconnected_', async () => { stream.on('_disconnected_', async () => {
if (defaultStore.state.serverDisconnectedBehavior === 'reload') { if (defaultStore.state.serverDisconnectedBehavior === 'reload') {

View file

@ -25,6 +25,7 @@ export async function fetchInstance() {
} }
export const emojiCategories = computed(() => { export const emojiCategories = computed(() => {
if (instance.emojis == null) return [];
const categories = new Set(); const categories = new Set();
for (const emoji of instance.emojis) { for (const emoji of instance.emojis) {
categories.add(emoji.category); categories.add(emoji.category);
@ -33,6 +34,7 @@ export const emojiCategories = computed(() => {
}); });
export const emojiTags = computed(() => { export const emojiTags = computed(() => {
if (instance.emojis == null) return [];
const tags = new Set(); const tags = new Set();
for (const emoji of instance.emojis) { for (const emoji of instance.emojis) {
for (const tag of emoji.aliases) { for (const tag of emoji.aliases) {

View file

@ -113,6 +113,16 @@ export const menuDef = {
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
to: '/channels', to: '/channels',
}, },
federation: {
title: 'federation',
icon: 'fas fa-globe',
to: '/federation',
},
emojis: {
title: 'emojis',
icon: 'fas fa-laugh',
to: '/emojis',
},
games: { games: {
title: 'games', title: 'games',
icon: 'fas fa-gamepad', icon: 'fas fa-gamepad',
@ -133,7 +143,7 @@ export const menuDef = {
title: 'switchUi', title: 'switchUi',
icon: 'fas fa-columns', icon: 'fas fa-columns',
action: (ev) => { action: (ev) => {
os.modalMenu([{ os.popupMenu([{
text: i18n.locale.default, text: i18n.locale.default,
action: () => { action: () => {
localStorage.setItem('ui', 'default'); localStorage.setItem('ui', 'default');

View file

@ -368,10 +368,10 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
}); });
} }
export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { export function popupMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let dispose; let dispose;
popup(import('@client/components/ui/modal-menu.vue'), { popup(import('@client/components/ui/popup-menu.vue'), {
items, items,
src, src,
align: options?.align, align: options?.align,

View file

@ -1,11 +1,11 @@
<template> <template>
<transition :name="$store.state.animation ? 'zoom' : ''" appear> <transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div class="_section"> <div class="mjndxjch">
<div class="mjndxjch _content"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p>
<p><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</p> <p>{{ $ts.pageLoadErrorDescription }}</p>
<p>{{ $ts.pageLoadErrorDescription }}</p> <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p>
</div> <p v-if="error" class="error">ERROR: {{ error }}</p>
</div> </div>
</transition> </transition>
</template> </template>
@ -19,6 +19,11 @@ export default defineComponent({
components: { components: {
MkButton, MkButton,
}, },
props: {
error: {
required: false,
}
},
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
@ -32,10 +37,11 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.mjndxjch { .mjndxjch {
padding: 32px;
text-align: center; text-align: center;
> p { > p {
margin: 0 0 8px 0; margin: 0 0 12px 0;
} }
> .button { > .button {
@ -45,8 +51,12 @@ export default defineComponent({
> img { > img {
vertical-align: bottom; vertical-align: bottom;
height: 128px; height: 128px;
margin-bottom: 16px; margin-bottom: 24px;
border-radius: 16px; border-radius: 16px;
} }
> .error {
opacity: 0.7;
}
} }
</style> </style>

View file

@ -4,14 +4,14 @@
<div id="debug"></div> <div id="debug"></div>
<section class="_formItem about"> <section class="_formItem about">
<div class="_formPanel panel" :class="{ playing: easterEggEngine != null }" ref="about"> <div class="_formPanel panel" :class="{ playing: easterEggEngine != null }" ref="about">
<img src="/static-assets/client/about-icon.png" alt="" class="icon" ref="icon" @load="iconLoaded" draggable="false"/> <img src="/static-assets/client/about-icon.png" alt="" class="icon" @load="iconLoaded" draggable="false" @click="gravity"/>
<div class="misskey">Misskey</div> <div class="misskey">Misskey</div>
<div class="version">v{{ version }}</div> <div class="version">v{{ version }}</div>
<span class="emoji" v-for="emoji in easterEggEmojis" :key="emoji.id" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> <span class="emoji" v-for="emoji in easterEggEmojis" :key="emoji.id" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span>
</div> </div>
</section> </section>
<section class="_formItem" style="text-align: center; padding: 0 16px;" @click="gravity"> <section class="_formItem" style="text-align: center; padding: 0 16px;">
{{ $ts._aboutMisskey.about }} {{ $ts._aboutMisskey.about }}<br><MkA class="_link" to="/docs/general/misskey">{{ $ts.learnMore }}</MkA>
</section> </section>
<FormGroup> <FormGroup>
<FormLink to="https://github.com/misskey-dev/misskey" external> <FormLink to="https://github.com/misskey-dev/misskey" external>
@ -54,7 +54,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import VanillaTilt from 'vanilla-tilt';
import { version } from '@client/config'; import { version } from '@client/config';
import FormLink from '@client/components/form/link.vue'; import FormLink from '@client/components/form/link.vue';
import FormBase from '@client/components/form/base.vue'; import FormBase from '@client/components/form/base.vue';
@ -62,46 +61,63 @@ import FormGroup from '@client/components/form/group.vue';
import FormKeyValueView from '@client/components/form/key-value-view.vue'; import FormKeyValueView from '@client/components/form/key-value-view.vue';
import MkLink from '@client/components/link.vue'; import MkLink from '@client/components/link.vue';
import { physics } from '@client/scripts/physics.ts'; import { physics } from '@client/scripts/physics.ts';
import * as os from '@client/os';
import * as symbols from '@client/symbols'; import * as symbols from '@client/symbols';
const patrons = [ const patrons = [
'Satsuki Yanagi', 'Satsuki Yanagi',
'noellabo', 'noellabo',
'Gargron', 'mametsuko',
'Atsuko Tominaga',
'AureoleArk', 'AureoleArk',
'Gargron',
'Nokotaro Takeda',
'Suji Yan',
'Hekovic',
'Gitmo Life Services',
'nenohi',
'naga_rus', 'naga_rus',
'Melilot', 'Melilot',
'Hekovic',
'Nokotaro Takeda',
'dansup',
'nenohi',
'motcha',
'nanami kan',
'Eduardo Quiros',
'Peter G.',
'YUKIMOCHI',
'Efertone', 'Efertone',
'makokunsan', 'oi_yekssim',
'nanami kan',
'motcha',
'dansup',
'Quinton Macejkovic',
'YUKIMOCHI',
'mewl hayabusa', 'mewl hayabusa',
'makokunsan',
'Peter G.',
'Nesakko',
'regtan',
'見当かなみ', '見当かなみ',
'natalie', 'natalie',
'Jerry',
'takimura', 'takimura',
'sikyosyounin', 'sikyosyounin',
'weepjp', 'YuzuRyo61',
'mydarkstar',
'Nesakko',
'sheeta.s', 'sheeta.s',
'osapon', 'osapon',
'YuzuRyo61',
'wara',
'mkatze', 'mkatze',
'kiritan',
'CG', 'CG',
'nafuchoco',
'Takumi Sugita',
'chidori ninokura',
'mydarkstar',
'kiritan',
'kabo2468y',
'weepjp',
'Liaizon Wakest',
'Steffen K9',
'Roujo',
'uroco @99',
'totokoro',
'public_yusuke',
'wara',
'S Y',
'Denshi', 'Denshi',
'Osushimaru', 'Osushimaru',
'Liaizon Wakest', '吴浥',
'DignifiedSilence',
't_w',
]; ];
export default defineComponent({ export default defineComponent({
@ -127,15 +143,6 @@ export default defineComponent({
} }
}, },
mounted() {
VanillaTilt.init(this.$refs.icon, {
max: 30,
perspective: 500,
scale: 1.125,
speed: 1000,
});
},
beforeUnmount() { beforeUnmount() {
if (this.easterEggEngine) { if (this.easterEggEngine) {
this.easterEggEngine.stop(); this.easterEggEngine.stop();
@ -163,7 +170,6 @@ export default defineComponent({
gravity() { gravity() {
if (!this.easterEggReady) return; if (!this.easterEggReady) return;
this.easterEggReady = false; this.easterEggReady = false;
this.$refs.icon.vanillaTilt.destroy();
this.easterEggEngine = physics(this.$refs.about); this.easterEggEngine = physics(this.$refs.about);
} }
} }

View file

@ -272,7 +272,7 @@ export default defineComponent({
showTypeMenu(e: MouseEvent) { showTypeMenu(e: MouseEvent) {
return new Promise<ThemeValue>((resolve) => { return new Promise<ThemeValue>((resolve) => {
os.modalMenu([{ os.popupMenu([{
text: this.$ts._theme.defaultValue, text: this.$ts._theme.defaultValue,
action: () => resolve(null), action: () => resolve(null),
}, { }, {

View file

@ -1,13 +1,13 @@
<template> <template>
<div class="_root"> <div class="_root">
<div class="_block" style="padding: 24px;"> <div class="_block" style="padding: 24px;">
<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()"> <MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()" class="_inputNoTopMargin">
<span>Endpoint</span> <template #label>Endpoint</template>
</MkInput> </MkInput>
<MkTextarea v-model:value="body" code> <MkTextarea v-model="body" code>
<span>Params (JSON or JSON5)</span> <template #label>Params (JSON or JSON5)</template>
</MkTextarea> </MkTextarea>
<MkSwitch v-model:value="withCredential"> <MkSwitch v-model="withCredential">
With credential With credential
</MkSwitch> </MkSwitch>
<MkButton primary full @click="send" :disabled="sending"> <MkButton primary full @click="send" :disabled="sending">
@ -16,8 +16,8 @@
</MkButton> </MkButton>
</div> </div>
<div v-if="res" class="_block" style="padding: 24px;"> <div v-if="res" class="_block" style="padding: 24px;">
<MkTextarea v-model:value="res" code readonly tall> <MkTextarea v-model="res" code readonly tall>
<span>Response</span> <template #label>Response</template>
</MkTextarea> </MkTextarea>
</div> </div>
</div> </div>

View file

@ -2,9 +2,13 @@
<div> <div>
<div class="_section"> <div class="_section">
<div class="_content"> <div class="_content">
<MkInput v-model:value="name">{{ $ts.name }}</MkInput> <MkInput v-model="name">
<template #label>{{ $ts.name }}</template>
</MkInput>
<MkTextarea v-model:value="description">{{ $ts.description }}</MkTextarea> <MkTextarea v-model="description">
<template #label>{{ $ts.description }}</template>
</MkTextarea>
<div class="banner"> <div class="banner">
<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton> <MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>

View file

@ -152,8 +152,8 @@ export default defineComponent({
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
-webkit-backdrop-filter: blur(16px); -webkit-backdrop-filter: var(--blur, blur(16px));
backdrop-filter: blur(16px); backdrop-filter: var(--blur, blur(16px));
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
} }

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