1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-23 14:46:44 +09:00

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

View File

@ -121,6 +121,12 @@ sensitive: "NSFW"
add: "Afegir"
reaction: "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."
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
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"
clearCachedFilesConfirm: "Segur que voleu eliminar tots els fitxers de la memòria cau?"
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"
mutedUsers: "Usuaris silenciats"
blockedUsers: "Usuaris bloquejats"
@ -227,9 +236,12 @@ preview: "Vista prèvia"
default: "Per defecte"
defaultValueIs: "Per defecte: {value}"
noCustomEmojis: "Cap emoji personalitzat"
noJobs: "No hi ha feines"
federating: "Federant"
blocked: "Bloquejat"
suspended: "Suspés"
all: "tot"
subscribing: "Subscrit a"
publishing: "S'està publicant"
notResponding: "Sense resposta"
instanceFollowing: "Seguits del servidor"
@ -254,11 +266,31 @@ removed: "Eliminat"
removeAreYouSure: "Segur que voleu retirar «{x}»?"
deleteAreYouSure: "Segur que voleu retirar «{x}»?"
resetAreYouSure: "Segur que voleu restablir-ho?"
areYouSure: "Està segur?"
saved: "S'ha desat"
messaging: "Xat"
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"
home: "Inici"
remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta."
activity: "Activitat"
images: "Imatges"
image: "Imatges"
@ -274,16 +306,34 @@ dark: "Fosc"
lightThemes: "Temes clars"
darkThemes: "Temes foscos"
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"
folderName: "Nom de la carpeta"
createFolder: "Crea una carpeta"
renameFolder: "Canvia el nom de la carpeta"
deleteFolder: "Elimina la carpeta"
folder: "Carpeta "
addFile: "Afegeix un fitxer"
emptyDrive: "La teva unitat és buida"
emptyFolder: "La carpeta està buida"
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"
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"
doNothing: "Ignora"
accept: "Accepta"
@ -353,33 +403,132 @@ notFound: "No s'ha trobat"
markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
help: "Ajuda"
invites: "Convida"
title: "Títol"
text: "Text"
enable: "Habilita"
next: "Següent"
retype: "Torneu a introduir-la"
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"
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"
docSource: "Font del document"
createAccount: "Crea un compte"
existingAccount: "Compte existent"
regenerate: "Regenera"
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"
openImageInNewTab: "Obre imatges a una nova pestanya"
dashboard: "Panell de control"
local: "Local"
remote: "Remot"
total: "Total"
weekOverWeekChanges: "Canvis l'última setmana"
dayOverDayChanges: "Canvis ahir"
appearance: "Aparença"
clientSettings: "Configuració del client"
accountSettings: "Configuració del compte"
promotion: "Promocionat"
promote: "Promoure"
numberOfDays: "Nombre de dies"
hideThisNote: "Amaga la publicació"
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"
installedDate: "Data d'instal·lació"
state: "Estat"
sort: "Ordena"
ascendingOrder: "Ascendent"
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"
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ó"
smtpUser: "Nom d'usuari"
smtpPass: "Contrasenya"
@ -389,12 +538,17 @@ clearCache: "Esborra la memòria cau"
showingPastTimeline: "Estàs veient una línia de temps antiga"
info: "Informació"
user: "Usuaris"
administration: "Administració"
middle: "Mitjà"
global: "Global"
searchByGoogle: "Cercar"
file: "Fitxers"
icon: "Icona"
replies: "Respondre"
renotes: "Impulsa"
_role:
_priority:
middle: "Mitjà"
_options:
antennaMax: "Nombre màxim d'antenes"
_email:
@ -403,9 +557,11 @@ _email:
_instanceMute:
instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
_theme:
description: "Descripció"
keys:
mention: "Menció"
renote: "Renotar"
divider: "Divisor"
_sfx:
note: "Notes"
notification: "Notificacions"
@ -448,6 +604,8 @@ _timelines:
local: "Local"
social: "Social"
global: "Global"
_play:
summary: "Descripció"
_pages:
contents: "Contingut"
blocks:

View File

@ -633,7 +633,7 @@ showInPage: "Show in page"
popout: "Pop-out"
volume: "Volume"
masterVolume: "Master volume"
notUseSound: "No sounds output."
notUseSound: "Disable sound"
useSoundOnlyWhenActive: "Output sounds only if CherryPick is active."
details: "Details"
chooseEmoji: "Select an emoji"
@ -1269,6 +1269,7 @@ cwNotationRequired: "If \"Hide content\" is enabled, a description must be provi
doReaction: "Add reaction"
code: "Code"
reloadRequiredToApplySettings: "Reloading is required to apply the settings."
decorate: "Decorate"
showUnreadNotificationsCount: "Show the number of unread notifications"
showCatOnly: "Show only cats"
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."
doItToContinue: "Mark the attachment file as sensitive to proceed."
_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}."
_timelineDescription:
home: "In the Home timeline, you can see notes from accounts you follow."
@ -2456,6 +2457,7 @@ _notification:
pollEnded: "Poll results have become available"
newNote: "New note"
unreadAntennaNote: "Antenna {name}"
roleAssigned: "Role given"
emptyPushNotificationMessage: "Push notifications have been updated"
achievementEarned: "Achievement unlocked"
testNotification: "Test notification"
@ -2478,6 +2480,7 @@ _notification:
receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests"
groupInvited: "Group invitations"
roleAssigned: "Role given"
achievementEarned: "Achievement unlocked"
app: "Notifications from linked apps"
_actions:

View File

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

1
locales/index.d.ts vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,49 +4,70 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<div class="banner" :style="bannerStyle">
<div class="fade"></div>
<div class="name"><i class="ti ti-device-tv"></i> {{ channel.name }}</div>
<div v-if="channel.isSensitive" class="sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
<div class="status">
<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 style="position: relative;">
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1" @click="updateLastReadedAt">
<div class="banner" :style="bannerStyle">
<div class="fade"></div>
<div class="name"><i class="ti ti-device-tv"></i> {{ channel.name }}</div>
<div v-if="channel.isSensitive" class="sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
<div class="status">
<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>
</div>
</div>
<article v-if="channel.description">
<p :title="channel.description">{{ channel.description.length > 85 ? channel.description.slice(0, 85) + '…' : channel.description }}</p>
</article>
<footer>
<span v-if="channel.lastNotedAt">
{{ i18n.ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/>
</span>
</footer>
</MkA>
<article v-if="channel.description">
<p :title="channel.description">{{ channel.description.length > 85 ? channel.description.slice(0, 85) + '…' : channel.description }}</p>
</article>
<footer>
<span v-if="channel.lastNotedAt">
{{ i18n.ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/>
</span>
</footer>
</MkA>
<div
v-if="channel.lastNotedAt && (channel.isFavorited || channel.isFollowing) && (!lastReadedAt || Date.parse(channel.lastNotedAt) > lastReadedAt)"
class="indicator"
></div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref, watch } from 'vue';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
const props = defineProps<{
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(() => {
if (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>

View File

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-for="(item, i) in items2">
<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>{{ item.text }}</span>
<span style="opacity: 0.7;">{{ item.text }}</span>
</span>
<span v-else-if="item.type === 'pending'" role="menuitem" :tabindex="i" :class="[$style.pending, $style.item]">
<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)">
<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"/>
<span>{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></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>
</div>
</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)">
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<span>{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></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>
</div>
</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)">
<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 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)"/>
<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 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>
<span 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>
<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>
</div>
</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)">
<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"/>
<span>{{ item.text }}</span>
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></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>
</div>
</button>
</template>
<span v-if="items2.length === 0" :class="[$style.none, $style.item]">
@ -229,6 +241,7 @@ onBeforeUnmount(() => {
.root {
padding: 8px 0;
box-sizing: border-box;
max-width: 100vw;
min-width: 200px;
overflow: auto;
overscroll-behavior: contain;
@ -268,7 +281,8 @@ onBeforeUnmount(() => {
}
.item {
display: block;
display: flex;
align-items: center;
position: relative;
padding: 5px 16px;
width: 100%;
@ -341,10 +355,6 @@ onBeforeUnmount(() => {
pointer-events: none;
font-size: 0.7em;
padding-bottom: 4px;
> span {
opacity: 0.7;
}
}
&.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 {
position: relative;
display: flex;
@ -407,6 +433,7 @@ onBeforeUnmount(() => {
.icon {
margin-right: 8px;
line-height: 1;
}
.caret {
@ -420,9 +447,8 @@ onBeforeUnmount(() => {
}
.indicator {
position: absolute;
top: 5px;
left: 13px;
display: flex;
align-items: center;
color: var(--indicator);
font-size: 12px;
animation: blink 1s infinite;

View File

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</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>
</MkFolder>

View File

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.divider"></div>
<I18n :src="i18n.ts._initialTutorial._timeline.description3" tag="div" style="padding: 0 16px;">
<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>
</I18n>
</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>
<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
<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>
</I18n>
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>

View File

@ -45,7 +45,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const whatIsNewMisskey = () => {
// 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 = () => {

View File

@ -125,13 +125,13 @@ function showMenu(ev) {
text: i18n.ts.help,
icon: 'ti ti-help-circle',
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);
}
function exploreOtherServers() {
window.open('https://join.misskey.page/instances', '_blank', 'noopener');
window.open('https://misskey-hub.net/servers/', '_blank', 'noopener');
}
</script>

View File

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

View File

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

View File

@ -39,6 +39,7 @@ type Keys =
`aiscriptSecure:${string}` |
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
`channelLastReadedAt:${string}` |
'neverShowNoteEditInfo'
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>
</div>
<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 v-if="$i != null" style="text-align: center;">
<MkButton primary rounded inline @click="iLoveCherryPick">I <Mfm text="$[jelly ❤]"/> #CherryPick</MkButton>
@ -241,73 +241,73 @@ const patronsWithIconWithCherryPick = [];
const patronsWithIconWithMisskey = [{
name: 'カイヤン',
icon: 'https://misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/a2820716883e408cb87773e377ce7c8d.jpg',
}, {
name: 'だれかさん',
icon: 'https://misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg',
icon: 'https://assets.misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg',
}, {
name: 'narazaka',
icon: 'https://misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg',
icon: 'https://assets.misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg',
}, {
name: 'ひとぅ',
icon: 'https://misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg',
icon: 'https://assets.misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg',
}, {
name: 'ぱーこ',
icon: 'https://misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/79c6602ffade489e8df2fcf2c2bc5d9d.jpg',
}, {
name: 'わっほー☆',
icon: 'https://misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg',
icon: 'https://assets.misskey-hub.net/patrons/d31d5d13924443a082f3da7966318a0a.jpg',
}, {
name: 'mollinaca',
icon: 'https://misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
icon: 'https://assets.misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
}, {
name: '坂本龍',
icon: 'https://misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
icon: 'https://assets.misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
}, {
name: 'takke',
icon: 'https://misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
}, {
name: 'ぺんぎん',
icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
}, {
name: 'かみらえっと',
icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
icon: 'https://assets.misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
}, {
name: 'へてて',
icon: 'https://misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
icon: 'https://assets.misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
}, {
name: 'spinlock',
icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
icon: 'https://assets.misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
}, {
name: 'じゅくま',
icon: 'https://misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
icon: 'https://assets.misskey-hub.net/patrons/3e56bdac69dd42f7a06e0f12cf2fc895.jpg',
}, {
name: '清遊あみ',
icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
icon: 'https://assets.misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg',
}, {
name: 'Nagi8410',
icon: 'https://misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg',
icon: 'https://assets.misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg',
}, {
name: '山岡士郎',
icon: 'https://misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg',
icon: 'https://assets.misskey-hub.net/patrons/84b9056341684266bb1eda3e680d094d.jpg',
}, {
name: 'よもやまたろう',
icon: 'https://misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg',
icon: 'https://assets.misskey-hub.net/patrons/4273c9cce50d445f8f7d0f16113d6d7f.jpg',
}, {
name: '花咲ももか',
icon: 'https://misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg',
icon: 'https://assets.misskey-hub.net/patrons/8c9b2b9128cb4fee99f04bb4f86f2efa.jpg',
}, {
name: 'カガミ',
icon: 'https://misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg',
icon: 'https://assets.misskey-hub.net/patrons/226ea3a4617749548580ec2d9a263e24.jpg',
}, {
name: 'フランギ・シュウ',
icon: 'https://misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
icon: 'https://assets.misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
}, {
name: '百日紅',
icon: 'https://misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
icon: 'https://assets.misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
}, {
name: 'taichan',
icon: 'https://misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
}];
const patronsWithCherryPick = [

View File

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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-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><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>
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFoldableSection>
<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"/>
</div>
</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'"/>
<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 v-else-if="tab === 'featured'">
<MkNotes :pagination="featuredPagination"/>
@ -69,6 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkPostForm from '@/components/MkPostForm.vue';
import MkTimeline from '@/components/MkTimeline.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 { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { miLocalStorage } from '@/local-storage.js';
const router = useRouter();
@ -97,7 +99,7 @@ const props = defineProps<{
}>();
const tab = ref('overview');
const channel = ref(null);
const channel = ref<Misskey.entities.Channel | null>(null);
const favorited = ref(false);
const searchQuery = ref('');
const searchPagination = ref();
@ -114,14 +116,23 @@ watch(() => props.channelId, async () => {
channel.value = await os.api('channels/show', {
channelId: props.channelId,
});
favorited.value = channel.value.isFavorited;
favorited.value = channel.value.isFavorited ?? false;
if (favorited.value || channel.value.isFollowing) {
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 });
function edit() {
router.push(`/channels/${channel.value.id}/edit`);
router.push(`/channels/${channel.value?.id}/edit`);
}
function openPostForm() {
@ -131,6 +142,8 @@ function openPostForm() {
}
function favorite() {
if (!channel.value) return;
os.apiWithDialog('channels/favorite', {
channelId: channel.value.id,
}).then(() => {
@ -139,6 +152,8 @@ function favorite() {
}
async function unfavorite() {
if (!channel.value) return;
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.unfavoriteConfirm,
@ -152,6 +167,8 @@ async function unfavorite() {
}
async function search() {
if (!channel.value) return;
const query = searchQuery.value.toString().trim();
if (query == null) return;
@ -176,6 +193,10 @@ const headerActions = computed(() => {
icon: 'ti ti-link',
text: i18n.ts.copyUrl,
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}`);
os.success();
},
@ -186,9 +207,14 @@ const headerActions = computed(() => {
icon: 'ti ti-share',
text: i18n.ts.share,
handler: async (): Promise<void> => {
if (!channel.value) {
console.warn('failed to share channel. channel.value is null.');
return;
}
navigator.share({
title: channel.value.name,
text: channel.value.description,
text: channel.value.description ?? undefined,
url: `${url}/channels/${channel.value.id}`,
});
},

View File

@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<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 * as Misskey from 'cherrypick-js';

View File

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

View File

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