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

Merge pull request #522 from kokonect-link/develop

Release: 4.12.1
This commit is contained in:
NoriDev 2024-11-05 13:27:31 +09:00 committed by GitHub
commit d3f4d31055
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
169 changed files with 3415 additions and 1069 deletions

View File

@ -23,8 +23,81 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
# 릴리즈 노트
이 문서는 CherryPick의 변경 사항만 포함합니다.
## 4.12.1
출시일: 2024/11/5<br>
기반 Misskey 버전: 2024.9.0<br>
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#202490](CHANGELOG.md#202490) 문서를 참고하십시오.
## NOTE
- `오브젝트 스토리지 (리모트)`의 내부 변경으로 인해 DB 마이그레이션 과정에서 이전에 설정한 값이 제거됩니다. 마이그레이션 이후 값을 다시 설정해 주십시오.
### General
- Feat: 사용자 메뉴에서 서버를 뮤트할 수 있음 (kokonect-link/cherrypick#502)
- 이전 빌드에 추가된 기능은 관리자 전용이며, 이 빌드에서 추가된 기능은 일반 사용자용 기능입니다.
- Feat: 새 노트 알림을 묶어서 표시할 수 있음 (yojo-art/cherrypick#328)
- Feat: 노트 게시를 예약할 수 있음 (kokonect-link/cherrypick#513, yojo-art/cherrypick#483, [Type4ny-Project/Type4ny@271c872c](https://github.com/Type4ny-Project/Type4ny/commit/271c872c97f215ef5d8e0be62251dd422a52e5b1))
### Client
- Enhance: (Friendly) 모바일 환경에서 계정 목록을 표시할 때 내 프로필을 표시함
- Enhance: 업데이트 및 마이그레이션 알림에서 CherryPick의 변경 사항만 표시함
- Enhance: 검색과 같은 입력 블록에서 `Enter`를 입력하면 자동으로 가상 키보드를 숨김
- Enhance: 환영 페이지의 타임라인에서 사용할 수 있는 일부 노트 메뉴 추가
- QR 코드 생성
- 새 탭에서 열기
- 리노트 목록
- 리액션 목록
- 텍스트 소스 보기
- 고양이체로 표시하지 않기
- Enhance: 노트 작성 폼의 사용자 선택 메뉴에서 현재 계정과 로그인된 추가 계정 영역을 구분함
- Enhance: 모바일 환경에서 토스트 알림의 디자인 및 배치를 개선함
- Enhance: 로그인 시 표시되는 환영 메시지의 표시 유무를 선택할 수 있음
- Ehhance: `텍스트 소스 보기`를 사용하면 자동으로 내용을 펼침
- Enhance: 사용자 메뉴에서 서버 정보 페이지로 갈 수 있는 바로 가기를 추가함
- Enhance: 사용자 페이지와 사용자 팝업 개선
- 사용자 페이지와 사용자 팝업에 `새 노트 알림 켜기` 버튼이 추가됨
- 사용자 페이지에 `사용자 노트 검색` 버튼이 추가됨
- Enhance: 상대방이 나를 차단한 경우 차단되었음을 알 수 있도록 개선함
- 차단되면 다음 기능들이 화면 상에서 사라지고 사용이 제한됩니다.
- 팔로우 버튼
- 사용자 페이지에서 기본 정보 외 다른 모든 정보는 열람할 수 없음
- 사용자 페이지에서 요약 탭 외에 모든 탭이 사용할 수 없게됨
- Fix: 임베디드 코드에서 CherryPick의 색상 설정이 반영되지 않음
- Fix: 임베디드 코드에 `fade``Temml(KaTex)`가 반영되지 않음
- Fix: 노트의 QR 코드를 생성했을 때 `링크 복사` 버튼을 누르면 잘못된 토스트 알림이 표시됨
- Fix: 노트 메뉴에 `링크 복사` 옵션이 표시되지 않음
- Fix: 서버 이름에 마크업 언어가 포함되어 있으면 외부 사이트로 이동할 때 표시되는 대화상자에서 서버 이름이 잘못 표시될 수 있음
- Fix: 로그인 하지 않은 사용자가 노트 내용에 포함된 이모지를 누르면 이모지 복사 및 리액션 메뉴에 접근할 수 있음
- Fix: 모바일 환경에서 노트 작성 폼의 미리보기 디자인이 잘못 표시될 수 있음
- Fix: 리버시에서 커스텀 이모지를 리액션으로 보낼 수 없음
- Fix: 리버시에서 리액션할 때 말풍선의 위치가 어긋나 보일 수 있음
- Fix: 특정 조건에서 노트 동작 버튼을 비활성화 해도 버튼이 사라지지 않음
- 노트에 답글을 작성할 수 없을 때
- 노트를 리노트할 수 없을 때
- Fix: 캡션이 512자를 초과하면 초과한 내용이 잘릴 수 있음 (kokonect-link/cherrypick#518)
- 캡션을 512자를 초과해서 작성하면 캡션 내용을 저장하기 전에 경고를 표시합니다.
- Fix: 노트 상세 페이지의 InstanceTicker에 커서를 올릴 때 마우스 포인터가 올바르게 표시되지 않음
- Fix: `UI에 흐림 효과 사용` 옵션이 토스트 알림에서 제대로 적용되지 않음
- 모바일 환경에서만 적용되는 문제를 해결합니다.
- Fix: 모바일 환경에서 제어판의 인디케이터가 잘못된 위치에 표시될 수 있음
- Fix: `답글을 자동으로 더 보기`를 활성화하면 3개 미만의 답글이 있는 노트에서 답글이 보이지 않음 (kokonect-link/cherrypick#521)
- Fix: 제어판에서 문의처 URL이 설정되지 않았을 때 표시되는 경고의 바로가기가 잘못 설정되어 있음
- Fix: 캡션이 설정된 이미지 위에 마우스 커서를 올려도 캡션이 표시되지 않음 (kokonect-link/cherrypick#514)
- Fix: 코드 편집기의 커서 위치가 올바르게 표시되지 않을 수 있음 (kokonect-link/cherrypick#520)
- Fix: 투표 기한을 `기간 지정`으로 설정한 경우 투표가 즉시 종료될 수 있음 (kokonect-link/cherrypick#523)
- Fix: 로그인하지 않은 사용자가 리버시 전적을 볼 수 없음 (yojo-art/cherrypick#404)
### Server
- Enhance: 노트 편집 제한 완화
- 1시간에 10번 편집할 수 있던 것을 5분에 10번 편집할 수 있도록 완화함.
- Enhance: 이모지를 등록할 때 시스템 사용자로 다시 업로드 하도록 변경함 (yojo-art/cherrypick#510)
- 이모지를 등록한 사용자가 계정을 삭제하면 이모지도 같이 삭제되기 때문에 변경되었습니다.
- Fix: 로컬 전용 노트를 편집하면 편집한 노트가 연합될 수 있음 (kokonect-link/cherrypick#519, [libnare/shiftkey@654821da](https://github.com/libnare/shiftkey/commit/654821da003be7471f3c6fc320bf50afcb599d4e))
---
## 4.12.0
출시일: 2024/10/08<br>
출시일: 2024/10/8<br>
기반 Misskey 버전: 2024.9.0<br>
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#202490](CHANGELOG.md#202490) 문서를 참고하십시오.

View File

@ -938,9 +938,9 @@ _aboutMisskey:
donate: "Misskey তে দান করুন"
morePatrons: "আরও অনেকে আমাদের সাহায্য করছেন। তাদের সবাইকে ধন্যবাদ 🥰"
patrons: "সমর্থনকারী"
_cfm:
cheatSheet: "CFM চিটশিট"
intro: "CFM একটি মার্কআপ ভাষা যা CherryPick-এর মধ্যে বিভিন্ন জায়গায় ব্যবহার করা যেতে পারে। এখানে আপনি CFM-এর সিনট্যাক্সগুলির একটি তালিকা দেখতে পারবেন।"
_mfc:
cheatSheet: "MFC চিটশিট"
intro: "MFC একটি মার্কআপ ভাষা যা CherryPick-এর মধ্যে বিভিন্ন জায়গায় ব্যবহার করা যেতে পারে। এখানে আপনি MFC-এর সিনট্যাক্সগুলির একটি তালিকা দেখতে পারবেন।"
dummy: "মিসকি ফেডিভার্সের বিশ্বকে প্রসারিত করে"
mention: "উল্লেখ"
mentionDescription: "@ চিহ্ন + ব্যবহারকারীর নাম একটি নির্দিষ্ট ব্যবহারকারীকে নির্দেশ করতে ব্যবহার করা যায়।"

View File

@ -513,8 +513,8 @@ showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cur
showReactionsCount: "Mostra el nombre de reaccions a les publicacions"
noHistory: "No hi ha un registre previ"
signinHistory: "Historial d'autenticacions"
enableAdvancedMfm: "Habilitar l'CFM avançat"
enableAnimatedMfm: "Habilitar l'CFM amb moviment"
enableAdvancedMfm: "Habilitar l'MFC avançat"
enableAnimatedMfm: "Habilitar l'MFC amb moviment"
doing: "Processant..."
category: "Categoria"
tags: "Etiquetes"
@ -1229,8 +1229,8 @@ remainingN: "Queden: {n}"
overwriteContentConfirm: "Vols substituir el contingut actual?"
seasonalScreenEffect: "Efectes de pantalla segons les estacions"
decorate: "Decorar"
addMfmFunction: "Afegeix funcions CFM"
enableQuickAddMfmFunction: "Activar accés ràpid per afegir funcions CFM"
addMfmFunction: "Afegeix funcions MFC"
enableQuickAddMfmFunction: "Activar accés ràpid per afegir funcions MFC"
bubbleGame: "Bubble Game"
sfx: "Efectes de so"
soundWillBePlayed: "Es reproduiran efectes de so"

View File

@ -474,8 +474,8 @@ native: "Výchozí"
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
noHistory: "Žádná historie"
signinHistory: "Historie přihlášení"
enableAdvancedMfm: "Zapnout pokročilé CFM"
enableAnimatedMfm: "Zapnout animované CFM"
enableAdvancedMfm: "Zapnout pokročilé MFC"
enableAnimatedMfm: "Zapnout animované MFC"
doing: "Procesuju..."
category: "Kategorie"
tags: "Štítky"

View File

@ -506,8 +506,8 @@ joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigen
showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
noHistory: "Kein Verlauf gefunden"
signinHistory: "Anmeldungsverlauf"
enableAdvancedMfm: "Erweitertes CFM aktivieren"
enableAnimatedMfm: "Animiertes CFM aktivieren"
enableAdvancedMfm: "Erweitertes MFC aktivieren"
enableAnimatedMfm: "Animiertes MFC aktivieren"
doing: "In Bearbeitung …"
category: "Kategorie"
tags: "Aliasse"
@ -1194,7 +1194,7 @@ cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Besch
doReaction: "Reagieren"
code: "Code"
decorate: "Dekorieren"
addMfmFunction: "CFM hinzufügen"
addMfmFunction: "MFC hinzufügen"
sfx: "Soundeffekte"
lastNDays: "Letzten {n} Tage"
surrender: "Abbrechen"
@ -1708,9 +1708,9 @@ _displayOfSensitiveMedia:
respect: "Sensible Medien verbergen"
ignore: "Sensible Medien anzeigen"
force: "Alle Medien verbergen"
_cfm:
cheatSheet: "CFM Spickzettel"
intro: "CFM ist eine CherryPick-exklusive Markup-Sprache, die in CherryPick an vielen Stellen verwendet werden kann. Hier kannst du eine Liste von verfügbarer CFM-Syntax einsehen."
_mfc:
cheatSheet: "MFC Spickzettel"
intro: "MFC ist eine CherryPick-exklusive Markup-Sprache, die in CherryPick an vielen Stellen verwendet werden kann. Hier kannst du eine Liste von verfügbarer MFC-Syntax einsehen."
dummy: "CherryPick erweitert die Welt des Fediverse"
mention: "Erwähnung"
mentionDescription: "Mit At-Zeichen und Benutzername kann ein individueller Nutzer angegeben werden."
@ -1773,7 +1773,7 @@ _cfm:
rotate: "Drehen"
rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel."
plain: "Schlicht"
plainDescription: "Deaktiviert jegliche CFM-Syntax, die sich innerhalb dieses CFM-Effekts befindet."
plainDescription: "Deaktiviert jegliche MFC-Syntax, die sich innerhalb dieses MFC-Effekts befindet."
_instanceTicker:
none: "Nie anzeigen"
remote: "Für Benutzer fremder Instanzen anzeigen"

View File

@ -1,5 +1,12 @@
---
_lang_: "English"
youBlocked: "Youre blocked"
youBlockedDescription: "You cant follow or see {user}s posts."
schedulePost: "Posting a scheduled note"
schedulePostList: "List of scheduled notes"
welcomeBackToast: "Display a welcome message when you log in after a certain period of time"
invalidTextLengthError: "Too many characters entered"
invalidTextLengthDescription: "The number of characters is limited to {limitValue} characters. The current number of characters entered is {value} characters."
autoLoadMoreReplies: "Show more automatically replies"
autoLoadMoreConversation: "Show more conversation automatically"
useAutoTranslate: "Automatic translation"
@ -10,7 +17,7 @@ widgets: "Widgets"
postNote: "Post note"
bottomNavbar: "Bottom navigation bar"
bottomNavbarDescription: "This setting is only available in a mobile environment."
scheduledNoteDelete: "Schedule note deletion"
scheduledNoteDelete: "Schedule deletion of note"
getQRCode: "Get QR code"
customSplashText: "Custom splash text"
customSplashTextDescription: "This text will be displayed on the loading page."
@ -168,6 +175,7 @@ receiveFollowRequest: "Follow request received"
followRequestAccepted: "Follow request accepted"
mention: "Mention"
mentions: "Mentions"
newNotes: "New notes"
directNotes: "Direct notes"
importAndExport: "Import / Export"
import: "Import"
@ -627,10 +635,10 @@ showNoteActionsOnlyHover: "Only show note actions on hover"
showReactionsCount: "See the number of reactions in notes"
noHistory: "No history available"
signinHistory: "Login history"
enableAdvancedMfm: "Enable advanced CFM"
enableAdvancedMfmDescription: "When enabled, various CFM features, such as CFM with animated are available."
enableAnimatedMfm: "Enable animated CFM"
enableAnimatedMfmDescription: "When enabled, moves text that uses CFM grammar or emoji."
enableAdvancedMfm: "Enable advanced MFC"
enableAdvancedMfmDescription: "When enabled, various MFC features, such as MFC with animated are available."
enableAnimatedMfm: "Enable animated MFC"
enableAnimatedMfmDescription: "When enabled, moves text that uses MFC grammar or emoji."
doing: "Processing..."
category: "Category"
tags: "Aliases"
@ -1164,7 +1172,7 @@ thisPostMayBeAnnoyingCancel: "Cancel"
thisPostMayBeAnnoyingIgnore: "Post anyway"
collapseRenotes: "Collapse renotes you've already seen"
collapseRenotesDescription: "Collapse notes that you've reacted to or renoted before."
collapseDefault: "Collapse notes using specific CFM syntax"
collapseDefault: "Collapse notes using specific MFC syntax"
internalServerError: "Internal Server Error"
internalServerErrorDescription: "The server has run into an unexpected error."
copyErrorInfo: "Copy error details"
@ -1360,8 +1368,8 @@ remainingN: "Remaining: {n}"
overwriteContentConfirm: "Are you sure you want to overwrite the current content?"
seasonalScreenEffect: "Seasonal Screen Effect"
decorate: "Decorate"
addMfmFunction: "Add CFM"
enableQuickAddMfmFunction: "Show advanced CFM picker"
addMfmFunction: "Add MFC"
enableQuickAddMfmFunction: "Show advanced MFC picker"
bubbleGame: "Bubble Game"
sfx: "Sound Effects"
soundWillBePlayed: "Sound will be played"
@ -1522,7 +1530,7 @@ _initialAccountSetting:
privacySetting: "Privacy settings"
fontSizeSetting: "Font size settings"
blurEffectsSetting: "Blur effects settings"
mfmAndAnimatedImagesSetting: "CFM and Animated images settings"
mfmAndAnimatedImagesSetting: "MFC and Animated images settings"
theseSettingsCanEditLater: "You can always change these settings later."
youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
followUsers: "Try following some users that interest you to build up your timeline."
@ -1956,6 +1964,7 @@ _role:
ltlAvailable: "Can view the local timeline"
canPublicNote: "Can send public notes"
canEditNote: "Note editing"
scheduleNoteMax: "Maximum number of scheduled notes"
mentionMax: "Maximum number of mentions in a note"
canInvite: "Can create instance invite codes"
inviteLimit: "Invite limit"
@ -2113,9 +2122,9 @@ _displayOfSensitiveMedia:
respect: "Hide media marked as sensitive"
ignore: "Display media marked as sensitive"
force: "Hide all media"
_cfm:
cheatSheet: "CFM Cheatsheet"
intro: "CFM is a CherryPick-exclusive markup language that can be used in many places. Here you can view a list of all available CFM syntax."
_mfc:
cheatSheet: "MFC Cheatsheet"
intro: "MFC is a CherryPick-exclusive markup language that can be used in many places. Here you can view a list of all available MFC syntax."
dummy: "CherryPick expands the world of the Fediverse"
mention: "Mention"
mentionDescription: "You can specify a user by using an At-Symbol and a username."
@ -2188,7 +2197,7 @@ _cfm:
bg: "Background color"
bgDescription: "Set the background color to the specified value."
plain: "Plain"
plainDescription: "Deactivates the effects of all CFM contained within this CFM effect."
plainDescription: "Deactivates the effects of all MFC contained within this MFC effect."
ruby: "Ruby"
rubyDescription: "Display ruby characters over the text."
_instanceTicker:
@ -2383,6 +2392,8 @@ _permissions:
"read:mutes": "View your list of muted users"
"write:mutes": "Edit your list of muted users"
"write:notes": "Compose or delete notes"
"read:notes-schedule": "View your list of scheduled notes"
"write:notes-schedule": "Compose or delete scheduled notes"
"read:notifications": "View your notifications"
"write:notifications": "Manage your notifications"
"read:reactions": "View your reactions"
@ -2712,6 +2723,7 @@ _notification:
reactedBySomeUsers: "{n} users reacted"
likedBySomeUsers: "{n} users liked your note"
renotedBySomeUsers: "Renote from {n} users"
notedBySomeUsers: "There are {n} new notes"
followedBySomeUsers: "Followed by {n} users"
flushNotification: "Clear notifications"
_types:
@ -2918,7 +2930,7 @@ _dataSaver:
description: "URL preview thumbnail images will no longer be loaded."
_code:
title: "Code highlighting"
description: "If code highlighting notations are used in CFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data."
description: "If code highlighting notations are used in MFC, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data."
_hemisphere:
N: "Northern Hemisphere"
S: "Southern Hemisphere"
@ -2958,7 +2970,7 @@ _reversi:
canPutEverywhere: "Tiles are placeable everywhere"
timeLimitForEachTurn: "Time limit for turn"
freeMatch: "Free Match"
lookingForPlayer: "Finding opponent..."
lookingForPlayer: "Finding opponent"
gameCanceled: "The game has been cancelled."
shareToTlTheGameWhenStart: "Share Game to timeline when started"
iStartedAGame: "The game has begun! #MisskeyReversi"

View File

@ -518,8 +518,8 @@ showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor"
showReactionsCount: "Mostrar el número de reacciones en las notas"
noHistory: "No hay datos en el historial"
signinHistory: "Historial de ingresos"
enableAdvancedMfm: "Habilitar CFM avanzado"
enableAnimatedMfm: "Habilitar CFM con movimiento"
enableAdvancedMfm: "Habilitar MFC avanzado"
enableAnimatedMfm: "Habilitar MFC con movimiento"
doing: "Voy en camino"
category: "Categoría"
tags: "Etiqueta"
@ -1229,8 +1229,8 @@ remainingN: "Faltan: {n}"
overwriteContentConfirm: "¿Quieres sustituir todo el contenido actual?"
seasonalScreenEffect: "Efectos de pantalla asociados a estaciones"
decorate: "Decorar"
addMfmFunction: "Añadir función CFM"
enableQuickAddMfmFunction: "Activar acceso rápido para añadir funciones CFM"
addMfmFunction: "Añadir función MFC"
enableQuickAddMfmFunction: "Activar acceso rápido para añadir funciones MFC"
bubbleGame: "Bubble Game"
sfx: "Efectos de sonido"
soundWillBePlayed: "Se reproducirán efectos sonoros"
@ -1837,9 +1837,9 @@ _displayOfSensitiveMedia:
respect: "Esconder medios marcados como sensibles"
ignore: "Mostrar medios marcados como sensibles"
force: "Esconder todala multimedia"
_cfm:
cheatSheet: "Hoja de referencia de CFM"
intro: "CFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de CherryPick. Aquí puede ver una lista de sintaxis disponibles en CFM."
_mfc:
cheatSheet: "Hoja de referencia de MFC"
intro: "MFC es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de CherryPick. Aquí puede ver una lista de sintaxis disponibles en MFC."
dummy: "CherryPick expande el mundo de la Fediverso"
mention: "Menciones"
mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular."
@ -1902,7 +1902,7 @@ _cfm:
rotate: "Rotar"
rotateDescription: "Rota el contenido a un ángulo especificado."
plain: "Plano"
plainDescription: "Desactiva los efectos de todo el contenido CFM con este efecto CFM."
plainDescription: "Desactiva los efectos de todo el contenido MFC con este efecto MFC."
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"
@ -2593,7 +2593,7 @@ _dataSaver:
description: "Desactiva la carga de vistas previas de las URLs."
_code:
title: "Resaltar código"
description: "Si se usa resaltado de código en CFM, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos."
description: "Si se usa resaltado de código en MFC, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos."
_hemisphere:
N: "Hemisferio norte"
S: "Hemisferio sur"

View File

@ -509,8 +509,8 @@ showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
showReactionsCount: "Afficher le nombre de réactions des notes"
noHistory: "Pas d'historique"
signinHistory: "Historique de connexion"
enableAdvancedMfm: "Activer la CFM avancée"
enableAnimatedMfm: "Activer le CFM animé"
enableAdvancedMfm: "Activer la MFC avancée"
enableAnimatedMfm: "Activer le MFC animé"
doing: "En cours..."
category: "Catégorie"
tags: "Étiquettes"
@ -1219,8 +1219,8 @@ remainingN: "Restants : {n}"
overwriteContentConfirm: "Voulez-vous remplacer le contenu actuel ?"
seasonalScreenEffect: "Effet d'écran saisonnier"
decorate: "Décorer"
addMfmFunction: "Insérer CFM"
enableQuickAddMfmFunction: "Afficher le sélecteur de CFM avancé"
addMfmFunction: "Insérer MFC"
enableQuickAddMfmFunction: "Afficher le sélecteur de MFC avancé"
bubbleGame: "Jeu de bulles"
sfx: "Effets sonores"
soundWillBePlayed: "Le son sera joué"
@ -1622,9 +1622,9 @@ _aboutMisskey:
projectMembers: "Membres du projet"
_displayOfSensitiveMedia:
force: "Masquer tous les médias"
_cfm:
cheatSheet: "Antisèche CFM"
intro: "CFM est un langage Markdown spécifique utilisable ici et là dans CherryPick. Vous pouvez vérifier ici les structures utilisables avec CFM."
_mfc:
cheatSheet: "Antisèche MFC"
intro: "MFC est un langage Markdown spécifique utilisable ici et là dans CherryPick. Vous pouvez vérifier ici les structures utilisables avec MFC."
dummy: "La Fédiverse s'agrandit avec CherryPick"
mention: "Mentionner"
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant une arobase suivie d'un nom d'utilisateur"
@ -2264,7 +2264,7 @@ _dataSaver:
description: "Les vignettes d'aperçu des URL ne seront plus chargées."
_code:
title: "Mise en évidence du code"
description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la CFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données."
description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFC, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données."
_reversi:
waitingBoth: "Préparez-vous"
total: "Total"

View File

@ -520,8 +520,8 @@ showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
showReactionsCount: "Lihat jumlah reaksi dalam catatan"
noHistory: "Tidak ada riwayat"
signinHistory: "Riwayat masuk"
enableAdvancedMfm: "Nyalakan CFM tingkat lanjut"
enableAnimatedMfm: "Nyalakan animasi CFM"
enableAdvancedMfm: "Nyalakan MFC tingkat lanjut"
enableAnimatedMfm: "Nyalakan animasi MFC"
doing: "Sedang berkerja..."
category: "Kategori"
tags: "Tandai"
@ -1230,7 +1230,7 @@ overwriteContentConfirm: "Apakah kamu yakin untuk menimpa konten saat ini?"
seasonalScreenEffect: "Efek layar musiman"
decorate: "Dekor"
addMfmFunction: "Tambahkan dekorasi"
enableQuickAddMfmFunction: "Tampilkan pemilih CFM tingkat lanjut"
enableQuickAddMfmFunction: "Tampilkan pemilih MFC tingkat lanjut"
bubbleGame: "Bubble Game"
sfx: "Efek Suara"
soundWillBePlayed: "Suara yang akan dimainkan"
@ -1846,9 +1846,9 @@ _displayOfSensitiveMedia:
respect: "Sembunyikan media yang ditandai sensitif"
ignore: "Tampilkan media yang ditandai sensitif"
force: "Sembunyikan semua media"
_cfm:
cheatSheet: "Contekan CFM"
intro: "CFM adalah CherryPick-exclusive Markup Language yang dapat digunakan di banyak tempat. Berikut kamu bisa melihat daftar dari syntax CFM yang ada."
_mfc:
cheatSheet: "Contekan MFC"
intro: "MFC adalah CherryPick-exclusive Markup Language yang dapat digunakan di banyak tempat. Berikut kamu bisa melihat daftar dari syntax MFC yang ada."
dummy: "CherryPick membentangkan dunia Fediverse"
mention: "Sebut"
mentionDescription: "Kamu dapat menentukan pengguna tertentu dengan menggunakan simbol-At dan nama engguna mereka."
@ -2603,7 +2603,7 @@ _dataSaver:
description: "Gambar kecil URL pratinjau tidak akan dimuat lagi."
_code:
title: "Penyorotan kode"
description: "Jika notasi penyorotan kode digunakan di CFM, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data."
description: "Jika notasi penyorotan kode digunakan di MFC, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data."
_hemisphere:
N: "Bumi belahan utara"
S: "Bumi belahan selatan"

70
locales/index.d.ts vendored
View File

@ -13,6 +13,34 @@ export interface Locale extends ILocale {
*
*/
"_lang_": string;
/**
*
*/
"youBlocked": string;
/**
* {user}
*/
"youBlockedDescription": ParameterizedString<"user">;
/**
* 稿
*/
"schedulePost": string;
/**
* 稿
*/
"schedulePostList": string;
/**
*
*/
"welcomeBackToast": string;
/**
*
*/
"invalidTextLengthError": string;
/**
* {limitValue}{value}
*/
"invalidTextLengthDescription": ParameterizedString<"limitValue" | "value">;
/**
*
*/
@ -699,6 +727,10 @@ export interface Locale extends ILocale {
*
*/
"mentions": string;
/**
* 稿
*/
"newNotes": string;
/**
* 稿
*/
@ -2564,19 +2596,19 @@ export interface Locale extends ILocale {
*/
"signinHistory": string;
/**
* CFMを有効にする
* MFCを有効にする
*/
"enableAdvancedMfm": string;
/**
* CFMのようなさまざまなCFM機能が使用できます
* MFCのようなさまざまなMFC機能が使用できます
*/
"enableAdvancedMfmDescription": string;
/**
* CFMを有効にする
* MFCを有効にする
*/
"enableAnimatedMfm": string;
/**
* CFM文法または絵文字を使用するテキストが動きます
* MFC文法または絵文字を使用するテキストが動きます
*/
"enableAnimatedMfmDescription": string;
/**
@ -4721,7 +4753,7 @@ export interface Locale extends ILocale {
*/
"collapseRenotesDescription": string;
/**
* CFM構文を含むートを省略して表示
* MFC構文を含むノートを省略して表示
*/
"collapseDefault": string;
/**
@ -5509,7 +5541,7 @@ export interface Locale extends ILocale {
*/
"addMfmFunction": string;
/**
* CFMのピッカーを表示する
* MFCのピッカーを表示する
*/
"enableQuickAddMfmFunction": string;
/**
@ -6155,7 +6187,7 @@ export interface Locale extends ILocale {
*/
"blurEffectsSetting": string;
/**
* CFMとアニメーション画像設定
* MFCとアニメーション画像設定
*/
"mfmAndAnimatedImagesSetting": string;
/**
@ -7707,6 +7739,10 @@ export interface Locale extends ILocale {
*
*/
"canEditNote": string;
/**
* 稿
*/
"scheduleNoteMax": string;
/**
*
*/
@ -8323,13 +8359,13 @@ export interface Locale extends ILocale {
*/
"force": string;
};
"_cfm": {
"_mfc": {
/**
* CFMチートシート
* MFCチートシート
*/
"cheatSheet": string;
/**
* CFMはCherryPick内の様々な場所で使用できる専用のマークアップ言語ですCFMで使用可能な構文一覧が確認できます
* MFCCherryPick内の様々な場所で使用できる専用のマークアップ言語ですMFCで使用可能な構文一覧が確認できます
*/
"intro": string;
/**
@ -9370,6 +9406,14 @@ export interface Locale extends ILocale {
*
*/
"write:notes": string;
/**
* 稿
*/
"read:notes-schedule": string;
/**
* 稿
*/
"write:notes-schedule": string;
/**
*
*/
@ -10656,6 +10700,10 @@ export interface Locale extends ILocale {
* {n}
*/
"renotedBySomeUsers": ParameterizedString<"n">;
/**
* {n}稿
*/
"notedBySomeUsers": ParameterizedString<"n">;
/**
* {n}
*/
@ -11430,7 +11478,7 @@ export interface Locale extends ILocale {
*/
"title": string;
/**
* CFMなどでコードハイライト記法が使われている場合
* MFCなどでコードハイライト記法が使われている場合
*/
"description": string;
};

View File

@ -530,8 +530,8 @@ showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mou
showReactionsCount: "Visualizza il numero di reazioni su una nota"
noHistory: "Nessuna cronologia"
signinHistory: "Storico degli accessi al profilo"
enableAdvancedMfm: "Attiva CFM avanzati"
enableAnimatedMfm: "Attiva CFM animati"
enableAdvancedMfm: "Attiva MFC avanzati"
enableAnimatedMfm: "Attiva MFC animati"
doing: "In corso..."
category: "Categoria"
tags: "Tag"
@ -1249,7 +1249,7 @@ overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?"
seasonalScreenEffect: "Schermate in base alla stagione"
decorate: "Decora"
addMfmFunction: "Aggiungi decorazioni"
enableQuickAddMfmFunction: "Attiva il selettore di funzioni CFM"
enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFC"
bubbleGame: "Bubble Game"
sfx: "Effetti sonori"
soundWillBePlayed: "Con musica ed effetti sonori"
@ -1890,9 +1890,9 @@ _displayOfSensitiveMedia:
respect: "Nascondere i media espliciti"
ignore: "Non nascondere i media espliciti"
force: "Nascondi tutti i media"
_cfm:
cheatSheet: "Bigliettino CFM"
intro: "CFM è un linguaggio Markdown particolare che si può usare in diverse parti di CherryPick. Qui puoi visualizzare a colpo d'occhio tutta la sintassi CFM utile."
_mfc:
cheatSheet: "Bigliettino MFC"
intro: "MFC è un linguaggio Markdown particolare che si può usare in diverse parti di CherryPick. Qui puoi visualizzare a colpo d'occhio tutta la sintassi MFC utile."
dummy: "Il Fediverso si espande con CherryPick"
mention: "Menzioni"
mentionDescription: "Si può menzionare un utente specifico digitando il suo nome utente subito dopo il segno @."

View File

@ -1,5 +1,12 @@
_lang_: "日本語"
youBlocked: "ブロックされています"
youBlockedDescription: "{user}さんのフォローやポストの表示はできません。"
schedulePost: "予約投稿"
schedulePostList: "予約投稿一覧"
welcomeBackToast: "一定時間が経過した後に接続したときに歓迎メッセージを表示"
invalidTextLengthError: "入力された文字数が多すぎます"
invalidTextLengthDescription: "文字数が{limitValue}文字に制限されています。現在入力された文字数は{value}文字です。"
autoLoadMoreReplies: "返信を自動でもっと見る"
autoLoadMoreConversation: "会話を自動でもっと見る"
useAutoTranslate: "自動翻訳"
@ -168,6 +175,7 @@ receiveFollowRequest: "フォローリクエストされました"
followRequestAccepted: "フォローが承認されました"
mention: "メンション"
mentions: "あなた宛て"
newNotes: "新規投稿"
directNotes: "ダイレクト投稿"
importAndExport: "インポートとエクスポート"
import: "インポート"
@ -634,10 +642,10 @@ showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表
showReactionsCount: "ノートのリアクション数を表示する"
noHistory: "履歴はありません"
signinHistory: "ログイン履歴"
enableAdvancedMfm: "高度なCFMを有効にする"
enableAdvancedMfmDescription: "有効にすると、動きのあるCFMのようなさまざまなCFM機能が使用できます。"
enableAnimatedMfm: "動きのあるCFMを有効にする"
enableAnimatedMfmDescription: "有効にすると、CFM文法または絵文字を使用するテキストが動きます。"
enableAdvancedMfm: "高度なMFCを有効にする"
enableAdvancedMfmDescription: "有効にすると、動きのあるMFCのようなさまざまなMFC機能が使用できます。"
enableAnimatedMfm: "動きのあるMFCを有効にする"
enableAnimatedMfmDescription: "有効にすると、MFC文法または絵文字を使用するテキストが動きます。"
doing: "やっています"
category: "カテゴリ"
tags: "タグ"
@ -1173,7 +1181,7 @@ thisPostMayBeAnnoyingCancel: "やめる"
thisPostMayBeAnnoyingIgnore: "このまま投稿"
collapseRenotes: "リノートのスマート省略"
collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示します。"
collapseDefault: "特定のCFM構文を含むートを省略して表示"
collapseDefault: "特定のMFC構文を含むノートを省略して表示"
internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
copyErrorInfo: "エラー情報をコピー"
@ -1370,7 +1378,7 @@ overwriteContentConfirm: "現在の内容に上書きされますがよろしい
seasonalScreenEffect: "季節に応じた画面の演出"
decorate: "デコる"
addMfmFunction: "装飾を追加"
enableQuickAddMfmFunction: "高度なCFMのピッカーを表示する"
enableQuickAddMfmFunction: "高度なMFCのピッカーを表示する"
bubbleGame: "バブルゲーム"
sfx: "効果音"
soundWillBePlayed: "サウンドが再生されます"
@ -1552,7 +1560,7 @@ _initialAccountSetting:
privacySetting: "プライバシー設定"
fontSizeSetting: "フォントサイズ設定"
blurEffectsSetting: "ぼかし効果設定"
mfmAndAnimatedImagesSetting: "CFMとアニメーション画像設定"
mfmAndAnimatedImagesSetting: "MFCとアニメーション画像設定"
theseSettingsCanEditLater: "これらの設定は後から変更できます。"
youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
@ -1995,6 +2003,7 @@ _role:
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集"
scheduleNoteMax: "予約投稿の最大数"
mentionMax: "ノート内の最大メンション数"
canInvite: "サーバー招待コードの発行"
inviteLimit: "招待コードの作成可能数"
@ -2173,9 +2182,9 @@ _displayOfSensitiveMedia:
ignore: "センシティブ設定されたメディアを隠さない"
force: "常にメディアを隠す"
_cfm:
cheatSheet: "CFMチートシート"
intro: "CFMは、CherryPick内の様々な場所で使用できる専用のマークアップ言語です。ここでは、CFMで使用可能な構文一覧が確認できます。"
_mfc:
cheatSheet: "MFCチートシート"
intro: "MFCは、CherryPick内の様々な場所で使用できる専用のマークアップ言語です。ここでは、MFCで使用可能な構文一覧が確認できます。"
dummy: "CherryPickでFediverseの世界が広がります"
mention: "メンション"
mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。"
@ -2458,6 +2467,8 @@ _permissions:
"read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する"
"write:notes": "ノートを作成・削除する"
"read:notes-schedule": "予約投稿を見る"
"write:notes-schedule": "予約投稿を作成・削除する"
"read:notifications": "通知を見る"
"write:notifications": "通知を操作する"
"read:reactions": "リアクションを見る"
@ -2808,6 +2819,7 @@ _notification:
reactedBySomeUsers: "{n}人がリアクションしました"
likedBySomeUsers: "{n}人がいいねしました"
renotedBySomeUsers: "{n}人がリノートしました"
notedBySomeUsers: "{n}件の新しい投稿があります"
followedBySomeUsers: "{n}人にフォローされました"
flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが完了しました"
@ -3032,7 +3044,7 @@ _dataSaver:
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
_code:
title: "コードハイライトを非表示"
description: "CFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
description: "MFCなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
_hemisphere:
N: "北半球"

View File

@ -525,8 +525,8 @@ showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示す
showReactionsCount: "ノートのリアクション数を表示する"
noHistory: "履歴はないわ。"
signinHistory: "ログイン履歴"
enableAdvancedMfm: "ややこしいCFMもありにする"
enableAnimatedMfm: "動きがやかましいCFMも許したる"
enableAdvancedMfm: "ややこしいMFCもありにする"
enableAnimatedMfm: "動きがやかましいMFCも許したる"
doing: "やっとるがな"
category: "カテゴリ"
tags: "タグ"
@ -1242,7 +1242,7 @@ overwriteContentConfirm: "今の内容に上書きされるけどいい?"
seasonalScreenEffect: "季節にあった画面の動き"
decorate: "デコる"
addMfmFunction: "装飾つける"
enableQuickAddMfmFunction: "ややこしいCFMのピッカーを出す"
enableQuickAddMfmFunction: "ややこしいMFCのピッカーを出す"
bubbleGame: "バブルゲーム"
sfx: "効果音"
soundWillBePlayed: "サウンドが再生されるで"
@ -1865,9 +1865,9 @@ _displayOfSensitiveMedia:
respect: "きわどいのは見とうない"
ignore: "きわどいのも見たい"
force: "常にメディアを隠すで"
_cfm:
cheatSheet: "CFMチートシート"
intro: "CFMは、CherryPick内の色んな所で使える専用のマークアップ言語やで。このページでCFMで使える構文一覧が確認できるで。"
_mfc:
cheatSheet: "MFCチートシート"
intro: "MFCは、CherryPick内の色んな所で使える専用のマークアップ言語やで。このページでMFCで使える構文一覧が確認できるで。"
dummy: "CherryPickでFediverseの世界が広がります"
mention: "メンション"
mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができるで。"

View File

@ -479,8 +479,8 @@ native: "기본"
showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기"
noHistory: "기록이 없십니다"
signinHistory: "로그인 기록"
enableAdvancedMfm: "복잡한 CFM 키기"
enableAnimatedMfm: "정신사나운 CFM 키기"
enableAdvancedMfm: "복잡한 MFC 키기"
enableAnimatedMfm: "정신사나운 MFC 키기"
doing: "잠만예"
category: "카테고리"
tags: "태그"

View File

@ -1,5 +1,12 @@
---
_lang_: "한국어"
youBlocked: "앗.. 차단당했어요.."
youBlockedDescription: "{user} 님을 팔로우하거나 해당 사용자의 게시물을 볼 수 없어요."
schedulePost: "노트 게시 예약"
schedulePostList: "노트 게시 예약 목록"
welcomeBackToast: "일정 시간이 지난 후 접속했을 때 환영 메시지 표시"
invalidTextLengthError: "입력된 문자수가 너무 많아요"
invalidTextLengthDescription: "문자수가 {limitValue}자로 제한되어 있어요. 현재 입력된 문자수는 {value}자예요."
autoLoadMoreReplies: "답글을 자동으로 더 보기"
autoLoadMoreConversation: "대화를 자동으로 더 보기"
useAutoTranslate: "자동 번역"
@ -22,7 +29,7 @@ showReplyTargetNoteInSemiTransparent: "답글 대상 노트를 반투명하게
noteFooterButton: "노트 동작 버튼"
collapseReplies: "답글로 작성된 노트 간략화하기"
collapseRepliesDescription: "답글로 작성된 노트를 접어서 표시해요.\n리액션한 노트는 영향을 받지 않아요."
repliedBy: "{user}님이 답글을 작성했어요"
repliedBy: "{user} 님이 답글을 작성했어요"
collapseLongNoteContent: "내용이 긴 노트 간략화하기"
alwaysShowCw: "'내용 가리기'로 설정한 내용을 항상 보이기"
forceRenoteVisibilitySelector: "리노트 공개 범위 지정"
@ -109,7 +116,7 @@ gotIt: "알겠어요"
cancel: "취소"
noThankYou: "나중에"
enterUsername: "사용자 이름 입력"
renotedBy: "{user}님이 리노트 했어요"
renotedBy: "{user} 님이 리노트 했어요"
noNotes: "노트가 없어요"
noNotifications: "표시할 알림이 없어요"
instance: "서버"
@ -168,6 +175,7 @@ receiveFollowRequest: "새로운 팔로우 요청이 있어요"
followRequestAccepted: "팔로우가 수락되었어요"
mention: "멘션"
mentions: "받은 멘션"
newNotes: "새 노트"
directNotes: "다이렉트 노트"
importAndExport: "가져오기 및 내보내기"
import: "가져오기"
@ -634,10 +642,10 @@ showNoteActionsOnlyHover: "노트에 커서를 올렸을 때에만 노트 동작
showReactionsCount: "노트의 리액션 수 표시하기"
noHistory: "기록이 없어요"
signinHistory: "로그인 기록"
enableAdvancedMfm: "고급 CFM 활성화"
enableAdvancedMfmDescription: "활성화하면 움직임이 있는 CFM과 같은 다양한 CFM 기능을 사용할 수 있게 돼요."
enableAnimatedMfm: "움직임이 있는 CFM 활성화"
enableAnimatedMfmDescription: "활성화하면 CFM 문법을 사용한 텍스트나 이모지가 움직이게 돼요."
enableAdvancedMfm: "고급 MFC 활성화"
enableAdvancedMfmDescription: "활성화하면 움직임이 있는 MFC과 같은 다양한 MFC 기능을 사용할 수 있게 돼요."
enableAnimatedMfm: "움직임이 있는 MFC 활성화"
enableAnimatedMfmDescription: "활성화하면 MFC 문법을 사용한 텍스트나 이모지가 움직이게 돼요."
doing: "잠시만 기다려 주세요"
category: "카테고리"
tags: "태그"
@ -979,7 +987,7 @@ administration: "관리"
accounts: "계정"
switch: "전환"
noMaintainerInformationWarning: "관리자 정보를 아직 설정하지 않았어요."
noInquiryUrlWarning: "문의처 주소를 아직 설정하지 않았어요."
noInquiryUrlWarning: "문의처 URL을 아직 설정하지 않았어요."
noBotProtectionWarning: "봇 방어가 설정되지 않았어요."
configure: "설정하기"
postToGallery: "갤러리에 업로드"
@ -1173,7 +1181,7 @@ thisPostMayBeAnnoyingCancel: "그만두기"
thisPostMayBeAnnoyingIgnore: "이대로 게시"
collapseRenotes: "이미 본 리노트를 간략화하기"
collapseRenotesDescription: "리액션이나 리노트한 노트를 접어서 표시해요."
collapseDefault: "특정 CFM 구문이 포함된 노트 간략화하기"
collapseDefault: "특정 MFC 구문이 포함된 노트 간략화하기"
internalServerError: "내부 서버 오류"
internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했어요."
copyErrorInfo: "오류 정보 복사"
@ -1370,7 +1378,7 @@ overwriteContentConfirm: "현재 내용을 덮어쓰기 하게 돼요. 그래도
seasonalScreenEffect: "계절에 따른 화면 연출"
decorate: "장식하기"
addMfmFunction: "장식 추가"
enableQuickAddMfmFunction: "고급 CFM 선택기 표시"
enableQuickAddMfmFunction: "고급 MFC 선택기 표시"
bubbleGame: "버블 게임"
sfx: "효과음"
soundWillBePlayed: "사운드가 재생돼요"
@ -1539,7 +1547,7 @@ _initialAccountSetting:
privacySetting: "프라이버시 설정"
fontSizeSetting: "글자 크기 설정"
blurEffectsSetting: "흐림 효과 설정"
mfmAndAnimatedImagesSetting: "CFM 및 움직이는 이미지 설정"
mfmAndAnimatedImagesSetting: "MFC 및 움직이는 이미지 설정"
theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있어요."
youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 맞게 조절할 수 있으니 꼭 확인해 보세요!"
followUsers: "관심사가 맞는 사람을 팔로우하여 타임라인을 가꾸어 보세요."
@ -1974,6 +1982,7 @@ _role:
ltlAvailable: "로컬 타임라인 보이기"
canPublicNote: "공개 노트 허용"
canEditNote: "노트 편집 허용"
scheduleNoteMax: "게시를 예약한 노트의 최대 수"
mentionMax: "노트에서 언급할 수 있는 멘션 수"
canInvite: "서버 초대 코드 발행"
inviteLimit: "초대 한도"
@ -2136,9 +2145,9 @@ _displayOfSensitiveMedia:
respect: "민감한 콘텐츠로 표시된 미디어 가리기"
ignore: "민감한 콘텐츠로 표시된 미디어 보이기"
force: "미디어 항상 가리기"
_cfm:
cheatSheet: "CFM 도움말"
intro: "CFM는 CherryPick 클라이언트의 다양한 곳에서 사용할 수 있는 전용 마크업 언어에요. 여기에서 CFM에서 사용할 수 있는 구문을 확인할 수 있어요."
_mfc:
cheatSheet: "MFC 도움말"
intro: "MFC는 CherryPick 클라이언트의 다양한 곳에서 사용할 수 있는 전용 마크업 언어에요. 여기에서 MFC에서 사용할 수 있는 구문을 확인할 수 있어요."
dummy: "CherryPick으로 연합우주의 세계가 펼쳐집니다"
mention: "멘션"
mentionDescription: "골뱅이표(@) 뒤에 사용자 이름을 넣어 특정 사용자를 지정할 수 있어요."
@ -2211,7 +2220,7 @@ _cfm:
bg: "배경색"
bgDescription: "지정한 값으로 배경색을 지정해요."
plain: "평문"
plainDescription: "안에 있는 CFM 구문을 모두 무시하고 평문으로 표시해요."
plainDescription: "안에 있는 MFC 구문을 모두 무시하고 평문으로 표시해요."
ruby: "루비"
rubyDescription: "글자 위에 루비를 표시해요."
_instanceTicker:
@ -2406,6 +2415,8 @@ _permissions:
"read:mutes": "뮤트 여부를 확인합니다"
"write:mutes": "뮤트를 하거나 해제합니다"
"write:notes": "노트를 작성하거나 삭제합니다"
"read:notes-schedule": "게시를 예약한 노트를 봅니다"
"write:notes-schedule": "노트 게시를 예약하거나 삭제합니다"
"read:notifications": "알림을 확인합니다"
"write:notifications": "알림을 모두 읽음 처리합니다"
"read:reactions": "리액션을 확인합니다"
@ -2738,6 +2749,7 @@ _notification:
reactedBySomeUsers: "{n}명이 리액션했어요"
likedBySomeUsers: "{n}명이 좋아요를 눌렀어요"
renotedBySomeUsers: "{n}명이 리노트했어요"
notedBySomeUsers: "{n}개의 새 노트가 있어요"
followedBySomeUsers: "{n}명에게 팔로우됨"
flushNotification: "모든 알림 지우기"
exportOfXCompleted: "{x} 내보내기에 성공했어요."
@ -2949,7 +2961,7 @@ _dataSaver:
description: "URL 미리보기의 썸네일 이미지를 불러오지 않아요."
_code:
title: "코드 문법 강조"
description: "CFM 등에서 코드 문법 강조 기법을 사용할 때, 클릭하기 전까지는 불러오지 않아요. 코드 문법 강조 기능은 강조할 언어마다 해당 정의 파일을 불러와야 하지만, 이를 자동으로 불러오지 않게 되어 데이터 사용량을 줄일 수 있어요."
description: "MFC 등에서 코드 문법 강조 기법을 사용할 때, 클릭하기 전까지는 불러오지 않아요. 코드 문법 강조 기능은 강조할 언어마다 해당 정의 파일을 불러와야 하지만, 이를 자동으로 불러오지 않게 되어 데이터 사용량을 줄일 수 있어요."
_hemisphere:
N: "북반구"
S: "남반구"
@ -3026,7 +3038,7 @@ _contextMenu:
appWithShift: "Shift 키로 애플리케이션"
native: "브라우저의 UI"
_embedCodeGen:
title: "임베디드 코드를 커스터마이즈"
title: "임베디드 코드 사용자화"
header: "해더를 표시"
autoload: "자동으로 다음 코드를 실행 (비권장)"
maxHeight: "최대 높이"
@ -3036,7 +3048,7 @@ _embedCodeGen:
rounded: "외곽선을 둥글게 하기"
border: "외곽선에 테두리를 씌우기"
applyToPreview: "미리보기에 반영"
generateCode: "임베디드 코드 만들기"
generateCode: "임베디드 코드 만들기"
codeGenerated: "코드를 만들었어요!"
codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용해 주세요."
_abuse:

View File

@ -508,8 +508,8 @@ showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszką"
showReactionsCount: "Wyświetl liczbę reakcji na notatkę"
noHistory: "Brak historii"
signinHistory: "Historia logowania"
enableAdvancedMfm: "Włącz zaawansowane CFM"
enableAnimatedMfm: "Włącz animowane CFM"
enableAdvancedMfm: "Włącz zaawansowane MFC"
enableAnimatedMfm: "Włącz animowane MFC"
doing: "Przetwarzanie..."
category: "Kategoria"
tags: "Tagi"
@ -1132,9 +1132,9 @@ _aboutMisskey:
donate: "Przekaż darowiznę na Misskey"
morePatrons: "Naprawdę doceniam wsparcie ze strony wielu niewymienionych tu osób. Dziękuję! 🥰"
patrons: "Wspierający"
_cfm:
cheatSheet: "Ściąga CFM"
intro: "CFM to język składniowy wyjątkowy dla CherryPick, który może być użyty w wielu miejscach. Tu znajdziesz listę wszystkich możliwych elementów składni CFM."
_mfc:
cheatSheet: "Ściąga MFC"
intro: "MFC to język składniowy wyjątkowy dla CherryPick, który może być użyty w wielu miejscach. Tu znajdziesz listę wszystkich możliwych elementów składni MFC."
dummy: "CherryPick rozszerza świat Fediwersum"
mention: "Wspomnij"
mentionDescription: "Używając znaku @ i nazwy użytkownika, możesz określić danego użytkownika."
@ -1193,7 +1193,7 @@ _cfm:
rotate: "Obróć"
rotateDescription: "Obraca zawartość o określony kąt."
plain: "Zwyczajny"
plainDescription: "Wyłącza efekty wszystkich CFM zawartych w tym efekcie CFM."
plainDescription: "Wyłącza efekty wszystkich MFC zawartych w tym efekcie MFC."
_instanceTicker:
none: "Nigdy nie pokazuj"
remote: "Pokaż dla zdalnych użytkowników"

View File

@ -513,8 +513,8 @@ showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor
showReactionsCount: "Ver o número de reações nas notas"
noHistory: "Ainda não há histórico"
signinHistory: "Histórico de acesso"
enableAdvancedMfm: "Habilitar CFM avançado"
enableAnimatedMfm: "Habilitar CFM animado"
enableAdvancedMfm: "Habilitar MFC avançado"
enableAnimatedMfm: "Habilitar MFC animado"
doing: "Processando..."
category: "Categoria"
tags: "Etiquetas"
@ -1227,8 +1227,8 @@ remainingN: "Restante: {n}"
overwriteContentConfirm: "Você tem certeza de que deseja sobrescrever o conteúdo atual?"
seasonalScreenEffect: "Efeito de Tela Sazonal"
decorate: "Decorar"
addMfmFunction: "Adicionar CFM"
enableQuickAddMfmFunction: "Exibir seleção avançada de CFM"
addMfmFunction: "Adicionar MFC"
enableQuickAddMfmFunction: "Exibir seleção avançada de MFC"
bubbleGame: "Bubble Game"
sfx: "Efeitos Sonoros"
soundWillBePlayed: "Sons serão reproduzidos"
@ -2566,7 +2566,7 @@ _dataSaver:
description: "Miniaturas na prévia de URLs não serão mais carregadas."
_code:
title: "Destaque de código"
description: "Se as notações de formatação de código forem utilizadas em CFM, elas não irão carregar até serem selecionadas. Destaque de código exige baixar arquivos de alta definição para cada linguagem de programação. Logo, desabilitar o carregamento automático desses arquivos diminui a quantidade de informação comunicada."
description: "Se as notações de formatação de código forem utilizadas em MFC, elas não irão carregar até serem selecionadas. Destaque de código exige baixar arquivos de alta definição para cada linguagem de programação. Logo, desabilitar o carregamento automático desses arquivos diminui a quantidade de informação comunicada."
_hemisphere:
N: "Hemisfério Norte"
S: "Hemisfério Sul"

View File

@ -519,8 +519,8 @@ showNoteActionsOnlyHover: "Показывать кнопки у заметок
showReactionsCount: "Видеть количество реакций на заметках"
noHistory: "История пока пуста"
signinHistory: "Журнал посещений"
enableAdvancedMfm: "Включить расширенный CFM"
enableAnimatedMfm: "Включить анимированную разметку CFM"
enableAdvancedMfm: "Включить расширенный MFC"
enableAnimatedMfm: "Включить анимированную разметку MFC"
doing: "В процессе"
category: "Категория"
tags: "Метки"
@ -1178,7 +1178,7 @@ code: "Код"
remainingN: "Остаётся: {n}"
seasonalScreenEffect: "Эффект времени года на экране"
decorate: "Украсить"
addMfmFunction: "Добавить CFM"
addMfmFunction: "Добавить MFC"
lastNDays: "Последние {n} сут"
hemisphere: "Место проживания"
enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки"
@ -1619,9 +1619,9 @@ _displayOfSensitiveMedia:
respect: "Скрывать содержимое не для всех"
ignore: "Показывать содержимое не для всех"
force: "Скрывать всё содержимое"
_cfm:
cheatSheet: "Подсказка по разметке CFM"
intro: "CFM — язык оформления текста, который придуман специально для CherryPick и готов для применения во многих местах. На этой странице собраны и кратко изложены способы его использовать."
_mfc:
cheatSheet: "Подсказка по разметке MFC"
intro: "MFC — язык оформления текста, который придуман специально для CherryPick и готов для применения во многих местах. На этой странице собраны и кратко изложены способы его использовать."
dummy: "CherryPick расширяет границы Федиверса."
mention: "Упоминание"
mentionDescription: "При помощи знака «собака» перед именем можно упомянуть какого-нибудь пользователя."

View File

@ -469,8 +469,8 @@ joinOrCreateGroup: "Požiadajte o pozvanie do existujúcej skupiny alebo vytvort
showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutí myši"
noHistory: "Žiadna história"
signinHistory: "História prihlásení"
enableAdvancedMfm: "Povolenie pokročilého CFM"
enableAnimatedMfm: "Povoliť animované CFM"
enableAdvancedMfm: "Povolenie pokročilého MFC"
enableAnimatedMfm: "Povoliť animované MFC"
doing: "Pracujem..."
category: "Kategórie"
tags: "Značky"
@ -1030,9 +1030,9 @@ _aboutMisskey:
donate: "Podporiť Misskey"
morePatrons: "Takisto oceňujeme podporu mnoých ďalších, ktorí tu nie sú uvedení. Ďakujeme! 🥰"
patrons: "Prispievatelia"
_cfm:
_mfc:
cheatSheet: "MFM Cheatsheet"
intro: "CFM je CherryPick exkluzívny značkovací jazyk, ktorý sa dá používať na viacerých miestach. Tu môžete vidieť zoznam všetkej dostupnej CFM syntaxe."
intro: "MFC je CherryPick exkluzívny značkovací jazyk, ktorý sa dá používať na viacerých miestach. Tu môžete vidieť zoznam všetkej dostupnej MFC syntaxe."
dummy: "CherryPick rozširuje svet Fediverza"
mention: "Zmienka"
mentionDescription: "Používateľa spomeniete použítím zavináča a mena používateľa"

View File

@ -525,8 +525,8 @@ showNoteActionsOnlyHover: "แสดงการดำเนินการโ
showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต"
noHistory: "ไม่มีประวัติ"
signinHistory: "ประวัติการเข้าสู่ระบบ"
enableAdvancedMfm: "เปิดใช้งาน CFM ขั้นสูง"
enableAnimatedMfm: "เปิดการใช้งาน CFM แบบเคลื่อนไหว"
enableAdvancedMfm: "เปิดใช้งาน MFC ขั้นสูง"
enableAnimatedMfm: "เปิดการใช้งาน MFC แบบเคลื่อนไหว"
doing: "กำลังประมวลผล......"
category: "หมวดหมู่"
tags: "นามแฝง"
@ -1242,7 +1242,7 @@ overwriteContentConfirm: "แน่ใจหรือไม่ว่าต้อ
seasonalScreenEffect: "เอฟเฟกต์หน้าจอตามฤดูกาล"
decorate: "ตกแต่ง"
addMfmFunction: "เพิ่มการตกแต่ง"
enableQuickAddMfmFunction: "แสดงตัวจิ้มเลือก CFM ขั้นสูง"
enableQuickAddMfmFunction: "แสดงตัวจิ้มเลือก MFC ขั้นสูง"
bubbleGame: "เกมบับเบิ้ล"
sfx: "เสียงเอฟเฟ็กต์"
soundWillBePlayed: "จะมีการเล่นเอฟเฟกต์เสียง"
@ -1866,9 +1866,9 @@ _displayOfSensitiveMedia:
respect: "ซ่อนสื่อที่มีเนื้อหาละเอียดอ่อน"
ignore: "แสดงสื่อที่มีเนื้อหาละเอียดอ่อน"
force: "ซ่อนสื่อทั้งหมด"
_cfm:
cheatSheet: "โค้ด CFM Cheat Sheet"
intro: "CFM เป็นภาษามาร์กอัปพิเศษเฉพาะของ CherryPick ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์ CFM ที่มีอยู่ทั้งหมดได้ที่นี่นะ"
_mfc:
cheatSheet: "โค้ด MFC Cheat Sheet"
intro: "MFC เป็นภาษามาร์กอัปพิเศษเฉพาะของ CherryPick ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์ MFC ที่มีอยู่ทั้งหมดได้ที่นี่นะ"
dummy: "CherryPick ขยายโลกของ Fediverse"
mention: "กล่าวถึง"
mentionDescription: "คุณสามารถระบุผู้ใช้โดยใช้ At-Symbol และชื่อผู้ใช้ได้นะ"
@ -1931,7 +1931,7 @@ _cfm:
rotate: "หมุนหน้าจอ"
rotateDescription: "เปลี่ยนเนื้อหาตามด้วยมุมที่ระบุไว้"
plain: "เรียบง่าย"
plainDescription: "ปิดการใช้งานเอฟเฟกต์ของ CFM ทั้งหมดที่มีอยู่ในเอฟเฟกต์ CFM นี้"
plainDescription: "ปิดการใช้งานเอฟเฟกต์ของ MFC ทั้งหมดที่มีอยู่ในเอฟเฟกต์ MFC นี้"
_instanceTicker:
none: "ไม่ต้องแสดง"
remote: "แสดงสำหรับผู้ใช้ระยะไกล"
@ -2648,7 +2648,7 @@ _dataSaver:
description: "ธัมบ์เนลแสดงตัวอย่าง URL จะไม่โหลดโดยอัตโนมัติ"
_code:
title: "ไฮไลต์โค้ด"
description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน CFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้"
description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFC ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้"
_hemisphere:
N: "ซีกโลกเหนือ"
S: "ซีกโลกใต้"

View File

@ -466,8 +466,8 @@ youHaveNoGroups: "Немає груп"
joinOrCreateGroup: "Отримуйте запрошення до груп або створюйте свої власні групи."
noHistory: "Історія порожня"
signinHistory: "Історія входів"
enableAdvancedMfm: "Увімкнути розширений CFM"
enableAnimatedMfm: "Увімкнути анімований CFM"
enableAdvancedMfm: "Увімкнути розширений MFC"
enableAnimatedMfm: "Увімкнути анімований MFC"
doing: "Виконується"
category: "Категорія"
tags: "Теги"
@ -1234,9 +1234,9 @@ _aboutMisskey:
donate: "Пожертвувати Misskey"
morePatrons: "Ми дуже цінуємо підтримку багатьох інших помічників, не перелічених тут. Дякуємо! 🥰"
patrons: "Підтримали"
_cfm:
cheatSheet: " Довідка CFM"
intro: "CFM це ексклюзивна мова розмітки тексту в CherryPick, яку можна використовувати в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
_mfc:
cheatSheet: " Довідка MFC"
intro: "MFC це ексклюзивна мова розмітки тексту в CherryPick, яку можна використовувати в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
dummy: "CherryPick розширює світ Федіверсу"
mention: "Згадка"
mentionDescription: "За допомогою знака \"@\" перед ім'ям можна згадати конкретного користувача."
@ -1292,7 +1292,7 @@ _cfm:
fontDescription: "Встановлює шрифт для контенту."
rotate: "Обертати"
plain: "Звичайний"
plainDescription: "Деактивує всі ефекти CFM, що містяться в цьому ефекті CFM."
plainDescription: "Деактивує всі ефекти MFC, що містяться в цьому ефекті MFC."
_instanceTicker:
none: "Не відображати"
remote: "Відображати для віддалених користувачів"

View File

@ -474,7 +474,7 @@ native: "Mahalliy"
showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda korsatish"
noHistory: "Tarix yo'q"
signinHistory: "kirish tarixi"
enableAdvancedMfm: "CFMni faollashtirish"
enableAdvancedMfm: "MFCni faollashtirish"
doing: "Bajarilmoqda..."
category: "kategoriya"
tags: "teg"

View File

@ -501,8 +501,8 @@ joinOrCreateGroup: "Tham gia hoặc tạo một nhóm mới."
showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
noHistory: "Không có dữ liệu"
signinHistory: "Lịch sử đăng nhập"
enableAdvancedMfm: "Xem bài CFM chất lượng cao."
enableAnimatedMfm: "Xem bài CFM có chuyển động"
enableAdvancedMfm: "Xem bài MFC chất lượng cao."
enableAnimatedMfm: "Xem bài MFC có chuyển động"
doing: "Đang xử lý..."
category: "Phân loại"
tags: "Thẻ"
@ -1471,9 +1471,9 @@ _aboutMisskey:
donate: "Ủng hộ Misskey"
morePatrons: "Chúng tôi cũng trân trọng sự hỗ trợ của nhiều người đóng góp khác không được liệt kê ở đây. Cảm ơn! 🥰"
patrons: "Người ủng hộ"
_cfm:
cheatSheet: "CFM Cheatsheet"
intro: "CFM là ngôn ngữ phát triển độc quyền của CherryPick có thể được sử dụng ở nhiều nơi. Tại đây bạn có thể xem danh sách tất cả các cú pháp CFM có sẵn."
_mfc:
cheatSheet: "MFC Cheatsheet"
intro: "MFC là ngôn ngữ phát triển độc quyền của CherryPick có thể được sử dụng ở nhiều nơi. Tại đây bạn có thể xem danh sách tất cả các cú pháp MFC có sẵn."
dummy: "CherryPick mở rộng thế giới Fediverse"
mention: "Nhắc đến"
mentionDescription: "Bạn có thể nhắc đến ai đó bằng cách sử dụng @tên người dùng."
@ -1536,7 +1536,7 @@ _cfm:
rotate: "Xoay"
rotateDescription: "Xoay nội dung theo một góc cụ thể."
plain: "Đơn giản"
plainDescription: "Vô hiệu hóa mọi hiệu ứng CFM chứa trong hiệu ứng CFM này."
plainDescription: "Vô hiệu hóa mọi hiệu ứng MFC chứa trong hiệu ứng MFC này."
_instanceTicker:
none: "Không hiển thị"
remote: "Hiện cho người dùng từ máy chủ khác"

View File

@ -529,8 +529,8 @@ showNoteActionsOnlyHover: "仅在悬停时显示帖子操作"
showReactionsCount: "显示帖子的回应数"
noHistory: "没有历史记录"
signinHistory: "登录历史"
enableAdvancedMfm: "启用扩展 CFM"
enableAnimatedMfm: "启用 CFM 动画"
enableAdvancedMfm: "启用扩展 MFC"
enableAnimatedMfm: "启用 MFC 动画"
doing: "正在进行"
category: "类别"
tags: "标签"
@ -1248,7 +1248,7 @@ overwriteContentConfirm: "将覆盖现有内容。确定吗?"
seasonalScreenEffect: "符合当前季节的画面效果"
decorate: "装饰"
addMfmFunction: "添加装饰"
enableQuickAddMfmFunction: "显示高级 CFM 选择器"
enableQuickAddMfmFunction: "显示高级 MFC 选择器"
bubbleGame: "泡泡游戏"
sfx: "音效"
soundWillBePlayed: "声音将会播放"
@ -1887,9 +1887,9 @@ _displayOfSensitiveMedia:
respect: "隐藏敏感媒体"
ignore: "显示敏感媒体"
force: "隐藏所有内容"
_cfm:
cheatSheet: "CFM代码速查表"
intro: "CFM是一种在CherryPick中的各个位置使用的专用标记语言。在这里您可以看到CFM中可用的语法列表。"
_mfc:
cheatSheet: "MFC代码速查表"
intro: "MFC是一种在CherryPick中的各个位置使用的专用标记语言。在这里您可以看到MFC中可用的语法列表。"
dummy: "通过CherryPick扩展联邦宇宙的世界"
mention: "提及"
mentionDescription: "可以使用 @+用户名 来指示特定用户"
@ -2679,7 +2679,7 @@ _dataSaver:
description: "将不再加载 URL 预览缩略图。"
_code:
title: "代码高亮"
description: "如果使用了代码高亮标记,例如在 CFM 中,则在点击之前不会加载。 代码高亮要求加载每种高亮语言的定义文件,由于这些文件不再自动加载,因此有望减少数据传输量。"
description: "如果使用了代码高亮标记,例如在 MFC 中,则在点击之前不会加载。 代码高亮要求加载每种高亮语言的定义文件,由于这些文件不再自动加载,因此有望减少数据传输量。"
_hemisphere:
N: "北半球"
S: "南半球"

View File

@ -532,8 +532,8 @@ showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項"
showReactionsCount: "顯示貼文的反應數目"
noHistory: "沒有歷史紀錄"
signinHistory: "登入歷史"
enableAdvancedMfm: "啟用進階 CFM"
enableAnimatedMfm: "啟用 CFM 動畫"
enableAdvancedMfm: "啟用進階 MFC"
enableAnimatedMfm: "啟用 MFC 動畫"
doing: "正在進行"
category: "類別"
tags: "標籤"
@ -1250,8 +1250,8 @@ remainingN: "剩餘:{n}"
overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
seasonalScreenEffect: "隨季節變換畫面的呈現"
decorate: "設置頭像裝飾"
addMfmFunction: "插入 CFM 功能語法"
enableQuickAddMfmFunction: "顯示高級 CFM 選擇器"
addMfmFunction: "插入 MFC 功能語法"
enableQuickAddMfmFunction: "顯示高級 MFC 選擇器"
bubbleGame: "氣泡遊戲"
sfx: "音效"
soundWillBePlayed: "將播放音效"
@ -1893,9 +1893,9 @@ _displayOfSensitiveMedia:
respect: "隱藏敏感檔案"
ignore: "顯示敏感檔案"
force: "隱藏所有檔案"
_cfm:
cheatSheet: "CFM代碼小抄"
intro: "CFM是CherryPick專用的標記語言可以在CherryPick中的各個位置使用。 您可以這裏看到CFM可用語法列表。"
_mfc:
cheatSheet: "MFC代碼小抄"
intro: "MFC是CherryPick專用的標記語言可以在CherryPick中的各個位置使用。 您可以這裏看到MFC可用語法列表。"
dummy: "CherryPick拓展了Fediverse的世界"
mention: "提及"
mentionDescription: "透過 @+用戶名 來標示特定使用者。"
@ -2688,7 +2688,7 @@ _dataSaver:
description: "將不再自動載入網址預覽縮圖。"
_code:
title: "程式碼突出顯示"
description: "如果使用了 CFM 的程式碼突顯標記,則在點擊之前不會載入。程式碼突顯要求加載每種程式語言的突顯定義檔案,但由於這些檔案不再自動載入,因此有望減少資料流量。"
description: "如果使用了 MFC 的程式碼突顯標記,則在點擊之前不會載入。程式碼突顯要求加載每種程式語言的突顯定義檔案,但由於這些檔案不再自動載入,因此有望減少資料流量。"
_hemisphere:
N: "北半球"
S: "南半球"

View File

@ -1,6 +1,6 @@
{
"name": "cherrypick",
"version": "4.12.0",
"version": "4.12.1",
"basedMisskeyVersion": "2024.9.0",
"codename": "nasubi",
"repository": {

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class ScheduleNote1699437894737 {
name = 'ScheduleNote1699437894737'
async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "note_schedule" ("id" character varying(32) NOT NULL, "note" jsonb NOT NULL, "userId" character varying(260) NOT NULL, "scheduledAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_3a1ae2db41988f4994268218436" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_e798958c40009bf0cdef4f28b5" ON "note_schedule" ("userId") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP TABLE "note_schedule"`);
}
}

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class RemoteObjectStorage1729518620697 {
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "useRemoteObjectStorage" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageBucket" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStoragePrefix" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageBaseUrl" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageEndpoint" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageRegion" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageAccessKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageSecretKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStoragePort" integer`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageUseSSL" boolean DEFAULT true`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageUseProxy" boolean DEFAULT true`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageSetPublicRead" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageS3ForcePathStyle" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageS3ForcePathStyle";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageSetPublicRead";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageUseProxy";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageUseSSL";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStoragePort";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageSecretKey";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageAccessKey";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageRegion";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageEndpoint";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageBaseUrl";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStoragePrefix";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageBucket";`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useRemoteObjectStorage";`);
}
}

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class DropObjectStorageRemoteSetting1730162520000 {
name = 'DropObjectStorageRemoteSetting1730162520000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteS3ForcePathStyle"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteSetPublicRead"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteUseProxy"`, undefined);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteUseSSL"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemotePort"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteSecretKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteAccessKey"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteRegion"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteEndpoint"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteBaseUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemotePrefix"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRemoteBucket"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useObjectStorageRemote"`);
}
}

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class YouBlockedImageUrl1730364663000 {
name = 'YouBlockedImageUrl1730364663000'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "youBlockedImageUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "youBlockedImageUrl"`);
}
}

View File

@ -112,7 +112,7 @@
"bullmq": "5.13.2",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.2",
"cfm-js": "0.24.0-cherrypick.8",
"mfc-js": "0.24.0-cherrypick.9",
"chalk": "5.3.0",
"chalk-template": "1.1.0",
"cherrypick-js": "workspace:*",

View File

@ -19,6 +19,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { DriveService } from '@/core/DriveService.js';
const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
@ -39,6 +40,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService,
private driveService: DriveService,
) {
this.emojisCache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); // 12h
@ -68,6 +70,15 @@ export class CustomEmojiService implements OnApplicationShutdown {
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}, moderator?: MiUser): Promise<MiEmoji> {
// システムユーザーとして再アップロード
if (!data.driveFile.user?.isRoot) {
data.driveFile = await this.driveService.uploadFromUrl({
url: data.driveFile.url,
user: null,
force: true,
});
}
const emoji = await this.emojisRepository.insertOne({
id: this.idService.gen(),
updatedAt: new Date(),

View File

@ -147,7 +147,7 @@ export class DriveService {
* @param isRemote If true, file is remote file
*/
@bindThis
private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number, isRemote: boolean): Promise<MiDriveFile> {
private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number, isRemote = false): Promise<MiDriveFile> {
// thunbnail, webpublic を必要なら生成
const alts = await this.generateAlts(path, type, !file.uri);
@ -170,16 +170,34 @@ export class DriveService {
ext = '';
}
const useObjectStorageRemote = isRemote && this.meta.useObjectStorageRemote;
const objectStorageBaseUrl = useObjectStorageRemote ? this.meta.objectStorageRemoteBaseUrl : this.meta.objectStorageBaseUrl;
const objectStorageUseSSL = useObjectStorageRemote ? this.meta.objectStorageRemoteUseSSL : this.meta.objectStorageUseSSL;
const objectStorageEndpoint = useObjectStorageRemote ? this.meta.objectStorageRemoteEndpoint : this.meta.objectStorageEndpoint;
const objectStoragePort = useObjectStorageRemote ? this.meta.objectStorageRemotePort : this.meta.objectStoragePort;
const objectStorageBucket = useObjectStorageRemote ? this.meta.objectStorageRemoteBucket : this.meta.objectStorageBucket;
const objectStoragePrefix = useObjectStorageRemote ? this.meta.objectStorageRemotePrefix : this.meta.objectStoragePrefix;
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
const objectStorageBaseUrl = useRemoteObjectStorage
? this.meta.remoteObjectStorageBaseUrl
: this.meta.objectStorageBaseUrl;
const objectStorageUseSSL = useRemoteObjectStorage
? this.meta.remoteObjectStorageUseSSL
: this.meta.objectStorageUseSSL;
const objectStorageEndpoint = useRemoteObjectStorage
? this.meta.remoteObjectStorageEndpoint
: this.meta.objectStorageEndpoint;
const objectStoragePort = useRemoteObjectStorage
? this.meta.remoteObjectStoragePort
: this.meta.objectStoragePort;
const objectStorageBucket = useRemoteObjectStorage
? this.meta.remoteObjectStorageBucket
: this.meta.objectStorageBucket;
const objectStoragePrefix = useRemoteObjectStorage
? this.meta.remoteObjectStoragePrefix
: this.meta.objectStoragePrefix;
const baseUrl = objectStorageBaseUrl
?? `${ objectStorageUseSSL ? 'https' : 'http' }://${ objectStorageEndpoint }${ objectStoragePort ? `:${objectStoragePort}` : '' }/${ objectStorageBucket }`;
?? `${ objectStorageUseSSL ? 'https' : 'http' }://${ objectStorageEndpoint }${ objectStoragePort ? `:${ objectStoragePort }` : '' }/${ objectStorageBucket }`;
// for original
const key = `${objectStoragePrefix}/${randomUUID()}${ext}`;
@ -380,13 +398,19 @@ export class DriveService {
* Upload to ObjectStorage
*/
@bindThis
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, isRemote: boolean, ext?: string | null, filename?: string) {
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, isRemote = false, ext?: string | null, filename?: string) {
if (type === 'image/apng') type = 'image/png';
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
const useObjectStorageRemote = isRemote && this.meta.useObjectStorageRemote;
const objectStorageBucket = useObjectStorageRemote ? this.meta.objectStorageRemoteBucket : this.meta.objectStorageBucket;
const objectStorageSetPublicRead = useObjectStorageRemote ? this.meta.objectStorageRemoteSetPublicRead : this.meta.objectStorageSetPublicRead;
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
const objectStorageBucket = useRemoteObjectStorage
? this.meta.remoteObjectStorageBucket
: this.meta.objectStorageBucket;
const objectStorageSetPublicRead = useRemoteObjectStorage
? this.meta.remoteObjectStorageSetPublicRead
: this.meta.objectStorageSetPublicRead;
const params = {
Bucket: objectStorageBucket,
@ -754,7 +778,7 @@ export class DriveService {
}
@bindThis
public async deleteFileSync(file: MiDriveFile, isExpired = false, isRemote: boolean, deleter?: MiUser) {
public async deleteFileSync(file: MiDriveFile, isExpired = false, isRemote = false, deleter?: MiUser) {
if (file.storedInternal) {
this.internalStorageService.del(file.accessKey!);
@ -829,9 +853,12 @@ export class DriveService {
}
@bindThis
public async deleteObjectStorageFile(key: string, isRemote: boolean) {
const useObjectStorageRemote = isRemote && this.meta.useObjectStorageRemote;
const objectStorageBucket = useObjectStorageRemote ? this.meta.objectStorageRemoteBucket : this.meta.objectStorageBucket;
public async deleteObjectStorageFile(key: string, isRemote = false) {
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
const objectStorageBucket = useRemoteObjectStorage
? this.meta.remoteObjectStorageBucket
: this.meta.objectStorageBucket;
try {
const param = {
Bucket: objectStorageBucket,

View File

@ -14,7 +14,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import type { DefaultTreeAdapterMap } from 'parse5';
import type * as mfm from 'cfm-js';
import type * as mfm from 'mfc-js';
const treeAdapter = parse5.defaultTreeAdapter;
type Node = DefaultTreeAdapterMap['node'];

View File

@ -4,7 +4,7 @@
*/
import { setImmediate } from 'node:timers/promises';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';

View File

@ -7,7 +7,7 @@ import { setImmediate } from 'node:timers/promises';
import util from 'util';
import { In, DataSource } from 'typeorm';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
import type { NotesRepository, UsersRepository } from '@/models/_.js';
@ -218,7 +218,7 @@ export class NoteUpdateService implements OnApplicationShutdown {
this.globalEventService.publishNoteStream(note.id, 'updated', { cw: note.cw, text: note.text });
//#region AP deliver
if (this.userEntityService.isLocalUser(user)) {
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
await (async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error

View File

@ -18,6 +18,7 @@ import {
UserWebhookDeliverJobData,
SystemWebhookDeliverJobData,
ScheduledNoteDeleteJobData,
ScheduleNotePostJobData,
} from '../queue/types.js';
import type { Provider } from '@nestjs/common';
@ -31,6 +32,7 @@ export type ObjectStorageQueue = Bull.Queue;
export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
export type ScheduledNoteDeleteQueue = Bull.Queue<ScheduledNoteDeleteJobData>;
export type ScheduleNotePostQueue = Bull.Queue<ScheduleNotePostJobData>;
const $system: Provider = {
provide: 'queue:system',
@ -92,6 +94,12 @@ const $scheduledNoteDelete: Provider = {
inject: [DI.config, DI.redisForJobQueue],
};
const $scheduleNotePost: Provider = {
provide: 'queue:scheduleNotePost',
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.SCHEDULE_NOTE_POST, baseQueueOptions(config, QUEUE.SCHEDULE_NOTE_POST, redisForJobQueue)),
inject: [DI.config, DI.redisForJobQueue],
};
@Module({
imports: [
],
@ -106,6 +114,7 @@ const $scheduledNoteDelete: Provider = {
$userWebhookDeliver,
$systemWebhookDeliver,
$scheduledNoteDelete,
$scheduleNotePost,
],
exports: [
$system,
@ -118,6 +127,7 @@ const $scheduledNoteDelete: Provider = {
$userWebhookDeliver,
$systemWebhookDeliver,
$scheduledNoteDelete,
$scheduleNotePost,
],
})
export class QueueModule implements OnApplicationShutdown {
@ -132,6 +142,7 @@ export class QueueModule implements OnApplicationShutdown {
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduledNoteDelete') public scheduledNoteDeleteQueue: ScheduledNoteDeleteQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
) {}
public async dispose(): Promise<void> {
@ -149,6 +160,7 @@ export class QueueModule implements OnApplicationShutdown {
this.userWebhookDeliverQueue.close(),
this.systemWebhookDeliverQueue.close(),
this.scheduledNoteDeleteQueue.close(),
this.scheduleNotePostQueue.close(),
]);
}

View File

@ -34,6 +34,7 @@ import type {
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue,
ScheduledNoteDeleteQueue,
ScheduleNotePostQueue,
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
@ -54,6 +55,7 @@ export class QueueService {
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduledNoteDelete') public scheduledNoteDeleteQueue: ScheduledNoteDeleteQueue,
@Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue,
) {
this.systemQueue.add('tickCharts', {
}, {

View File

@ -36,6 +36,7 @@ export type RolePolicies = {
ltlAvailable: boolean;
canPublicNote: boolean;
canEditNote: boolean;
scheduleNoteMax: number;
mentionLimit: number;
canInvite: boolean;
inviteLimit: number;
@ -72,6 +73,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
ltlAvailable: true,
canPublicNote: true,
canEditNote: true,
scheduleNoteMax: 5,
mentionLimit: 20,
canInvite: false,
inviteLimit: 0,
@ -379,6 +381,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
scheduleNoteMax: calc('scheduleNoteMax', vs => Math.max(...vs)),
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),

View File

@ -19,26 +19,41 @@ import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/c
export class S3Service {
constructor(
private httpRequestService: HttpRequestService,
) {
}
) {}
@bindThis
public getS3Client(meta: MiMeta, isRemote: boolean): S3Client {
const useObjectStorageRemote = isRemote && meta.useObjectStorageRemote;
public getS3Client(meta: MiMeta, isRemote = false): S3Client {
const useRemoteObjectStorage = isRemote && meta.useRemoteObjectStorage;
const objectStorageEndpoint = useObjectStorageRemote ? meta.objectStorageRemoteEndpoint : meta.objectStorageEndpoint;
const objectStorageUseSSL = useObjectStorageRemote ? meta.objectStorageRemoteUseSSL : meta.objectStorageUseSSL;
const objectStorageUseProxy = useObjectStorageRemote ? meta.objectStorageRemoteUseProxy : meta.objectStorageUseProxy;
const objectStorageAccessKey = useObjectStorageRemote ? meta.objectStorageRemoteAccessKey : meta.objectStorageAccessKey;
const objectStorageSecretKey = useObjectStorageRemote ? meta.objectStorageRemoteSecretKey : meta.objectStorageSecretKey;
const objectStorageRegion = useObjectStorageRemote ? meta.objectStorageRemoteRegion : meta.objectStorageRegion;
const objectStorageS3ForcePathStyle = useObjectStorageRemote ? meta.objectStorageRemoteS3ForcePathStyle : meta.objectStorageS3ForcePathStyle;
const objectStorageEndpoint = useRemoteObjectStorage
? meta.remoteObjectStorageEndpoint
: meta.objectStorageEndpoint;
const objectStorageUseSSL = useRemoteObjectStorage
? meta.remoteObjectStorageUseSSL
: meta.objectStorageUseSSL;
const objectStorageAccessKey = useRemoteObjectStorage
? meta.remoteObjectStorageAccessKey
: meta.objectStorageAccessKey;
const objectStorageSecretKey = useRemoteObjectStorage
? meta.remoteObjectStorageSecretKey
: meta.objectStorageSecretKey;
const objectStorageRegion = useRemoteObjectStorage
? meta.remoteObjectStorageRegion
: meta.objectStorageRegion;
const objectStorageS3ForcePathStyle = useRemoteObjectStorage
? meta.remoteObjectStorageS3ForcePathStyle
: meta.objectStorageS3ForcePathStyle;
const u = objectStorageEndpoint
? `${objectStorageUseSSL ? 'https' : 'http'}://${objectStorageEndpoint}`
: `${objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
? `${ objectStorageUseSSL ? 'https' : 'http' }://${ objectStorageEndpoint }`
: `${ objectStorageUseSSL ? 'https' : 'http' }://example.com`; // dummy url to select http(s) agent
const agent = this.httpRequestService.getAgentByUrl(new URL(u), !objectStorageUseProxy);
const agent = this.httpRequestService.getAgentByUrl(new URL(u), !objectStorageUseSSL);
const handlerOption: NodeHttpHandlerOptions = {};
if (objectStorageUseSSL) {
handlerOption.httpsAgent = agent as https.Agent;
@ -52,7 +67,7 @@ export class S3Service {
accessKeyId: objectStorageAccessKey,
secretAccessKey: objectStorageSecretKey,
} : undefined,
region: objectStorageRegion ? objectStorageRegion : undefined, // empty string is converted to undefined
region: objectStorageRegion || undefined, // 空文字列もundefinedにするため ?? は使わない
tls: objectStorageUseSSL,
forcePathStyle: objectStorageEndpoint ? objectStorageS3ForcePathStyle : false, // AWS with endPoint omitted
requestHandler: new NodeHttpHandler(handlerOption),
@ -60,7 +75,7 @@ export class S3Service {
}
@bindThis
public async upload(meta: MiMeta, input: PutObjectCommandInput, isRemote: boolean) {
public async upload(meta: MiMeta, input: PutObjectCommandInput, isRemote = false) {
const client = this.getS3Client(meta, isRemote);
return new Upload({
client,
@ -72,7 +87,7 @@ export class S3Service {
}
@bindThis
public delete(meta: MiMeta, input: DeleteObjectCommandInput, isRemote: boolean) {
public delete(meta: MiMeta, input: DeleteObjectCommandInput, isRemote = false) {
const client = this.getS3Client(meta, isRemote);
return client.send(new DeleteObjectCommand(input));
}

View File

@ -4,7 +4,7 @@
*/
import { Injectable } from '@nestjs/common';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { MfmService } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';

View File

@ -6,7 +6,7 @@
import { createPublicKey, randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';

View File

@ -104,6 +104,7 @@ export class MetaEntityService {
infoImageUrl: instance.infoImageUrl,
serverErrorImageUrl: instance.serverErrorImageUrl,
notFoundImageUrl: instance.notFoundImageUrl,
youBlockedImageUrl: instance.youBlockedImageUrl,
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,

View File

@ -142,6 +142,27 @@ export class NotificationEntityService implements OnModuleInit {
note: noteIfNeed,
users,
});
} else if (notification.type === 'note:grouped') {
const users = (await Promise.all(notification.notifierIds.map(notifier => {
const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(notifier) : null;
if (packedUser) {
return packedUser;
}
return this.userEntityService.pack(notifier, { id: meId });
}))).filter(x => x != null);
// if all users have been deleted, don't show this notification
if (users.length === 0) {
return null;
}
return await awaitAll({
id: notification.id,
createdAt: new Date(notification.createdAt).toISOString(),
type: notification.type,
noteIds: notification.noteIds,
users,
});
}
// #endregion
@ -213,6 +234,7 @@ export class NotificationEntityService implements OnModuleInit {
if ('notifierId' in notification) userIds.push(notification.notifierId);
if (notification.type === 'reaction:grouped') userIds.push(...notification.reactions.map(x => x.userId));
if (notification.type === 'renote:grouped') userIds.push(...notification.userIds);
if (notification.type === 'note:grouped') userIds.push(...notification.notifierIds);
}
const users = userIds.length > 0 ? await this.usersRepository.find({
where: { id: In(userIds) },

View File

@ -92,5 +92,6 @@ export const DI = {
userMemosRepository: Symbol('userMemosRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
noteScheduleRepository: Symbol('noteScheduleRepository'),
//#endregion
};

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { unique } from '@/misc/prelude/array.js';
export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { unique } from '@/misc/prelude/array.js';
export function extractHashtags(nodes: mfm.MfmNode[]): string[] {

View File

@ -5,7 +5,7 @@
// test is located in test/extract-mentions
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
// TODO: 重複を削除

View File

@ -157,6 +157,12 @@ export class MiMeta {
})
public infoImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public youBlockedImageUrl: string | null;
@Column('boolean', {
default: false,
})
@ -510,74 +516,74 @@ export class MiMeta {
@Column('boolean', {
default: false,
})
public useObjectStorageRemote: boolean;
public useRemoteObjectStorage: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteBucket: string | null;
public remoteObjectStorageBucket: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemotePrefix: string | null;
public remoteObjectStoragePrefix: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteBaseUrl: string | null;
public remoteObjectStorageBaseUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteEndpoint: string | null;
public remoteObjectStorageEndpoint: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteRegion: string | null;
public remoteObjectStorageRegion: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteAccessKey: string | null;
public remoteObjectStorageAccessKey: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public objectStorageRemoteSecretKey: string | null;
public remoteObjectStorageSecretKey: string | null;
@Column('integer', {
nullable: true,
})
public objectStorageRemotePort: number | null;
public remoteObjectStoragePort: number | null;
@Column('boolean', {
default: true,
})
public objectStorageRemoteUseSSL: boolean;
public remoteObjectStorageUseSSL: boolean;
@Column('boolean', {
default: true,
})
public objectStorageRemoteUseProxy: boolean;
public remoteObjectStorageUseProxy: boolean;
@Column('boolean', {
default: false,
})
public objectStorageRemoteSetPublicRead: boolean;
public remoteObjectStorageSetPublicRead: boolean;
@Column('boolean', {
default: true,
})
public objectStorageRemoteS3ForcePathStyle: boolean;
public remoteObjectStorageS3ForcePathStyle: boolean;
@Column('boolean', {
default: false,

View File

@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Entity, Index, Column, PrimaryColumn } from 'typeorm';
import { MiNote } from '@/models/Note.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
import { MiChannel } from './Channel.js';
import { EventSchema } from './Event.js';
import type { MiDriveFile } from './DriveFile.js';
type MinimumUser = {
id: MiUser['id'];
host: MiUser['host'];
username: MiUser['username'];
uri: MiUser['uri'];
};
export type MiScheduleNoteType={
/** Date.toISOString() */
createdAt: string;
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUsers: MinimumUser[];
channel?: MiChannel['id'];
poll: {
multiple: boolean;
choices: string[];
/** Date.toISOString() */
expiresAt: string | null
} | undefined;
renote?: MiNote['id'];
localOnly: boolean;
cw?: string | null;
reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
files: MiDriveFile['id'][];
text?: string | null;
reply?: MiNote['id'];
event?: {
/** Date.toISOString() */
start: string;
/** Date.toISOString() */
end: string | null;
title: string;
metadata: EventSchema;
} | null;
disableRightClick: boolean,
apMentions?: MinimumUser[] | null;
apHashtags?: string[] | null;
apEmojis?: string[] | null;
}
@Entity('note_schedule')
export class MiNoteSchedule {
@PrimaryColumn(id())
public id: string;
@Column('jsonb')
public note: MiScheduleNoteType;
@Index()
@Column('varchar', {
length: 260,
})
public userId: MiUser['id'];
@Column('timestamp with time zone')
public scheduledAt: Date;
}

View File

@ -140,4 +140,10 @@ export type MiGroupedNotification = MiNotification | {
createdAt: string;
noteId: MiNote['id'];
userIds: string[];
} | {
type: 'note:grouped';
id: string;
createdAt: string;
notifierIds: MiUser['id'][];
noteIds: string[];
};

View File

@ -44,6 +44,7 @@ import {
MiNote,
MiNoteFavorite,
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
MiNoteUnread,
MiPage,
@ -537,6 +538,12 @@ const $abuseReportResolversRepository: Provider = {
inject: [DI.db],
};
const $noteScheduleRepository: Provider = {
provide: DI.noteScheduleRepository,
useFactory: (db: DataSource) => db.getRepository(MiNoteSchedule).extend(miRepository as MiRepository<MiNoteSchedule>),
inject: [DI.db],
};
@Module({
imports: [],
providers: [
@ -615,6 +622,7 @@ const $abuseReportResolversRepository: Provider = {
$abuseReportResolversRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
$noteScheduleRepository,
],
exports: [
$usersRepository,
@ -692,6 +700,7 @@ const $abuseReportResolversRepository: Provider = {
$abuseReportResolversRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
$noteScheduleRepository,
],
})
export class RepositoryModule {

View File

@ -85,6 +85,7 @@ import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { MiNoteSchedule } from '@/models/NoteSchedule.js';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
export interface MiRepository<T extends ObjectLiteral> {
@ -166,6 +167,7 @@ export {
MiNote,
MiNoteFavorite,
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
MiNoteUnread,
MiPage,
@ -283,3 +285,4 @@ export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlas
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
export type NoteScheduleRepository = Repository<MiNoteSchedule>;

View File

@ -144,6 +144,10 @@ export const packedMetaLiteSchema = {
type: 'string',
optional: false, nullable: true,
},
youBlockedImageUrl: {
type: 'string',
optional: false, nullable: true,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,

View File

@ -300,6 +300,10 @@ export const packedRolePoliciesSchema = {
type: 'boolean',
optional: false, nullable: false,
},
scheduleNoteMax: {
type: 'integer',
optional: false, nullable: false,
},
},
} as const;

View File

@ -84,6 +84,7 @@ import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { MiNoteSchedule } from '@/models/NoteSchedule.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
@ -165,6 +166,7 @@ export const entities = [
MiNote,
MiNoteFavorite,
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
MiNoteUnread,
MiPage,

View File

@ -42,6 +42,7 @@ import { AggregateRetentionProcessorService } from './processors/AggregateRetent
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
import { ScheduledNoteDeleteProcessorService } from './processors/ScheduledNoteDeleteProcessorService.js';
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
@Module({
imports: [
@ -85,6 +86,7 @@ import { ScheduledNoteDeleteProcessorService } from './processors/ScheduledNoteD
AggregateRetentionProcessorService,
QueueProcessorService,
ScheduledNoteDeleteProcessorService,
ScheduleNotePostProcessorService,
],
exports: [
QueueProcessorService,

View File

@ -45,6 +45,7 @@ import { BakeBufferedReactionsProcessorService } from './processors/BakeBuffered
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { ScheduledNoteDeleteProcessorService } from './processors/ScheduledNoteDeleteProcessorService.js';
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QUEUE, baseQueueOptions } from './const.js';
@ -87,6 +88,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private objectStorageQueueWorker: Bull.Worker;
private endedPollNotificationQueueWorker: Bull.Worker;
private scheduledNoteDeleteQueueWorker: Bull.Worker;
private schedulerNotePostQueueWorker: Bull.Worker;
constructor(
@Inject(DI.config)
@ -130,6 +132,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
private cleanProcessorService: CleanProcessorService,
private scheduledNoteDeleteProcessorService: ScheduledNoteDeleteProcessorService,
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
) {
this.logger = this.queueLoggerService.logger;
@ -529,6 +532,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
});
}
//#endregion
//#region schedule note post
{
this.schedulerNotePostQueueWorker = new Bull.Worker(QUEUE.SCHEDULE_NOTE_POST, (job) => this.scheduleNotePostProcessorService.process(job), {
...baseQueueOptions(this.config, QUEUE.SCHEDULE_NOTE_POST, this.redisForJobQueue),
autorun: false,
});
}
//#endregion
}
@bindThis
@ -544,6 +556,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.objectStorageQueueWorker.run(),
this.endedPollNotificationQueueWorker.run(),
this.scheduledNoteDeleteQueueWorker.run(),
this.schedulerNotePostQueueWorker.run(),
]);
}
@ -560,6 +573,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.objectStorageQueueWorker.close(),
this.endedPollNotificationQueueWorker.close(),
this.scheduledNoteDeleteQueueWorker.close(),
this.schedulerNotePostQueueWorker.close(),
]);
}

View File

@ -18,6 +18,7 @@ export const QUEUE = {
USER_WEBHOOK_DELIVER: 'userWebhookDeliver',
SYSTEM_WEBHOOK_DELIVER: 'systemWebhookDeliver',
SCHEDULED_NOTE_DELETE: 'scheduledNoteDelete',
SCHEDULE_NOTE_POST: 'scheduleNotePost',
};
export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE], redisConnection: Redis.Redis): Bull.QueueOptions {

View File

@ -108,7 +108,7 @@ export class DeleteAccountProcessorService {
cursor = files.at(-1)?.id ?? null;
for (const file of files) {
await this.driveService.deleteFileSync(file, false, isRemote);
await this.driveService.deleteFileSync(file, undefined, isRemote);
}
}

View File

@ -38,6 +38,7 @@ export class DeleteDriveFilesProcessorService {
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
const isRemote = user ? this.userEntityService.isRemoteUser(user) : false;
if (user == null) {
return;
}
@ -65,8 +66,7 @@ export class DeleteDriveFilesProcessorService {
cursor = files.at(-1)?.id ?? null;
for (const file of files) {
const isRemote = file.user ? this.userEntityService.isRemoteUser(file.user) : false;
await this.driveService.deleteFileSync(file, false, isRemote);
await this.driveService.deleteFileSync(file, undefined, isRemote);
deletedCount++;
}

View File

@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import type { ChannelsRepository, DriveFilesRepository, MiDriveFile, NoteScheduleRepository, NotesRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { ScheduleNotePostJobData } from '../types.js';
@Injectable()
export class ScheduleNotePostProcessorService {
private logger: Logger;
constructor(
@Inject(DI.noteScheduleRepository)
private noteScheduleRepository: NoteScheduleRepository,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
private noteCreateService: NoteCreateService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
}
@bindThis
public async process(job: Bull.Job<ScheduleNotePostJobData>): Promise<void> {
this.noteScheduleRepository.findOneBy({ id: job.data.scheduleNoteId }).then(async (data) => {
if (!data) {
this.logger.warn(`Schedule note ${job.data.scheduleNoteId} not found`);
} else {
const me = await this.usersRepository.findOneBy({ id: data.userId });
const note = data.note;
//idの形式でキューに積んであったのをDBから取り寄せる
const reply = note.reply ? await this.notesRepository.findOneBy({ id: note.reply }) : undefined;
const renote = note.reply ? await this.notesRepository.findOneBy({ id: note.renote }) : undefined;
const channel = note.channel ? await this.channelsRepository.findOneBy({ id: note.channel, isArchived: false }) : undefined;
let files: MiDriveFile[] = [];
const fileIds = note.files ?? null;
if (fileIds != null && fileIds.length > 0 && me) {
files = await this.driveFilesRepository.createQueryBuilder('file')
.where('file.userId = :userId AND file.id IN (:...fileIds)', {
userId: me.id,
fileIds,
})
.orderBy('array_position(ARRAY[:...fileIds], "id"::text)')
.setParameters({ fileIds })
.getMany();
}
if (
!data.userId ||
!me ||
(note.reply && !reply) ||
(note.renote && !renote) ||
(note.channel && !channel) ||
(note.files.length !== files.length)
) {
//キューに積んだときは有った物が消滅してたら予約投稿をキャンセルする
this.logger.warn('cancel schedule note');
await this.noteScheduleRepository.remove(data);
return;
}
await this.noteCreateService.create(me, {
...note,
createdAt: new Date(note.createdAt), //typeORMのjsonbで何故かstringにされるから戻す
files,
poll: note.poll ? {
choices: note.poll.choices,
multiple: note.poll.multiple,
expiresAt: note.poll.expiresAt ? new Date(note.poll.expiresAt) : null,
} : undefined,
event: note.event ? {
start: new Date(note.event.start),
end: note.event.end ? new Date(note.event.end) : null,
title: note.event.title,
metadata: note.event.metadata,
} : undefined,
reply,
renote,
channel,
});
await this.noteScheduleRepository.remove(data);
}
});
}
}

View File

@ -137,3 +137,7 @@ export type ThinUser = {
export type ScheduledNoteDeleteJobData = {
noteId: MiNote['id'];
};
export type ScheduleNotePostJobData = {
scheduleNoteId: MiNote['id'];
}

View File

@ -312,6 +312,9 @@ import * as ep___notes_reactions_create from './endpoints/notes/reactions/create
import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
import * as ep___notes_renotes from './endpoints/notes/renotes.js';
import * as ep___notes_replies from './endpoints/notes/replies.js';
import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js';
import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
import * as ep___notes_search from './endpoints/notes/search.js';
import * as ep___notes_show from './endpoints/notes/show.js';
@ -728,6 +731,9 @@ const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create'
const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default };
const $notes_renotes: Provider = { provide: 'ep:notes/renotes', useClass: ep___notes_renotes.default };
const $notes_replies: Provider = { provide: 'ep:notes/replies', useClass: ep___notes_replies.default };
const $notes_schedule_create: Provider = { provide: 'ep:notes/schedule/create', useClass: ep___notes_schedule_create.default };
const $notes_schedule_delete: Provider = { provide: 'ep:notes/schedule/delete', useClass: ep___notes_schedule_delete.default };
const $notes_schedule_list: Provider = { provide: 'ep:notes/schedule/list', useClass: ep___notes_schedule_list.default };
const $notes_searchByTag: Provider = { provide: 'ep:notes/search-by-tag', useClass: ep___notes_searchByTag.default };
const $notes_search: Provider = { provide: 'ep:notes/search', useClass: ep___notes_search.default };
const $notes_show: Provider = { provide: 'ep:notes/show', useClass: ep___notes_show.default };
@ -1149,6 +1155,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_reactions_delete,
$notes_renotes,
$notes_replies,
$notes_schedule_create,
$notes_schedule_delete,
$notes_schedule_list,
$notes_searchByTag,
$notes_search,
$notes_show,
@ -1562,6 +1571,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_reactions_delete,
$notes_renotes,
$notes_replies,
$notes_schedule_create,
$notes_schedule_delete,
$notes_schedule_list,
$notes_searchByTag,
$notes_search,
$notes_show,

View File

@ -317,6 +317,9 @@ import * as ep___notes_reactions_create from './endpoints/notes/reactions/create
import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
import * as ep___notes_renotes from './endpoints/notes/renotes.js';
import * as ep___notes_replies from './endpoints/notes/replies.js';
import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js';
import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
import * as ep___notes_search from './endpoints/notes/search.js';
import * as ep___notes_show from './endpoints/notes/show.js';
@ -731,6 +734,9 @@ const eps = [
['notes/reactions/delete', ep___notes_reactions_delete],
['notes/renotes', ep___notes_renotes],
['notes/replies', ep___notes_replies],
['notes/schedule/create', ep___notes_schedule_create],
['notes/schedule/delete', ep___notes_schedule_delete],
['notes/schedule/list', ep___notes_schedule_list],
['notes/search-by-tag', ep___notes_searchByTag],
['notes/search', ep___notes_search],
['notes/show', ep___notes_show],

View File

@ -94,6 +94,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
youBlockedImageUrl: {
type: 'string',
optional: false, nullable: true,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
@ -305,53 +309,53 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
useObjectStorageRemote: {
useRemoteObjectStorage: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: false,
},
objectStorageRemoteBaseUrl: {
remoteObjectStorageBaseUrl: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteBucket: {
remoteObjectStorageBucket: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemotePrefix: {
remoteObjectStoragePrefix: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteEndpoint: {
remoteObjectStorageEndpoint: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteRegion: {
remoteObjectStorageRegion: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemotePort: {
remoteObjectStoragePort: {
type: 'number',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteAccessKey: {
remoteObjectStorageAccessKey: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteSecretKey: {
remoteObjectStorageSecretKey: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
},
objectStorageRemoteUseSSL: {
remoteObjectStorageUseSSL: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: false,
},
objectStorageRemoteUseProxy: {
remoteObjectStorageUseProxy: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: false,
},
objectStorageRemoteSetPublicRead: {
remoteObjectStorageSetPublicRead: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: false,
},
enableIpLogging: {
type: 'boolean',
@ -489,7 +493,7 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
objectStorageRemoteS3ForcePathStyle: {
remoteObjectStorageS3ForcePathStyle: {
type: 'boolean',
optional: false, nullable: false,
},
@ -654,6 +658,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
serverErrorImageUrl: instance.serverErrorImageUrl,
notFoundImageUrl: instance.notFoundImageUrl,
infoImageUrl: instance.infoImageUrl,
youBlockedImageUrl: instance.youBlockedImageUrl,
iconUrl: instance.iconUrl,
app192IconUrl: instance.app192IconUrl,
app512IconUrl: instance.app512IconUrl,
@ -707,19 +712,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
objectStorageUseProxy: instance.objectStorageUseProxy,
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
useObjectStorageRemote: instance.useObjectStorageRemote,
objectStorageRemoteBaseUrl: instance.objectStorageRemoteBaseUrl,
objectStorageRemoteBucket: instance.objectStorageRemoteBucket,
objectStorageRemotePrefix: instance.objectStorageRemotePrefix,
objectStorageRemoteEndpoint: instance.objectStorageRemoteEndpoint,
objectStorageRemoteRegion: instance.objectStorageRemoteRegion,
objectStorageRemotePort: instance.objectStorageRemotePort,
objectStorageRemoteAccessKey: instance.objectStorageRemoteAccessKey,
objectStorageRemoteSecretKey: instance.objectStorageRemoteSecretKey,
objectStorageRemoteUseSSL: instance.objectStorageRemoteUseSSL,
objectStorageRemoteUseProxy: instance.objectStorageRemoteUseProxy,
objectStorageRemoteSetPublicRead: instance.objectStorageRemoteSetPublicRead,
objectStorageRemoteS3ForcePathStyle: instance.objectStorageRemoteS3ForcePathStyle,
useRemoteObjectStorage: instance.useRemoteObjectStorage,
remoteObjectStorageBaseUrl: instance.remoteObjectStorageBaseUrl,
remoteObjectStorageBucket: instance.remoteObjectStorageBucket,
remoteObjectStoragePrefix: instance.remoteObjectStoragePrefix,
remoteObjectStorageEndpoint: instance.remoteObjectStorageEndpoint,
remoteObjectStorageRegion: instance.remoteObjectStorageRegion,
remoteObjectStoragePort: instance.remoteObjectStoragePort,
remoteObjectStorageAccessKey: instance.remoteObjectStorageAccessKey,
remoteObjectStorageSecretKey: instance.remoteObjectStorageSecretKey,
remoteObjectStorageUseSSL: instance.remoteObjectStorageUseSSL,
remoteObjectStorageUseProxy: instance.remoteObjectStorageUseProxy,
remoteObjectStorageSetPublicRead: instance.remoteObjectStorageSetPublicRead,
remoteObjectStorageS3ForcePathStyle: instance.remoteObjectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro,
ctav3SaKey: instance.ctav3SaKey,

View File

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue, ScheduleNotePostQueue } from '@/core/QueueModule.js';
export const meta = {
tags: ['admin'],
@ -55,6 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
) {
super(meta, paramDef, async (ps, me) => {
const deliverJobCounts = await this.deliverQueue.getJobCounts();

View File

@ -54,6 +54,7 @@ export const paramDef = {
serverErrorImageUrl: { type: 'string', nullable: true },
infoImageUrl: { type: 'string', nullable: true },
notFoundImageUrl: { type: 'string', nullable: true },
youBlockedImageUrl: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true },
app192IconUrl: { type: 'string', nullable: true },
app512IconUrl: { type: 'string', nullable: true },
@ -129,19 +130,19 @@ export const paramDef = {
objectStorageUseProxy: { type: 'boolean' },
objectStorageSetPublicRead: { type: 'boolean' },
objectStorageS3ForcePathStyle: { type: 'boolean' },
useObjectStorageRemote: { type: 'boolean' },
objectStorageRemoteBaseUrl: { type: 'string', nullable: true },
objectStorageRemoteBucket: { type: 'string', nullable: true },
objectStorageRemotePrefix: { type: 'string', nullable: true },
objectStorageRemoteEndpoint: { type: 'string', nullable: true },
objectStorageRemoteRegion: { type: 'string', nullable: true },
objectStorageRemotePort: { type: 'integer', nullable: true },
objectStorageRemoteAccessKey: { type: 'string', nullable: true },
objectStorageRemoteSecretKey: { type: 'string', nullable: true },
objectStorageRemoteUseSSL: { type: 'boolean' },
objectStorageRemoteUseProxy: { type: 'boolean' },
objectStorageRemoteSetPublicRead: { type: 'boolean' },
objectStorageRemoteS3ForcePathStyle: { type: 'boolean' },
useRemoteObjectStorage: { type: 'boolean' },
remoteObjectStorageBaseUrl: { type: 'string', nullable: true },
remoteObjectStorageBucket: { type: 'string', nullable: true },
remoteObjectStoragePrefix: { type: 'string', nullable: true },
remoteObjectStorageEndpoint: { type: 'string', nullable: true },
remoteObjectStorageRegion: { type: 'string', nullable: true },
remoteObjectStoragePort: { type: 'integer', nullable: true },
remoteObjectStorageAccessKey: { type: 'string', nullable: true },
remoteObjectStorageSecretKey: { type: 'string', nullable: true },
remoteObjectStorageUseSSL: { type: 'boolean' },
remoteObjectStorageUseProxy: { type: 'boolean' },
remoteObjectStorageSetPublicRead: { type: 'boolean' },
remoteObjectStorageS3ForcePathStyle: { type: 'boolean' },
enableIpLogging: { type: 'boolean' },
enableActiveEmailValidation: { type: 'boolean' },
enableVerifymailApi: { type: 'boolean' },
@ -221,7 +222,7 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private moduleRef: ModuleRef,
private moduleRef: ModuleRef,
private metaService: MetaService,
private moderationLogService: ModerationLogService,
) {
@ -302,6 +303,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.notFoundImageUrl = ps.notFoundImageUrl;
}
if (ps.youBlockedImageUrl !== undefined) {
set.youBlockedImageUrl = ps.youBlockedImageUrl;
}
if (ps.backgroundImageUrl !== undefined) {
set.backgroundImageUrl = ps.backgroundImageUrl;
}
@ -542,56 +547,56 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
}
if (ps.useObjectStorageRemote !== undefined) {
set.useObjectStorageRemote = ps.useObjectStorageRemote;
if (ps.useRemoteObjectStorage !== undefined) {
set.useRemoteObjectStorage = ps.useRemoteObjectStorage;
}
if (ps.objectStorageRemoteBaseUrl !== undefined) {
set.objectStorageRemoteBaseUrl = ps.objectStorageRemoteBaseUrl;
if (ps.remoteObjectStorageBaseUrl !== undefined) {
set.remoteObjectStorageBaseUrl = ps.remoteObjectStorageBaseUrl;
}
if (ps.objectStorageRemoteBucket !== undefined) {
set.objectStorageRemoteBucket = ps.objectStorageRemoteBucket;
if (ps.remoteObjectStorageBucket !== undefined) {
set.remoteObjectStorageBucket = ps.remoteObjectStorageBucket;
}
if (ps.objectStorageRemotePrefix !== undefined) {
set.objectStorageRemotePrefix = ps.objectStorageRemotePrefix;
if (ps.remoteObjectStoragePrefix !== undefined) {
set.remoteObjectStoragePrefix = ps.remoteObjectStoragePrefix;
}
if (ps.objectStorageRemoteEndpoint !== undefined) {
set.objectStorageRemoteEndpoint = ps.objectStorageRemoteEndpoint;
if (ps.remoteObjectStorageEndpoint !== undefined) {
set.remoteObjectStorageEndpoint = ps.remoteObjectStorageEndpoint;
}
if (ps.objectStorageRemoteRegion !== undefined) {
set.objectStorageRemoteRegion = ps.objectStorageRemoteRegion;
if (ps.remoteObjectStorageRegion !== undefined) {
set.remoteObjectStorageRegion = ps.remoteObjectStorageRegion;
}
if (ps.objectStorageRemotePort !== undefined) {
set.objectStorageRemotePort = ps.objectStorageRemotePort;
if (ps.remoteObjectStoragePort !== undefined) {
set.remoteObjectStoragePort = ps.remoteObjectStoragePort;
}
if (ps.objectStorageRemoteAccessKey !== undefined) {
set.objectStorageRemoteAccessKey = ps.objectStorageRemoteAccessKey;
if (ps.remoteObjectStorageAccessKey !== undefined) {
set.remoteObjectStorageAccessKey = ps.remoteObjectStorageAccessKey;
}
if (ps.objectStorageRemoteSecretKey !== undefined) {
set.objectStorageRemoteSecretKey = ps.objectStorageRemoteSecretKey;
if (ps.remoteObjectStorageSecretKey !== undefined) {
set.remoteObjectStorageSecretKey = ps.remoteObjectStorageSecretKey;
}
if (ps.objectStorageRemoteUseSSL !== undefined) {
set.objectStorageRemoteUseSSL = ps.objectStorageRemoteUseSSL;
if (ps.remoteObjectStorageUseSSL !== undefined) {
set.remoteObjectStorageUseSSL = ps.remoteObjectStorageUseSSL;
}
if (ps.objectStorageRemoteUseProxy !== undefined) {
set.objectStorageRemoteUseProxy = ps.objectStorageRemoteUseProxy;
if (ps.remoteObjectStorageUseProxy !== undefined) {
set.remoteObjectStorageUseProxy = ps.remoteObjectStorageUseProxy;
}
if (ps.objectStorageRemoteSetPublicRead !== undefined) {
set.objectStorageRemoteSetPublicRead = ps.objectStorageRemoteSetPublicRead;
if (ps.remoteObjectStorageSetPublicRead !== undefined) {
set.remoteObjectStorageSetPublicRead = ps.remoteObjectStorageSetPublicRead;
}
if (ps.objectStorageRemoteS3ForcePathStyle !== undefined) {
set.objectStorageRemoteS3ForcePathStyle = ps.objectStorageRemoteS3ForcePathStyle;
if (ps.remoteObjectStorageS3ForcePathStyle !== undefined) {
set.remoteObjectStorageS3ForcePathStyle = ps.remoteObjectStorageS3ForcePathStyle;
}
if (ps.translatorType !== undefined) {

View File

@ -157,6 +157,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
prevGroupedNotification.id = notification.id;
continue;
}
if (prev.type === 'note' && notification.type === 'note') {
if (prevGroupedNotification.type !== 'note:grouped') {
groupedNotifications[groupedNotifications.length - 1] = {
type: 'note:grouped',
id: '',
createdAt: notification.createdAt,
noteIds: [notification.noteId],
notifierIds: [prev.notifierId!],
};
prevGroupedNotification = groupedNotifications.at(-1)!;
}
if (!(prevGroupedNotification as FilterUnionByProperty<MiGroupedNotification, 'type', 'note:grouped'>).notifierIds.includes(notification.notifierId)) {
(prevGroupedNotification as FilterUnionByProperty<MiGroupedNotification, 'type', 'note:grouped'>).notifierIds.push(notification.notifierId!);
}
(prevGroupedNotification as FilterUnionByProperty<MiGroupedNotification, 'type', 'note:grouped'>).noteIds.push(notification.noteId!);
prevGroupedNotification.id = notification.id;
continue;
}
groupedNotifications.push(notification);
}

View File

@ -4,7 +4,7 @@
*/
import RE2 from 're2';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { JSDOM } from 'jsdom';

View File

@ -0,0 +1,393 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import ms from 'ms';
import { In } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { isPureRenote } from 'cherrypick-js/note.js';
import type { MiUser } from '@/models/User.js';
import type {
UsersRepository,
NotesRepository,
BlockingsRepository,
DriveFilesRepository,
ChannelsRepository,
NoteScheduleRepository,
} from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiNote } from '@/models/Note.js';
import type { MiChannel } from '@/models/Channel.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js';
import { IdService } from '@/core/IdService.js';
import { MiScheduleNoteType } from '@/models/NoteSchedule.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
prohibitMoved: true,
limit: {
duration: ms('1hour'),
max: 300,
},
kind: 'write:notes-schedule',
errors: {
scheduleNoteMax: {
message: 'Schedule note max.',
code: 'SCHEDULE_NOTE_MAX',
id: '168707c3-e7da-4031-989e-f42aa3a274b2',
},
noSuchRenoteTarget: {
message: 'No such renote target.',
code: 'NO_SUCH_RENOTE_TARGET',
id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4',
},
cannotReRenote: {
message: 'You can not Renote a pure Renote.',
code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE',
id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
},
cannotRenoteDueToVisibility: {
message: 'You can not Renote due to target visibility.',
code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY',
id: 'be9529e9-fe72-4de0-ae43-0b363c4938af',
},
noSuchReplyTarget: {
message: 'No such reply target.',
code: 'NO_SUCH_REPLY_TARGET',
id: '749ee0f6-d3da-459a-bf02-282e2da4292c',
},
cannotReplyToPureRenote: {
message: 'You can not reply to a pure Renote.',
code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
},
cannotCreateAlreadyExpiredPoll: {
message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
id: '04da457d-b083-4055-9082-955525eda5a5',
},
cannotCreateAlreadyExpiredSchedule: {
message: 'Schedule is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_SCHEDULE',
id: '8a9bfb90-fc7e-4878-a3e8-d97faaf5fb07',
},
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb',
},
noSuchSchedule: {
message: 'No such schedule.',
code: 'NO_SUCH_SCHEDULE',
id: '44dee229-8da1-4a61-856d-e3a4bbc12032',
},
youHaveBeenBlocked: {
message: 'You have been blocked by this user.',
code: 'YOU_HAVE_BEEN_BLOCKED',
id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3',
},
noSuchFile: {
message: 'Some files are not found.',
code: 'NO_SUCH_FILE',
id: 'b6992544-63e7-67f0-fa7f-32444b1b5306',
},
cannotRenoteOutsideOfChannel: {
message: 'Cannot renote outside of channel.',
code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
id: '33510210-8452-094c-6227-4a6c05d99f00',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
visibleUserIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id',
} },
cw: { type: 'string', nullable: true, minLength: 1, maxLength: 100 },
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
disableRightClick: { type: 'boolean', default: false },
noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false },
replyId: { type: 'string', format: 'misskey:id', nullable: true },
renoteId: { type: 'string', format: 'misskey:id', nullable: true },
// anyOf内にバリデーションを書いても最初の一つしかチェックされない
// See https://github.com/misskey-dev/misskey/pull/10082
text: {
type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
nullable: true,
},
fileIds: {
type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
items: { type: 'string', format: 'misskey:id' },
},
mediaIds: {
type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
items: { type: 'string', format: 'misskey:id' },
},
poll: {
type: 'object',
nullable: true,
properties: {
choices: {
type: 'array',
uniqueItems: true,
minItems: 2,
maxItems: 10,
items: { type: 'string', minLength: 1, maxLength: 50 },
},
multiple: { type: 'boolean' },
expiresAt: { type: 'integer', nullable: true },
expiredAfter: { type: 'integer', nullable: true, minimum: 1 },
},
required: ['choices'],
},
event: {
type: 'object',
nullable: true,
properties: {
title: { type: 'string', minLength: 1, maxLength: 128, nullable: false },
start: { type: 'integer', nullable: false },
end: { type: 'integer', nullable: true },
metadata: { type: 'object' },
},
},
scheduleNote: {
type: 'object',
nullable: false,
properties: {
scheduledAt: { type: 'integer', nullable: false },
},
},
},
// (re)note with text, files and poll are optional
anyOf: [
{ required: ['text'] },
{ required: ['renoteId'] },
{ required: ['fileIds'] },
{ required: ['mediaIds'] },
{ required: ['poll'] },
],
required: ['scheduleNote'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.noteScheduleRepository)
private noteScheduleRepository: NoteScheduleRepository,
@Inject(DI.blockingsRepository)
private blockingsRepository: BlockingsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
private queueService: QueueService,
private roleService: RoleService,
private idService: IdService,
) {
super({
...meta,
}, paramDef, async (ps, me) => {
const scheduleNoteCount = await this.noteScheduleRepository.countBy({ userId: me.id });
const scheduleNoteMax = (await this.roleService.getUserPolicies(me.id)).scheduleNoteMax;
if (scheduleNoteCount >= scheduleNoteMax) {
throw new ApiError(meta.errors.scheduleNoteMax);
}
let visibleUsers: MiUser[] = [];
if (ps.visibleUserIds) {
visibleUsers = await this.usersRepository.findBy({
id: In(ps.visibleUserIds),
});
}
let files: MiDriveFile[] = [];
const fileIds = ps.fileIds ?? ps.mediaIds ?? null;
if (fileIds != null) {
files = await this.driveFilesRepository.createQueryBuilder('file')
.where('file.userId = :userId AND file.id IN (:...fileIds)', {
userId: me.id,
fileIds,
})
.orderBy('array_position(ARRAY[:...fileIds], "id"::text)')
.setParameters({ fileIds })
.getMany();
if (files.length !== fileIds.length) {
throw new ApiError(meta.errors.noSuchFile);
}
}
let renote: MiNote | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
} else if (isPureRenote(renote)) {
throw new ApiError(meta.errors.cannotReRenote);
}
// Check blocking
if (renote.userId !== me.id) {
const blockExist = await this.blockingsRepository.exist({
where: {
blockerId: renote.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
if (renote.visibility === 'followers' && renote.userId !== me.id) {
// 他人のfollowers noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
} else if (renote.visibility === 'specified') {
// specified / direct noteはreject
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
}
}
let reply: MiNote | null = null;
if (ps.replyId != null) {
// Fetch reply
reply = await this.notesRepository.findOneBy({ id: ps.replyId });
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
} else if (isPureRenote(reply)) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
}
// Check blocking
if (reply.userId !== me.id) {
const blockExist = await this.blockingsRepository.exist({
where: {
blockerId: reply.userId,
blockeeId: me.id,
},
});
if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
}
if (ps.poll) {
let scheduleNote_scheduledAt = Date.now();
if (typeof ps.scheduleNote.scheduledAt === 'number') {
scheduleNote_scheduledAt = ps.scheduleNote.scheduledAt;
}
if (typeof ps.poll.expiresAt === 'number') {
if (ps.poll.expiresAt < scheduleNote_scheduledAt) {
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
}
} else if (typeof ps.poll.expiredAfter === 'number') {
ps.poll.expiresAt = scheduleNote_scheduledAt + ps.poll.expiredAfter;
}
}
if (typeof ps.scheduleNote.scheduledAt === 'number') {
if (ps.scheduleNote.scheduledAt < Date.now()) {
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredSchedule);
}
} else {
throw new ApiError(meta.errors.cannotCreateAlreadyExpiredSchedule);
}
const note:MiScheduleNoteType = {
createdAt: new Date(ps.scheduleNote.scheduledAt!).toISOString(),
files: files.map(f => f.id),
poll: ps.poll ? {
choices: ps.poll.choices,
multiple: ps.poll.multiple ?? false,
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt).toISOString() : null,
} : undefined,
text: ps.text ?? undefined,
reply: reply?.id,
renote: renote?.id,
cw: ps.cw,
localOnly: false,
reactionAcceptance: ps.reactionAcceptance,
visibility: ps.visibility,
visibleUsers,
apMentions: ps.noExtractMentions ? [] : undefined,
apHashtags: ps.noExtractHashtags ? [] : undefined,
apEmojis: ps.noExtractEmojis ? [] : undefined,
event: ps.event ? {
start: new Date(ps.event.start!).toISOString(),
end: ps.event.end ? new Date(ps.event.end).toISOString() : null,
title: ps.event.title!,
metadata: ps.event.metadata ?? {},
} : undefined,
disableRightClick: ps.disableRightClick,
};
if (ps.scheduleNote.scheduledAt) {
me.token = null;
const noteId = this.idService.gen(new Date().getTime());
await this.noteScheduleRepository.insert({
id: noteId,
note: note,
userId: me.id,
scheduledAt: new Date(ps.scheduleNote.scheduledAt),
});
const delay = new Date(ps.scheduleNote.scheduledAt).getTime() - Date.now();
await this.queueService.ScheduleNotePostQueue.add(String(delay), {
scheduleNoteId: noteId,
}, {
delay,
removeOnComplete: true,
jobId: noteId,
});
}
return '';
});
}
}

View File

@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import type { NoteScheduleRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { QueueService } from '@/core/QueueService.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
kind: 'write:notes-schedule',
limit: {
duration: ms('1hour'),
max: 300,
},
errors: {
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'a58056ba-8ba1-4323-8ebf-e0b585bc244f',
},
permissionDenied: {
message: 'Permission denied.',
code: 'PERMISSION_DENIED',
id: 'c0da2fed-8f61-4c47-a41d-431992607b5c',
httpStatusCode: 403,
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' },
},
required: ['noteId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.noteScheduleRepository)
private noteScheduleRepository: NoteScheduleRepository,
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
const note = await this.noteScheduleRepository.findOneBy({ id: ps.noteId });
if (note === null) {
throw new ApiError(meta.errors.noSuchNote);
}
if (note.userId !== me.id) {
throw new ApiError(meta.errors.permissionDenied);
}
await this.noteScheduleRepository.delete({ id: ps.noteId });
await this.queueService.ScheduleNotePostQueue.remove(ps.noteId);
});
}
}

View File

@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import type { MiNote, MiNoteSchedule, NoteScheduleRepository } from '@/models/_.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueryService } from '@/core/QueryService.js';
import { Packed } from '@/misc/json-schema.js';
import { noteVisibilities } from '@/types.js';
export const meta = {
tags: ['notes'],
requireCredential: true,
kind: 'read:notes-schedule',
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: { type: 'string', format: 'misskey:id', optional: false, nullable: false },
note: {
type: 'object',
optional: false, nullable: false,
properties: {
createdAt: { type: 'string', optional: false, nullable: false },
text: { type: 'string', optional: true, nullable: false },
cw: { type: 'string', optional: true, nullable: true },
fileIds: { type: 'array', optional: false, nullable: false, items: { type: 'string', format: 'misskey:id', optional: false, nullable: false } },
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], optional: false, nullable: false },
visibleUsers: {
type: 'array', optional: false, nullable: false, items: {
type: 'object',
optional: false, nullable: false,
ref: 'UserLite',
},
},
user: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
isSchedule: { type: 'boolean', optional: false, nullable: false },
},
},
userId: { type: 'string', optional: false, nullable: false },
scheduledAt: { type: 'string', optional: false, nullable: false },
},
},
},
limit: {
duration: ms('1hour'),
max: 300,
},
errors: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.noteScheduleRepository)
private noteScheduleRepository: NoteScheduleRepository,
private userEntityService: UserEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.noteScheduleRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.userId = :userId', { userId: me.id });
const scheduleNotes = await query.limit(ps.limit).getMany();
const user = await this.userEntityService.pack(me, me);
const scheduleNotesPack: {
id: string;
note: {
text?: string;
cw?: string|null;
fileIds: string[];
visibility: typeof noteVisibilities[number];
visibleUsers: Packed<'UserLite'>[];
reactionAcceptance: MiNote['reactionAcceptance'];
user: Packed<'User'>;
createdAt: string;
isSchedule: boolean;
};
userId: string;
scheduledAt: string;
}[] = await Promise.all(scheduleNotes.map(async (item: MiNoteSchedule) => {
return {
...item,
scheduledAt: item.scheduledAt.toISOString(),
note: {
...item.note,
text: item.note.text ?? '',
user: user,
visibility: item.note.visibility ?? 'public',
reactionAcceptance: item.note.reactionAcceptance ?? null,
visibleUsers: item.note.visibleUsers ? await userEntityService.packMany(item.note.visibleUsers.map(u => u.id), me) : [],
fileIds: item.note.files ? item.note.files : [],
createdAt: item.scheduledAt.toISOString(),
isSchedule: true,
id: item.id,
},
};
}));
return scheduleNotesPack;
});
}
}

View File

@ -23,7 +23,7 @@ export const meta = {
kind: 'write:notes',
limit: {
duration: ms('1hour'),
duration: ms('5min'),
max: 10,
minInterval: ms('1sec'),
},

View File

@ -34,6 +34,7 @@ import type {
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue,
ScheduledNoteDeleteQueue,
ScheduleNotePostQueue,
} from '@/core/QueueModule.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
@ -126,6 +127,7 @@ export class ClientServerService {
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduledNoteDelete') public scheduledNoteDeleteQueue: ScheduledNoteDeleteQueue,
@Inject('queue:scheduleNotePost') public scheduleNotePostQueue: ScheduleNotePostQueue,
) {
//this.createServer = this.createServer.bind(this);
}
@ -196,6 +198,7 @@ export class ClientServerService {
serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
youBlockedImageUrl: meta.youBlockedImageUrl ?? 'https://xn--931a.moe/assets/error.jpg',
instanceUrl: this.config.url,
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)),
now: Date.now(),
@ -255,6 +258,7 @@ export class ClientServerService {
this.userWebhookDeliverQueue,
this.systemWebhookDeliverQueue,
this.scheduledNoteDeleteQueue,
this.scheduleNotePostQueue,
].map(q => new BullMQAdapter(q)),
serverAdapter: bullBoardServerAdapter,
});

View File

@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { In, IsNull } from 'typeorm';
import { Feed } from 'feed';
import { parse as mfmParse } from 'cfm-js';
import { parse as mfmParse } from 'mfc-js';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';

View File

@ -44,6 +44,7 @@ export const groupedNotificationTypes = [
...notificationTypes,
'reaction:grouped',
'renote:grouped',
'note:grouped',
] as const;
export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as const;

View File

@ -4,7 +4,7 @@
*/
import * as assert from 'assert';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import { Test } from '@nestjs/testing';
import { CoreModule } from '@/core/CoreModule.js';

View File

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { parse } from 'cfm-js';
import { parse } from 'mfc-js';
import { extractMentions } from '@/misc/extract-mentions.js';
describe('Extract mentions', () => {

View File

@ -1717,6 +1717,10 @@ declare namespace entities {
NotesRenotesResponse,
NotesRepliesRequest,
NotesRepliesResponse,
NotesScheduleCreateRequest,
NotesScheduleDeleteRequest,
NotesScheduleListRequest,
NotesScheduleListResponse,
NotesSearchByTagRequest,
NotesSearchByTagResponse,
NotesSearchRequest,
@ -2862,6 +2866,18 @@ type NotesRequest = operations['notes']['requestBody']['content']['application/j
// @public (undocumented)
type NotesResponse = operations['notes']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesScheduleCreateRequest = operations['notes___schedule___create']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesScheduleDeleteRequest = operations['notes___schedule___delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesScheduleListRequest = operations['notes___schedule___list']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesScheduleListResponse = operations['notes___schedule___list']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json'];
@ -2989,7 +3005,7 @@ type PartialRolePolicyOverride = Partial<{
}>;
// @public (undocumented)
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
// @public (undocumented)
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "cherrypick-js",
"version": "4.12.0",
"version": "4.12.1",
"basedMisskeyVersion": "2024.9.0",
"description": "CherryPick SDK for JavaScript",
"license": "MIT",

View File

@ -3409,6 +3409,39 @@ declare module '../api.js' {
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
request<E extends 'notes/schedule/create', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
request<E extends 'notes/schedule/delete', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
*/
request<E extends 'notes/schedule/list', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*

View File

@ -457,6 +457,10 @@ import type {
NotesRenotesResponse,
NotesRepliesRequest,
NotesRepliesResponse,
NotesScheduleCreateRequest,
NotesScheduleDeleteRequest,
NotesScheduleListRequest,
NotesScheduleListResponse,
NotesSearchByTagRequest,
NotesSearchByTagResponse,
NotesSearchRequest,
@ -926,6 +930,9 @@ export type Endpoints = {
'notes/reactions/delete': { req: NotesReactionsDeleteRequest; res: EmptyResponse };
'notes/renotes': { req: NotesRenotesRequest; res: NotesRenotesResponse };
'notes/replies': { req: NotesRepliesRequest; res: NotesRepliesResponse };
'notes/schedule/create': { req: NotesScheduleCreateRequest; res: EmptyResponse };
'notes/schedule/delete': { req: NotesScheduleDeleteRequest; res: EmptyResponse };
'notes/schedule/list': { req: NotesScheduleListRequest; res: NotesScheduleListResponse };
'notes/search-by-tag': { req: NotesSearchByTagRequest; res: NotesSearchByTagResponse };
'notes/search': { req: NotesSearchRequest; res: NotesSearchResponse };
'notes/show': { req: NotesShowRequest; res: NotesShowResponse };

View File

@ -460,6 +460,10 @@ export type NotesRenotesRequest = operations['notes___renotes']['requestBody']['
export type NotesRenotesResponse = operations['notes___renotes']['responses']['200']['content']['application/json'];
export type NotesRepliesRequest = operations['notes___replies']['requestBody']['content']['application/json'];
export type NotesRepliesResponse = operations['notes___replies']['responses']['200']['content']['application/json'];
export type NotesScheduleCreateRequest = operations['notes___schedule___create']['requestBody']['content']['application/json'];
export type NotesScheduleDeleteRequest = operations['notes___schedule___delete']['requestBody']['content']['application/json'];
export type NotesScheduleListRequest = operations['notes___schedule___list']['requestBody']['content']['application/json'];
export type NotesScheduleListResponse = operations['notes___schedule___list']['responses']['200']['content']['application/json'];
export type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json'];
export type NotesSearchByTagResponse = operations['notes___search-by-tag']['responses']['200']['content']['application/json'];
export type NotesSearchRequest = operations['notes___search']['requestBody']['content']['application/json'];

View File

@ -2948,6 +2948,33 @@ export type paths = {
*/
post: operations['notes___replies'];
};
'/notes/schedule/create': {
/**
* notes/schedule/create
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
post: operations['notes___schedule___create'];
};
'/notes/schedule/delete': {
/**
* notes/schedule/delete
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
post: operations['notes___schedule___delete'];
};
'/notes/schedule/list': {
/**
* notes/schedule/list
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
*/
post: operations['notes___schedule___list'];
};
'/notes/search-by-tag': {
/**
* notes/search-by-tag
@ -5195,6 +5222,7 @@ export type components = {
canImportMuting: boolean;
canImportUserLists: boolean;
canEditNote: boolean;
scheduleNoteMax: number;
};
ReversiGameLite: {
/** Format: id */
@ -5306,6 +5334,7 @@ export type components = {
serverErrorImageUrl: string | null;
infoImageUrl: string | null;
notFoundImageUrl: string | null;
youBlockedImageUrl: string | null;
iconUrl: string | null;
maxNoteTextLength: number;
ads: {
@ -5437,6 +5466,7 @@ export type operations = {
serverErrorImageUrl: string | null;
infoImageUrl: string | null;
notFoundImageUrl: string | null;
youBlockedImageUrl: string | null;
iconUrl: string | null;
app192IconUrl: string | null;
app512IconUrl: string | null;
@ -5482,18 +5512,18 @@ export type operations = {
objectStorageUseSSL: boolean;
objectStorageUseProxy: boolean;
objectStorageSetPublicRead: boolean;
useObjectStorageRemote?: boolean;
objectStorageRemoteBaseUrl?: string | null;
objectStorageRemoteBucket?: string | null;
objectStorageRemotePrefix?: string | null;
objectStorageRemoteEndpoint?: string | null;
objectStorageRemoteRegion?: string | null;
objectStorageRemotePort?: number | null;
objectStorageRemoteAccessKey?: string | null;
objectStorageRemoteSecretKey?: string | null;
objectStorageRemoteUseSSL?: boolean;
objectStorageRemoteUseProxy?: boolean;
objectStorageRemoteSetPublicRead?: boolean;
useRemoteObjectStorage: boolean;
remoteObjectStorageBaseUrl: string | null;
remoteObjectStorageBucket: string | null;
remoteObjectStoragePrefix: string | null;
remoteObjectStorageEndpoint: string | null;
remoteObjectStorageRegion: string | null;
remoteObjectStoragePort: number | null;
remoteObjectStorageAccessKey: string | null;
remoteObjectStorageSecretKey: string | null;
remoteObjectStorageUseSSL: boolean;
remoteObjectStorageUseProxy: boolean;
remoteObjectStorageSetPublicRead: boolean;
enableIpLogging: boolean;
enableActiveEmailValidation: boolean;
enableVerifymailApi: boolean;
@ -5528,7 +5558,7 @@ export type operations = {
name: string | null;
shortName: string | null;
objectStorageS3ForcePathStyle: boolean;
objectStorageRemoteS3ForcePathStyle: boolean;
remoteObjectStorageS3ForcePathStyle: boolean;
privacyPolicyUrl: string | null;
inquiryUrl: string | null;
repositoryUrl: string | null;
@ -10124,6 +10154,7 @@ export type operations = {
serverErrorImageUrl?: string | null;
infoImageUrl?: string | null;
notFoundImageUrl?: string | null;
youBlockedImageUrl?: string | null;
iconUrl?: string | null;
app192IconUrl?: string | null;
app512IconUrl?: string | null;
@ -10198,19 +10229,19 @@ export type operations = {
objectStorageUseProxy?: boolean;
objectStorageSetPublicRead?: boolean;
objectStorageS3ForcePathStyle?: boolean;
useObjectStorageRemote?: boolean;
objectStorageRemoteBaseUrl?: string | null;
objectStorageRemoteBucket?: string | null;
objectStorageRemotePrefix?: string | null;
objectStorageRemoteEndpoint?: string | null;
objectStorageRemoteRegion?: string | null;
objectStorageRemotePort?: number | null;
objectStorageRemoteAccessKey?: string | null;
objectStorageRemoteSecretKey?: string | null;
objectStorageRemoteUseSSL?: boolean;
objectStorageRemoteUseProxy?: boolean;
objectStorageRemoteSetPublicRead?: boolean;
objectStorageRemoteS3ForcePathStyle?: boolean;
useRemoteObjectStorage?: boolean;
remoteObjectStorageBaseUrl?: string | null;
remoteObjectStorageBucket?: string | null;
remoteObjectStoragePrefix?: string | null;
remoteObjectStorageEndpoint?: string | null;
remoteObjectStorageRegion?: string | null;
remoteObjectStoragePort?: number | null;
remoteObjectStorageAccessKey?: string | null;
remoteObjectStorageSecretKey?: string | null;
remoteObjectStorageUseSSL?: boolean;
remoteObjectStorageUseProxy?: boolean;
remoteObjectStorageSetPublicRead?: boolean;
remoteObjectStorageS3ForcePathStyle?: boolean;
enableIpLogging?: boolean;
enableActiveEmailValidation?: boolean;
enableVerifymailApi?: boolean;
@ -19443,8 +19474,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
};
};
};
@ -23771,6 +23802,247 @@ export type operations = {
};
};
};
/**
* notes/schedule/create
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
notes___schedule___create: {
requestBody: {
content: {
'application/json': {
/**
* @default public
* @enum {string}
*/
visibility?: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: string[];
cw?: string | null;
/**
* @default null
* @enum {string|null}
*/
reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** @default false */
disableRightClick?: boolean;
/** @default false */
noExtractMentions?: boolean;
/** @default false */
noExtractHashtags?: boolean;
/** @default false */
noExtractEmojis?: boolean;
/** Format: misskey:id */
replyId?: string | null;
/** Format: misskey:id */
renoteId?: string | null;
text?: string | null;
fileIds?: string[];
mediaIds?: string[];
poll?: ({
choices: string[];
multiple?: boolean;
expiresAt?: number | null;
expiredAfter?: number | null;
}) | null;
event?: ({
title?: string;
start?: number;
end?: number | null;
metadata?: Record<string, never>;
}) | null;
scheduleNote: {
scheduledAt?: number;
};
};
};
};
responses: {
/** @description OK (without any results) */
204: {
content: never;
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description To many requests */
429: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* notes/schedule/delete
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
notes___schedule___delete: {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
noteId: string;
};
};
};
responses: {
/** @description OK (without any results) */
204: {
content: never;
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description To many requests */
429: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* notes/schedule/list
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
*/
notes___schedule___list: {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
sinceId?: string;
/** Format: misskey:id */
untilId?: string;
/** @default 10 */
limit?: number;
};
};
};
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': ({
/** Format: misskey:id */
id: string;
note: {
createdAt: string;
text?: string;
cw?: string | null;
fileIds: string[];
/** @enum {string} */
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUsers: components['schemas']['UserLite'][];
user: components['schemas']['User'];
/**
* @default null
* @enum {string|null}
*/
reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
isSchedule: boolean;
};
userId: string;
scheduledAt: string;
})[];
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description To many requests */
429: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* notes/search-by-tag
* @description No description provided.

View File

@ -42,6 +42,8 @@ export const permissions = [
'read:mutes',
'write:mutes',
'write:notes',
'read:notes-schedule',
'write:notes-schedule',
'read:notifications',
'write:notifications',
'read:reactions',

View File

@ -24,7 +24,7 @@
"@vue/compiler-sfc": "3.5.10",
"astring": "1.9.0",
"buraha": "0.0.1",
"cfm-js": "0.24.0-cherrypick.8",
"mfc-js": "0.24.0-cherrypick.9",
"cherrypick-js": "workspace:*",
"estree-walker": "3.0.3",
"frontend-shared": "workspace:*",
@ -32,6 +32,7 @@
"rollup": "4.22.5",
"sass": "1.79.3",
"shiki": "1.12.0",
"temml": "0.10.29",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0",

View File

@ -48,11 +48,11 @@ $height: 2ex;
display: flex;
align-items: center;
height: $height;
border-radius: .3rem;
border-radius: .5rem;
overflow: clip;
color: #000;
margin-top: 5px;
padding: 1px 3px 1px 0;
padding: 1px 5px 1px 0;
text-shadow: /* .866 ≈ sin(60deg) */
1px 0 1px #000,
.866px .5px 1px #000,

View File

@ -4,8 +4,9 @@
*/
import { VNode, h, SetupContext, provide } from 'vue';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import * as Misskey from 'cherrypick-js';
import temml from 'temml/dist/temml.mjs';
import { host } from '@@/js/config.js';
import EmUrl from '@/components/EmUrl.vue';
import EmTime from '@/components/EmTime.vue';
@ -224,6 +225,9 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
case 'sparkle': {
return genEl(token.children, scale);
}
case 'fade': {
return genEl(token.children, scale);
}
case 'rotate': {
const degrees = safeParseFloat(token.props.args.deg) ?? 90;
style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
@ -413,6 +417,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
normal: props.plain,
host: props.author.host,
useOriginalSize: scale >= 2.5,
menu: props.enableEmojiMenu,
menuReaction: props.enableEmojiMenuReaction,
})];
}
}
@ -428,11 +434,15 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}
case 'mathInline': {
return [h('code', token.props.formula)];
const ret = document.createElement('span');
temml.render(token.props.formula, ret, {});
return [h('span', { innerHTML: ret.innerHTML })];
}
case 'mathBlock': {
return [h('code', token.props.formula)];
const ret = document.createElement('div');
temml.render(token.props.formula, ret, { displayMode: true });
return [h('div', { innerHTML: ret.innerHTML })];
}
case 'search': {

View File

@ -121,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, inject, ref, shallowRef } from 'vue';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import * as Misskey from 'cherrypick-js';
import { shouldCollapsed, shouldMfmCollapsed } from '@@/js/collapsed.js';
import { url } from '@@/js/config.js';

View File

@ -155,7 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, inject, ref } from 'vue';
import * as mfm from 'cfm-js';
import * as mfm from 'mfc-js';
import * as Misskey from 'cherrypick-js';
import { shouldCollapsed, shouldMfmCollapsed } from '@@/js/collapsed.js';
import { url } from '@@/js/config.js';

View File

@ -19,6 +19,12 @@ $monospace-font: "Pretendard JP", Pretendard, "JetBrains Mono", "Fira code", "Fi
--marginHalf: 10px;
--margin: var(--marginFull);
--cherry: rgb(255, 188, 220);
--pick: rgb(177, 211, 255);
--misskey: rgb(134, 179, 0);
--cast: rgb(181, 151, 246);
--ella: rgb(150, 198, 234);
}
html {
@ -315,7 +321,7 @@ rt {
font-family: $monospace-font;
}
// CFM -----------------------------
// MFC -----------------------------
._mfm_blur_ {
filter: blur(6px);

View File

@ -4,8 +4,8 @@
*/
import tinycolor from 'tinycolor2';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
import lightTheme from '@@/themes/_light-cherrypick.json5';
import darkTheme from '@@/themes/_dark-cherrypick.json5';
import type { BundledTheme } from 'shiki/themes';
export type Theme = {

View File

@ -79,6 +79,7 @@ export const ROLE_POLICIES = [
'ltlAvailable',
'canPublicNote',
'canEditNote',
'scheduleNoteMax',
'mentionLimit',
'canInvite',
'inviteLimit',
@ -119,6 +120,7 @@ export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM';
export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg';
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg';
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
export const DEFAULT_YOU_BLOCKED_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg';
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'fade', 'rotate', 'ruby', 'unixtime'];
export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {

View File

@ -0,0 +1,104 @@
// ダークテーマのベーステーマ
// このテーマが直接使われることは無い
{
id: 'dark',
name: 'Dark',
author: 'noridev & syuilo',
desc: 'CherryPick default dark theme',
kind: 'dark',
props: {
accent: '#ffc5e6',
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
love: '#dd2e44',
focus: ':alpha<0.3<@accent',
bg: 'rgb(28, 28, 37)',
acrylicBg: ':alpha<0.5<@bg',
fg: 'rgb(236, 239, 244)',
fgTransparentWeak: ':alpha<0.75<@fg',
fgTransparent: ':alpha<0.5<@fg',
fgHighlighted: '#fff',
fgOnAccent: '#000',
fgOnWhite: '@accent',
divider: 'rgb(63, 63, 80)',
indicator: '@accent',
panel: 'rgb(35, 35, 47)',
panelHighlight: ':lighten<3<@panel',
panelHeaderBg: 'rgb(45, 45, 66)',
panelHeaderFg: '@fg',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
panelBorder: '" solid 1px var(--divider)',
acrylicPanel: ':alpha<0.5<@panel',
windowHeader: ':alpha<0.85<@panel',
popup: ':lighten<3<@panel',
shadow: 'rgba(0, 0, 0, 0.3)',
header: '@bg',
navBg: '@panel',
navFg: '@fg',
navHoverFg: '@pick',
navActive: '@accent',
navIndicator: '@indicator',
link: '@accent',
hashtag: '#4cb8d4',
mention: '#da6d35',
mentionMe: '#d44c4c',
renote: '@accent',
renoteHover: ':lighten<5<@renote',
nameHover: ':darken<5<@fg',
modalBg: 'rgba(0, 0, 0, 0.5)',
modalBgX2: 'rgba(0, 0, 0, 0.9)',
scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
dateLabelFg: '@fg',
infoBg: 'rgb(0, 0, 0)',
infoFg: '@accent',
infoWarnBg: '#42321c',
infoWarnFg: '#ffbd3e',
folderHeaderBg: 'rgba(255, 255, 255, 0.05)',
folderHeaderHoverBg: 'rgba(255, 255, 255, 0.1)',
buttonBg: ':lighten<5<@panel',
buttonHoverBg: ':lighten<10<@panel',
buttonGradateA: '@cherry',
buttonGradateB: '@pick',
switchBg: 'rgba(255, 255, 255, 0.15)',
switchOffBg: 'rgba(255, 255, 255, 0.1)',
switchOffFg: ':alpha<0.8<@fg',
switchOnBg: '@accentedBg',
switchOnFg: '@accent',
inputBorder: 'rgba(255, 255, 255, 0.1)',
inputBorderHover: 'rgba(255, 255, 255, 0.2)',
listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
driveFolderBg: ':alpha<0.3<@accent',
wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
badge: '#ffa9c3',
patron: '#a3faff',
messageBg: '@bg',
success: '#86b300',
error: '#ec4137',
warn: '#ecb637',
codeString: '#ffb675',
codeNumber: '#cfff9e',
codeBoolean: '#c59eff',
deckBg: '#000',
htmlThemeColor: '@bg',
chatReadBg: ':lighten<1<@bg',
cherry: 'rgb(255, 207, 230)',
pick: 'rgb(185, 216, 255)',
pickLighten: ':lighten<10<@pick',
X3: 'rgba(255, 255, 255, 0.05)',
X4: 'rgba(255, 255, 255, 0.1)',
X5: 'rgba(255, 255, 255, 0.05)',
X6: 'rgba(255, 255, 255, 0.15)',
X7: 'rgba(255, 255, 255, 0.05)',
X11: 'rgba(0, 0, 0, 0.3)',
X12: 'rgba(255, 255, 255, 0.1)',
X13: 'rgba(255, 255, 255, 0.15)',
},
codeHighlighter: {
base: 'one-dark-pro',
},
}

View File

@ -9,7 +9,7 @@
kind: 'dark',
props: {
accent: '#86b300',
accent: '#ffc5e6',
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',

View File

@ -0,0 +1,104 @@
// ライトテーマのベーステーマ
// このテーマが直接使われることは無い
{
id: 'light',
name: 'Light',
author: 'noridev & syuilo',
desc: 'CherryPick default light theme',
kind: 'light',
props: {
accent: '#6ba5e3',
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',
love: '#dd2e44',
focus: ':alpha<0.3<@accent',
bg: 'rgb(238, 241, 252)',
acrylicBg: ':alpha<0.5<@bg',
fg: '#577096',
fgTransparentWeak: ':alpha<0.75<@fg',
fgTransparent: ':alpha<0.5<@fg',
fgHighlighted: ':darken<3<@fg',
fgOnAccent: '#fff',
fgOnWhite: '@accent',
divider: 'rgb(223, 223, 223)',
indicator: '@accent',
panel: 'rgb(246, 249, 255)',
panelHighlight: ':darken<3<@panel',
panelHeaderBg: ':lighten<3<@panel',
panelHeaderFg: '@fg',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
panelBorder: '" solid 1px var(--divider)',
acrylicPanel: ':alpha<0.5<@panel',
windowHeader: ':alpha<0.85<@panel',
popup: ':lighten<3<@panel',
shadow: 'rgba(0, 0, 0, 0.1)',
header: '@bg',
navBg: '#eff4ff',
navFg: '@fg',
navHoverFg: '@pick',
navActive: '@accent',
navIndicator: '@indicator',
link: '#44a4c1',
hashtag: '#ff9156',
mention: '@accent',
mentionMe: '@mention',
renote: '@accent',
renoteHover: ':lighten<5<@renote',
nameHover: ':lighten<5<@fg',
modalBg: 'rgba(0, 0, 0, 0.3)',
modalBgX2: 'rgba(0, 0, 0, 0.9)',
scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
dateLabelFg: '@fg',
infoBg: '#e5f5ff',
infoFg: '#72818a',
infoWarnBg: '#fff0db',
infoWarnFg: '#8f6e31',
folderHeaderBg: 'rgba(0, 0, 0, 0.05)',
folderHeaderHoverBg: 'rgba(0, 0, 0, 0.1)',
buttonBg: ':darken<5<@panel',
buttonHoverBg: ':darken<10<@panel',
buttonGradateA: '@cherry',
buttonGradateB: '@pick',
switchBg: 'rgba(0, 0, 0, 0.15)',
switchOffBg: 'rgba(0, 0, 0, 0.1)',
switchOffFg: '@panel',
switchOnBg: '@accent',
switchOnFg: '@fgOnAccent',
inputBorder: 'rgba(0, 0, 0, 0.1)',
inputBorderHover: 'rgba(0, 0, 0, 0.2)',
listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
driveFolderBg: ':alpha<0.3<@accent',
wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
badge: '#ffa9c3',
patron: '#a3faff',
messageBg: '#dedede',
success: '#86b300',
error: '#ec4137',
warn: '#ecb637',
codeString: '#b98710',
codeNumber: '#0fbbbb',
codeBoolean: '#62b70c',
deckBg: ':darken<3<@bg',
htmlThemeColor: '@bg',
chatReadBg: ':lighten<1<@bg',
cherry: 'rgb(255, 188, 220)',
pick: 'rgb(177, 211, 255)',
pickLighten: ':lighten<10<@pick',
X3: 'rgba(0, 0, 0, 0.05)',
X4: 'rgba(0, 0, 0, 0.1)',
X5: 'rgba(0, 0, 0, 0.05)',
X6: 'rgba(0, 0, 0, 0.25)',
X7: 'rgba(0, 0, 0, 0.05)',
X11: 'rgba(0, 0, 0, 0.1)',
X12: 'rgba(0, 0, 0, 0.1)',
X13: 'rgba(0, 0, 0, 0.15)',
},
codeHighlighter: {
base: 'catppuccin-latte',
},
}

View File

@ -9,7 +9,7 @@
kind: 'light',
props: {
accent: '#86b300',
accent: '#6ba5e3',
accentDarken: ':darken<10<@accent',
accentLighten: ':lighten<10<@accent',
accentedBg: ':alpha<0.15<@accent',

View File

@ -24,8 +24,8 @@
mention: '#da6d35',
mentionMe: '#d44c4c',
renote: '@accent',
infoFg: '@accent',
infoBg: 'rgb(0, 0, 0)',
infoFg: '@accent',
buttonGradateA: '@cherry',
buttonGradateB: '@pick',
badge: '#ffa9c3',

View File

@ -39,7 +39,7 @@
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
"canvas-confetti": "1.9.3",
"cfm-js": "0.24.0-cherrypick.8",
"mfc-js": "0.24.0-cherrypick.9",
"chart.js": "4.4.4",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.0.1",
@ -70,7 +70,7 @@
"sass": "1.79.3",
"shiki": "1.12.0",
"strict-event-emitter-types": "2.0.0",
"temml": "0.10.20",
"temml": "0.10.29",
"textarea-caret": "3.1.0",
"three": "0.169.0",
"throttle-debounce": "5.0.2",

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