Merge remote-branch 'misskey/develop'

This commit is contained in:
NoriDev 2023-12-22 23:06:33 +09:00
commit 8f4fdd905f
25 changed files with 378 additions and 112 deletions

View file

@ -5,15 +5,14 @@
- -
### Client ### Client
- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正 -
- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正
### Server ### Server
- -
--> -->
## 2023.x.x (unreleased) ## 2023.12.0
### Note ### Note
- Node.js 20.10.0が最小要件になりました - Node.js 20.10.0が最小要件になりました
@ -63,12 +62,16 @@
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように - Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように - Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
- Enhance: 絵文字の詳細ページに記載される情報を追加 - Enhance: 絵文字の詳細ページに記載される情報を追加
- Enhance: リアクションの表示幅制限を設定可能に
- Enhance: Unicode 15.0のサポート - Enhance: Unicode 15.0のサポート
- Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように - Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように
- MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました - MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました
- 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります - 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります
(例: ` ```js ` → Javascript, ` ```ais ` → AiScript (例: ` ```js ` → Javascript, ` ```ais ` → AiScript
- Enhance: 絵文字などのオートコンプリートでShift+Tabを押すと前の候補を選択できるように - Enhance: 絵文字などのオートコンプリートでShift+Tabを押すと前の候補を選択できるように
- Enhance: チャンネルに新規の投稿がある場合にバッジを表示させる
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
- Enhance: 設定したタグをトレンドに表示させないようにする項目を管理画面で設定できるように
- Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Fix: コードエディタが正しく表示されない問題を修正 - Fix: コードエディタが正しく表示されない問題を修正
@ -84,10 +87,16 @@
- Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正 - Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正
- Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。 - Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。
- Fix: 「画像が1枚のみのメディアリストの高さ」を「デフォルト」以外に設定していると、CWの中などに添付された画像が見られないバグを修正 - Fix: 「画像が1枚のみのメディアリストの高さ」を「デフォルト」以外に設定していると、CWの中などに添付された画像が見られないバグを修正
- Fix: DeepL TranslationのPro accountトグルスイッチが表示されていなかったのを修正
- Fix: twitterの埋め込みカード内リンクからリンク先を開けない問題を修正
- Fix: WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように
- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正
- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正
### Server ### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
- Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように - Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように
- Enhance: カスタム絵文字のインポート時の動作を改善
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303 - Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正 - Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402 - Fix: api.jsonの生成ロジックを改善 #12402
@ -124,7 +133,6 @@
- 例: `$[unixtime 1701356400]` - 例: `$[unixtime 1701356400]`
- Enhance: プラグインでエラーが発生した場合のハンドリングを強化 - Enhance: プラグインでエラーが発生した場合のハンドリングを強化
- Enhance: 細かなUIのブラッシュアップ - Enhance: 細かなUIのブラッシュアップ
- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加
- Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339 - Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339
- Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236 - Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236
- Fix: プラグインでノートの表示を書き換えられない問題を修正 - Fix: プラグインでノートの表示を書き換えられない問題を修正
@ -152,7 +160,7 @@
### General ### General
- Feat: アイコンデコレーション機能 - Feat: アイコンデコレーション機能
- サーバーで用意された画像をアイコンに重ねることができます - サーバーで用意された画像をアイコンに重ねることができます
- 画像のテンプレートはこちらです: https://misskey-hub.net/avatar-decoration-template.png - 画像のテンプレートはこちらです: https://misskey-hub.net/brand-assets/
- 最大でも黄色いエリア内にデコレーションを収めることを推奨します。 - 最大でも黄色いエリア内にデコレーションを収めることを推奨します。
- 画像は512x512pxを推奨します。 - 画像は512x512pxを推奨します。
- Feat: チャンネル設定にリノート/引用リノートの可否を設定できる項目を追加 - Feat: チャンネル設定にリノート/引用リノートの可否を設定できる項目を追加
@ -169,7 +177,7 @@
### Client ### Client
- Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました - Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました
- 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください - 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください
https://misskey-hub.net/docs/advanced/publish-on-your-website.html https://misskey-hub.net/docs/for-developers/publish-on-your-website/
- Feat: 通知をグルーピングして表示するオプション(オプトアウト) - Feat: 通知をグルーピングして表示するオプション(オプトアウト)
- Feat: Misskeyの基本的なチュートリアルを実装 - Feat: Misskeyの基本的なチュートリアルを実装
- Feat: スワイプしてタイムラインを再読込できるように - Feat: スワイプしてタイムラインを再読込できるように

View file

@ -7,10 +7,10 @@
--- ---
<a href="https://misskey-hub.net/instances.html"> <a href="https://misskey-hub.net/servers/">
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a> <img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
<a href="https://misskey-hub.net/docs/install.html"> <a href="https://misskey-hub.net/docs/for-admin/install/guides/">
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a> <img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
<a href="./CONTRIBUTING.md"> <a href="./CONTRIBUTING.md">
@ -51,7 +51,7 @@ With CherryPick's built in drive, you get cloud storage right in your social med
## Documentation ## Documentation
CherryPick Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it. CherryPick Documentation can be found at [Misskey Hub](https://misskey-hub.net/docs/), some of the links and graphics above also lead to specific portions of it.
## Sponsors ## Sponsors

View file

@ -121,6 +121,12 @@ sensitive: "NSFW"
add: "Afegir" add: "Afegir"
reaction: "Reaccions" reaction: "Reaccions"
reactions: "Reaccions" reactions: "Reaccions"
emojiPicker: "Selecció d'emojis"
pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
pinnedEmojisSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
emojiPickerDisplay: "Visualitza el selector d'emojis"
overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció"
overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
attachCancel: "Eliminar el fitxer adjunt" attachCancel: "Eliminar el fitxer adjunt"
@ -213,6 +219,9 @@ clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federar
clearCachedFiles: "Esborra la memòria cau" clearCachedFiles: "Esborra la memòria cau"
clearCachedFilesConfirm: "Segur que voleu eliminar tots els fitxers de la memòria cau?" clearCachedFilesConfirm: "Segur que voleu eliminar tots els fitxers de la memòria cau?"
blockedInstances: "Instàncies bloquejades" blockedInstances: "Instàncies bloquejades"
blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància."
silencedInstances: "Instàncies silenciades"
silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades."
muteAndBlock: "Silencia i bloca" muteAndBlock: "Silencia i bloca"
mutedUsers: "Usuaris silenciats" mutedUsers: "Usuaris silenciats"
blockedUsers: "Usuaris bloquejats" blockedUsers: "Usuaris bloquejats"
@ -227,9 +236,12 @@ preview: "Vista prèvia"
default: "Per defecte" default: "Per defecte"
defaultValueIs: "Per defecte: {value}" defaultValueIs: "Per defecte: {value}"
noCustomEmojis: "Cap emoji personalitzat" noCustomEmojis: "Cap emoji personalitzat"
noJobs: "No hi ha feines"
federating: "Federant" federating: "Federant"
blocked: "Bloquejat" blocked: "Bloquejat"
suspended: "Suspés" suspended: "Suspés"
all: "tot"
subscribing: "Subscrit a"
publishing: "S'està publicant" publishing: "S'està publicant"
notResponding: "Sense resposta" notResponding: "Sense resposta"
instanceFollowing: "Seguits del servidor" instanceFollowing: "Seguits del servidor"
@ -254,11 +266,31 @@ removed: "Eliminat"
removeAreYouSure: "Segur que voleu retirar «{x}»?" removeAreYouSure: "Segur que voleu retirar «{x}»?"
deleteAreYouSure: "Segur que voleu retirar «{x}»?" deleteAreYouSure: "Segur que voleu retirar «{x}»?"
resetAreYouSure: "Segur que voleu restablir-ho?" resetAreYouSure: "Segur que voleu restablir-ho?"
areYouSure: "Està segur?"
saved: "S'ha desat" saved: "S'ha desat"
messaging: "Xat" messaging: "Xat"
upload: "Puja" upload: "Puja"
keepOriginalUploading: "Guarda la imatge original"
keepOriginalUploadingDescription: "Guarda la imatge pujada com hi és. Si està apagat, una versió per a la visualització a la xarxa serà generada quan sigui pujada."
fromDrive: "Des de la unitat"
fromUrl: "Des d'un enllaç"
uploadFromUrl: "Carrega des d'un enllaç"
uploadFromUrlDescription: "Enllaç del fitxer que vols carregar"
uploadFromUrlRequested: "Càrrega sol·licitada"
uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot prendre un temps"
explore: "Explora"
messageRead: "Vist"
noMoreHistory: "No hi resta més per veure"
startMessaging: "Començar a xatejar"
nUsersRead: "Vist per {n}"
agreeTo: "Accepto que {0}"
agree: "Hi estic d'acord"
agreeBelow: "Hi estic d'acord amb el següent"
basicNotesBeforeCreateAccount: "Notes importants"
termsOfService: "Condicions d'ús"
start: "Comença" start: "Comença"
home: "Inici" home: "Inici"
remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta."
activity: "Activitat" activity: "Activitat"
images: "Imatges" images: "Imatges"
image: "Imatges" image: "Imatges"
@ -274,16 +306,34 @@ dark: "Fosc"
lightThemes: "Temes clars" lightThemes: "Temes clars"
darkThemes: "Temes foscos" darkThemes: "Temes foscos"
syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu" syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu"
drive: "Unitat"
fileName: "Nom del Fitxer"
selectFile: "Selecciona fitxers"
selectFiles: "Selecciona fitxers"
selectFolder: "Selecció de carpeta"
selectFolders: "Selecció de carpeta"
renameFile: "Canvia el nom del fitxer" renameFile: "Canvia el nom del fitxer"
folderName: "Nom de la carpeta" folderName: "Nom de la carpeta"
createFolder: "Crea una carpeta" createFolder: "Crea una carpeta"
renameFolder: "Canvia el nom de la carpeta" renameFolder: "Canvia el nom de la carpeta"
deleteFolder: "Elimina la carpeta" deleteFolder: "Elimina la carpeta"
folder: "Carpeta "
addFile: "Afegeix un fitxer" addFile: "Afegeix un fitxer"
emptyDrive: "La teva unitat és buida"
emptyFolder: "La carpeta està buida" emptyFolder: "La carpeta està buida"
unableToDelete: "No es pot eliminar" unableToDelete: "No es pot eliminar"
inputNewFileName: "Introduïu el nom de fitxer nou"
inputNewDescription: "Inserta una nova llegenda"
inputNewFolderName: "Introduïu el nom de la carpeta nova"
circularReferenceFolder: "La carpeta destinatària és una subcarpeta de la carpeta a la qual la desitges moure"
hasChildFilesOrFolders: "No és possible esborrar aquesta carpeta ja que no és buida"
copyUrl: "Copia l'URL" copyUrl: "Copia l'URL"
rename: "Canvia el nom" rename: "Canvia el nom"
avatar: "Icona"
banner: "Bàner"
displayOfSensitiveMedia: "Visualització de contingut sensible"
whenServerDisconnected: "Quan es perdi la connexió al servidor"
disconnectedFromServer: "Desconnectat pel servidor"
reload: "Actualitza" reload: "Actualitza"
doNothing: "Ignora" doNothing: "Ignora"
accept: "Accepta" accept: "Accepta"
@ -353,33 +403,132 @@ notFound: "No s'ha trobat"
markAsReadAllUnreadNotes: "Marca-ho tot com a llegit" markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
help: "Ajuda" help: "Ajuda"
invites: "Convida" invites: "Convida"
title: "Títol"
text: "Text"
enable: "Habilita"
next: "Següent" next: "Següent"
retype: "Torneu a introduir-la"
noteOf: "Publicació de: {user}" noteOf: "Publicació de: {user}"
quoteAttached: "Frase adjunta"
quoteQuestion: "Vols annexar-la com a cita?"
noMessagesYet: "Encara no hi ha missatges"
newMessageExists: "Has rebut un nou missatge"
onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge"
signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar"
invitations: "Convida" invitations: "Convida"
invitationCode: "Codi d'invitació"
checking: "Comprovació en curs..."
available: "Disponible"
unavailable: "No és disponible"
usernameInvalidFormat: "Pots fer servir lletres (majúscules i minúscules), números i barres baixes (\"_\")"
tooShort: "Massa curt"
tooLong: "Massa llarg"
weakPassword: "Contrasenya insegura"
normalPassword: "Bona contrasenya"
strongPassword: "Contrasenya segura"
passwordMatched: "Correcte!"
passwordNotMatched: "No coincideix"
signinWith: "Inicia sessió amb amb {x}"
signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes."
or: "O"
language: "Idioma"
uiLanguage: "Idioma de l'interfície"
aboutX: "Respecte a {x}"
emojiStyle: "Estil d'emoji"
native: "Nadiu"
disableDrawer: "No mostrar els menús en calaixos"
showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor"
noHistory: "No hi ha un registre previ"
signinHistory: "Historial d'autenticacions"
enableAdvancedMfm: "Habilitar l'MFM avançat"
enableAnimatedMfm: "Habilitar l'MFM amb moviment"
doing: "Processant..."
category: "Categoria"
tags: "Etiquetes" tags: "Etiquetes"
docSource: "Font del document" docSource: "Font del document"
createAccount: "Crea un compte" createAccount: "Crea un compte"
existingAccount: "Compte existent" existingAccount: "Compte existent"
regenerate: "Regenera" regenerate: "Regenera"
fontSize: "Mida del text" fontSize: "Mida del text"
mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única imatge"
limitTo: "Limita a {x}"
noFollowRequests: "No tens sol·licituds de seguiment" noFollowRequests: "No tens sol·licituds de seguiment"
openImageInNewTab: "Obre imatges a una nova pestanya"
dashboard: "Panell de control" dashboard: "Panell de control"
local: "Local" local: "Local"
remote: "Remot" remote: "Remot"
total: "Total" total: "Total"
weekOverWeekChanges: "Canvis l'última setmana"
dayOverDayChanges: "Canvis ahir"
appearance: "Aparença" appearance: "Aparença"
clientSettings: "Configuració del client" clientSettings: "Configuració del client"
accountSettings: "Configuració del compte" accountSettings: "Configuració del compte"
promotion: "Promocionat"
promote: "Promoure"
numberOfDays: "Nombre de dies"
hideThisNote: "Amaga la publicació" hideThisNote: "Amaga la publicació"
showFeaturedNotesInTimeline: "Mostra publicacions destacades en la línia de temps" showFeaturedNotesInTimeline: "Mostra publicacions destacades en la línia de temps"
objectStorage: "Emmagatzematge d'objectes\n"
useObjectStorage: "Utilitzar l'emmagatzematge d'objectes"
objectStorageBaseUrl: "Base d'enllaç"
objectStorageBaseUrlDesc: "Prefix d'enllaç utilitzat per a fer referencia als fitxers. Especifica l'enllaç del teu CDN o Proxy si n'estàs utilitzant qualsevol, en cas contrari, especifica l'enllaç al que es pot accedir públicament segons la guia de servei que vosté utilitza.\nPer l'ús d'S3 utilitza 'https://<bucket>.s3.amazonaws.com' I per a GCS o serveis equivalents utilitza 'https://storage.googleapis.com/<bucket>'."
newNoteRecived: "Hi ha publicacions noves" newNoteRecived: "Hi ha publicacions noves"
installedDate: "Data d'instal·lació" installedDate: "Data d'instal·lació"
state: "Estat" state: "Estat"
sort: "Ordena" sort: "Ordena"
ascendingOrder: "Ascendent" ascendingOrder: "Ascendent"
descendingOrder: "Descendent" descendingOrder: "Descendent"
removeAllFollowing: "Deixar de seguir tots els usuaris seguits"
removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
userSuspended: "Aquest usuari ha sigut suspès"
userSilenced: "Aquest usuari està sent silenciat"
yourAccountSuspendedTitle: "Aquest compte és suspès"
yourAccountSuspendedDescription: "Aquest compte ha sigut suspès a causa de la violació de les condicions d'ús o similars. Contacta l'administrador si en vol saber més. Si us plau, no en faci un altre compte."
tokenRevoked: "Codi de seguretat no vàlid"
tokenRevokedDescription: "La petició més recent ha estat denegada perquè contenia un codi de seguretat no vàlid. Actualitza la pàgina i torna-ho a provar."
accountDeleted: "Compte eliminat amb èxit"
accountDeletedDescription: "Aquest compte ha sigut eliminat"
menu: "Menú"
divider: "Divisor"
addItem: "Afegir element"
rearrange: "Torna a ordenar"
relays: "Relés"
addRelay: "Afegeix relés"
inboxUrl: "Enllaç de la safata d'entrada"
addedRelays: "Relés afegits"
serviceworkerInfo: "És obligatòria l'activació per a obtenir notificacions push"
deletedNote: "Publicacions eliminades" deletedNote: "Publicacions eliminades"
invisibleNote: "Publicacions amagades" invisibleNote: "Publicacions amagades"
enableInfiniteScroll: "Carrega més automàticament\n"
visibility: "Visibilitat"
poll: "Enquesta"
useCw: "Amaga el contingut"
enablePlayer: "Obre el reproductor de vídeo"
disablePlayer: "Tanca el reproductor de vídeo"
expandTweet: "Expandir post"
themeEditor: "Editor de temes"
description: "Descripció"
describeFile: "Afegir subtitulació"
enterFileDescription: "Afegeix un títol"
author: "Autor"
leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?"
manage: "Administració"
plugins: "Extensions"
preferencesBackups: "Configuracions de les Còpies de seguretat"
deck: "Escriptori"
undeck: "Tanca l'escriptori"
useBlurEffectForModal: "Utilitzar l'efecte de difuminació a modals"
useFullReactionPicker: "Utilitza el cercador de reaccions d'escala sencera"
width: "Amplada"
height: "Alçària"
large: "Gran"
medium: "Mitjà"
small: "Petit"
generateAccessToken: "Genera codi d'accés"
permission: "Permisos"
enableAll: "Habilita tot"
disableAll: "Deshabilita tot"
tokenRequested: "Donar accés al compte"
smtpHost: "Amfitrió" smtpHost: "Amfitrió"
smtpUser: "Nom d'usuari" smtpUser: "Nom d'usuari"
smtpPass: "Contrasenya" smtpPass: "Contrasenya"
@ -389,12 +538,17 @@ clearCache: "Esborra la memòria cau"
showingPastTimeline: "Estàs veient una línia de temps antiga" showingPastTimeline: "Estàs veient una línia de temps antiga"
info: "Informació" info: "Informació"
user: "Usuaris" user: "Usuaris"
administration: "Administració"
middle: "Mitjà"
global: "Global" global: "Global"
searchByGoogle: "Cercar" searchByGoogle: "Cercar"
file: "Fitxers" file: "Fitxers"
icon: "Icona"
replies: "Respondre" replies: "Respondre"
renotes: "Impulsa" renotes: "Impulsa"
_role: _role:
_priority:
middle: "Mitjà"
_options: _options:
antennaMax: "Nombre màxim d'antenes" antennaMax: "Nombre màxim d'antenes"
_email: _email:
@ -403,9 +557,11 @@ _email:
_instanceMute: _instanceMute:
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat." instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
_theme: _theme:
description: "Descripció"
keys: keys:
mention: "Menció" mention: "Menció"
renote: "Renotar" renote: "Renotar"
divider: "Divisor"
_sfx: _sfx:
note: "Notes" note: "Notes"
notification: "Notificacions" notification: "Notificacions"
@ -448,6 +604,8 @@ _timelines:
local: "Local" local: "Local"
social: "Social" social: "Social"
global: "Global" global: "Global"
_play:
summary: "Descripció"
_pages: _pages:
contents: "Contingut" contents: "Contingut"
blocks: blocks:

View file

@ -633,7 +633,7 @@ showInPage: "Show in page"
popout: "Pop-out" popout: "Pop-out"
volume: "Volume" volume: "Volume"
masterVolume: "Master volume" masterVolume: "Master volume"
notUseSound: "No sounds output." notUseSound: "Disable sound"
useSoundOnlyWhenActive: "Output sounds only if CherryPick is active." useSoundOnlyWhenActive: "Output sounds only if CherryPick is active."
details: "Details" details: "Details"
chooseEmoji: "Select an emoji" chooseEmoji: "Select an emoji"
@ -1269,6 +1269,7 @@ cwNotationRequired: "If \"Hide content\" is enabled, a description must be provi
doReaction: "Add reaction" doReaction: "Add reaction"
code: "Code" code: "Code"
reloadRequiredToApplySettings: "Reloading is required to apply the settings." reloadRequiredToApplySettings: "Reloading is required to apply the settings."
decorate: "Decorate"
showUnreadNotificationsCount: "Show the number of unread notifications" showUnreadNotificationsCount: "Show the number of unread notifications"
showCatOnly: "Show only cats" showCatOnly: "Show only cats"
additionalPermissionsForFlash: "Allow to add permission to Play" additionalPermissionsForFlash: "Allow to add permission to Play"
@ -1431,7 +1432,7 @@ _initialTutorial:
sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines." sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines."
doItToContinue: "Mark the attachment file as sensitive to proceed." doItToContinue: "Mark the attachment file as sensitive to proceed."
_done: _done:
title: "The tutorial is complete! 🎉" title: "You've completed the tutorial! 🎉"
description: "The functions introduced here are just a small part. For a more detailed understanding of using CherryPick, please refer to {link}." description: "The functions introduced here are just a small part. For a more detailed understanding of using CherryPick, please refer to {link}."
_timelineDescription: _timelineDescription:
home: "In the Home timeline, you can see notes from accounts you follow." home: "In the Home timeline, you can see notes from accounts you follow."
@ -2456,6 +2457,7 @@ _notification:
pollEnded: "Poll results have become available" pollEnded: "Poll results have become available"
newNote: "New note" newNote: "New note"
unreadAntennaNote: "Antenna {name}" unreadAntennaNote: "Antenna {name}"
roleAssigned: "Role given"
emptyPushNotificationMessage: "Push notifications have been updated" emptyPushNotificationMessage: "Push notifications have been updated"
achievementEarned: "Achievement unlocked" achievementEarned: "Achievement unlocked"
testNotification: "Test notification" testNotification: "Test notification"
@ -2478,6 +2480,7 @@ _notification:
receiveFollowRequest: "Received follow requests" receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests" followRequestAccepted: "Accepted follow requests"
groupInvited: "Group invitations" groupInvited: "Group invitations"
roleAssigned: "Role given"
achievementEarned: "Achievement unlocked" achievementEarned: "Achievement unlocked"
app: "Notifications from linked apps" app: "Notifications from linked apps"
_actions: _actions:

View file

@ -1986,6 +1986,7 @@ _notification:
youWereInvitedToGroup: "Invité·e au groupe" youWereInvitedToGroup: "Invité·e au groupe"
pollEnded: "Les résultats du sondage sont disponibles" pollEnded: "Les résultats du sondage sont disponibles"
unreadAntennaNote: "Antenne {name}" unreadAntennaNote: "Antenne {name}"
roleAssigned: "Rôle attribué"
emptyPushNotificationMessage: "Les notifications push ont été mises à jour" emptyPushNotificationMessage: "Les notifications push ont été mises à jour"
achievementEarned: "Accomplissement" achievementEarned: "Accomplissement"
testNotification: "Tester la notification" testNotification: "Tester la notification"

1
locales/index.d.ts vendored
View file

@ -2665,6 +2665,7 @@ export interface Locale {
"receiveFollowRequest": string; "receiveFollowRequest": string;
"followRequestAccepted": string; "followRequestAccepted": string;
"groupInvited": string; "groupInvited": string;
"roleAssigned": string;
"achievementEarned": string; "achievementEarned": string;
"app": string; "app": string;
}; };

View file

@ -2564,6 +2564,7 @@ _notification:
receiveFollowRequest: "フォロー申請を受け取った" receiveFollowRequest: "フォロー申請を受け取った"
followRequestAccepted: "フォローが受理された" followRequestAccepted: "フォローが受理された"
groupInvited: "グループに招待された" groupInvited: "グループに招待された"
roleAssigned: "ロールが付与された"
achievementEarned: "実績の獲得" achievementEarned: "実績の獲得"
app: "連携アプリからの通知" app: "連携アプリからの通知"

View file

@ -640,7 +640,7 @@ showInPage: "페이지로 보기"
popout: "새 창으로 열기" popout: "새 창으로 열기"
volume: "음량" volume: "음량"
masterVolume: "마스터 볼륨" masterVolume: "마스터 볼륨"
notUseSound: "사운드를 출력하지 않음" notUseSound: "사운드 출력 비활성화"
useSoundOnlyWhenActive: "CherryPick이 활성화된 경우에만 사운드 출력" useSoundOnlyWhenActive: "CherryPick이 활성화된 경우에만 사운드 출력"
details: "자세히" details: "자세히"
chooseEmoji: "이모지 선택" chooseEmoji: "이모지 선택"
@ -2473,6 +2473,7 @@ _notification:
pollEnded: "투표 결과가 발표됐어요!" pollEnded: "투표 결과가 발표됐어요!"
newNote: "새 게시물" newNote: "새 게시물"
unreadAntennaNote: "안테나 {name}" unreadAntennaNote: "안테나 {name}"
roleAssigned: "새 역할이 할당됐어요!"
emptyPushNotificationMessage: "새 알림이 있어요!" emptyPushNotificationMessage: "새 알림이 있어요!"
achievementEarned: "도전 과제를 달성했어요!" achievementEarned: "도전 과제를 달성했어요!"
testNotification: "알림 테스트" testNotification: "알림 테스트"

View file

@ -2260,6 +2260,7 @@ _notification:
pollEnded: "問卷調查已產生結果" pollEnded: "問卷調查已產生結果"
newNote: "新的貼文" newNote: "新的貼文"
unreadAntennaNote: "天線 {name}" unreadAntennaNote: "天線 {name}"
roleAssigned: "已授予角色"
emptyPushNotificationMessage: "推送通知已更新" emptyPushNotificationMessage: "推送通知已更新"
achievementEarned: "獲得成就" achievementEarned: "獲得成就"
testNotification: "通知測試" testNotification: "通知測試"

View file

@ -1,7 +1,7 @@
{ {
"name": "cherrypick", "name": "cherrypick",
"version": "4.6.0-beta.6", "version": "4.6.0-beta.6",
"basedMisskeyVersion": "2023.12.0-beta.6", "basedMisskeyVersion": "2023.12.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -4,7 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> <div style="position: relative;">
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1" @click="updateLastReadedAt">
<div class="banner" :style="bannerStyle"> <div class="banner" :style="bannerStyle">
<div class="fade"></div> <div class="fade"></div>
<div class="name"><i class="ti ti-device-tv"></i> {{ channel.name }}</div> <div class="name"><i class="ti ti-device-tv"></i> {{ channel.name }}</div>
@ -37,16 +38,36 @@ SPDX-License-Identifier: AGPL-3.0-only
</span> </span>
</footer> </footer>
</MkA> </MkA>
<div
v-if="channel.lastNotedAt && (channel.isFavorited || channel.isFollowing) && (!lastReadedAt || Date.parse(channel.lastNotedAt) > lastReadedAt)"
class="indicator"
></div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed, ref, watch } from 'vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
const props = defineProps<{ const props = defineProps<{
channel: Record<string, any>; channel: Record<string, any>;
}>(); }>();
const getLastReadedAt = (): number | null => {
return miLocalStorage.getItemAsJson(`channelLastReadedAt:${props.channel.id}`) ?? null;
};
const lastReadedAt = ref(getLastReadedAt());
watch(() => props.channel.id, () => {
lastReadedAt.value = getLastReadedAt();
});
const updateLastReadedAt = () => {
lastReadedAt.value = props.channel.lastNotedAt ? Date.parse(props.channel.lastNotedAt) : Date.now();
};
const bannerStyle = computed(() => { const bannerStyle = computed(() => {
if (props.channel.bannerUrl) { if (props.channel.bannerUrl) {
return { backgroundImage: `url(${props.channel.bannerUrl})` }; return { backgroundImage: `url(${props.channel.bannerUrl})` };
@ -170,4 +191,17 @@ const bannerStyle = computed(() => {
} }
} }
.indicator {
position: absolute;
top: 0;
right: 0;
transform: translate(25%, -25%);
background-color: var(--accent);
border: solid var(--bg) 4px;
border-radius: 100%;
width: 1.5rem;
height: 1.5rem;
aspect-ratio: 1 / 1;
}
</style> </style>

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-for="(item, i) in items2"> <template v-for="(item, i) in items2">
<div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div> <div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div>
<span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]"> <span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]">
<span>{{ item.text }}</span> <span style="opacity: 0.7;">{{ item.text }}</span>
</span> </span>
<span v-else-if="item.type === 'pending'" role="menuitem" :tabindex="i" :class="[$style.pending, $style.item]"> <span v-else-if="item.type === 'pending'" role="menuitem" :tabindex="i" :class="[$style.pending, $style.item]">
<span><MkEllipsis/></span> <span><MkEllipsis/></span>
@ -23,32 +23,44 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="item.type === 'link'" role="menuitem" :to="item.to" :tabindex="i" class="_button" :class="$style.item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <MkA v-else-if="item.type === 'link'" role="menuitem" :to="item.to" :tabindex="i" class="_button" :class="$style.item" @click.passive="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
<span>{{ item.text }}</span> <div :class="$style.item_content">
<span :class="$style.item_content_text">{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</div>
</MkA> </MkA>
<a v-else-if="item.type === 'a'" role="menuitem" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button" :class="$style.item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <a v-else-if="item.type === 'a'" role="menuitem" :href="item.href" :target="item.target" :download="item.download" :tabindex="i" class="_button" :class="$style.item" @click="close(true)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<span>{{ item.text }}</span> <div :class="$style.item_content">
<span :class="$style.item_content_text">{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</div>
</a> </a>
<button v-else-if="item.type === 'user'" role="menuitem" :tabindex="i" class="_button" :class="[$style.item, { [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else-if="item.type === 'user'" role="menuitem" :tabindex="i" class="_button" :class="[$style.item, { [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/> <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> <div v-if="item.indicate" :class="$style.item_content">
<span :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</div>
</button> </button>
<button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/> <MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
<span :class="$style.switchText">{{ item.text }}</span> <div :class="$style.item_content">
<span :class="[$style.item_content_text, $style.switchText]">{{ item.text }}</span>
</div>
</button> </button>
<button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)"> <button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)">
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
<span style="pointer-events: none;">{{ item.text }}</span> <div :class="$style.item_content">
<span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span> <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
</div>
</button> </button>
<button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
<span>{{ item.text }}</span> <div :class="$style.item_content">
<span :class="$style.item_content_text">{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</div>
</button> </button>
</template> </template>
<span v-if="items2.length === 0" :class="[$style.none, $style.item]"> <span v-if="items2.length === 0" :class="[$style.none, $style.item]">
@ -229,6 +241,7 @@ onBeforeUnmount(() => {
.root { .root {
padding: 8px 0; padding: 8px 0;
box-sizing: border-box; box-sizing: border-box;
max-width: 100vw;
min-width: 200px; min-width: 200px;
overflow: auto; overflow: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
@ -268,7 +281,8 @@ onBeforeUnmount(() => {
} }
.item { .item {
display: block; display: flex;
align-items: center;
position: relative; position: relative;
padding: 5px 16px; padding: 5px 16px;
width: 100%; width: 100%;
@ -341,10 +355,6 @@ onBeforeUnmount(() => {
pointer-events: none; pointer-events: none;
font-size: 0.7em; font-size: 0.7em;
padding-bottom: 4px; padding-bottom: 4px;
> span {
opacity: 0.7;
}
} }
&.pending { &.pending {
@ -374,6 +384,22 @@ onBeforeUnmount(() => {
} }
} }
.item_content {
width: 100%;
max-width: 100vw;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
text-overflow: ellipsis;
}
.item_content_text {
max-width: calc(100vw - 4rem);
text-overflow: ellipsis;
overflow: hidden;
}
.switch { .switch {
position: relative; position: relative;
display: flex; display: flex;
@ -407,6 +433,7 @@ onBeforeUnmount(() => {
.icon { .icon {
margin-right: 8px; margin-right: 8px;
line-height: 1;
} }
.caret { .caret {
@ -420,9 +447,8 @@ onBeforeUnmount(() => {
} }
.indicator { .indicator {
position: absolute; display: flex;
top: 5px; align-items: center;
left: 13px;
color: var(--indicator); color: var(--indicator);
font-size: 12px; font-size: 12px;
animation: blink 1s infinite; animation: blink 1s infinite;

View file

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template> <template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template> <template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
<a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a> <a href="https://misskey-hub.net/docs/for-users/onboarding/warning/" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
<MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch> <MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch>
</MkFolder> </MkFolder>

View file

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.divider"></div> <div :class="$style.divider"></div>
<I18n :src="i18n.ts._initialTutorial._timeline.description3" tag="div" style="padding: 0 16px;"> <I18n :src="i18n.ts._initialTutorial._timeline.description3" tag="div" style="padding: 0 16px;">
<template #link> <template #link>
<a href="https://misskey-hub.net/docs/features/timeline.html" target="_blank" class="_link">{{ i18n.ts.help }}</a> <a href="https://misskey-hub.net/docs/for-users/features/timeline/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
</template> </template>
</I18n> </I18n>
</div> </div>

View file

@ -130,7 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div> <div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div>
<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;"> <I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
<template #link> <template #link>
<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a> <a href="https://misskey-hub.net/docs/for-users/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
</template> </template>
</I18n> </I18n>
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div> <div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>

View file

@ -45,7 +45,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const whatIsNewMisskey = () => { const whatIsNewMisskey = () => {
// modal.value.close(); // modal.value.close();
window.open(`https://misskey-hub.net/docs/releases.html#_${basedMisskeyVersion.replace(/\./g, '-')}`, '_blank'); window.open(`https://misskey-hub.net/docs/releases/#_${basedMisskeyVersion.replace(/\./g, '')}`, '_blank');
}; };
const whatIsNewCherryPick = () => { const whatIsNewCherryPick = () => {

View file

@ -125,13 +125,13 @@ function showMenu(ev) {
text: i18n.ts.help, text: i18n.ts.help,
icon: 'ti ti-help-circle', icon: 'ti ti-help-circle',
action: () => { action: () => {
window.open('https://misskey-hub.net/help.md', '_blank', 'noopener'); window.open('https://misskey-hub.net/docs/for-users/', '_blank', 'noopener');
}, },
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
} }
function exploreOtherServers() { function exploreOtherServers() {
window.open('https://join.misskey.page/instances', '_blank', 'noopener'); window.open('https://misskey-hub.net/servers/', '_blank', 'noopener');
} }
</script> </script>

View file

@ -87,7 +87,7 @@ type Tab = {
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
tabs?: Tab[]; tabs?: Tab[];
tab?: string; tab?: string;
actions?: PageHeaderItem[]; actions?: PageHeaderItem[] | null;
thin?: boolean; thin?: boolean;
displayMyAvatar?: boolean; displayMyAvatar?: boolean;
}>(), { }>(), {

View file

@ -87,7 +87,7 @@ const canBack = ref(['index', 'explore', 'my-notifications', 'messaging'].includ
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
tabs?: Tab[]; tabs?: Tab[];
tab?: string; tab?: string;
actions?: PageHeaderItem[]; actions?: PageHeaderItem[] | null;
thin?: boolean; thin?: boolean;
displayMyAvatar?: boolean; displayMyAvatar?: boolean;
}>(), { }>(), {

View file

@ -39,6 +39,7 @@ type Keys =
`aiscriptSecure:${string}` | `aiscriptSecure:${string}` |
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~) 'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~); 'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
`channelLastReadedAt:${string}` |
'neverShowNoteEditInfo' 'neverShowNoteEditInfo'
export const miLocalStorage = { export const miLocalStorage = {

View file

@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button> <button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button>
</div> </div>
<div style="text-align: center;"> <div style="text-align: center;">
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a> {{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/about-misskey/" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>
</div> </div>
<div v-if="$i != null" style="text-align: center;"> <div v-if="$i != null" style="text-align: center;">
<MkButton primary rounded inline @click="iLoveCherryPick">I <Mfm text="$[jelly ❤]"/> #CherryPick</MkButton> <MkButton primary rounded inline @click="iLoveCherryPick">I <Mfm text="$[jelly ❤]"/> #CherryPick</MkButton>
@ -241,73 +241,73 @@ const patronsWithIconWithCherryPick = [];
const patronsWithIconWithMisskey = [{ const patronsWithIconWithMisskey = [{
name: 'カイヤン', name: 'カイヤン',
icon: 'https://misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg', icon: 'https://assets.misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg',
}, { }, {
name: 'だれかさん', name: 'だれかさん',
icon: 'https://misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg', icon: 'https://assets.misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg',
}, { }, {
name: 'narazaka', name: 'narazaka',
icon: 'https://misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg', icon: 'https://assets.misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg',
}, { }, {
name: 'ひとぅ', name: 'ひとぅ',
icon: 'https://misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg', icon: 'https://assets.misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg',
}, { }, {
name: 'ぱーこ', name: 'ぱーこ',
icon: 'https://misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg', icon: 'https://assets.misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg',
}, { }, {
name: 'わっほー☆', name: 'わっほー☆',
icon: 'https://misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg', icon: 'https://assets.misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg',
}, { }, {
name: 'mollinaca', name: 'mollinaca',
icon: 'https://misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg', icon: 'https://assets.misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
}, { }, {
name: '坂本龍', name: '坂本龍',
icon: 'https://misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg', icon: 'https://assets.misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
}, { }, {
name: 'takke', name: 'takke',
icon: 'https://misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg', icon: 'https://assets.misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
}, { }, {
name: 'ぺんぎん', name: 'ぺんぎん',
icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg', icon: 'https://assets.misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
}, { }, {
name: 'かみらえっと', name: 'かみらえっと',
icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg', icon: 'https://assets.misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
}, { }, {
name: 'へてて', name: 'へてて',
icon: 'https://misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg', icon: 'https://assets.misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
}, { }, {
name: 'spinlock', name: 'spinlock',
icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg', icon: 'https://assets.misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
}, { }, {
name: 'じゅくま', name: 'じゅくま',
icon: 'https://misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg', icon: 'https://assets.misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
}, { }, {
name: '清遊あみ', name: '清遊あみ',
icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg', icon: 'https://assets.misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
}, { }, {
name: 'Nagi8410', name: 'Nagi8410',
icon: 'https://misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg', icon: 'https://assets.misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg',
}, { }, {
name: '山岡士郎', name: '山岡士郎',
icon: 'https://misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg', icon: 'https://assets.misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg',
}, { }, {
name: 'よもやまたろう', name: 'よもやまたろう',
icon: 'https://misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg', icon: 'https://assets.misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg',
}, { }, {
name: '花咲ももか', name: '花咲ももか',
icon: 'https://misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg', icon: 'https://assets.misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg',
}, { }, {
name: 'カガミ', name: 'カガミ',
icon: 'https://misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg', icon: 'https://assets.misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg',
}, { }, {
name: 'フランギ・シュウ', name: 'フランギ・シュウ',
icon: 'https://misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg', icon: 'https://assets.misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
}, { }, {
name: '百日紅', name: '百日紅',
icon: 'https://misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg', icon: 'https://assets.misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
}, { }, {
name: 'taichan', name: 'taichan',
icon: 'https://misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png', icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
}]; }];
const patronsWithCherryPick = [ const patronsWithCherryPick = [

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/> <XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton> <MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton> <MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" :class="$style.banner"> <div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
<div :class="$style.bannerStatus"> <div :class="$style.bannerStatus">
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div> <div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div> <div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFoldableSection> <MkFoldableSection>
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template> <template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
<div v-if="channel.pinnedNotes.length > 0" class="_gaps"> <div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/> <MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
</div> </div>
</MkFoldableSection> </MkFoldableSection>
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- スマホタブレットの場合キーボードが表示されると投稿が見づらくなるのでデスクトップ場合のみ自動でフォーカスを当てる --> <!-- スマホタブレットの場合キーボードが表示されると投稿が見づらくなるのでデスクトップ場合のみ自動でフォーカスを当てる -->
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after"/> <MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
</div> </div>
<div v-else-if="tab === 'featured'"> <div v-else-if="tab === 'featured'">
<MkNotes :pagination="featuredPagination"/> <MkNotes :pagination="featuredPagination"/>
@ -69,6 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, ref } from 'vue'; import { computed, watch, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
@ -89,6 +90,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { PageHeaderItem } from '@/types/page-header.js'; import { PageHeaderItem } from '@/types/page-header.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js'; import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { miLocalStorage } from '@/local-storage.js';
const router = useRouter(); const router = useRouter();
@ -97,7 +99,7 @@ const props = defineProps<{
}>(); }>();
const tab = ref('overview'); const tab = ref('overview');
const channel = ref(null); const channel = ref<Misskey.entities.Channel | null>(null);
const favorited = ref(false); const favorited = ref(false);
const searchQuery = ref(''); const searchQuery = ref('');
const searchPagination = ref(); const searchPagination = ref();
@ -114,14 +116,23 @@ watch(() => props.channelId, async () => {
channel.value = await os.api('channels/show', { channel.value = await os.api('channels/show', {
channelId: props.channelId, channelId: props.channelId,
}); });
favorited.value = channel.value.isFavorited; favorited.value = channel.value.isFavorited ?? false;
if (favorited.value || channel.value.isFollowing) { if (favorited.value || channel.value.isFollowing) {
tab.value = 'timeline'; tab.value = 'timeline';
} }
if ((favorited.value || channel.value.isFollowing) && channel.value.lastNotedAt) {
const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.value.id}`) ?? 0;
const lastNotedAt = Date.parse(channel.value.lastNotedAt);
if (lastNotedAt > lastReadedAt) {
miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, lastNotedAt);
}
}
}, { immediate: true }); }, { immediate: true });
function edit() { function edit() {
router.push(`/channels/${channel.value.id}/edit`); router.push(`/channels/${channel.value?.id}/edit`);
} }
function openPostForm() { function openPostForm() {
@ -131,6 +142,8 @@ function openPostForm() {
} }
function favorite() { function favorite() {
if (!channel.value) return;
os.apiWithDialog('channels/favorite', { os.apiWithDialog('channels/favorite', {
channelId: channel.value.id, channelId: channel.value.id,
}).then(() => { }).then(() => {
@ -139,6 +152,8 @@ function favorite() {
} }
async function unfavorite() { async function unfavorite() {
if (!channel.value) return;
const confirm = await os.confirm({ const confirm = await os.confirm({
type: 'warning', type: 'warning',
text: i18n.ts.unfavoriteConfirm, text: i18n.ts.unfavoriteConfirm,
@ -152,6 +167,8 @@ async function unfavorite() {
} }
async function search() { async function search() {
if (!channel.value) return;
const query = searchQuery.value.toString().trim(); const query = searchQuery.value.toString().trim();
if (query == null) return; if (query == null) return;
@ -176,6 +193,10 @@ const headerActions = computed(() => {
icon: 'ti ti-link', icon: 'ti ti-link',
text: i18n.ts.copyUrl, text: i18n.ts.copyUrl,
handler: async (): Promise<void> => { handler: async (): Promise<void> => {
if (!channel.value) {
console.warn('failed to copy channel URL. channel.value is null.');
return;
}
copyToClipboard(`${url}/channels/${channel.value.id}`); copyToClipboard(`${url}/channels/${channel.value.id}`);
os.success(); os.success();
}, },
@ -186,9 +207,14 @@ const headerActions = computed(() => {
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.share, text: i18n.ts.share,
handler: async (): Promise<void> => { handler: async (): Promise<void> => {
if (!channel.value) {
console.warn('failed to share channel. channel.value is null.');
return;
}
navigator.share({ navigator.share({
title: channel.value.name, title: channel.value.name,
text: channel.value.description, text: channel.value.description ?? undefined,
url: `${url}/channels/${channel.value.id}`, url: `${url}/channels/${channel.value.id}`,
}); });
}, },

View file

@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// SPECIFICATION: https://misskey-hub.net/docs/features/share-form.html // SPECIFICATION: https://misskey-hub.net/docs/for-users/features/share-form/
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import * as Misskey from 'cherrypick-js'; import * as Misskey from 'cherrypick-js';

View file

@ -82,12 +82,12 @@ import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
import { antennasCache, userListsCache } from '@/cache.js'; import { antennasCache, userListsCache } from '@/cache.js';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
import { deviceKind } from '@/scripts/device-kind.js'; import { deviceKind } from '@/scripts/device-kind.js';
import { unisonReload } from '@/scripts/unison-reload.js'; import { unisonReload } from '@/scripts/unison-reload.js';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import { miLocalStorage } from '@/local-storage.js';
const showEl = ref(false); const showEl = ref(false);
const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly'); const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly');
@ -198,12 +198,17 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
limit: 100, limit: 100,
}); });
const items: MenuItem[] = [ const items: MenuItem[] = [
...channels.map(channel => ({ ...channels.map(channel => {
const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null;
const hasUnreadNote = (lastReadedAt && channel.lastNotedAt) ? Date.parse(channel.lastNotedAt) > lastReadedAt : !!(!lastReadedAt && channel.lastNotedAt);
return {
type: 'link' as const, type: 'link' as const,
text: channel.name, text: channel.name,
indicate: channel.hasUnreadNote, indicate: hasUnreadNote,
to: `/channels/${channel.id}`, to: `/channels/${channel.id}`,
})), };
}),
(channels.length === 0 ? undefined : { type: 'divider' }), (channels.length === 0 ? undefined : { type: 'divider' }),
{ {
type: 'link' as const, type: 'link' as const,

View file

@ -115,7 +115,7 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.help, text: i18n.ts.help,
icon: 'ti ti-help-circle', icon: 'ti ti-help-circle',
action: () => { action: () => {
window.open('https://misskey-hub.net/help.html', '_blank', 'noopener'); window.open('https://misskey-hub.net/docs/for-users/', '_blank', 'noopener');
}, },
}, { }, {
type: 'link', type: 'link',