diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6cf2071b38846071561dea91c5f9072ce328d1..1d788e152258d0643be35c970ea10a19ea1d1f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ - Fix: リモートユーザーã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä¸€è¦§ãŒã™ã¹ã¦è¦‹ãˆã¦ã—ã¾ã†ã®ã‚’ä¿®æ£ * ã™ã¹ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä¸€è¦§ã‚’見ãˆãªã„よã†ã«ã—ã¾ã™ - Enhance: モデレーターã¯ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä¸€è¦§ã‚’見られるよã†ã« +- Fix: 特定ã®ã‚ーワードをå«ã‚€ãƒŽãƒ¼ãƒˆãŒæŠ•ç¨¿ã•ã‚ŒãŸéš›ã€ã‚¨ãƒ©ãƒ¼ã«å‡ºæ¥ã‚‹ã‚ˆã†ãªè¨å®šé …ç›®ã‚’è¿½åŠ #13207 + * デフォルトã¯ç©ºæ¬„ãªã®ã§é©ç”¨å‰ã¨åŒç‰ã®å‹•ä½œã«ãªã‚Šã¾ã™ ### Client - Feat: æ–°ã—ã„ã‚²ãƒ¼ãƒ ã‚’è¿½åŠ @@ -49,6 +51,12 @@ - Enhance: MFMã®å±žæ€§ã§ã‚ªãƒ¼ãƒˆã‚³ãƒ³ãƒ—リートãŒä½¿ç”¨ã§ãるよã†ã« #12735 - Enhance: 絵文å—編集ダイアãƒã‚°ã‚’モーダルã§ã¯ãªãウィンドウã§è¡¨ç¤ºã™ã‚‹ã‚ˆã†ã« - Enhance: リモートã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ãƒ¡ãƒ‹ãƒ¥ãƒ¼ã‹ã‚‰ç›´æŽ¥ãƒªãƒ¢ãƒ¼ãƒˆã§è¡¨ç¤ºã§ãるよã†ã« +- Enhance: リモートã¸ã®å¼•ç”¨ãƒªãƒŽãƒ¼ãƒˆã¨åŒä¸€ã®ãƒªãƒ³ã‚¯ã«ã¯ãƒªãƒ³ã‚¯ãƒ—レビューを表示ã—ãªã„よã†ã« +- Enhance: コードã®ã‚·ãƒ³ã‚¿ãƒƒã‚¯ã‚¹ãƒã‚¤ãƒ©ã‚¤ãƒˆã«ãƒ†ãƒ¼ãƒžã‚’é©ç”¨ã§ãるよã†ã« +- Enhance: リアクション権é™ãŒãªã„å ´åˆã€ãƒãƒ¼ãƒˆã«ãƒ•ã‚©ãƒ¼ãƒ«ãƒãƒƒã‚¯ã™ã‚‹ã®ã§ã¯ãªãリアクションピッカーãªã©ã‹ã‚‰æ‰“ã¦ãªã„よã†ã« + - リモートã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ãƒãƒ¼ã‚«ãƒ«ã®ã¿ã®ã‚«ã‚¹ã‚¿ãƒ 絵文å—をリアクションã—よã†ã¨ã—ãŸå ´åˆ + - センシティブãªãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’èªã‚ã¦ã„ãªã„ユーザーã«ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ãªã‚«ã‚¹ã‚¿ãƒ 絵文å—をリアクションã—よã†ã¨ã—ãŸå ´åˆ + - ãƒãƒ¼ãƒ«ãŒå¿…è¦ãªçµµæ–‡å—をリアクションã—よã†ã¨ã—ãŸå ´åˆ - Fix: ãƒã‚¤ãƒ†ã‚£ãƒ–モードã®çµµæ–‡å—ãŒãƒ¢ãƒŽã‚¯ãƒã«ãªã‚‰ãªã„よã†ã« - Fix: v2023.12.0ã§è¿½åŠ ã•ã‚ŒãŸã€Œãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã‚¢ã‚¤ã‚³ãƒ³ã‚‚ã—ãã¯ãƒãƒŠãƒ¼ç”»åƒã‚’未è¨å®šçŠ¶æ…‹ã«ã§ãる機能ã€ãŒç®¡ç†ç”»é¢ä¸Šã§æ£ã—ã表示ã•ã‚Œã¦ã„ãªã„å•é¡Œã‚’ä¿®æ£ - Fix: AiScriptã®`readline`関数ãŒä¸æ£ãªå€¤ã‚’è¿”ã™ã“ã¨ãŒã‚ã‚‹å•é¡Œã®v2023.12.0時点ã§ã®ä¿®æ£ãŒPlay以外ã«é©ç”¨ã•ã‚Œã¦ã„ãªã„ã®ã‚’ä¿®æ£ @@ -62,6 +70,13 @@ - Enhance: ページé·ç§»æ™‚ã«Playerã‚’é–‰ã˜ã‚‹ã‚ˆã†ã« - Fix: iOSã§å¤§ããªç”»åƒã‚’変æ›ã—ã¦ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã§ããªã„å•é¡Œã‚’ä¿®æ£ - Fix: 「アニメーション画åƒã‚’å†ç”Ÿã—ãªã„ã€ã‚‚ã—ãã¯ã€Œãƒ‡ãƒ¼ã‚¿ã‚»ãƒ¼ãƒãƒ¼ï¼ˆã‚¢ã‚¤ã‚³ãƒ³ï¼‰ã€ã‚’有効ã«ã—ã¦ã„ã¦ã‚‚ã€ã‚¢ã‚¤ã‚³ãƒ³ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã®ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ãŒåœæ¢ã•ã‚Œãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: ç”»åƒã‚’クãƒãƒƒãƒ—ã™ã‚‹ã¨ã‚¯ãƒãƒƒãƒ—後ã®è§£åƒåº¦ãŒç•°æ§˜ã«ä½Žããªã‚‹å•é¡Œã®ä¿®æ£ +- Fix: ç”»åƒã‚’クãƒãƒƒãƒ—時ã€æ£å¸¸ã«å®Œäº†ã§ããªã„å•é¡Œã®ä¿®æ£ +- Fix: ã‚ャプションãŒç©ºã®ç”»åƒã‚’クãƒãƒƒãƒ—ã™ã‚‹ã¨ã‚ャプションã«nullã¨ã„ã†æ–‡å—列ãŒå…¥ã£ã¦ã—ã¾ã†å•é¡Œã®ä¿®æ£ +- Fix: プãƒãƒ•ã‚£ãƒ¼ãƒ«ã‚’編集ã—ã¦ã‚‚リãƒãƒ¼ãƒ‰ã™ã‚‹ã¾ã§åæ˜ ã•ã‚Œãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: エラー画åƒURLã‚’è¨å®šã—ãŸå¾Œè§£é™¤ã™ã‚‹ã¨ï¼Œãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ç”»åƒãŒè¡¨ç¤ºã•ã‚Œãªã„å•é¡Œã®ä¿®æ£ +- Fix: MkCodeEditorã§è¡ŒãŒãšã‚Œã¦ã„ã£ã¦ã—ã¾ã†å•é¡Œã®ä¿®æ£ +- Fix: Summaly proxy利用時ã«ãƒ—レイヤーãŒå‹•ä½œã—ãªã„ã“ã¨ãŒã‚ã‚‹ã®ã‚’ä¿®æ£ #13196 ### Server - Enhance: 連åˆå…ˆã®ãƒ¬ãƒ¼ãƒˆãƒªãƒŸãƒƒãƒˆã«å¼•ã£ã‹ã‹ã£ãŸéš›ã«ãƒªãƒˆãƒ©ã‚¤ã™ã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã—㟠@@ -76,6 +91,7 @@ - Fix: properly handle cc followers - Fix: ジョブã«é–¢ã™ã‚‹è¨å®šã®åå‰ã‚’ä¿®æ£ relashionshipJobPerSec -> relationshipJobPerSec - Fix: コントãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«->モデレーション->「誰ã§ã‚‚æ–°è¦ç™»éŒ²ã§ãるよã†ã«ã™ã‚‹ã€ã®åˆæœŸå€¤ã‚’ONã‹ã‚‰OFFã«å¤‰æ›´ #13122 +- Enhance: 連åˆå‘ã‘ã®ãƒŽãƒ¼ãƒˆé…信を軽é‡åŒ– #13192 ### Service Worker - Enhance: オフライン表示ã®ãƒ‡ã‚¶ã‚¤ãƒ³ã‚’改善・多言語対応 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f6c1f4f82468f1f845c8dac5450a2a84a7bb463..ac0a1ba3c1c55ea085d0fb3f3e8d78ca70198959 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -286,18 +286,17 @@ export const argTypes = { min: 1, max: 4, }, + }, }; ``` Also, you can use msw to mock API requests in the storybook. Creating a `MyComponent.stories.msw.ts` file to define the mock handlers. ```ts -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; export const handlers = [ - rest.post('/api/notes/timeline', (req, res, ctx) => { - return res( - ctx.json([]), - ); + http.post('/api/notes/timeline', ({ request }) => { + return HttpResponse.json([]); }), ]; ``` diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index eeafcafed24dc2e1144df72f22ead4e6fc6d559a..13bbb43f765554a6a6ad382584874cbc809a41b6 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1041,6 +1041,9 @@ resetPasswordConfirm: "Vols canviar la teva contrasenya?" sensitiveWords: "Paraules sensibles" sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automà ticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per lÃnies noves." sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular." +prohibitedWords: "Paraules prohibides" +prohibitedWordsDescription: "Quan intenteu publicar una Nota que conté una paraula prohibida, feu que es converteixi en un error. Es poden dividir i establir múltiples lÃnies." +prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular." hiddenTags: "Etiquetes ocultes" hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automà ticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per lÃnies noves." notesSearchNotAvailable: "La cerca de notes no es troba disponible." @@ -1157,6 +1160,12 @@ edited: "Editat" notificationRecieveConfig: "Parà metres de notificacions" mutualFollow: "Seguidor mutu" fileAttachedOnly: "Només notes amb adjunts" +showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la lÃnia de temps" +hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la lÃnia de temps" +showRepliesToOthersInTimelineAll: "Mostrar les respostes a altres a usuaris que segueixes a la lÃnia de temps" +hideRepliesToOthersInTimelineAll: "Ocultar les teves respostes a tots els usuaris que segueixes a la lÃnia de temps" +confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les teves respostes a tots els que segueixes a la teva lÃnia de temps?" +confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la lÃnia de temps?" externalServices: "Serveis externs" impressum: "Impressum" impressumUrl: "Adreça URL impressum" @@ -1187,7 +1196,25 @@ seasonalScreenEffect: "Efectes de pantalla segons les estacions" decorate: "Decorar" addMfmFunction: "Afegeix funcions MFM" enableQuickAddMfmFunction: "Activar accés rà pid per afegir funcions MFM" +bubbleGame: "Bubble Game" +sfx: "Efectes de so" +soundWillBePlayed: "Es reproduiran efectes de so" +showReplay: "Veure reproducció" +replay: "Reproduir" +replaying: "Reproduint" +ranking: "Classificació" lastNDays: "Últims {n} dies" +backToTitle: "Torna al tÃtol" +hemisphere: "Geolocalització" +withSensitive: "Incloure notes amb fitxers sensibles" +userSaysSomethingSensitive: "La publicació de {name} conte material sensible" +enableHorizontalSwipe: "Lliscar per canviar de pestanya" +_bubbleGame: + howToPlay: "Com es juga" + _howToPlay: + section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa." + section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts." + section3: "El joc s'acabarà quan els objectes sobresurtin de la caixa. Intenta aconseguir la puntuació més gran possible fusionant objectes mentre impedeixes que sobresurtin de la caixa!" _announcement: forExistingUsers: "Anunci per usuaris registrats" forExistingUsersDescription: "Aquest avÃs només es mostrarà als usuaris existents fins al moment de la publicació. Si no també es mostrarà als usuaris que es registrin després de la publicació." @@ -1209,8 +1236,32 @@ _initialAccountSetting: privacySetting: "Configuració de seguretat" theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard." youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pà gina de configuració. Assegureu-vos de comprovar-ho més tard." + followUsers: "Prova de seguir usuaris que t'interessin per construir la teva lÃnia de temps." + pushNotificationDescription: "Activant les notificacions emergents et permetrà rebre notificacions de {name} directament al teu dispositiu." + initialAccountSettingCompleted: "Configuració del perfil completada!" + haveFun: "Disfruta {name}!" + youCanContinueTutorial: "Pots continuar amb un tutorial per aprendre a Fer servir {name} (MissKey) o tu pots estalviar i començar a fer-lo servir ja." + startTutorial: "Començar el tutorial" + skipAreYouSure: "Et vols saltar la configuració del perfil?" + laterAreYouSure: "Vols continuar la configuració del perfil més tard?" _initialTutorial: + launchTutorial: "Començar tutorial" + title: "Tutorial" + wellDone: "Ben fet!" + skipAreYouSure: "Sortir del tutorial?" + _landing: + title: "Benvingut al tutorial" + description: "Aquà aprendrà s el bà sic per poder fer servir Misskey i les seves caracterÃstiques." + _note: + title: "Què és una Nota?" + description: "Les publicacions a Misskey es diuen 'Notes'. Les Notes s'ordenen cronològicament a la lÃnia de temps i s'actualitzen de forma automà tica." + reply: "Fes clic en aquest botó per contestar a un missatge. També és possible contestar a una contestació, continuant la conversació en forma de fil." + renote: "Pots compartir una Nota a la teva pròpia lÃnia de temps. Inclús pots citar-les amb els teus comentaris." + reaction: "Pots afegir reaccions a les Notes. Entrarem més en detall a la pròxima pà gina." + menu: "Pots veure els detalls de les Notes, copiar enllaços i fer diferents accions." _reaction: + title: "Què són les Reaccions?" + description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada." letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!" reactToContinue: "Afegeix una reacció per continuar." reactNotification: "Rebrà s notificacions en temps real quan un usuari reaccioni a les teves notes." @@ -1272,9 +1323,75 @@ _serverSettings: shortName: "Nom curt" shortNameDescription: "Una abreviatura del nom de la instà ncia que es poguà mostrar en cas que el nom oficial sigui massa llarg" fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les lÃnies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat." + fanoutTimelineDbFallback: "Carregar de la base de dades" + fanoutTimelineDbFallbackDescription: "Quan s'activa, la lÃnia de temps fa servir la base de dades per consultes adicionals si la lÃnia de temps no es troba a la memòria cau. Si és desactiva la cà rrega del servidor és veure reduïda, però també és reduirà el nombre de lÃnies de temps que és poden obtenir." +_accountMigration: + moveFrom: "Migrar un altre compte a aquest" + moveFromSub: "Crear un à lies per un altre compte" + moveFromLabel: "Compte original #{n}" + moveFromDescription: "Has de crear un à lies del compte que vols migrar en aquest compte.\nFes servir aquest format per posar el compte que vols migrar: @nomusuari@servidor.exemple.com\nPer esborrar l'à lies deixa el camp en blanc (no és recomanable de fer)" + moveTo: "Migrar aquest compte a un altre" + moveToLabel: "Compte al qual es vol migrar:" + moveCannotBeUndone: "Les migracions dels comptes no es poden desfer." + moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automà tica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automà tica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)" + moveAccountHowTo: "Per fer la migració, primer has de crear un à lies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'à lies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com" + startMigration: "Migrar" + migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podrà s tornar a fer servir aquest compte mai més." + movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer." + postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte." + movedTo: "Nou compte:" _achievements: + earnedAt: "Desbloquejat el" _types: + _notes1: + title: "AquÃ, configurant el meu msky" + description: "Publica la teva primera Nota" + flavor: "Passa-t'ho bé fent servir Miskey!" + _notes10: + title: "Algunes notes" + description: "Publica 10 notes" + _notes100: + title: "Un piló de notes" + description: "Publica 100 notes" + _notes500: + title: "Cobert de notes" + description: "Publica 500 notes" + _notes1000: + title: "Un piló de notes" + description: "1 000 notes publicades" + _notes5000: + title: "Desbordament de notes" + description: "5 000 notes publicades" + _notes10000: + title: "Supernota" + description: "10 000 notes publicades" + _notes20000: + title: "Necessito... Més... Notes!" + description: "20 000 notes publicades" + _notes30000: + title: "Notes notes notes!" + description: "30 000 notes publicades" + _notes40000: + title: "Fà brica de notes" + description: "40 000 notes publicades" + _notes50000: + title: "Planeta de notes" + description: "50 000 notes publicades" + _notes60000: + title: "Quà sar de notes" + description: "60 000 notes publicades" + _notes70000: + title: "Forat negre de notes" + description: "70 000 notes publicades" + _notes80000: + title: "Galà xia de notes" + description: "80 000 notes publicades" + _notes90000: + title: "Univers de notes" + description: "90 000 notes publicades" _notes100000: + title: "ALL YOUR NOTE ARE BELONG TO US" + description: "100 000 notes publicades" flavor: "Segur que tens moltes coses a dir?" _login3: title: "Principiant I" @@ -1347,42 +1464,413 @@ _achievements: description: "És la primera vegada que et segueixo" _following10: title: "Segueix-me... Segueix-me..." + description: "Seguir 10 usuaris" + _following50: + title: "Molts amics" + description: "Seguir 50 comptes" + _following100: + title: "100 amics" + description: "Segueixes 100 comptes" + _following300: + title: "Sobrecà rrega d'amics" + description: "Segueixes 300 comptes" + _followers1: + title: "Primer seguidor" + description: "1 seguidor guanyat" + _followers10: + title: "Segueix-me!" + description: "10 seguidors guanyats" + _followers50: + title: "Venen en manada" + description: "50 seguidors guanyats" + _followers100: + title: "Popular" + description: "100 seguidors guanyats" + _followers300: + title: "Si us plau, d'un en un!" + description: "300 seguidors guanyats" + _followers500: + title: "Torre de rà dio" + description: "500 seguidors guanyats" + _followers1000: + title: "Influenciador" + description: "1 000 seguidors guanyats" + _collectAchievements30: + title: "Col·leccionista d'èxits " + description: "Desbloqueja 30 assoliments" + _viewAchievements3min: + title: "M'agraden els èxits " + description: "Mira la teva llista d'assoliments durant més de 3 minuts" + _iLoveMisskey: + title: "Estimo Misskey" + description: "Publica \"I ⤠#Misskey\"" + flavor: "L'equip de desenvolupament de Misskey agraeix el vostre suport!" + _foundTreasure: + title: "A la Recerca del Tresor" + description: "Has trobat el tresor amagat" + _client30min: + title: "Parem una estona" + description: "Mantingues obert Misskey per 30 minuts" + _client60min: + title: "A totes amb Misskey" + description: "Mantingues Misskey obert per 60 minuts" + _noteDeletedWithin1min: + title: "No et preocupis" + description: "Esborra una nota al minut de publicar-la" + _postedAtLateNight: + title: "Nocturn" + description: "Publica una nota a altes hores de la nit " + flavor: "És hora d'anar a dormir." + _postedAt0min0sec: + title: "Rellotge xerraire" + description: "Publica una nota a les 0:00" + flavor: "Tic tac, tic tac, tic tac, DING!" + _selfQuote: + title: "Autoreferència " + description: "Cita una nota teva" + _htl20npm: + title: "LÃnia de temps fluida" + description: "La teva lÃnia de temps va a més de 20npm (notes per minut)" + _viewInstanceChart: + title: "Analista " + description: "Mira els grà fics de la teva instà ncia " + _outputHelloWorldOnScratchpad: + title: "Hola, món!" + description: "Escriu \"hola, món\" al bloc de notes" _open3windows: title: "Multi finestres" description: "I va obrir més de tres finestres" _driveFolderCircularReference: title: "Consulteu la secció de bucle" + description: "Intenta crear carpetes recursives al Disc" + _reactWithoutRead: + title: "De veritat has llegit això?" + description: "Reaccions a una nota de més de 100 carà cters publicada fa menys de 3 segons " + _clickedClickHere: + title: "Fer clic" + description: "Has fet clic aquà " + _justPlainLucky: + title: "Ha sigut sort" + description: "Oportunitat de guanyar-lo amb una probabilitat d'un 0.005% cada 10 segons" + _setNameToSyuilo: + title: "soc millor" + description: "Posat \"siuylo\" com a nom" + _passedSinceAccountCreated1: + title: "Primer aniversari" + description: "Ja ha passat un any d'ençà que vas crear el teu compte" + _passedSinceAccountCreated2: + title: "Segon aniversari" + description: "Ja han passat dos anys d'ençà que vas crear el teu compte" + _passedSinceAccountCreated3: + title: "Tres anys" + description: "Ja han passat tres anys d'ençà que vas crear el teu compte" + _loggedInOnBirthday: + title: "Felicitats!" + description: "T'has identificat el dia del teu aniversari" + _loggedInOnNewYearsDay: + title: "Bon any nou!" + description: "T'has identificat el primer dia de l'any " + flavor: "A per un altre any memorable a la teva instà ncia " + _cookieClicked: + title: "Un joc en què fas clic a les galetes" + description: "Pica galetes" + flavor: "Espera, ets al lloc web correcte?" + _brainDiver: + title: "Busseja Ments" + description: "Publica un enllaç al Busseja Ments" + flavor: "Misskey-Misskey La-Tu-Ma" + _smashTestNotificationButton: + title: "Sobrecà rrega de proves" + description: "Envia moltes notificacions de prova en un perÃode de temps molt curt" + _tutorialCompleted: + title: "Diploma del Curs Elemental de Misskey" + description: "Has completat el tutorial" + _bubbleGameExplodingHead: + title: "🤯" + description: "L'objecte més gran del joc de la bombolla " + _bubbleGameDoubleExplodingHead: + title: "Doble 🤯" + description: "Dos dels objectes més grans del joc de la bombolla al mateix temps" + flavor: "Pots emplenar una carmanyola com aquesta 🤯🤯 una mica" _role: + new: "Nou rol" + edit: "Editar el rol" + name: "Nom del rol" + description: "Descripció del rol" + permission: "Permisos de rol" + descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bà siques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor." assignTarget: "Assignar " + descriptionOfAssignTarget: "<b>Manual</b> per canviar manualment qui és part d'aquest rol i qui no.\n<b>Condicional</b> per afegir o eliminar de manera automà tica els usuaris d'aquest rol basat en una determinada condició." + manual: "Manual" + manualRoles: "Rols manuals" + conditional: "Condicional" + conditionalRoles: "Rols condicionals" + condition: "Condició" + isConditionalRole: "Aquest és un rol condicional" + isPublic: "Rol públic" + descriptionOfIsPublic: "Aquest rol es mostrarà al perfil dels usuaris al que se'ls assigni." + options: "Opcions" + policies: "PolÃtiques" + baseRole: "Plantilla de rols" + useBaseValue: "Fer servir els valors de la plantilla de rols" + chooseRoleToAssign: "Selecciona els rols a assignar" + iconUrl: "URL de la icona " + asBadge: "Mostrar com a insÃgnia " + descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol." + isExplorable: "Fer el rol explorable" + descriptionOfIsExplorable: "La lÃnia de temps d'aquest rol i la llista d'usuaris seran públics si s'activa." + displayOrder: "Posició " + descriptionOfDisplayOrder: "Com més gran és el número, més dalt la seva posició a la interfÃcie." + canEditMembersByModerator: "Permetre que els moderadors editin la llista d'usuaris en aquest rol" + descriptionOfCanEditMembersByModerator: "Quan s'activa, els moderadors, aixà com els administradors, podran afegir i treure usuaris d'aquest rol. Si es troba desactivat, només els administradors poden assignar usuaris." priority: "Prioritat" _priority: low: "Baixa" middle: "Mitjà " high: "Alta" _options: + gtlAvailable: "Pot veure la lÃnia de temps global" + ltlAvailable: "Pot veure la lÃnia de temps local" + canPublicNote: "Pot enviar notes públiques" + canInvite: "Pot crear invitacions a la instà ncia " + inviteLimit: "LÃmit d'invitacions " + inviteLimitCycle: "Temps de refresc de les invitacions" + inviteExpirationTime: "Interval de caducitat de les invitacions" canManageCustomEmojis: "Gestiona els emojis personalitzats" canManageAvatarDecorations: "Gestiona les decoracions dels avatars " + driveCapacity: "Capacitat del disc" + alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles" + pinMax: "Nombre mà xim de notes fixades" antennaMax: "Nombre mà xim d'antenes" + wordMuteMax: "Nombre mà xim de carà cters permesos a les paraules silenciades" + webhookMax: "Nombre mà xim de Webhooks" + clipMax: "Nombre mà xim de clips" + noteEachClipsMax: "Nombre mà xim de notes dintre d'un clip" + userListMax: "Nombre mà xim de llistes d'usuaris " + userEachUserListsMax: "Nombre mà xim d'usuaris dintre d'una llista d'usuaris " + rateLimitFactor: "Limitador" + descriptionOfRateLimitFactor: "LÃmits baixos són menys restrictius, lÃmits alts són més restrictius." + canHideAds: "Pot amagar els anuncis" + canSearchNotes: "Pot cercar notes" + canUseTranslator: "Pot fer servir el traductor" + avatarDecorationLimit: "Nombre mà xim de decoracions que es poden aplicar els avatars" + _condition: + isLocal: "Usuari local" + isRemote: "Usuari remot" + createdLessThan: "Han passat menys de X a passat des de la creació del compte" + createdMoreThan: "Han passat més de X des de la creació del compte" + followersLessThanOrEq: "Té menys de X seguidors" + followersMoreThanOrEq: "Té X o més seguidors" + followingLessThanOrEq: "Segueix X o menys comptes" + followingMoreThanOrEq: "Segueix a X o més comptes" + notesLessThanOrEq: "Les publicacions són menys o igual a " + notesMoreThanOrEq: "Les publicacions són més o igual a " + and: "AND condicional " + or: "OR condicional" + not: "NOT condicional" +_sensitiveMediaDetection: + description: "Redueix els esforços de moderació grà cies al reconeixement automà tic dels fitxers amb contingut sensible mitjançant Machine Learing. Això augmentarà la cà rrega del servidor." + sensitivity: "Sensibilitat de la detecció " + sensitivityDescription: "Reduint la sensibilitat provocarà menys falsos positius. D'altra banda incrementant-ho generarà més falsos negatius." + setSensitiveFlagAutomatically: "Marcar com a sensible" + setSensitiveFlagAutomaticallyDescription: "Els resultats de la detecció interna seran desats, inclòs si aquesta opció es troba desactivada." + analyzeVideos: "Activar anà lisis de vÃdeos " + analyzeVideosDescription: "Analitzar els vÃdeos a més de les imatges. Això incrementarà lleugerament la cà rrega del servidor." +_emailUnavailable: + used: "Aquest correu electrònic ja s'està fent servir" + format: "El format del correu electrònic és invà lid " + disposable: "No es poden fer servir adreces de correu electrònic d'un sol ús " + mx: "Aquest servidor de correu electrònic no és và lid " + smtp: "Aquest servidor de correu electrònic no respon" + banned: "No pots registrar-te amb aquesta adreça de correu electrònic " _ffVisibility: public: "Publicar" + followers: "Visible només per a seguidors " + private: "Privat" +_signup: + almostThere: "Ja quasi estem" + emailAddressInfo: "Si us plau, escriu la teva adreça de correu electrònic. No es farà pública." + emailSent: "S'ha enviat un correu de confirmació a ({email}). Si us plau, fes clic a l'enllaç per completar el registre." +_accountDelete: + accountDelete: "Eliminar el compte" + mayTakeTime: "Com l'eliminació d'un compte consumeix bastants recursos, pot trigar un temps perquè es completi l'esborrat, depenent si tens molt contingut i la quantitat de fitxer que hagis pujat." + sendEmail: "Una vegada hagi finalitzat l'esborrat del compte rebrà s un correu electrònic a l'adreça que tinguis registrada en aquest compte." + requestAccountDelete: "Demanar l'eliminació del compte" + started: "Ha començat l'esborrat del compte." + inProgress: "L'esborrat es troba en procés " _ad: back: "Tornar" + reduceFrequencyOfThisAd: "Mostrar menys aquest anunci" + hide: "No mostrar mai" + timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor." + adsSettings: "Configuració d'anuncis " + notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)" + setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real" + adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix." +_forgotPassword: + enterEmail: "Escriu l'adreça de correu electrònic amb la que et vas registrar. S'enviarà un correu electrònic amb un enllaç perquè puguis canviar-la." + ifNoEmail: "Si no vas fer servir una adreça de correu electrònic per registrar-te, si us plau posa't en contacte amb l'administrador." + contactAdmin: "Aquesta instà ncia no suporta registrar-se amb correu electrònic. Si us plau, contacta amb l'administrador del servidor." +_gallery: + my: "La meva Galeria " + liked: "Publicacions que t'han agradat" + like: "M'agrada " + unlike: "Ja no m'agrada" _email: _follow: title: "t'ha seguit" + _receiveFollowRequest: + title: "Has rebut una sol·licitud de seguiment" +_plugin: + install: "Instal·lar un afegit " + installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança." + manage: "Gestionar els afegits" + viewSource: "Veure l'origen " +_preferencesBackups: + list: "Llista de còpies de seguretat" + saveNew: "Fer una còpia de seguretat nova" + loadFile: "Carregar des d'un fitxer" + apply: "Aplicar en aquest dispositiu" + save: "Desar els canvis" + inputName: "Escriu un nom per aquesta còpia de seguretat" + cannotSave: "No s'ha pogut desar" + nameAlreadyExists: "Ja existeix una còpia de seguretat anomenada \"{name}\". Escriu un nom diferent." + applyConfirm: "Vols aplicar la còpia de seguretat \"{name}\" a aquest dispositiu? La configuració actual del dispositiu serà esborrada." + saveConfirm: "Desar còpia de seguretat com {name}?" + deleteConfirm: "Esborrar la còpia de seguretat {name}?" + renameConfirm: "Vols canvia el nom de la còpia de seguretat de \"{old}\" a \"{new}\"?" + noBackups: "No hi ha còpies de seguretat. Pots fer una còpia de seguretat de la configuració d'aquest dispositiu al servidor fent servir \"Crear nova còpia de seguretat\"" + createdAt: "Creat el: {date} {time}" + updatedAt: "Actualitzat el: {date} {time}" + cannotLoad: "Hi ha hagut un error al carregar" + invalidFile: "Format del fitxer no và lid " +_registry: + scope: "Àmbit " + key: "Clau" + keys: "Claus" + domain: "Domini" + createKey: "Crear una clau" +_aboutMisskey: + about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014" + contributors: "Col·laboradors principals" + allContributors: "Tots els col·laboradors " + source: "Codi font" + translation: "Tradueix Misskey" + donate: "Fes un donatiu a Misskey" + morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Grà cies! 🥰" + patrons: "Patrocinadors" + projectMembers: "Membres del projecte" +_displayOfSensitiveMedia: + respect: "Ocultar imatges o vÃdeos marcats com a sensibles" + ignore: "Mostrar imatges o vÃdeos marcats com a sensibles" + force: "Ocultar totes les imatges o vÃdeos " +_instanceTicker: + none: "No mostrar mai" + remote: "Mostrar per usuaris remots" + always: "Mostrar sempre" +_serverDisconnectedBehavior: + reload: "Recarregar automà ticament " + dialog: "Mostrar finestres de confirmació " + quiet: "Mostrar un avÃs que no molesti" +_channel: + create: "Crear un canal" + edit: "Editar canal" + setBanner: "Estableix el bà ner " + removeBanner: "Eliminar el.bà ner" + featured: "Popular" + owned: "Propietat" + following: "Seguin" + usersCount: "{n} Participants" + notesCount: "{n} Notes" + nameAndDescription: "Nom i descripció " + nameOnly: "Nom només " + allowRenoteToExternal: "Permet la citació i l'impuls fora del canal" _instanceMute: instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat." _theme: description: "Descripció" keys: + navHoverFg: "Text barra lateral (en passar per sobre)" + navActive: "Text barra lateral (actiu)" + navIndicator: "Indicador barra lateral" + link: "Enllaç" + hashtag: "Etiqueta" mention: "Menció" + mentionMe: "Mencions (jo)" renote: "Renotar" + modalBg: "Fons del modal" divider: "Divisor" + scrollbarHandle: "Maneta de la barra de desplaçament" + scrollbarHandleHover: "Maneta de la barra de desplaçament (en passar-hi per sobre)" + dateLabelFg: "Text de l'etiqueta de la data" + infoBg: "Fons d'informació " + infoFg: "Text d'informació " + infoWarnBg: "Fons avÃs " + infoWarnFg: "Text avÃs " + toastBg: "Fons notificació " + toastFg: "Text notificació " + buttonBg: "Fons botó " + buttonHoverBg: "Fons botó (en passar-hi per sobre)" + inputBorder: "Contorn del cap d'introducció " + listItemHoverBg: "Fons dels elements d'una llista" + driveFolderBg: "Fons de la carpeta Disc" + wallpaperOverlay: "Superposició del fons de pantalla " + badge: "InsÃgnia " + messageBg: "Fons del xat" + accentDarken: "Accent (fosc)" + accentLighten: "Accent (clar)" + fgHighlighted: "Text ressaltat" _sfx: note: "Notes" + noteMy: "Nota (per mi)" notification: "Notificacions" antenna: "Antenes" + channel: "Notificacions dels canals" + reaction: "Quan se selecciona una reacció " +_soundSettings: + driveFile: "Fer servir un fitxer d'à udio del disc" + driveFileWarn: "Seleccionar un fitxer d'à udio del disc" + driveFileTypeWarn: "Fitxer no suportat " + driveFileTypeWarnDescription: "Seleccionar un fitxer d'à udio " + driveFileDurationWarn: "L'à udio és massa llarg" + driveFileDurationWarnDescription: "Els à udios molt llargs pot interrompre l'ús de Misskey. Vols continuar?" +_ago: + future: "Futur " + justNow: "Ara mateix" + secondsAgo: "Fa {n} segons" + minutesAgo: "Fa {n} minuts" + hoursAgo: "Fa {n} hores" + daysAgo: "Fa {n} dies" + weeksAgo: "Fa {n} setmanes" + monthsAgo: "Fa {n} mesos" + yearsAgo: "Fa {n} anys" + invalid: "Res" +_timeIn: + seconds: "En {n} segons" + minutes: "En {n} minuts" + hours: "En {n} hores" + days: "En {n} dies" + weeks: "En {n} setmanes" + months: "En {n} mesos" + years: "En {n} anys" +_time: + second: "Segon(s)" + minute: "Minut(s)" + hour: "Hor(a)(es)" + day: "Di(a)(es)" _2fa: + alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor." + registerTOTP: "Registrar una aplicació autenticadora" + step1: "Primer instal·la una aplicació autenticadora (com {a} o {b}) al teu dispositiu." + step2: "Després escaneja el codi QR que es mostra en aquesta pantalla." + step2Click: "Fent clic en aquest codi QR et permetrà registrar l'autenticació de doble factor a la teva clau de seguretat o en l'aplicació d'autenticació del teu dispositiu." + step2Uri: "Escriu la següent URI si està s fent servir una aplicació d'escriptori " + step3Title: "Escriu un codi d'autenticació" + step3: "Escriu el codi d'autenticació (token) que es mostra a la teva aplicació per finalitzar la configuració." + setupCompleted: "Configuració terminada" + step4: "D'ara endavant quan accedeixis se't demanarà el token que has introduït." + securityKeyNotSupported: "El teu navegador no suporta claus de seguretat" + removeKeyConfirm: "Esborrar la còpia de seguretat {name}?" renewTOTPCancel: "No, grà cies" _antennaSources: all: "Totes les publicacions" @@ -1401,6 +1889,8 @@ _widgets: chooseList: "Tria una llista" _cw: show: "Carregar més" +_poll: + deadlineTime: "Hor(a)(es)" _visibility: home: "Inici" followers: "Seguidors" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 241e4cfc7b7d247ec533896d1926626fe4c3ad6f..6f838d4880c5856f663c4044095ee11e22d04b8e 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1005,6 +1005,7 @@ resetPasswordConfirm: "Opravdu chcete resetovat heslo?" sensitiveWords: "Citlivá slova" sensitiveWordsDescription: "Viditelnost vÅ¡ech poznámek obsahujÃcÃch nÄ›které z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést vÃce tak, že je oddÄ›lÃte pomocà řádků." sensitiveWordsDescription2: "Použità mezer vytvořà výrazy AND a obklopenà klÃÄových slov lomÃtky je zmÄ›nà na regulárnà výraz." +prohibitedWordsDescription2: "Použità mezer vytvořà výrazy AND a obklopenà klÃÄových slov lomÃtky je zmÄ›nà na regulárnà výraz." notesSearchNotAvailable: "Vyhledávánà poznámek je nedostupné." license: "Licence" unfavoriteConfirm: "Opravdu chcete odstranit z oblÃbených?" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 909ee5d45cfa3dfad490c23d5ec55860bd565793..67178924e35debe5b446ece0f7b94e432f116ee5 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1037,6 +1037,7 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?" sensitiveWords: "Sensible Wörter" sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden." sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden." +prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden." hiddenTags: "Ausgeblendete Hashtags" hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden." notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar." diff --git a/locales/en-US.yml b/locales/en-US.yml index 4c859861f7dee9e1feb88d88d4dd3b7de6efcbee..8a32d8307cfd1a7ca3b6b6f275185129dc2b307f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1078,6 +1078,7 @@ resetPasswordConfirm: "Really reset your password?" sensitiveWords: "Sensitive words" sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks." sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." +prohibitedWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." hiddenTags: "Hidden hashtags" hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines." notesSearchNotAvailable: "Note search is unavailable." @@ -2023,54 +2024,30 @@ _permissions: "read:flash-likes": "View list of liked Plays" "write:flash-likes": "Edit list of liked Plays" "read:admin:abuse-user-reports": "View user reports" - "write:admin:delete-account": "Delete account" + "write:admin:delete-account": "Delete user account" "write:admin:delete-all-files-of-a-user": "Delete all files of a user" - "read:admin:index-stats": "View information about database indexes" - "read:admin:table-stats": "View information about database tables" - "read:admin:user-ips": "View user IP address" "read:admin:meta": "View instance metadata" - "write:admin:reset-password": "Reset user passwords" - "write:admin:resolve-abuse-user-report": "Resolve user reports" - "write:admin:send-email": "Send Email" + "write:admin:reset-password": "Reset user password" + "write:admin:send-email": "Send email" "read:admin:server-info": "View server info" "read:admin:show-moderation-log": "View moderation log" - "read:admin:show-user": "View user information" - "read:admin:show-users": "View users" + "read:admin:show-user": "View private user info" + "read:admin:show-users": "View private user info" "write:admin:suspend-user": "Suspend user" - "write:admin:unset-user-avatar": "Remove avatar from user" - "write:admin:unset-user-banner": "Remove banner from user" + "write:admin:unset-user-avatar": "Remove user avatar" + "write:admin:unset-user-banner": "Remove user banner" "write:admin:unsuspend-user": "Unsuspend user" - "write:admin:meta": "Edit instance metadata" - "write:admin:user-note": "Edit user note" - "write:admin:roles": "Edit roles" + "write:admin:meta": "Manage instance metadata" + "write:admin:user-note": "Manage moderation note" + "write:admin:roles": "Manage roles" "read:admin:roles": "View roles" - "write:admin:relays": "Edit relays" + "write:admin:relays": "Manage relays" "read:admin:relays": "View relays" - "write:admin:invite-codes": "Edit invite codes" + "write:admin:invite-codes": "Manage invite codes" "read:admin:invite-codes": "View invite codes" - "write:admin:announcements": "Edit announcements" + "write:admin:announcements": "Manage announcements" "read:admin:announcements": "View announcements" - "write:admin:avatar-decorations": "Edit avatar decorations" - "read:admin:avatar-decorations": "View avatar decorations" - "write:admin:federation": "Edit remote instance information" - "write:admin:account": "Edit users" - "read:admin:account": "View information about user" - "write:admin:emoji": "Edit emojis" - "read:admin:emoji": "View emojis" - "write:admin:queue": "Edit queue" - "read:admin:queue": "View queue" - "write:admin:promo": "Edit promo" - "write:admin:drive": "Edit user drive" - "read:admin:drive": "View user drive" - "read:admin:stream": "Using the Websocket API for Admin" - "write:admin:ad": "Edit ads" - "read:admin:ad": "View ads" - "write:invite-codes": "Create Invitation Code" - "read:invite-codes": "View Invitation Code" - "write:clip-favorite": "Edit clips and likes" - "read:clip-favorite": "View clips and likes" - "read:federation": "View information about remote instance" - "write:report-abuse": "Report abuse" + "write:admin:avatar-decorations": "Manage avatar decorations" _auth: shareAccessTitle: "Granting application permissions" shareAccess: "Would you like to authorize \"{name}\" to access this account?" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index e8e9761d26e5685e77568d63a8f35e086fcf465b..96926dc38dbfdbd3ea025487d6d2b6c8f8a5aee0 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?" sensitiveWords: "Palabras sensibles" sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de lÃnea" sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares." +prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares." hiddenTags: "Hashtags ocultos" hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por lÃnea." notesSearchNotAvailable: "No se puede buscar una nota" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 4093dee6d3628b5cb682586ccbb8a52184434cf9..30a5955ff3a6ea7e242f61062505231b292cd4a1 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1038,6 +1038,7 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?" sensitiveWords: "Kata sensitif" sensitiveWordsDescription: "Visibilitas dari semua catatan mengandung kata yang telah diatur akan dijadikan \"Beranda\" secara otomatis. Kamu dapat mendaftarkan kata tersebut lebih dari satu dengan menuliskannya di baris baru." sensitiveWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler." +prohibitedWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler." hiddenTags: "Tagar tersembunyi" hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris." notesSearchNotAvailable: "Pencarian catatan tidak tersedia." diff --git a/locales/index.d.ts b/locales/index.d.ts index 686fa57cd5a8e372becb6f64c59d91c2762e2675..012cbe055a2d764bac31fac474b454ec34d663b7 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4329,6 +4329,18 @@ export interface Locale extends ILocale { * スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã«ãªã‚Šã€ã‚ーワードをスラッシュã§å›²ã‚€ã¨æ£è¦è¡¨ç¾ã«ãªã‚Šã¾ã™ã€‚ */ "sensitiveWordsDescription2": string; + /** + * ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ + */ + "prohibitedWords": string; + /** + * è¨å®šã—ãŸãƒ¯ãƒ¼ãƒ‰ãŒå«ã¾ã‚Œã‚‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—よã†ã¨ã—ãŸéš›ã€ã‚¨ãƒ©ãƒ¼ã¨ãªã‚‹ã‚ˆã†ã«ã—ã¾ã™ã€‚改行ã§åŒºåˆ‡ã£ã¦è¤‡æ•°è¨å®šã§ãã¾ã™ã€‚ + */ + "prohibitedWordsDescription": string; + /** + * スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã«ãªã‚Šã€ã‚ーワードをスラッシュã§å›²ã‚€ã¨æ£è¦è¡¨ç¾ã«ãªã‚Šã¾ã™ã€‚ + */ + "prohibitedWordsDescription2": string; /** * éžè¡¨ç¤ºãƒãƒƒã‚·ãƒ¥ã‚¿ã‚° */ diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 0c033373432af3686abd925757f18fdf3f424f3f..69720871c978ba732b388b6f17566a7aee87f9bc 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -103,7 +103,7 @@ defaultNoteVisibility: "Privacy predefinita delle note" follow: "Segui" followRequest: "Richiesta di follow" followRequests: "Richieste di follow" -unfollow: "Interrompi following" +unfollow: "Smetti di seguire" followRequestPending: "Richiesta in approvazione" enterEmoji: "Inserisci emoji" renote: "Rinota" @@ -381,9 +381,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "Abilita hCaptcha" hcaptchaSiteKey: "Chiave del sito" hcaptchaSecretKey: "Chiave segreta" +mcaptcha: "mCaptcha" enableMcaptcha: "Abilita hCaptcha" mcaptchaSiteKey: "Chiave del sito" mcaptchaSecretKey: "Chiave segreta" +mcaptchaInstanceUrl: "URL della istanza mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Abilita reCAPTCHA" recaptchaSiteKey: "Chiave del sito" @@ -631,6 +633,7 @@ medium: "Medio" small: "Piccolo" generateAccessToken: "Genera token di accesso" permission: "Autorizzazioni " +adminPermission: "Privilegi amministrativi" enableAll: "Abilita tutto" disableAll: "Disabilita tutto" tokenRequested: "Autorizza accesso al profilo" @@ -674,6 +677,7 @@ useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifi other: "Ulteriori" regenerateLoginToken: "Genera di nuovo un token di connessione" regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi." +theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate" setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi." fileIdOrUrl: "ID o URL del file" behavior: "Comportamento" @@ -872,7 +876,7 @@ pubSub: "Publish/Subscribe del profilo" lastCommunication: "La comunicazione più recente" resolved: "Risolto" unresolved: "Non risolto" -breakFollow: "Interrompi follow" +breakFollow: "Impedire di seguirmi" breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?" itsOn: "Abilitato" itsOff: "Disabilitato" @@ -888,6 +892,8 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di classic: "Classico" muteThread: "Silenzia conversazione" unmuteThread: "Riattiva la conversazione" +followingVisibility: "Visibilità dei profili seguiti" +followersVisibility: "Visibilità dei profili che ti seguono" continueThread: "Altre conversazioni" deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?" incorrectPassword: "La password è errata." @@ -1039,6 +1045,7 @@ resetPasswordConfirm: "Vuoi davvero ripristinare la password?" sensitiveWords: "Parole esplicite" sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga." sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare." +prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare." hiddenTags: "Hashtag nascosti" hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga." notesSearchNotAvailable: "Non è possibile cercare tra le Note." @@ -1057,6 +1064,8 @@ limitWidthOfReaction: "Limita la larghezza delle reazioni e ridimensionale" noteIdOrUrl: "ID della Nota o URL" video: "Video" videos: "Video" +audio: "Audio" +audioFiles: "Audio" dataSaver: "Risparmia dati" accountMigration: "Migrazione del profilo" accountMoved: "Questo profilo ha migrato altrove:" @@ -1187,7 +1196,27 @@ remainingN: "Rimangono: {n}" overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?" seasonalScreenEffect: "Schermate in base alla stagione" decorate: "Decora" +addMfmFunction: "Aggiungi decorazioni" +enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM" +bubbleGame: "Bubble Game" +sfx: "Effetti sonori" +soundWillBePlayed: "Verrà riprodotto il suono" +showReplay: "Vedi i replay" +replay: "Replay" +replaying: "Replay in corso" +ranking: "Classifica" lastNDays: "Ultimi {n} giorni" +backToTitle: "Torna al titolo" +hemisphere: "Geolocalizzazione" +withSensitive: "Mostra le Note con allegati espliciti" +userSaysSomethingSensitive: "Note da {name} con allegati espliciti" +enableHorizontalSwipe: "Trascina per invertire i tab" +_bubbleGame: + howToPlay: "Come giocare" + _howToPlay: + section1: "Regola la posizione e rilascia l'oggetto nella casella." + section2: "Ottieni un punteggio, quando due oggetti dello stesso tipo si toccano e si trasformano in un oggetto diverso." + section3: "Se gli oggetti traboccano dalla scatola, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dalla scatola!" _announcement: forExistingUsers: "Solo ai profili attuali" forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio." @@ -1558,6 +1587,13 @@ _achievements: _tutorialCompleted: title: "Attestato di partecipazione al corso per principianti di Misskey" description: "Ha completato il tutorial" + _bubbleGameExplodingHead: + title: "🤯" + description: "Estrai l'oggetto più grande dal Bubble Game" + _bubbleGameDoubleExplodingHead: + title: "Doppio 🤯" + description: "Due oggetti più grossi contemporaneamente nel Bubble Game" + flavor: "Ha le dimensioni di una bento-box 🤯 🤯" _role: new: "Nuovo ruolo" edit: "Modifica ruolo" @@ -1648,6 +1684,7 @@ _emailUnavailable: disposable: "Indirizzo email non utilizzabile" mx: "Server email non corretto" smtp: "Il server email non risponde" + banned: "Non puoi registrarti con questo indirizzo email" _ffVisibility: public: "Pubblica" followers: "Mostra solo ai follower" @@ -1940,6 +1977,26 @@ _permissions: "write:flash": "Modifica Play" "read:flash-likes": "Visualizza lista di Play piaciuti" "write:flash-likes": "Modifica lista di Play piaciuti" + "read:admin:abuse-user-reports": "Mostra i report dai profili utente" + "write:admin:delete-account": "Elimina l'account utente" + "write:admin:delete-all-files-of-a-user": "Elimina i file dell'account utente" + "read:admin:index-stats": "Visualizza informazioni sugli indici del database" + "read:admin:table-stats": "Visualizza informazioni sulle tabelle del database" + "read:admin:user-ips": "Visualizza indirizzi IP degli account" + "read:admin:meta": "Visualizza i metadati dell'istanza" + "write:admin:reset-password": "Ripristina la password dell'account utente" + "write:admin:resolve-abuse-user-report": "Risolvere le segnalazioni dagli account utente" + "write:admin:send-email": "Spedire email" + "read:admin:server-info": "Vedere le informazioni sul server" + "read:admin:show-moderation-log": "Vedere lo storico di moderazione" + "read:admin:show-user": "Vedere le informazioni private degli account utente" + "read:admin:show-users": "Vedere le informazioni private degli account utente" + "write:admin:suspend-user": "Sospendere i profili" + "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili" + "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili" + "write:admin:unsuspend-user": "Togliere la sospensione ai profili" + "write:admin:meta": "Modificare i metadati dell'istanza" + "write:admin:user-note": "Scrivere annotazioni di moderazione" _auth: shareAccessTitle: "Permessi dell'applicazione" shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f6879acda15840c50f3b4070e92d2f42d95ec8c4..9714f8f66845f9982828b8fa15dc04c82d67dda3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1078,6 +1078,9 @@ resetPasswordConfirm: "パスワードリセットã—ã¾ã™ã‹ï¼Ÿ" sensitiveWords: "センシティブワード" sensitiveWordsDescription: "è¨å®šã—ãŸãƒ¯ãƒ¼ãƒ‰ãŒå«ã¾ã‚Œã‚‹ãƒŽãƒ¼ãƒˆã®å…¬é–‹ç¯„囲をホームã«ã—ã¾ã™ã€‚改行ã§åŒºåˆ‡ã£ã¦è¤‡æ•°è¨å®šã§ãã¾ã™ã€‚" sensitiveWordsDescription2: "スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã«ãªã‚Šã€ã‚ーワードをスラッシュã§å›²ã‚€ã¨æ£è¦è¡¨ç¾ã«ãªã‚Šã¾ã™ã€‚" +prohibitedWords: "ç¦æ¢ãƒ¯ãƒ¼ãƒ‰" +prohibitedWordsDescription: "è¨å®šã—ãŸãƒ¯ãƒ¼ãƒ‰ãŒå«ã¾ã‚Œã‚‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—よã†ã¨ã—ãŸéš›ã€ã‚¨ãƒ©ãƒ¼ã¨ãªã‚‹ã‚ˆã†ã«ã—ã¾ã™ã€‚改行ã§åŒºåˆ‡ã£ã¦è¤‡æ•°è¨å®šã§ãã¾ã™ã€‚" +prohibitedWordsDescription2: "スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã«ãªã‚Šã€ã‚ーワードをスラッシュã§å›²ã‚€ã¨æ£è¦è¡¨ç¾ã«ãªã‚Šã¾ã™ã€‚" hiddenTags: "éžè¡¨ç¤ºãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°" hiddenTagsDescription: "è¨å®šã—ãŸã‚¿ã‚°ã‚’トレンドã«è¡¨ç¤ºã•ã›ãªã„よã†ã«ã—ã¾ã™ã€‚改行ã§åŒºåˆ‡ã£ã¦è¤‡æ•°è¨å®šã§ãã¾ã™ã€‚" notesSearchNotAvailable: "ノート検索ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 5d5b175e0c2844bb1b60f721f77bd7c60934cd8f..b4787c636f116bb78c73c8de4aaa49f8400b1927 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1043,6 +1043,7 @@ resetPasswordConfirm: "パスワード作り直ã™ã‚“ã§ãˆãˆãªï¼Ÿ" sensitiveWords: "ã‘ã£ãŸã„ãªå˜èªž" sensitiveWordsDescription: "è¨å®šã—ãŸå˜èªžãŒå…¥ã£ã¨ã‚‹ãƒŽãƒ¼ãƒˆã®å…¬é–‹ç¯„囲をホームã«ã—ãŸã‚‹ã‚。改行ã§åŒºåˆ‡ã£ãŸã‚‰è¤‡æ•°è¨å®šã§ãã‚‹ã§ã€‚" sensitiveWordsDescription2: "スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã€ã‚ーワードをスラッシュã§å›²ã‚“ã らæ£è¦è¡¨ç¾ã‚„。" +prohibitedWordsDescription2: "スペースã§åŒºåˆ‡ã‚‹ã¨AND指定ã€ã‚ーワードをスラッシュã§å›²ã‚“ã らæ£è¦è¡¨ç¾ã‚„。" hiddenTags: "見ãˆã¦ã¸ã‚“ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°" hiddenTagsDescription: "è¨å®šã—ãŸã‚¿ã‚°ã‚’最近æµè¡Œã‚Šã®ã¨ã“ã«è¦‹ãˆã‚“よã†ã«ã™ã‚“ã§ã€‚複数è¨å®šã™ã‚‹ã¨ãã¯æ”¹è¡Œã§åŒºåˆ‡ã£ã¦ãªã€‚" notesSearchNotAvailable: "ãªã‚“ã‹ãƒŽãƒ¼ãƒˆæŽ¢ã›ã¸ã‚“。" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 29bfe5394d76a12c5f50f5449d4b26bf166d98cb..b1702114be09eb157effcf7088c1952803ab58c9 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -40,7 +40,7 @@ favorites: "질겨찾기" unfavorite: "질겨찾기서 á„‹á…¥á‡ì• 기" favorited: "ì§ˆê²¨ì°¾ê¸°ì— ë‹´ì•—ì‹ë‹ˆë‹¤." alreadyFavorited: "벌시로 ì§ˆê²¨ì°¾ê¸°ì— ë‹´ê¸° 잇ì‹ë‹ˆë‹¤." -cantFavorite: "ì§ˆê²¨ì°¾ê¸°ì— ëª¬ 담았ì‹ë‹ˆë‹¤." +cantFavorite: "ì§ˆê²¨ì°¾ê¸°ì— ëª¬ ë‹´ì•—ì‹ë‹ˆë‹¤." pin: "í”„ë¡œí•„ì— ë¶™ì´ê¸°" unpin: "프로필서 ë 기" copyContent: "ë‚´ìš© 복사하기" @@ -124,6 +124,7 @@ reactions: "반엉" reactionSettingDescription2: "꺼시서 ë‘ê³ , 누질ë¼ì„œ ë‰ìºê³ , ‘+’럴 누질ë¼ì„œ 옇ì‹ë‹ˆë‹¤." rememberNoteVisibility: "공개 범위럴 기억하기" attachCancel: "붙임 빼기" +deleteFile: "íŒŒì¼ ë‰ìºê¸°" markAsSensitive: "수ᇚ힘 ì„¤ì •" unmarkAsSensitive: "수ᇚ힘 무루기" enterFileName: "íŒŒì¼ ì´ëŸ¼ 서기" @@ -463,6 +464,8 @@ onlyOneFileCanBeAttached: "메시지엔 íŒŒì¼ í•˜ë‚˜ê¹Œì œë°–ì— ëª¬ ë„£ì‹ë‹ˆ invitations: "초대하기" invitationCode: "초대장" checking: "í•™ì¸í•˜ê³ 잇ì‹ë‹ˆë‹¤" +tooShort: "억수로 짜립니다" +tooLong: "억수로 집니다" passwordMatched: "맞ì‹ë‹ˆë‹¤" passwordNotMatched: "안 맞ì‹ë‹ˆë‹¤" signinFailed: "ë¡œê·¸ì¸ ëª¬ í–ˆì‹ë‹ˆë‹¤. ê³ ì´ë¦„ì´ëž‘ 비밀번호 ì œëŒ€ë¡œ ì¼ëŠ”ê°€ 확ì¸í•´ 주ì´ì†Œ." @@ -571,7 +574,11 @@ userSilenced: "ìš” ê²Œì •ì€... 수ᇚ혀 있ì‹ë‹ˆë‹¤." relays: "ë¦´ë ˆì´" addRelay: "ë¦´ë ˆì´ ì˜‡ê¸°" addedRelays: "ì˜‡ì€ ë¦´ë ˆì´" +deletedNote: "ë‰ìº” 걸" enableInfiniteScroll: "알아서 ë” ë³´ê¸°" +useCw: "ë‚´ìš© 수ᇚ후기" +description: "설멩" +describeFile: "캡션 옇기" author: "ë§¨ë˜ ì‚¬ëžŒ" manage: "간리" emailServer: "ì „ìžìš°íŽœ 서버" @@ -600,6 +607,7 @@ renotesCount: "리노트한 수" renotedCount: "ë¦¬ë…¸íŠ¸ë´ ìˆ˜" followingCount: "팔로우 수" followersCount: "팔로워 수" +noteFavoritesCount: "질겨찾기한 노트 수" clips: "í´ë¦½ 맨걸기" clearCache: "ìºì‹œ 비우기" unlikeConfirm: "좋네예럴 무룹니꺼?" @@ -608,6 +616,7 @@ user: "사용ìž" administration: "간리" on: "í‚´" off: "껌" +hide: "수ᇚ후기" clickToFinishEmailVerification: "[{ok}]럴 누질ë¼ì„œ ì „ìžìš°íŽœ ì •ë©©ì–¼ 껕내ì´ì†Œ." searchByGoogle: "찾기" tenMinutes: "ì‹ ë¶„" @@ -626,9 +635,11 @@ role: "ì˜‰í• " noRole: "ì˜‰í• ì´ ì—†ì‹ë‹ˆë‹¤" thisPostMayBeAnnoyingCancel: "ì•„ì´ì˜ˆ" likeOnly: "좋네예마" +myClips: "ë‚´ í´ë¦½" icon: "아바타" replies: "답하기" renotes: "리노트" +attach: "옇기" _initialAccountSetting: startTutorial: "길ë¼ìž¡ì´ 하기" _initialTutorial: @@ -641,9 +652,52 @@ _initialTutorial: title: "길ë¼ìž¡ì´ê°€ 껕낫ì‹ë‹ˆë‹¤!🎉" _achievements: _types: + _notes1: + description: "첫 노트럴 섯어예" + _notes10: + description: "노트럴 10번 섰어예" + _notes100: + description: "노트럴 100번 섰어예" + _notes500: + description: "노트럴 500번 섰어예" + _notes1000: + description: "노트럴 1,000번 섰어예" + _notes5000: + description: "노트럴 5,000번 섰어예" + _notes10000: + description: "노트럴 10,000번 섰어예" + _notes20000: + description: "노트럴 20,000번 섰어예" + _notes30000: + description: "노트럴 30,000번 섰어예" + _notes40000: + description: "노트럴 40,000번 섰어예" + _notes50000: + description: "노트럴 50,000번 섰어예" + _notes60000: + description: "노트럴 60,000번 섰어예" + _notes70000: + description: "노트럴 70,000번 섰어예" + _notes80000: + description: "노트럴 80,000번 섰어예" + _notes90000: + description: "노트럴 90,000번 섰어예" + _notes100000: + description: "노트럴 100,000번 섰어예" + _noteClipped1: + description: "첫 노트럴 í´ë¦½í–‡ì–´ì˜ˆ" + _noteFavorited1: + description: "첫 노트럴 ì§ˆê²¨ì°¾ê¸°ì— ë‹´ì•—ì–´ì˜ˆ" + _myNoteFavorited1: + description: "다런 ì‚¬ëžŒì´ ë‚´ 노트럴 ì§ˆê²¨ì°¾ê¸°ì— ë‹´ì•—ì‹ë‹ˆë‹¤" + _iLoveMisskey: + description: "“I ⤠#Misskeyâ€ëŸ´ 섰어예" + _postedAt0min0sec: + description: "0분 0ì´ˆì— ë…¸íŠ¸ë¥¼ 섰어예" _tutorialCompleted: description: "길ë¼ìž¡ì´ëŸ´ 껕냇ì‹ë‹ˆë‹¤" _gallery: + my: "ë‚´ 걸" liked: "좋네예한 걸" like: "좋네예!" unlike: "좋네예 무루기" @@ -654,7 +708,12 @@ _serverDisconnectedBehavior: reload: "알아서 새로곤침" _channel: removeBanner: "배너 ë‰ìºê¸°" + usersCount: "{n}명 참여" + notesCount: "노트 {n}ê°œ" +_menuDisplay: + hide: "수ᇚ후기" _theme: + description: "설멩" keys: mention: "멘션" _sfx: @@ -663,6 +722,9 @@ _sfx: _2fa: step3Title: "í•™ì¸ ê¸°í˜¸ëŸ´ 서기" renewTOTPCancel: "뎃어예" +_permissions: + "read:favorites": "질겨찾기 보기" + "write:favorites": "질겨찾기 곤치기" _widgets: profile: "프로필" instanceInfo: "서버 ì •ë³´" @@ -674,7 +736,10 @@ _widgets: _userList: chooseList: "리스트 개리기" _cw: + hide: "수ᇚ후기" show: "ë” ë³¼ëž˜ì˜ˆ" + chars: "ê±¸ìž {count}ê°œ" + files: "íŒŒì¼ {count}ê°œ" _visibility: home: "ëœë¨¸ë¦¬" followers: "팔로워" @@ -682,6 +747,7 @@ _profile: name: "ì´ëŸ¼" username: "ì‚¬ìš©ìž ì´ëŸ¼" _exportOrImport: + favoritedNotes: "질겨찾기한 노트" clips: "í´ë¦½ 맨걸기" followingList: "팔로잉" muteList: "수ᇚ후기" @@ -692,16 +758,20 @@ _charts: _timelines: home: "ëœë¨¸ë¦¬" _play: + my: "ë‚´ í”Œë ˆì´" script: "스í¬ë¦½íŠ¸" + summary: "설멩" _pages: like: "좋네예" unlike: "좋네예 무루기" + my: "ë‚´ 페ì´ì§€" blocks: image: "ì´ë¯¸ì§€" _note: id: "노트 ì•„ì´ë””" _notification: youWereFollowed: "새 팔로워가 잇ì‹ë‹ˆë‹¤" + newNote: "새 걸" _types: follow: "팔로잉" mention: "멘션" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 718d04caaeb267c0c7f5697f1db0d22c1005d32a..1231209b36911567dae3833fc3575a4b0c270568 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -279,7 +279,7 @@ uploadFromUrl: "URL 업로드" uploadFromUrlDescription: "ì—…ë¡œë“œí•˜ë ¤ëŠ” 파ì¼ì˜ URL" uploadFromUrlRequested: "업로드를 ìš”ì²í–ˆìŠµë‹ˆë‹¤" uploadFromUrlMayTakeTime: "업로드가 완료ë 때까지 ì‹œê°„ì´ ì†Œìš”ë 수 있습니다." -explore: "발견하기" +explore: "둘러보기" messageRead: "ì½ìŒ" noMoreHistory: "ì´ê²ƒë³´ë‹¤ ê³¼ê±°ì˜ ê¸°ë¡ì´ 없습니다" startMessaging: "대화 시작하기" @@ -1022,7 +1022,7 @@ internalServerError: "내부 서버 오류" internalServerErrorDescription: "내부 서버ì—ì„œ 예기치 ì•Šì€ ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤." copyErrorInfo: "오류 ì •ë³´ 복사" joinThisServer: "ì´ ì„œë²„ì— ê°€ìž…" -exploreOtherServers: "다른 서버 둘러보기" +exploreOtherServers: "다른 서버 찾기" letsLookAtTimeline: "타임ë¼ì¸ 구경하기" disableFederationConfirm: "ì •ë§ë¡œ ì—°í•©ì„ ë„ì‹œê² ìŠµë‹ˆê¹Œ?" disableFederationConfirmWarn: "ì—°í•©ì„ ë„ë”ë¼ë„ ê²Œì‹œë¬¼ì´ ë¹„ê³µê°œë¡œ ì „í™˜ë˜ëŠ” ê²ƒì€ ì•„ë‹™ë‹ˆë‹¤. ëŒ€ë¶€ë¶„ì˜ ê²½ìš° ì—°í•©ì„ ë¹„í™œì„±í™”í• í•„ìš”ê°€ 없습니다." @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "비밀번호를 ìž¬ì„¤ì •í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" sensitiveWords: "민ê°í•œ 단어" sensitiveWordsDescription: "ì„¤ì •í•œ 단어가 í¬í•¨ëœ ë…¸íŠ¸ì˜ ê³µê°œ 범위를 '홈'으로 ê°•ì œí•©ë‹ˆë‹¤. 개행으로 구분하여 여러 개를 ì§€ì •í• ìˆ˜ 있습니다." sensitiveWordsDescription2: "공백으로 구분하면 AND ì§€ì •ì´ ë˜ë©°, 키워드를 슬래시로 둘러싸면 ì •ê·œ 표현ì‹ì´ ë©ë‹ˆë‹¤." +prohibitedWordsDescription2: "공백으로 구분하면 AND ì§€ì •ì´ ë˜ë©°, 키워드를 슬래시로 둘러싸면 ì •ê·œ 표현ì‹ì´ ë©ë‹ˆë‹¤." hiddenTags: "숨긴 해시태그" hiddenTagsDescription: "ì„¤ì •í•œ 태그를 íŠ¸ë Œë“œì— í‘œì‹œí•˜ì§€ ì•Šë„ë¡ í•©ë‹ˆë‹¤. 줄 바꿈으로 하나씩 ë‚˜ëˆ ì„œ ì„¤ì •í• ìˆ˜ 있습니다." notesSearchNotAvailable: "노트 ê²€ìƒ‰ì„ ì´ìš©í•˜ì‹¤ 수 없습니다." @@ -1797,7 +1798,7 @@ _instanceMute: title: "ì§€ì •í•œ ì„œë²„ì˜ ë…¸íŠ¸ë¥¼ 숨ê¹ë‹ˆë‹¤." heading: "ë®¤íŠ¸í• ì„œë²„" _theme: - explore: "테마 찾아보기" + explore: "테마 둘러보기" install: "테마 설치" manage: "테마 관리" code: "테마 코드" @@ -2022,7 +2023,7 @@ _permissions: "write:report-abuse": "위반 ë‚´ìš© ì‹ ê³ í•˜ê¸°" _auth: shareAccessTitle: "어플리케ì´ì…˜ì˜ ì ‘ê·¼ 허가" - shareAccess: "\"{name}\" ì´ ê³„ì •ì— ì ‘ê·¼í•˜ëŠ” ê²ƒì„ í—ˆìš©í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + shareAccess: "‘{name}’ì—ì„œ ê³„ì •ì— ì ‘ê·¼í•˜ëŠ” ê²ƒì„ í—ˆìš©í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" shareAccessAsk: "ì´ ì• í”Œë¦¬ì¼€ì´ì…˜ì´ ê³„ì •ì— ì ‘ê·¼í•˜ëŠ” ê²ƒì„ í—ˆìš©í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" permission: "{name}ì—ì„œ ë‹¤ìŒ ê¶Œí•œì„ ìš”ì²í•˜ì˜€ìŠµë‹ˆë‹¤" permissionAsk: "ì´ ì•±ì€ ë‹¤ìŒì˜ ê¶Œí•œì„ ìš”ì²í•©ë‹ˆë‹¤" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 60682fe961cfa80b00c500cf77de32ac3d1b1c3b..d014b7fc259e72c5093dedd201eb3ec668123890 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1015,6 +1015,7 @@ resetPasswordConfirm: "СброÑить пароль?" sensitiveWords: "ЧувÑтвительные Ñлова" sensitiveWordsDescription: "УÑтановите общедоÑтупный диапазон заметки, Ñодержащей заданное Ñлово, на домашний. Можно Ñделать неÑколько наÑтроек, разделив их переноÑами Ñтрок." sensitiveWordsDescription2: "Разделение пробелом Ñоздаёт Ñпецификацию AND, а разделение коÑой чертой Ñоздаёт регулÑрное выражение." +prohibitedWordsDescription2: "Разделение пробелом Ñоздаёт Ñпецификацию AND, а разделение коÑой чертой Ñоздаёт регулÑрное выражение." notesSearchNotAvailable: "ПоиÑк заметок недоÑтупен" license: "ЛицензиÑ" unfavoriteConfirm: "Удалить избранное?" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index b14d855566c61969fec1b1411d5a91a3a06627b8..cfba65685b641c1f4ab5bfc3372c7b96530635e1 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "รีเซ็ตรหัสผ่านขà¸à¸‡à¸„ุ sensitiveWords: "คำที่มีเนื้à¸à¸«à¸²à¸¥à¸°à¹€à¸à¸µà¸¢à¸”à¸à¹ˆà¸à¸™" sensitiveWordsDescription: "à¸à¸²à¸£à¹€à¸›à¸´à¸”เผยโน้ตทั้งหมดที่มีคำที่à¸à¸³à¸«à¸™à¸”ค่าไว้จะถูà¸à¸•à¸±à¹‰à¸‡à¸„่าเป็น \"หน้าà¹à¸£à¸\" โดยà¸à¸±à¸•à¹‚นมัติ คุณยังสามารถà¹à¸ªà¸”งหลายรายà¸à¸²à¸£à¹„ด้โดยà¹à¸¢à¸à¸£à¸²à¸¢à¸à¸²à¸£à¹‚ดยใช้ตัวà¹à¸šà¹ˆà¸‡à¸šà¸£à¸£à¸—ัดได้นะ" sensitiveWordsDescription2: "à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸Šà¹ˆà¸à¸‡à¸§à¹ˆà¸²à¸‡à¸™à¸±à¹‰à¸™à¸à¸²à¸ˆà¸ˆà¸°à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸´à¸žà¸ˆà¸™à¹Œ AND à¹à¸¥à¸°à¸„ำหลัà¸à¸—ี่มีเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¸—ับล้à¸à¸¡à¸£à¸à¸šà¸ˆà¸°à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸›à¹‡à¸™à¸™à¸´à¸žà¸ˆà¸™à¹Œà¸—ั่วไปนะ" +prohibitedWordsDescription2: "à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸Šà¹ˆà¸à¸‡à¸§à¹ˆà¸²à¸‡à¸™à¸±à¹‰à¸™à¸à¸²à¸ˆà¸ˆà¸°à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸´à¸žà¸ˆà¸™à¹Œ AND à¹à¸¥à¸°à¸„ำหลัà¸à¸—ี่มีเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¸—ับล้à¸à¸¡à¸£à¸à¸šà¸ˆà¸°à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸›à¹‡à¸™à¸™à¸´à¸žà¸ˆà¸™à¹Œà¸—ั่วไปนะ" hiddenTags: "à¹à¸®à¸Šà¹à¸—็à¸à¸—ี่ซ่à¸à¸™à¸à¸¢à¸¹à¹ˆ" hiddenTagsDescription: "เลืà¸à¸à¹à¸—็à¸à¸—ี่จะไม่à¹à¸ªà¸”งในรายà¸à¸²à¸£à¹€à¸—รนด์ สามารถลงทะเบียนหลายà¹à¸—็à¸à¹„ด้โดยขึ้นบรรทัดใหม่" notesSearchNotAvailable: "à¸à¸²à¸£à¸„้นหาโน้ตไม่พร้à¸à¸¡à¹ƒà¸Šà¹‰à¸‡à¸²à¸™" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 09c210011c46369592fe9763bc205b18e7114f47..4a36e30db896ea715c1024fa8b7ca143a2305bfd 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "确定é‡ç½®å¯†ç ?" sensitiveWords: "æ•æ„Ÿè¯" sensitiveWordsDescription: "将包å«è®¾ç½®è¯çš„帖åçš„å¯è§èŒƒå›´è®¾ç½®ä¸ºé¦–页。å¯ä»¥é€šè¿‡ç”¨æ¢è¡Œç¬¦åˆ†éš”æ¥è®¾ç½®å¤šä¸ªã€‚" sensitiveWordsDescription2: "AND æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”,æ£åˆ™è¡¨è¾¾å¼ç”¨æ–œçº¿åŒ…裹。" +prohibitedWordsDescription2: "AND æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”,æ£åˆ™è¡¨è¾¾å¼ç”¨æ–œçº¿åŒ…裹。" hiddenTags: "éšè—æ ‡ç¾" hiddenTagsDescription: "è®¾å®šçš„æ ‡ç¾å°†ä¸ä¼šåœ¨æ—¶é—´çº¿ä¸Šæ˜¾ç¤ºã€‚å¯ä½¿ç”¨æ¢è¡Œæ¥è®¾ç½®å¤šä¸ªæ ‡ç¾ã€‚" notesSearchNotAvailable: "帖å检索ä¸å¯ç”¨" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 872a90bc6aa958fc25e36fca978c2404c2955d7d..ed2bd1cf3a29a7d29abc530fc40a5a79a77112a1 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1041,6 +1041,7 @@ resetPasswordConfirm: "é‡è¨å¯†ç¢¼ï¼Ÿ" sensitiveWords: "æ•æ„Ÿè©ž" sensitiveWordsDescription: "å°‡å«æœ‰è¨å®šè©žå½™çš„貼文å¯è¦‹æ€§è¨ç‚ºç™¼é€è‡³é¦–é 。å¯ä»¥ç”¨æ›è¡Œä¾†é€²è¡Œè¤‡æ•¸çš„è¨å®šã€‚" sensitiveWordsDescription2: "ç©ºæ ¼ä»£è¡¨ã€Œä»¥åŠã€ï¼ˆAND),斜線包åœé—œéµå—代表使用æ£è¦è¡¨é”å¼ã€‚" +prohibitedWordsDescription2: "ç©ºæ ¼ä»£è¡¨ã€Œä»¥åŠã€ï¼ˆAND),斜線包åœé—œéµå—代表使用æ£è¦è¡¨é”å¼ã€‚" hiddenTags: "éš±è—標籤" hiddenTagsDescription: "è¨å®šçš„標籤ä¸æœƒåœ¨è¶¨å‹¢ä¸é¡¯ç¤ºï¼Œæ›è¡Œå¯ä»¥è¨å®šå¤šå€‹æ¨™ç±¤ã€‚" notesSearchNotAvailable: "無法使用æœå°‹è²¼æ–‡åŠŸèƒ½ã€‚" diff --git a/package.json b/package.json index 7af66b8e377dae8e22e49d862fe4ff8625ac1eef..fdd23f02c630744ee9e1685907bbb87374d89615 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "sharkey", - "version": "2024.2.0-beta.9", + "version": "2024.2.0-beta.11", "codename": "shonk", "repository": { "type": "git", "url": "https://git.joinsharkey.org/Sharkey/Sharkey.git" }, - "packageManager": "pnpm@8.12.1", + "packageManager": "pnpm@8.15.1", "workspaces": [ "packages/frontend", "packages/backend", diff --git a/packages/backend/migration/1707429690000-prohibited-words.js b/packages/backend/migration/1707429690000-prohibited-words.js new file mode 100644 index 0000000000000000000000000000000000000000..2dd62d8ff88abd0f0772c997abc62dafa10b014c --- /dev/null +++ b/packages/backend/migration/1707429690000-prohibited-words.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class prohibitedWords1707429690000 { + name = 'prohibitedWords1707429690000' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWords" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWords"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 296eddfe3ca73a7348667257bb1bf5871e570745..e5c70d120994a1d96d757fb8c68ae7d4a121ac98 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -84,7 +84,7 @@ "@nestjs/testing": "10.2.10", "@peertube/http-signature": "1.7.0", "@transfem-org/sfm-js": "0.24.4", - "@simplewebauthn/server": "9.0.1", + "@simplewebauthn/server": "9.0.2", "@sinonjs/fake-timers": "11.2.2", "@smithy/node-http-handler": "2.1.10", "@swc/cli": "0.1.63", @@ -98,12 +98,12 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.2", - "bullmq": "5.1.5", + "bullmq": "5.1.9", "cacheable-lookup": "7.0.0", - "cbor": "9.0.1", + "cbor": "9.0.2", "chalk": "5.3.0", "chalk-template": "1.1.0", - "chokidar": "3.5.3", + "chokidar": "3.6.0", "cli-highlight": "2.1.11", "color-convert": "2.0.1", "content-disposition": "0.5.4", @@ -205,7 +205,7 @@ "@types/jsrsasign": "10.5.12", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.11.10", + "@types/node": "20.11.17", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.14", "@types/oauth": "0.9.4", diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 5a1fe3d0890335c5b7d35bf33d9d0e5605fa0dee..1de71212e9aff99a9ea427f6266b0c845e4c6ba1 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -408,7 +408,7 @@ export class CustomEmojiService implements OnApplicationShutdown { */ @bindThis public checkDuplicate(name: string): Promise<boolean> { - return this.emojisRepository.exist({ where: { name, host: IsNull() } }); + return this.emojisRepository.exists({ where: { name, host: IsNull() } }); } @bindThis diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 8daee148eb8614f480db6eabe51ae1f0b94e0b28..89722965c11aae697f97c6abb1436fae5f69f7a5 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -40,6 +40,8 @@ export class EmailService { public async sendEmail(to: string, subject: string, html: string, text: string) { const meta = await this.metaService.fetch(true); + if (!meta.enableEmail) return; + const iconUrl = `${this.config.url}/static-assets/mi-white.png`; const emailSettingUrl = `${this.config.url}/settings/email`; diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 5a2417c9cdb4b8fa822cf2cbad3d7710314d870e..712530108e28429f17bb5c06ef9aa51460270825 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -163,7 +163,7 @@ export class HashtagService { const instance = await this.metaService.fetch(); const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); if (hiddenTags.includes(hashtag)) return; - if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return; + if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return; // YYYYMMDDHHmm (10分間隔) const now = new Date(); diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts index b40fd46291ecdbc2f1dc4102a84d7dba4e9ff351..7ce8dc96a18102310e9d1e6e5ad391003b0c4272 100644 --- a/packages/backend/src/core/InstanceActorService.ts +++ b/packages/backend/src/core/InstanceActorService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { IsNull } from 'typeorm'; +import { IsNull, Not } from 'typeorm'; import type { MiLocalUser } from '@/models/User.js'; import type { UsersRepository } from '@/models/_.js'; import { MemorySingleCache } from '@/misc/cache.js'; @@ -27,6 +27,14 @@ export class InstanceActorService { this.cache = new MemorySingleCache<MiLocalUser>(Infinity); } + @bindThis + public async realLocalUsersPresent(): Promise<boolean> { + return await this.usersRepository.existsBy({ + host: IsNull(), + username: Not(ACTOR_USERNAME), + }); + } + @bindThis public async getInstanceActor(): Promise<MiLocalUser> { const cached = this.cache.get(); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 51c4252715a55bb8e9e842fd1ea89ae3afe084b3..5cfcd93a64511276a0774d85b4352bb2eb66c598 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -419,6 +419,10 @@ export class MfmService { }, text: (node) => { + if (!node.props.text.match(/[\r\n]/)) { + return doc.createTextNode(node.props.text); + } + const el = doc.createElement('span'); const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x)); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 2848be9fe1472075912021bed219e72642fe71c0..0d032011b66eb47bd2d6585fc2545e4e6af32d4f 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -153,6 +153,8 @@ type Option = { export class NoteCreateService implements OnApplicationShutdown { #shutdownController = new AbortController(); + public static ContainsProhibitedWordsError = class extends Error {}; + constructor( @Inject(DI.config) private config: Config, @@ -257,7 +259,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.visibility === 'public' && data.channel == null) { const sensitiveWords = meta.sensitiveWords; - if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { + if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { data.visibility = 'home'; @@ -429,13 +431,19 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.visibility === 'public' && data.channel == null) { const sensitiveWords = meta.sensitiveWords; - if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { + if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { data.visibility = 'home'; } } + if (!user.host) { + if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) { + throw new NoteCreateService.ContainsProhibitedWordsError(); + } + } + const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { @@ -795,7 +803,7 @@ export class NoteCreateService implements OnApplicationShutdown { }); // 通知 if (data.reply.userHost === null) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: data.reply.userId, threadId: data.reply.threadId ?? data.reply.id, @@ -830,7 +838,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Notify if (data.renote.userHost === null) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: data.renote.userId, threadId: data.renote.threadId ?? data.renote.id, @@ -1057,7 +1065,7 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: u.id, threadId: note.threadId ?? note.id, diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 98762f790e97b3bab177497f89f24e3d9bacc8ac..c0d17fc7acee5f97b53e821fecf18fdd8d8145f3 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -145,6 +145,8 @@ type Option = { export class NoteEditService implements OnApplicationShutdown { #shutdownController = new AbortController(); + public static ContainsProhibitedWordsError = class extends Error {}; + constructor( @Inject(DI.config) private config: Config, @@ -266,7 +268,7 @@ export class NoteEditService implements OnApplicationShutdown { if (data.visibility === 'public' && data.channel == null) { const sensitiveWords = meta.sensitiveWords; - if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { + if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { data.visibility = 'home'; @@ -612,7 +614,7 @@ export class NoteEditService implements OnApplicationShutdown { if (data.reply) { // 通知 if (data.reply.userHost === null) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: data.reply.userId, threadId: data.reply.threadId ?? data.reply.id, @@ -647,7 +649,7 @@ export class NoteEditService implements OnApplicationShutdown { // Notify if (data.renote.userHost === null) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: data.renote.userId, threadId: data.renote.threadId ?? data.renote.id, @@ -751,7 +753,7 @@ export class NoteEditService implements OnApplicationShutdown { @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: u.id, threadId: note.threadId ?? note.id, diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index c73cf765922bcb8a85d4c79366e669d95af08ba4..11791a44121c89e803bafd104b4ca694c0a5dc85 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -49,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown { //#endregion // スレッドミュート - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: userId, threadId: note.threadId ?? note.id, @@ -70,7 +70,7 @@ export class NoteReadService implements OnApplicationShutdown { // 2秒経ã£ã¦ã‚‚æ—¢èªã«ãªã‚‰ãªã‹ã£ãŸã‚‰ã€Œæœªèªã®æŠ•ç¨¿ãŒã‚ã‚Šã¾ã™ã‚ˆã€ã‚¤ãƒ™ãƒ³ãƒˆã‚’発行ã™ã‚‹ setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => { - const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } }); + const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } }); if (!exist) return; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 11c972982e50ff6edb09f4d465943b2c62103566..6cce4a61ea4eb973f7e09ab1295352b111131639 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -248,7 +248,7 @@ export class ReactionService { // リアクションã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ãªã‚‰é€šçŸ¥ã‚’ä½œæˆ if (note.userHost === null) { - const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ where: { userId: note.userId, threadId: note.threadId ?? note.id, diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 32e3dee937c58850d3ebdc0e8bf6319f5c7af9b7..5941944a20441ebe55c7b2a0efb3dad63492f594 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -17,6 +17,7 @@ import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUsedUsername } from '@/models/UsedUsername.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; import { bindThis } from '@/decorators.js'; import UsersChart from '@/core/chart/charts/users.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -38,6 +39,7 @@ export class SignupService { private userEntityService: UserEntityService, private idService: IdService, private metaService: MetaService, + private instanceActorService: InstanceActorService, private usersChart: UsersChart, ) { } @@ -75,16 +77,16 @@ export class SignupService { const secret = generateUserToken(); // Check username duplication - if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { + if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new Error('DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) { + if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) { throw new Error('USED_USERNAME'); } - const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0; + const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent(); if (!opts.ignorePreservedUsernames && !isTheFirstUser) { const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 93e9fbbd70d7b7f564ad79286ab0b6b4674b1297..e82a7e06f9f338dc9c0a391a94c8c44964965a9c 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -144,7 +144,7 @@ export class UserFollowingService implements OnModuleInit { let autoAccept = false; // éµã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã‚ã£ã¦ã‚‚ã€æ—¢ã«ãƒ•ã‚©ãƒãƒ¼ã•ã‚Œã¦ã„ãŸå ´åˆã¯ã‚¹ãƒ«ãƒ¼ - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followerId: follower.id, followeeId: followee.id, @@ -156,7 +156,7 @@ export class UserFollowingService implements OnModuleInit { // フォãƒãƒ¼ã—ã¦ã„るユーザーã¯è‡ªå‹•æ‰¿èªã‚ªãƒ—ション if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { - const isFollowed = await this.followingsRepository.exist({ + const isFollowed = await this.followingsRepository.exists({ where: { followerId: followee.id, followeeId: follower.id, @@ -170,7 +170,7 @@ export class UserFollowingService implements OnModuleInit { if (followee.isLocked && !autoAccept) { autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs( follower, - (oldSrc, newSrc) => this.followingsRepository.exist({ + (oldSrc, newSrc) => this.followingsRepository.exists({ where: { followeeId: followee.id, followerId: newSrc.id, @@ -233,7 +233,7 @@ export class UserFollowingService implements OnModuleInit { this.cacheService.userFollowingsCache.refresh(follower.id); - const requestExist = await this.followRequestsRepository.exist({ + const requestExist = await this.followRequestsRepository.exists({ where: { followeeId: followee.id, followerId: follower.id, @@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit { } } - const requestExist = await this.followRequestsRepository.exist({ + const requestExist = await this.followRequestsRepository.exists({ where: { followeeId: followee.id, followerId: follower.id, diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 5dec36c89e31664ffcb332e2412fcca48031e81a..15b98abe635b0bd94b877facf7609d290b4876fc 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -43,13 +43,13 @@ export class UtilityService { } @bindThis - public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean { - if (sensitiveWords.length === 0) return false; + public isKeyWordIncluded(text: string, keyWords: string[]): boolean { + if (keyWords.length === 0) return false; if (text === '') return false; const regexpregexp = /^\/(.+)\/(.*)$/; - const matched = sensitiveWords.some(filter => { + const matched = keyWords.some(filter => { // represents RegExp const regexp = filter.match(regexpregexp); // This should never happen due to input sanitisation. diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 3f01c0289a2cfcb9e64c6054b82c1457633fa16e..acbe59b8bda15da646839a098efee415f3998aa7 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -629,7 +629,7 @@ export class ApInboxService { return 'skip: follower not found'; } - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followerId: follower.id, followeeId: actor.id, @@ -686,14 +686,14 @@ export class ApInboxService { return 'skip: フォãƒãƒ¼è§£é™¤ã—よã†ã¨ã—ã¦ã„るユーザーã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“'; } - const requestExist = await this.followRequestsRepository.exist({ + const requestExist = await this.followRequestsRepository.exists({ where: { followerId: actor.id, followeeId: followee.id, }, }); - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followerId: actor.id, followeeId: followee.id, diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index b900def2e450ad70bebb5adfd773e726010fb700..0716e4312358787921bd7942e9aad6f8b40a3aef 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -25,8 +25,21 @@ export class ApMfmService { } @bindThis - public getNoteHtml(note: MiNote): string | null { - if (!note.text) return ''; - return this.mfmService.toHtml(mfm.parse(note.text), note.mentionedRemoteUsers ? JSON.parse(note.mentionedRemoteUsers) : []); + public getNoteHtml(note: MiNote, apAppend?: string) { + let noMisskeyContent = false; + const srcMfm = (note.text ?? '') + (apAppend ?? ''); + + const parsed = mfm.parse(srcMfm); + + if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) { + noMisskeyContent = true; + } + + const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers)); + + return { + content, + noMisskeyContent, + }; } } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 19899629c3bd1fb60f63b6727b52027ada5cab6d..e07bb8be7878eba72cf9d7b3e3607741a7bef907 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -345,7 +345,7 @@ export class ApRendererService { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } }); + const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } }); if (inReplyToUserExist) { if (inReplyToNote.uri) { @@ -409,17 +409,15 @@ export class ApRendererService { poll = await this.pollsRepository.findOneBy({ noteId: note.id }); } - let apText = text; + let apAppend = ''; if (quote) { - apText += `\n\nRE: ${quote}`; + apAppend += `\n\nRE: ${quote}`; } const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; - const content = this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: apText, - })); + const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend); const emojis = await this.getEmojis(note.emojis); const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji)); @@ -432,9 +430,6 @@ export class ApRendererService { const asPoll = poll ? { type: 'Question', - content: this.apMfmService.getNoteHtml(Object.assign({}, note, { - text: text, - })), [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ type: 'Note', @@ -452,11 +447,13 @@ export class ApRendererService { attributedTo, summary: summary ?? undefined, content: content ?? undefined, - _misskey_content: text, - source: { - content: text, - mediaType: 'text/x.misskeymarkdown', - }, + ...(noMisskeyContent ? {} : { + _misskey_content: text, + source: { + content: text, + mediaType: 'text/x.misskeymarkdown', + }, + }), _misskey_quote: quote, quoteUrl: quote, quoteUri: quote, @@ -639,7 +636,7 @@ export class ApRendererService { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } }); + const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } }); if (inReplyToUserExist) { if (inReplyToNote.uri) { @@ -796,6 +793,7 @@ export class ApRendererService { 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', { + Key: 'sec:Key', // as non-standards manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', sensitive: 'as:sensitive', diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 8507c6f9496a1da03f0add40fe3d8734cfdb1b95..a4d497a7ff2b50f95d60338b5bfb7388497d3879 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -225,23 +225,42 @@ export class ApPersonService implements OnModuleInit { return null; } - private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>> { + private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>>> { + if (user == null) throw new Error('failed to create user: user is null'); + const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => { - if (img == null) return null; - if (user == null) throw new Error('failed to create user: user is null'); + // if we have an explicitly missing image, return an + // explicitly-null set of values + if ((img == null) || (typeof img === 'object' && img.url == null)) { + return { id: null, url: null, blurhash: null }; + } + return this.apImageService.resolveImage(user, img).catch(() => null); })); + /* + we don't want to return nulls on errors! if the database fields + are already null, nothing changes; if the database has old + values, we should keep those. The exception is if the remote has + actually removed the images: in that case, the block above + returns the special {id:null}&c value, and we return those + */ return { - avatarId: avatar?.id ?? null, - bannerId: banner?.id ?? null, - backgroundId: background?.id ?? null, - avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null, - bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null, - backgroundUrl: background ? this.driveFileEntityService.getPublicUrl(background) : null, - avatarBlurhash: avatar?.blurhash ?? null, - bannerBlurhash: banner?.blurhash ?? null, - backgroundBlurhash: background?.blurhash ?? null + ...( avatar ? { + avatarId: avatar.id, + avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null, + avatarBlurhash: avatar.blurhash, + } : {}), + ...( banner ? { + bannerId: banner.id, + bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner) : null, + bannerBlurhash: banner.blurhash, + } : {}), + ...( background ? { + backgroundId: background.id, + backgroundUrl: background.url ? this.driveFileEntityService.getPublicUrl(background) : null, + backgroundBlurhash: background.blurhash, + } : {}), }; } diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 305946b8a648b3e9b3d37baa2a2690c540299394..f358875f729d6eb41bc7107b94fea44081a68fb6 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -51,14 +51,14 @@ export class ChannelEntityService { const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null; - const isFollowing = meId ? await this.channelFollowingsRepository.exist({ + const isFollowing = meId ? await this.channelFollowingsRepository.exists({ where: { followerId: meId, followeeId: channel.id, }, }) : false; - const isFavorited = meId ? await this.channelFavoritesRepository.exist({ + const isFavorited = meId ? await this.channelFavoritesRepository.exists({ where: { userId: meId, channelId: channel.id, diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 96422894fd47f4afe2369b253e994d9d41f4a2fd..2133f80f1af884222c3eef27b4aec930776dfb3c 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -46,7 +46,7 @@ export class ClipEntityService { description: clip.description, isPublic: clip.isPublic, favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }), - isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined, + isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 5b97cfad5ee70f8684ecdd1f00f1ffefcd6ff5eb..655c4c5ada6df6854c20a8641a245c0f4b17fc11 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -31,6 +31,7 @@ export class EmojiEntityService { category: emoji.category, // || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚(publicUrlã¯stringãªã®ã§??ã¯ã ã‚) url: emoji.publicUrl || emoji.originalUrl, + localOnly: emoji.localOnly ? true : undefined, isSensitive: emoji.isSensitive ? true : undefined, roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined, }; diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 70faa2b38063abdcab4b0c135d766ae56ef5d3aa..c1b9f9a791ffbdc081517657a42cd9398438fda3 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -47,7 +47,7 @@ export class FlashEntityService { summary: flash.summary, script: flash.script, likedCount: flash.likedCount, - isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined, + isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index d7b960e0d9c0b6999cf91f8160128c7373d59224..2a615a9216255178bdade6a5f01f07c185f5c2b1 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -53,7 +53,7 @@ export class GalleryPostEntityService { tags: post.tags.length > 0 ? post.tags : undefined, isSensitive: post.isSensitive, likedCount: post.likedCount, - isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined, + isLiked: meId ? await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 1cbd5cb70c182f36563b469dcecee75ba1e1843b..a59de4985c8e8e9fb8bf1bde1398361cc527f08c 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -114,7 +114,7 @@ export class NoteEntityService implements OnModuleInit { hide = false; } else { if (packedNote.renote) { - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followeeId: packedNote.renote.userId, followerId: meId, @@ -124,7 +124,7 @@ export class NoteEntityService implements OnModuleInit { hide = !isFollowing; } else { // フォãƒãƒ¯ãƒ¼ã‹ã©ã†ã‹ - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followeeId: packedNote.userId, followerId: meId, diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index bc26362aba9a059ded31a4f34edbb08ab4835e42..27c9011f6d53c4c10de2e0a435afd69927da9b54 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -104,7 +104,7 @@ export class PageEntityService { eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null, attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)), likedCount: page.likedCount, - isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined, + isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6c1a02d9d8866bf204abcbbf24993fbb7036dc25..d24d97ed17d71791e93e978c9015c10a27b6e403 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -153,43 +153,43 @@ export class UserEntityService implements OnModuleInit { followerId: me, followeeId: target, }), - this.followingsRepository.exist({ + this.followingsRepository.exists({ where: { followerId: target, followeeId: me, }, }), - this.followRequestsRepository.exist({ + this.followRequestsRepository.exists({ where: { followerId: me, followeeId: target, }, }), - this.followRequestsRepository.exist({ + this.followRequestsRepository.exists({ where: { followerId: target, followeeId: me, }, }), - this.blockingsRepository.exist({ + this.blockingsRepository.exists({ where: { blockerId: me, blockeeId: target, }, }), - this.blockingsRepository.exist({ + this.blockingsRepository.exists({ where: { blockerId: target, blockeeId: me, }, }), - this.mutingsRepository.exist({ + this.mutingsRepository.exists({ where: { muterId: me, muteeId: target, }, }), - this.renoteMutingsRepository.exist({ + this.renoteMutingsRepository.exists({ where: { muterId: me, muteeId: target, @@ -216,7 +216,7 @@ export class UserEntityService implements OnModuleInit { /* const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); - const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({ + const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exists({ where: { antennaId: In(myAntennas.map(x => x.id)), read: false, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 9629012c743a8759e9f2304a96cc3f4f8ba7e13d..149955aa5a90b371565288db0ab582be11760b13 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -76,6 +76,11 @@ export class MiMeta { }) public sensitiveWords: string[]; + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public prohibitedWords: string[]; + @Column('varchar', { length: 1024, array: true, default: '{}', }) diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts index 99a58f8773e320f76656aeee7071bb0e0e8d9679..954eb98d575dc6a97a1eb25e852fbc4e0b3380b4 100644 --- a/packages/backend/src/models/json-schema/emoji.ts +++ b/packages/backend/src/models/json-schema/emoji.ts @@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = { type: 'string', optional: false, nullable: false, }, + localOnly: { + type: 'boolean', + optional: true, nullable: false, + }, isSensitive: { type: 'boolean', optional: true, nullable: false, diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index d2e6185aa3a6ac55fe67de2af9177d376f7b41ad..584853c1f3e5a75ae5228aaaa63aae815430f064 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -176,12 +176,12 @@ export class SignupApiService { } if (instance.emailRequiredForSignup) { - if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { + if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new FastifyReplyError(400, 'DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) { + if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) { throw new FastifyReplyError(400, 'USED_USERNAME'); } diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index b18a7e0e412d7123b0c8107ed01e537c4e25cf61..14fd69a1a247de39744b40a408def32444433237 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { Packed } from '@/misc/json-schema.js'; @@ -46,13 +47,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private userEntityService: UserEntityService, private signupService: SignupService, + private instanceActorService: InstanceActorService, ) { super(meta, paramDef, async (ps, _me, token) => { const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; - const noUsers = (await this.usersRepository.countBy({ - host: IsNull(), - })) === 0; - if ((!noUsers && !me?.isRoot) || token !== null) throw new Error('access denied'); + const realUsers = await this.instanceActorService.realLocalUsersPresent(); + if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied'); const { account, secret } = await this.signupService.signup({ username: ps.username, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c6edd6c9a155f5fbc866983604f17995402c47db..6201d1554dbc3479d743ea2da9b2fc23c3006951 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -160,6 +160,13 @@ export const meta = { type: 'string', }, }, + prohibitedWords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, bannedEmailDomains: { type: 'array', optional: true, nullable: false, @@ -549,6 +556,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- blockedHosts: instance.blockedHosts, silencedHosts: instance.silencedHosts, sensitiveWords: instance.sensitiveWords, + prohibitedWords: instance.prohibitedWords, preservedUsernames: instance.preservedUsernames, bubbleInstances: instance.bubbleInstances, hcaptchaSecretKey: instance.hcaptchaSecretKey, diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index ab69dfba9635493927eac23672a7c3021d569e49..339b7a8aa4d9a13e934b436fb0ee7df3121cc5ad 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw e; }); - const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } }); + const exist = await this.promoNotesRepository.exists({ where: { noteId: note.id } }); if (exist) { throw new ApiError(meta.errors.alreadyPromoted); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 8c0d2f8876b7bc6aa76f011c27f59ea2f13e1fc7..dce02532713ec81d696d82410ab3e43552c1c092 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -41,6 +41,11 @@ export const paramDef = { type: 'string', }, }, + prohibitedWords: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, @@ -185,6 +190,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (Array.isArray(ps.sensitiveWords)) { set.sensitiveWords = ps.sensitiveWords.filter(Boolean); } + if (Array.isArray(ps.prohibitedWords)) { + set.prohibitedWords = ps.prohibitedWords.filter(Boolean); + } if (Array.isArray(ps.silencedHosts)) { let lastValue = ''; set.silencedHosts = ps.silencedHosts.sort().filter((h) => { diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index e0baeb35656ef48798d09198348471c2bb04c12a..602c34b1e63ee108bd764a76b512d185099cddfa 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- const accessToken = secureRndstr(32); // Fetch exist access token - const exist = await this.accessTokensRepository.exist({ + const exist = await this.accessTokensRepository.exists({ where: { appId: session.appId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 1dc456318033ddc5bd748170029b545a161ef375..ea7d2076b0f489513b0e0f7833cce4295d0c22ee 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // Check if already blocking - const exist = await this.blockingsRepository.exist({ + const exist = await this.blockingsRepository.exists({ where: { blockerId: blocker.id, blockeeId: blockee.id, diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index a6e6bcb5b3f7c7df581bf5a341438b72186e1cd4..b0d66fd05cce57172b18b5dc9af197b4a65aa8d7 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // Check not blocking - const exist = await this.blockingsRepository.exist({ + const exist = await this.blockingsRepository.exists({ where: { blockerId: blocker.id, blockeeId: blockee.id, diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index 015b2cfa859754a7673cb549d0bb90b3c65042b7..b4c6a4940ba302639b0c25413c8383df791fcdcd 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.noSuchClip); } - const exist = await this.clipFavoritesRepository.exist({ + const exist = await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 85e6312b6abef733ca6055b68da6aa336c01e945..8c1f491f8d2d8c780459a3cffa65a464fb997456 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -38,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private driveFilesRepository: DriveFilesRepository, ) { super(meta, paramDef, async (ps, me) => { - const exist = await this.driveFilesRepository.exist({ + const exist = await this.driveFilesRepository.exists({ where: { md5: ps.md5, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index 1003249c0cd7d47803af1355ac3eaa51cfa6556a..5878200828f359727b0744f386039d8ce6f39a10 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } // if already liked - const exist = await this.flashLikesRepository.exist({ + const exist = await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 9037944ef9efb364e144a23f2d1f0b6d6cc1778a..1d0691407d9f4a4e2f051e8092882b72287583c8 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // Check if already following - const exist = await this.followingsRepository.exist({ + const exist = await this.followingsRepository.exists({ where: { followerId: follower.id, followeeId: followee.id, diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index f44692ba6d7e744e001688a3b4ac36af35942b57..f761968c90f97a990e463567d8c7d7c8536a34a7 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // Check not following - const exist = await this.followingsRepository.exist({ + const exist = await this.followingsRepository.exists({ where: { followerId: follower.id, followeeId: followee.id, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index cc424261b49c3317e90a1c79ee8f30f61b130f24..576cff4e9130f35c475dfd74d8ec3d2b0d45e580 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } // if already liked - const exist = await this.galleryLikesRepository.exist({ + const exist = await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index 71db8710af923e3455a85f8b2b3d0f30e19c91a3..771f3395ce00a4acc7bfac66b423bbba03e627cf 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private downloadService: DownloadService, ) { super(meta, paramDef, async (ps, me) => { - const userExist = await this.usersRepository.exist({ where: { id: me.id } }); + const userExist = await this.usersRepository.exists({ where: { id: me.id } }); if (!userExist) throw new ApiError(meta.errors.noSuchUser); const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file === null) throw new ApiError(meta.errors.noSuchFile); diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 98d866f867d4fa653dde407197a1a159ab8b2e93..545d16082c42bebcdb12d4d42cfc5484379d06f1 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ) { super(meta, paramDef, async (ps, me) => { if (ps.tokenId) { - const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } }); + const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } }); if (tokenExist) { await this.accessTokensRepository.delete({ @@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); } } else if (ps.token) { - const tokenExist = await this.accessTokensRepository.exist({ where: { token: ps.token } }); + const tokenExist = await this.accessTokensRepository.exists({ where: { token: ps.token } }); if (tokenExist) { await this.accessTokensRepository.delete({ diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index cb52bf6b516684bcba34f67f95fc901069176bfb..6d553e5d79e11426f73658df83073e926f2747d5 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -6,10 +6,11 @@ import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import JSON5 from 'json5'; -import type { AdsRepository, UsersRepository } from '@/models/_.js'; +import type { AdsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MetaService } from '@/core/MetaService.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; @@ -337,14 +338,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- @Inject(DI.config) private config: Config, - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - @Inject(DI.adsRepository) private adsRepository: AdsRepository, private userEntityService: UserEntityService, private metaService: MetaService, + private instanceActorService: InstanceActorService, ) { super(meta, paramDef, async (ps, me) => { const instance = await this.metaService.fetch(true); @@ -427,9 +426,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ...(ps.detail ? { cacheRemoteFiles: instance.cacheRemoteFiles, cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, - requireSetup: (await this.usersRepository.countBy({ - host: IsNull(), - })) === 0, + requireSetup: !await this.instanceActorService.realLocalUsersPresent(), } : {}), }; diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 49c2b5707deb6f2b2ab41c146ecb43de13ec9fc0..1d931150e191e31e4804188353846502137e7ad5 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // Check if already muting - const exist = await this.mutingsRepository.exist({ + const exist = await this.mutingsRepository.exists({ where: { muterId: muter.id, muteeId: mutee.id, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 44ca1b9c16dd2536c0c53a8b582a7366f4664c42..dbcea2932e737fd4326665614fdaa60a6de2ec23 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -17,6 +17,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; import { isPureRenote } from '@/misc/is-pure-renote.js'; +import { MetaService } from '@/core/MetaService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -117,6 +119,12 @@ export const meta = { code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL', id: '33510210-8452-094c-6227-4a6c05d99f00', }, + + containsProhibitedWords: { + message: 'Cannot post because it contains prohibited words.', + code: 'CONTAINS_PROHIBITED_WORDS', + id: 'aa6e01d3-a85c-669d-758a-76aab43af334', + }, }, } as const; @@ -271,7 +279,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // Check blocking if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: renote.userId, blockeeId: me.id, @@ -319,7 +327,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // Check blocking if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: reply.userId, blockeeId: me.id, @@ -351,31 +359,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } // æŠ•ç¨¿ã‚’ä½œæˆ - const note = await this.noteCreateService.create(me, { - createdAt: new Date(), - files: files, - poll: ps.poll ? { - choices: ps.poll.choices, - multiple: ps.poll.multiple ?? false, - expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, - } : undefined, - text: ps.text ?? undefined, - reply, - renote, - cw: ps.cw, - localOnly: ps.localOnly, - reactionAcceptance: ps.reactionAcceptance, - visibility: ps.visibility, - visibleUsers, - channel, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, - }); - - return { - createdNote: await this.noteEntityService.pack(note, me), - }; + try { + const note = await this.noteCreateService.create(me, { + createdAt: new Date(), + files: files, + poll: ps.poll ? { + choices: ps.poll.choices, + multiple: ps.poll.multiple ?? false, + expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null, + } : undefined, + text: ps.text ?? undefined, + reply, + renote, + cw: ps.cw, + localOnly: ps.localOnly, + reactionAcceptance: ps.reactionAcceptance, + visibility: ps.visibility, + visibleUsers, + channel, + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, + }); + + return { + createdNote: await this.noteEntityService.pack(note, me), + }; + } catch (e) { + // TODO: ä»–ã®Errorã‚‚ã“ã“ã§ã‚ャッãƒã—ã¦ã‚¨ãƒ©ãƒ¼ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’当ã¦ã‚‹ã‚ˆã†ã«ã—ãŸã„ + if (e instanceof NoteCreateService.ContainsProhibitedWordsError) { + throw new ApiError(meta.errors.containsProhibitedWords); + } + + throw e; + } }); } } diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts index 0c9c0d3baf91390ccc7e9dbdd8e97f92e52ff85a..44796d9290ea31175c380aad42ea874ba888a154 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -311,7 +311,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // Check blocking if (renote.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: renote.userId, blockeeId: me.id, @@ -349,7 +349,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // Check blocking if (reply.userId !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: reply.userId, blockeeId: me.id, diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index ed3dce7f35433d56b0d9d3688186873ed34647c2..bfa621aa38f3346db112f8c42ae3feb33662e2cc 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); // if already favorited - const exist = await this.noteFavoritesRepository.exist({ + const exist = await this.noteFavoritesRepository.exists({ where: { noteId: note.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 8c18982b50a70a487d2f9b9758bcddb10118f831..bee60f080d47268b977daf760f558decc527bc31 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } // if already liked - const exist = await this.pageLikesRepository.exist({ + const exist = await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index f427939a7a2d531134e71eb95b6309c01d17d77d..4899408ddd41412192a8c14eb86bf422b9e4cc85 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw err; }); - const exist = await this.promoReadsRepository.exist({ + const exist = await this.promoReadsRepository.exists({ where: { noteId: note.id, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 5706e46b96216ca937853d1cf527d63466827f4d..314a45ed61eb66c9f5ea583c64091c3cf4c7d2da 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followeeId: user.id, followerId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 794fb04f102b1f4f40b020cca47384c7c015aae0..86f55c5a12bfd29442f135c50e1793fcfdc8b385 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -109,7 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const isFollowing = await this.followingsRepository.exist({ + const isFollowing = await this.followingsRepository.exists({ where: { followeeId: user.id, followerId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index fa2e3338b88e5f724c8a365c45e97e1e52dc076c..dd9b459a1f4916c3581ed19692bcf91ee61570fc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -90,7 +90,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const listExist = await this.userListsRepository.exist({ + const listExist = await this.userListsRepository.exists({ where: { id: ps.listId, isPublic: true, @@ -121,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- }); if (currentUser.id !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: currentUser.id, blockeeId: me.id, @@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } } - const exist = await this.userListMembershipsRepository.exist({ + const exist = await this.userListMembershipsRepository.exists({ where: { userListId: userList.id, userId: currentUser.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index 864cdc2ee0106823aab122e427db3a814d1c12ef..e5b3a73b556e741eee14067f7475ab021b9610f2 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const userListExist = await this.userListsRepository.exist({ + const userListExist = await this.userListsRepository.exists({ where: { id: ps.listId, isPublic: true, @@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchList); } - const exist = await this.userListFavoritesRepository.exist({ + const exist = await this.userListFavoritesRepository.exists({ where: { userId: me.id, userListId: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index c4ceec575b92f880b1f78d1df999e4e3776582aa..0984270943b23df2da1be3415a55b88b0d347baa 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // Check blocking if (user.id !== me.id) { - const blockExist = await this.blockingsRepository.exist({ + const blockExist = await this.blockingsRepository.exists({ where: { blockerId: user.id, blockeeId: me.id, @@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } } - const exist = await this.userListMembershipsRepository.exist({ + const exist = await this.userListMembershipsRepository.exists({ where: { userListId: userList.id, userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index df44870b041e22e8a5108d4c3ab0deaadc6705ae..10efbaafd8bed99105840d4f9886cecd455eab58 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { userListId: ps.listId, }); if (me !== null) { - additionalProperties.isLiked = await this.userListFavoritesRepository.exist({ + additionalProperties.isLiked = await this.userListFavoritesRepository.exists({ where: { userId: me.id, userListId: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts index d51d57343e29c97c67ce0bc5664aac8905aaa5fe..0935584cbc6c17ef1f6bf0f1c106f22ecf6cbcca 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts @@ -45,7 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private userListFavoritesRepository: UserListFavoritesRepository, ) { super(meta, paramDef, async (ps, me) => { - const userListExist = await this.userListsRepository.exist({ + const userListExist = await this.userListsRepository.exists({ where: { id: ps.listId, isPublic: true, diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index e0245814c47a7aed8914a40a5a63466655eedf66..0434833df879e832b97888f10641bb6a4ea4307e 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -43,7 +43,7 @@ class UserListChannel extends Channel { this.withRenotes = params.withRenotes ?? true; // Check existence and owner - const listExist = await this.userListsRepository.exist({ + const listExist = await this.userListsRepository.exists({ where: { id: this.listId, userId: this.user!.id, diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 0280b051f5d2dc26fd986cba4e1d77efa3221b58..1bc8cb591c81b9dc7a0c8af048101321d93840e3 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -16,12 +16,14 @@ describe('Note', () => { let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; + let tom: misskey.entities.SignupResponse; beforeAll(async () => { const connection = await initTestDb(true); Notes = connection.getRepository(MiNote); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); + tom = await signup({ username: 'tom', host: 'example.com' }); }, 1000 * 60 * 2); test('投稿ã§ãã‚‹', async () => { @@ -607,6 +609,77 @@ describe('Note', () => { assert.strictEqual(note2.status, 200); assert.strictEqual(note2.body.createdNote.visibility, 'home'); }); + + test('ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ã‚’å«ã‚€æŠ•ç¨¿ã¯ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ (å˜èªžæŒ‡å®š)', async () => { + const prohibited = await api('admin/update-meta', { + prohibitedWords: [ + 'test', + ], + }, alice); + + assert.strictEqual(prohibited.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note1 = await api('/notes/create', { + text: 'hogetesthuge', + }, alice); + + assert.strictEqual(note1.status, 400); + assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS'); + }); + + test('ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ã‚’å«ã‚€æŠ•ç¨¿ã¯ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ (æ£è¦è¡¨ç¾)', async () => { + const prohibited = await api('admin/update-meta', { + prohibitedWords: [ + '/Test/i', + ], + }, alice); + + assert.strictEqual(prohibited.status, 204); + + const note2 = await api('/notes/create', { + text: 'hogetesthuge', + }, alice); + + assert.strictEqual(note2.status, 400); + assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS'); + }); + + test('ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ã‚’å«ã‚€æŠ•ç¨¿ã¯ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ (スペースアンド)', async () => { + const prohibited = await api('admin/update-meta', { + prohibitedWords: [ + 'Test hoge', + ], + }, alice); + + assert.strictEqual(prohibited.status, 204); + + const note2 = await api('/notes/create', { + text: 'hogeTesthuge', + }, alice); + + assert.strictEqual(note2.status, 400); + assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS'); + }); + + test('ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ã‚’å«ã‚“ã§ã„ã¦ã‚‚リモートノートã¯ã‚¨ãƒ©ãƒ¼ã«ãªã‚‰ãªã„', async () => { + const prohibited = await api('admin/update-meta', { + prohibitedWords: [ + 'test', + ], + }, alice); + + assert.strictEqual(prohibited.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note1 = await api('/notes/create', { + text: 'hogetesthuge', + }, tom); + + assert.strictEqual(note1.status, 200); + }); }); describe('notes/delete', () => { diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b79041c86d7e6a3b6a58a3c35b9797500227959 --- /dev/null +++ b/packages/backend/test/unit/ApMfmService.ts @@ -0,0 +1,44 @@ +import * as assert from 'assert'; +import { Test } from '@nestjs/testing'; + +import { CoreModule } from '@/core/CoreModule.js'; +import { ApMfmService } from '@/core/activitypub/ApMfmService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { MiNote } from '@/models/Note.js'; + +describe('ApMfmService', () => { + let apMfmService: ApMfmService; + + beforeAll(async () => { + const app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + }).compile(); + apMfmService = app.get<ApMfmService>(ApMfmService); + }); + + describe('getNoteHtml', () => { + test('Do not provide _misskey_content for simple text', () => { + const note: MiNote = { + text: 'テã‚スト #ã‚¿ã‚° @mention 🊠:emoji: https://example.com', + mentionedRemoteUsers: '[]', + } as any; + + const { content, noMisskeyContent } = apMfmService.getNoteHtml(note); + + assert.equal(noMisskeyContent, true, 'noMisskeyContent'); + assert.equal(content, '<p>テã‚スト <a href="http://misskey.local/tags/ã‚¿ã‚°" rel="tag">#ã‚¿ã‚°</a> <a href="http://misskey.local/@mention" class="u-url mention">@mention</a> 🊠​:emoji:​ <a href="https://example.com">https://example.com</a></p>', 'content'); + }); + + test('Provide _misskey_content for MFM', () => { + const note: MiNote = { + text: '$[tada foo]', + mentionedRemoteUsers: '[]', + } as any; + + const { content, noMisskeyContent } = apMfmService.getNoteHtml(note); + + assert.equal(noMisskeyContent, false, 'noMisskeyContent'); + assert.equal(content, '<p><i>foo</i></p>', 'content'); + }); + }); +}); diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts index 8f40ae493529f7572a8bbb7f69a701b8150e56e4..b714f2c0fbdf39f74770196afb662826f1167600 100644 --- a/packages/backend/test/unit/MfmService.ts +++ b/packages/backend/test/unit/MfmService.ts @@ -33,6 +33,12 @@ describe('MfmService', () => { const output = '<p><span>foo<br>bar<br>baz</span></p>'; assert.equal(mfmService.toHtml(mfm.parse(input)), output); }); + + test('Do not generate unnecessary span', () => { + const input = 'foo $[tada bar]'; + const output = '<p>foo <i>bar</i></p>'; + assert.equal(mfmService.toHtml(mfm.parse(input)), output); + }); }); describe('fromHtml', () => { diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts index 80e5157c5a2d7a3d5150f8fcf8f06ed2006b1637..f0feff9f784c245a6ef5d6f68d8993856202b84d 100644 --- a/packages/frontend/.storybook/mocks.ts +++ b/packages/frontend/.storybook/mocks.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { type SharedOptions, rest } from 'msw'; +import { type SharedOptions, http, HttpResponse } from 'msw'; export const onUnhandledRequest = ((req, print) => { if (req.url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(req.url.pathname)) { @@ -13,19 +13,31 @@ export const onUnhandledRequest = ((req, print) => { }) satisfies SharedOptions['onUnhandledRequest']; export const commonHandlers = [ - rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => { - const { codepoints } = req.params; + http.get('/fluent-emoji/:codepoints.png', async ({ params }) => { + const { codepoints } = params; const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob()); - return res(ctx.set('Content-Type', 'image/png'), ctx.body(value)); + return new HttpResponse(value, { + headers: { + 'Content-Type': 'image/png', + }, + }); }), - rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => { - const { codepoints } = req.params; + http.get('/fluent-emojis/:codepoints.png', async ({ params }) => { + const { codepoints } = params; const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob()); - return res(ctx.set('Content-Type', 'image/png'), ctx.body(value)); + return new HttpResponse(value, { + headers: { + 'Content-Type': 'image/png', + }, + }); }), - rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => { - const { codepoints } = req.params; + http.get('/twemoji/:codepoints.svg', async ({ params }) => { + const { codepoints } = params; const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob()); - return res(ctx.set('Content-Type', 'image/svg+xml'), ctx.body(value)); + return new HttpResponse(value, { + headers: { + 'Content-Type': 'image/svg+xml', + }, + }); }), ]; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 707c15f5c7304437684b342fcf47abceadefd3f3..f5c968ec3c9383a917c1762ca211c44001034841 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -30,7 +30,7 @@ "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.3", "@vue/compiler-sfc": "3.4.15", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2", "astring": "1.8.6", "broadcast-channel": "7.0.0", "buraha": "0.0.1", @@ -72,7 +72,7 @@ "typescript": "5.3.3", "uuid": "9.0.1", "v-code-diff": "1.7.2", - "vite": "5.0.12", + "vite": "5.1.0", "vue": "3.4.15", "vuedraggable": "next" }, @@ -102,7 +102,7 @@ "@types/estree": "1.0.5", "@types/matter-js": "0.19.6", "@types/micromatch": "4.0.6", - "@types/node": "20.11.10", + "@types/node": "20.11.17", "@types/punycode": "2.1.3", "@types/sanitize-html": "2.9.5", "@types/throttle-debounce": "5.0.2", @@ -115,7 +115,7 @@ "@vue/runtime-core": "3.4.15", "acorn": "8.11.3", "cross-env": "7.0.3", - "cypress": "13.6.3", + "cypress": "13.6.4", "eslint": "8.56.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-vue": "9.20.1", @@ -123,10 +123,10 @@ "happy-dom": "10.0.3", "intersection-observer": "0.12.2", "micromatch": "4.0.5", - "msw": "2.1.2", - "msw-storybook-addon": "1.10.0", + "msw": "2.1.7", + "msw-storybook-addon": "2.0.0-beta.1", "nodemon": "3.0.3", - "prettier": "3.2.4", + "prettier": "3.2.5", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.3", diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index f6a3e40305e1c88794ec9f3e17f80125f1fa4072..65bd7cf0f13c0f5a6fac165a63122e57ffcbb814 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -60,12 +60,6 @@ export async function common(createVue: () => App<Element>) { }); } - const splash = document.getElementById('splash'); - // 念ã®ãŸã‚nullãƒã‚§ãƒƒã‚¯(HTMLãŒå¤ã„å ´åˆãŒã‚ã‚‹ãŸã‚(ãã®ã†ã¡æ¶ˆã™)) - if (splash) splash.addEventListener('transitionend', () => { - splash.remove(); - }); - let isClientUpdated = false; //#region クライアントãŒæ›´æ–°ã•ã‚ŒãŸã‹ãƒã‚§ãƒƒã‚¯ @@ -293,5 +287,10 @@ function removeSplash() { if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; + + // transitionendイベントãŒç™ºç«ã—ãªã„å ´åˆãŒã‚ã‚‹ãŸã‚ + window.setTimeout(() => { + splash.remove(); + }, 1000); } } diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts index 77e7c84d5c9cf65fc7516bb25b281f990778bb65..dc2697f25c42fb5f0911dcde8ef9e1fae9311588 100644 --- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts +++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { abuseUserReport } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAbuseReport from './MkAbuseReport.vue'; @@ -44,9 +44,9 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => { - action('POST /api/admin/resolve-abuse-user-report')(await req.json()); - return res(ctx.json({})); + http.post('/api/admin/resolve-abuse-user-report', async ({ request }) => { + action('POST /api/admin/resolve-abuse-user-report')(await request.json()); + return HttpResponse.json({}); }), ], }, diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts index dc842b3d1bd2b81460c7272ceb754f6c193fcb82..771452cb5fde2eec5e444a6375618c14cb2fc5ec 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts +++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAbuseReportWindow from './MkAbuseReportWindow.vue'; @@ -44,9 +44,9 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/report-abuse', async (req, res, ctx) => { - action('POST /api/users/report-abuse')(await req.json()); - return res(ctx.json({})); + http.post('/api/users/report-abuse', async ({ request }) => { + action('POST /api/users/report-abuse')(await request.json()); + return HttpResponse.json({}); }), ], }, diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts index 6d972467b153d178f7262a7b97437a9874a86f7c..81e9529de21065f1413046b0df5e1da93373381e 100644 --- a/packages/frontend/src/components/MkAchievements.stories.impl.ts +++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAchievements from './MkAchievements.vue'; @@ -39,8 +39,8 @@ export const Empty = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/achievements', (req, res, ctx) => { - return res(ctx.json([])); + http.post('/api/users/achievements', () => { + return HttpResponse.json([]); }), ], }, @@ -52,8 +52,8 @@ export const All = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/achievements', (req, res, ctx) => { - return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })))); + http.post('/api/users/achievements', () => { + return HttpResponse.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))); }), ], }, diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts index 969519386fc9733da1fd23a485330aaa728b820a..3ca8c5b8646a26e287f6d38cfec3c14543b52226 100644 --- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions'; import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAutocomplete from './MkAutocomplete.vue'; @@ -99,11 +99,11 @@ export const User = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/users/search-by-username-and-host', () => { + return HttpResponse.json([ userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'), userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'), - ])); + ]); }), ], }, @@ -132,12 +132,12 @@ export const Hashtag = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/hashtags/search', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/hashtags/search', () => { + return HttpResponse.json([ '気象è¦å ±æ³¨æ„å ±', '気象è¦å ±', 'æ°—è±¡æƒ…å ±', - ])); + ]); }), ], }, diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts index d41b64695fdcfb1fc14e0c364b26a7420096d310..a9b4540ca97596af422cec346c275c4f85de68f0 100644 --- a/packages/frontend/src/components/MkAvatars.stories.impl.ts +++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAvatars from './MkAvatars.vue'; @@ -38,12 +38,12 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/show', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/users/show', () => { + return HttpResponse.json([ userDetailed('17'), userDetailed('20'), userDetailed('18'), - ])); + ]); }), ], }, diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index b06bb70e99ed09eb4661d6b0fcc00eff011f6ad8..5ad5892b7ce06c51c9050cf0b6f6591b8e05bfc3 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -5,14 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only <!-- eslint-disable vue/no-v-html --> <template> -<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }]" v-html="html"></div> +<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div> </template> <script lang="ts" setup> import { ref, computed, watch } from 'vue'; import { bundledLanguagesInfo } from 'shiki'; import type { BuiltinLanguage } from 'shiki'; -import { getHighlighter } from '@/scripts/code-highlighter.js'; +import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ code: string; @@ -21,11 +22,23 @@ const props = defineProps<{ }>(); const highlighter = await getHighlighter(); - +const darkMode = defaultStore.reactiveState.darkMode; const codeLang = ref<BuiltinLanguage | 'aiscript'>('js'); + +const [lightThemeName, darkThemeName] = await Promise.all([ + getTheme('light', true), + getTheme('dark', true), +]); + const html = computed(() => highlighter.codeToHtml(props.code, { lang: codeLang.value, - theme: 'dark-plus', + themes: { + fallback: 'dark-plus', + light: lightThemeName, + dark: darkThemeName, + }, + defaultColor: false, + cssVariablePrefix: '--shiki-', })); async function fetchLanguage(to: string): Promise<void> { @@ -79,6 +92,16 @@ watch(() => props.lang, (to) => { margin: .5em 0; overflow: auto; border-radius: var(--radius-sm); + border: 1px solid var(--divider); + font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; + + color: var(--shiki-fallback); + background-color: var(--shiki-fallback-bg); + + & span { + color: var(--shiki-fallback); + background-color: var(--shiki-fallback-bg); + } & pre, & code { @@ -86,6 +109,26 @@ watch(() => props.lang, (to) => { } } +.light.codeBlockRoot :global(.shiki) { + color: var(--shiki-light); + background-color: var(--shiki-light-bg); + + & span { + color: var(--shiki-light); + background-color: var(--shiki-light-bg); + } +} + +.dark.codeBlockRoot :global(.shiki) { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); + + & span { + color: var(--shiki-dark); + background-color: var(--shiki-dark-bg); + } +} + .codeBlockRoot.codeEditor { min-width: 100%; height: 100%; @@ -94,6 +137,7 @@ watch(() => props.lang, (to) => { padding: 12px; margin: 0; border-radius: var(--radius-sm); + border: none; min-height: 130px; pointer-events: none; min-width: calc(100% - 24px); @@ -105,6 +149,11 @@ watch(() => props.lang, (to) => { text-rendering: inherit; text-transform: inherit; white-space: pre; + + & span { + display: inline-block; + min-height: 1em; + } } } </style> diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue index 05b5fe8da1a743800c13f28c238890fcb5915072..b34eb8aaf6792930c02007f898ee253590030009 100644 --- a/packages/frontend/src/components/MkCode.vue +++ b/packages/frontend/src/components/MkCode.vue @@ -53,7 +53,6 @@ function copy() { } .codeBlockCopyButton { - color: #D4D4D4; position: absolute; top: 8px; right: 8px; @@ -67,8 +66,7 @@ function copy() { .codeBlockFallbackRoot { display: block; overflow-wrap: anywhere; - color: #D4D4D4; - background: #1E1E1E; + background: var(--bg); padding: 1em; margin: .5em 0; overflow: auto; @@ -93,8 +91,8 @@ function copy() { border-radius: var(--radius-sm); padding: 24px; margin-top: 4px; - color: #D4D4D4; - background: #1E1E1E; + color: var(--fg); + background: var(--bg); } .codePlaceholderContainer { diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue index 7334fc4d0e4305ce27d6722beede37d6436f1afb..5ff6c801a5687119e46329b8123100a4bd748fe4 100644 --- a/packages/frontend/src/components/MkCodeEditor.vue +++ b/packages/frontend/src/components/MkCodeEditor.vue @@ -196,10 +196,11 @@ watch(v, newValue => { resize: none; text-align: left; color: transparent; - caret-color: rgb(225, 228, 232); + caret-color: var(--fg); background-color: transparent; border: 0; border-radius: var(--radius-sm); + box-sizing: border-box; outline: 0; min-width: calc(100% - 24px); height: 100%; @@ -212,6 +213,6 @@ watch(v, newValue => { } .textarea::selection { - color: #fff; + color: var(--bg); } </style> diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue index 5340c1fd5fa30a713fa54e70dbfb096cae73276e..6a9d97ab5a3ee0eb0abe74ef30da48b5d6aaf554 100644 --- a/packages/frontend/src/components/MkCodeInline.vue +++ b/packages/frontend/src/components/MkCodeInline.vue @@ -18,8 +18,7 @@ const props = defineProps<{ display: inline-block; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; overflow-wrap: anywhere; - color: #D4D4D4; - background: #1E1E1E; + background: var(--bg); padding: .1em; border-radius: .3em; } diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 0a1ddd3171db7ef9a349e9f50365f73e1c2a36a2..745453646c48085aa1b656ca92eb20dd48669d3f 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -63,18 +63,25 @@ const loading = ref(true); const ok = async () => { const promise = new Promise<Misskey.entities.DriveFile>(async (res) => { - const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); + const croppedImage = await cropper?.getCropperImage(); + const croppedSection = await cropper?.getCropperSelection(); + + // 拡大率を計算ã—ã€(ã»ã¼)å…ƒã®å¤§ãã•ã«æˆ»ã™ + const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth; + const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate; + + const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender }); croppedCanvas?.toBlob(blob => { if (!blob) return; const formData = new FormData(); formData.append('file', blob); formData.append('name', `cropped_${props.file.name}`); formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false'); - formData.append('comment', props.file.comment ?? 'null'); + if (props.file.comment) { formData.append('comment', props.file.comment);} formData.append('i', $i!.token); - if (props.uploadFolder || props.uploadFolder === null) { - formData.append('folderId', props.uploadFolder ?? 'null'); - } else if (defaultStore.state.uploadFolder) { + if (props.uploadFolder) { + formData.append('folderId', props.uploadFolder); + } else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) { formData.append('folderId', defaultStore.state.uploadFolder); } diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 1db03a5eb9ec4155a4bafca1791dda37167e660f..a4c5e07cd9cb40b95d7da9a0461d84ae899f2343 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -118,6 +118,7 @@ import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; import { $i } from '@/account.js'; +import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -126,6 +127,7 @@ const props = withDefaults(defineProps<{ asDrawer?: boolean; asWindow?: boolean; asReactionPicker?: boolean; // 今ã¯ä½¿ã‚ã‚Œã¦ãªã„ãŒå°†æ¥çš„ã«ä½¿ã„ãㆠ+ targetNote?: Misskey.entities.Note; }>(), { showPinned: true, }); @@ -340,7 +342,7 @@ watch(q, () => { }); function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { - return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) ?? false; + return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji); } function focus() { diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 4068a79f085f280a9c2069c0245b2b84f4eb6d37..1c0f9a5a33ef36e8364917aaae6844f74dcdc746 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only :showPinned="showPinned" :pinnedEmojis="pinnedEmojis" :asReactionPicker="asReactionPicker" + :targetNote="targetNote" :asDrawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen" @@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> +import * as Misskey from 'misskey-js'; import { shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; @@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{ showPinned?: boolean; pinnedEmojis?: string[], asReactionPicker?: boolean; + targetNote?: Misskey.entities.Note; choseAndClose?: boolean; }>(), { manualShowing: null, diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index 1a2c55e785d569fc4a53d9130a657bd1546538ef..2a6828f24255c78955fe487ad4bd1238bf475a10 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -13,12 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only :front="true" @closed="emit('closed')" > - <MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/> + <MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" :targetNote="targetNote" asWindow :class="$style.picker" @chosen="chosen"/> </MkWindow> </template> <script lang="ts" setup> import { } from 'vue'; +import * as Misskey from 'misskey-js'; import MkWindow from '@/components/MkWindow.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; @@ -26,6 +27,7 @@ withDefaults(defineProps<{ src?: HTMLElement; showPinned?: boolean; asReactionPicker?: boolean; + targetNote?: Misskey.entities.Note }>(), { showPinned: true, }); diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue index 67d32c505adb0fd903703b281f1c4b2aba4e491e..bf7d43fd43dd944039fc779db8aaff29b6962ffc 100644 --- a/packages/frontend/src/components/MkHorizontalSwipe.vue +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only </Transition> </div> </template> - <script lang="ts" setup> import { ref, shallowRef, computed, nextTick, watch } from 'vue'; import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import { defaultStore } from '@/store.js'; +import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js'; const rootEl = shallowRef<HTMLDivElement>(); @@ -49,16 +49,16 @@ const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontal // â–¼ ã—ãã„値 â–¼ // // スワイプã¨åˆ¤å®šã•ã‚Œã‚‹æœ€å°ã®è·é›¢ -const MIN_SWIPE_DISTANCE = 50; +const MIN_SWIPE_DISTANCE = 20; // スワイプ時ã®å‹•ä½œã‚’発ç«ã™ã‚‹æœ€å°ã®è·é›¢ -const SWIPE_DISTANCE_THRESHOLD = 125; +const SWIPE_DISTANCE_THRESHOLD = 70; // スワイプをä¸æ–ã™ã‚‹Yæ–¹å‘ã®ç§»å‹•è·é›¢ const SWIPE_ABORT_Y_THRESHOLD = 75; // スワイプã§ãる最大ã®è·é›¢ -const MAX_SWIPE_DISTANCE = 150; +const MAX_SWIPE_DISTANCE = 120; // â–² ã—ãã„値 â–² // @@ -68,7 +68,6 @@ let startScreenY: number | null = null; const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value)); const pullDistance = ref(0); -const isSwiping = ref(false); const isSwipingForClass = ref(false); let swipeAborted = false; @@ -77,6 +76,8 @@ function touchStart(event: TouchEvent) { if (event.touches.length !== 1) return; + if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return; + startScreenX = event.touches[0].screenX; startScreenY = event.touches[0].screenY; } @@ -90,6 +91,8 @@ function touchMove(event: TouchEvent) { if (swipeAborted) return; + if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return; + let distanceX = event.touches[0].screenX - startScreenX; let distanceY = event.touches[0].screenY - startScreenY; @@ -139,6 +142,8 @@ function touchEnd(event: TouchEvent) { if (!isSwiping.value) return; + if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return; + const distance = event.changedTouches[0].screenX - startScreenX; if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) { @@ -162,6 +167,24 @@ function touchEnd(event: TouchEvent) { }, 400); } +/** 横スワイプã«é–¢ä¸Žã™ã‚‹å¯èƒ½æ€§ã®ã‚ã‚‹è¦ç´ を調ã¹ã‚‹ */ +function hasSomethingToDoWithXSwipe(el: HTMLElement) { + if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true; + if (el.isContentEditable) return true; + if (el.scrollWidth > el.clientWidth) return true; + + const style = window.getComputedStyle(el); + if (['absolute', 'fixed', 'sticky'].includes(style.position)) return true; + if (['scroll', 'auto'].includes(style.overflowX)) return true; + if (style.touchAction === 'pan-x') return true; + + if (el.parentElement && el.parentElement !== rootEl.value) { + return hasSomethingToDoWithXSwipe(el.parentElement); + } else { + return false; + } +} + const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined); watch(tabModel, (newTab, oldTab) => { @@ -182,6 +205,7 @@ watch(tabModel, (newTab, oldTab) => { <style lang="scss" module> .transitionRoot { + touch-action: pan-y pinch-zoom; display: grid; grid-template-columns: 100%; overflow: clip; diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts index 2ea32dd3b67a5afb31e6ec374bc1dc95d25275c8..2abe1a8770524f7055d019dd4109fb1161be9da9 100644 --- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts +++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed, inviteCode } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkInviteCode from './MkInviteCode.vue'; @@ -39,8 +39,8 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/show', (req, res, ctx) => { - return res(ctx.json(userDetailed(req.params.userId as string))); + http.post('/api/users/show', ({ params }) => { + return HttpResponse.json(userDetailed(params.userId as string)); }), ], }, diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 79b2b91154f6074910ec635ca8495bd8272d7cde..02bf987bde39a3dd9952adbb873a59ebc901c5e5 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -119,6 +119,7 @@ function close() { margin-top: 12px; font-size: 0.8em; line-height: 1.5em; + text-align: center; } > .indicatorWithValue { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 0abad81d97c64d6e2ed37c379a960490653897da..ff9bf3c395074297eeb1a261dea68cc18b9ae1b8 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -285,13 +285,11 @@ const quoteButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); -const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null; -const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null; const isMyRenote = $i && ($i.id === note.value.userId); const showContent = ref(defaultStore.state.uncollapseCW); const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); -const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter(u => u !== renoteUrl && u !== renoteUri) : null); +const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null); const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const isDeleted = ref(false); @@ -624,7 +622,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); if (props.mock) { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index d31f77bbc28d9b6985646ad349944fbc3cf40f6d..a2e3a747d9e93beebd2db879343dd393b164d4e8 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -303,8 +303,6 @@ const quoteButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); -const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null; -const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null; const isMyRenote = $i && ($i.id === note.value.userId); const showContent = ref(defaultStore.state.uncollapseCW); const isDeleted = ref(false); @@ -313,7 +311,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translating = ref(false); const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; -const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null; +const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null; const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null); const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); @@ -612,7 +610,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index cb2db0b6a5e0cca06af1eead945e8b7b728a8341..ee0963a9b502709b84dd39384a259196366b85a6 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -76,7 +76,7 @@ const buttonsLeft = computed(() => { }); const buttonsRight = computed(() => { const buttons = [{ - icon: 'ph-arrow-clockwise ph-bold ph-lg', + icon: 'ph-arrows-clockwise ph-bold ph-lg', title: i18n.ts.reload, onClick: reload, }, { diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue index e96369799798fd5312cd01d757dd2fca29ae8a6f..3898f40ca6c716443b2add96b78564f26e61b333 100644 --- a/packages/frontend/src/components/MkPullToRefresh.vue +++ b/packages/frontend/src/components/MkPullToRefresh.vue @@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { i18n } from '@/i18n.js'; import { getScrollContainer } from '@/scripts/scroll.js'; +import { isHorizontalSwipeSwiping } from '@/scripts/touch.js'; const SCROLL_STOP = 10; const MAX_PULL_DISTANCE = Infinity; @@ -129,7 +130,7 @@ function moveEnd() { function moving(event: TouchEvent | PointerEvent) { if (!isPullStart.value || isRefreshing.value || disabled) return; - if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) { + if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value) || isHorizontalSwipeSwiping.value) { pullDistance.value = 0; isPullEnd.value = false; moveEnd(); @@ -148,6 +149,10 @@ function moving(event: TouchEvent | PointerEvent) { if (event.cancelable) event.preventDefault(); } + if (pullDistance.value > SCROLL_STOP) { + event.stopPropagation(); + } + isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD; } diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index e43841e5c9e4d564613654daa71ef6cb38aeac2d..356020a0de23959cc0b1c01c32f3f1cf762e5a0f 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -32,6 +32,8 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as sound from '@/scripts/sound.js'; +import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; +import { customEmojis } from '@/custom-emojis.js'; const props = defineProps<{ reaction: string; @@ -48,13 +50,19 @@ const emit = defineEmits<{ const buttonEl = shallowRef<HTMLElement>(); -const canToggle = computed(() => !props.reaction.match(/@\w/) && $i); +const isCustomEmoji = computed(() => props.reaction.includes(':')); +const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null); + +const canToggle = computed(() => { + return !props.reaction.match(/@\w/) && $i + && (emoji.value && checkReactionPermissions($i, props.note, emoji.value)) + || !isCustomEmoji.value; +}); +const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); async function toggleReaction() { if (!canToggle.value) return; - // TODO: ãã®çµµæ–‡å—を使ã†æ¨©é™ãŒã‚ã‚‹ã‹ã©ã†ã‹ç¢ºèª - const oldReaction = props.note.myReaction; if (oldReaction) { const confirm = await os.confirm({ @@ -101,8 +109,8 @@ async function toggleReaction() { } async function menu(ev) { - if (!canToggle.value) return; - if (!props.reaction.includes(':')) return; + if (!canGetInfo.value) return; + os.popupMenu([{ text: i18n.ts.info, icon: 'ph-info ph-bold ph-lg', diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 6ac8297c0285a9d71bd6d6be1ced3efbd0b2a4c7..1ed6c0d822596ae4c9dc374d112bfa13d370df4f 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div :class="$style.caption"><slot name="caption"></slot></div> - <MkButton v-if="manualSave && changed" primary @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> </div> </template> @@ -138,6 +138,7 @@ function show() { active: computed(() => v.value === option.props?.value), action: () => { v.value = option.props?.value; + changed.value = true; emit('changeByUser', v.value); }, }); @@ -288,6 +289,10 @@ function show() { padding-left: 6px; } +.save { + margin: 8px 0 0 0; +} + .chevron { transition: transform 0.1s ease-out; } diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 95bbe71f9689dc41a6ca95986ada2c6fa8ebd363..e998b081eece91d797fb192eac69e2e77f700a8a 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -19,7 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; -import { ChannelConnection as Connection } from 'misskey-js'; import MkNotes from '@/components/MkNotes.vue'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; import { useStream } from '@/stream.js'; @@ -90,8 +89,8 @@ function prepend(note) { } } -let connection: Connection; -let connection2: Connection; +let connection: Misskey.ChannelConnection | null = null; +let connection2: Misskey.ChannelConnection | null = null; let paginationQuery: Paging | null = null; const stream = useStream(); @@ -163,7 +162,7 @@ function connectChannel() { roleId: props.role, }); } - if (props.src !== 'directs' && props.src !== 'mentions') connection.on('note', prepend); + if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend); } function disconnectChannel() { diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 440dcd84fa34fce9e4fb0c5119898d9227cef376..9fa6fb1ab6ded4c6a6bf865e2cd53c69071c7b9b 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-if="player.url.startsWith('http://') || player.url.startsWith('https://')" sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin" scrolling="no" - :allow="player.allow.join(';')" + :allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')" :class="$style.playerIframe" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :style="{ border: 0 }" diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts index 45c7da40ce2e51171e07aa0cd00ae0a571a7dcdf..c1b380bd1ba35f1a2b5d5469aa6a2a4d9868a32e 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; import { userDetailed } from '../../.storybook/fakes.js'; import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue'; @@ -38,17 +38,17 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/users', () => { + return HttpResponse.json([ userDetailed('44'), userDetailed('49'), - ])); + ]); }), - rest.post('/api/pinned-users', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/pinned-users', () => { + return HttpResponse.json([ userDetailed('44'), userDetailed('49'), - ])); + ]); }), ], }, diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue index 46459df6a663584ea7abd3d884f985329b7920b9..86a5c812bdf769fe9754b3cfa6bc93d719675701 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import Misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; import MkFolder from '@/components/MkFolder.vue'; import XUser from '@/components/MkUserSetupDialog.User.vue'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts index 5182db12b2e9f20996434ca41d9ca0bc15c9fae0..7177d256e188fff2975093936fd0df8e4d073d3a 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; import { userDetailed } from '../../.storybook/fakes.js'; import MkUserSetupDialog from './MkUserSetupDialog.vue'; @@ -38,17 +38,17 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/users', () => { + return HttpResponse.json([ userDetailed('44'), userDetailed('49'), - ])); + ]); }), - rest.post('/api/pinned-users', (req, res, ctx) => { - return res(ctx.json([ + http.post('/api/pinned-users', () => { + return HttpResponse.json([ userDetailed('44'), userDetailed('49'), - ])); + ]); }), ], }, diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index 84bcd3886d29f7cee746ffb7fbc51ec608b41015..dc74c4928d68bc7389d76321b9007cd2c8143079 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -286,13 +286,11 @@ const quoteButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); -const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null; -const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null; const isMyRenote = $i && ($i.id === note.value.userId); const showContent = ref(defaultStore.state.uncollapseCW); const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); -const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter(u => u !== renoteUrl && u !== renoteUri) : null); +const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null); const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const isDeleted = ref(false); @@ -625,7 +623,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); if (props.mock) { diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index ca3ea09b2a7290b4a1f853b2860ddde3e5d8f2ca..ef17a2e8a6bf4559aa34d555d2dd4d38904a5d72 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -312,8 +312,6 @@ const quoteButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); -const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null; -const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null; const isMyRenote = $i && ($i.id === note.value.userId); const showContent = ref(defaultStore.state.uncollapseCW); const isDeleted = ref(false); @@ -322,7 +320,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translating = ref(false); const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; -const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null; +const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null; const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null); const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); @@ -621,7 +619,7 @@ function react(viaKeyboard = false): void { } } else { blur(); - reactionPicker.show(reactButton.value ?? null, reaction => { + reactionPicker.show(reactButton.value ?? null, note.value, reaction => { sound.playMisskeySfx('reaction'); misskeyApi('notes/reactions/create', { diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts index b35b6114fdd4e3e4af4050a687720a562ae04495..d05306908789ddd726d749d5144d7e319140b260 100644 --- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts +++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts @@ -7,7 +7,7 @@ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../../.storybook/mocks.js'; import MkUrl from './MkUrl.vue'; export const Default = { @@ -59,8 +59,8 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.get('/url', (req, res, ctx) => { - return res(ctx.json({ + http.get('/url', () => { + return HttpResponse.json({ title: 'Misskey Hub', icon: 'https://misskey-hub.net/favicon.ico', description: 'Misskeyã¯ã‚ªãƒ¼ãƒ—ンソースã®åˆ†æ•£åž‹ã‚½ãƒ¼ã‚·ãƒ£ãƒ«ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚ングプラットフォームã§ã™ã€‚', @@ -74,7 +74,7 @@ export const Default = { sitename: 'misskey-hub.net', sensitive: false, url: 'https://misskey-hub.net/', - })); + }); }), ], }, diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index dc7474835ded2d24e844be3e65dac3b587fb5298..aeb87e659b6a93419530b2687f96cf950f02b4c8 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -4,7 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<KeepAlive :max="defaultStore.state.numberOfPageCache"> +<KeepAlive + :max="defaultStore.state.numberOfPageCache" + :exclude="pageCacheController" +> <Suspense :timeout="0"> <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/> @@ -16,9 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue'; -import { IRouter, Resolved } from '@/nirax.js'; +import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue'; +import { IRouter, Resolved, RouteDef } from '@/nirax.js'; import { defaultStore } from '@/store.js'; +import { globalEvents } from '@/events.js'; +import MkLoadingPage from '@/pages/_loading_.vue'; const props = defineProps<{ router?: IRouter; @@ -46,20 +51,47 @@ function resolveNested(current: Resolved, d = 0): Resolved | null { } const current = resolveNested(router.current)!; -const currentPageComponent = shallowRef(current.route.component); +const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage); const currentPageProps = ref(current.props); const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props))); function onChange({ resolved, key: newKey }) { const current = resolveNested(resolved); - if (current == null) return; + if (current == null || 'redirect' in current.route) return; currentPageComponent.value = current.route.component; currentPageProps.value = current.props; key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props)); + + nextTick(() => { + // ページé·ç§»å®Œäº†å¾Œã«å†ã³ã‚ャッシュを有効化 + if (clearCacheRequested.value) { + clearCacheRequested.value = false; + } + }); } router.addListener('change', onChange); +// #region ã‚ャッシュ制御 + +/** + * ã‚ャッシュクリアãŒæœ‰åŠ¹ã«ãªã£ãŸã‚‰ã€å…¨ã‚ャッシュをクリアã™ã‚‹ + * + * keepAliveå´ã«watcherãŒã‚ã‚‹ã®ã§ã™ã消ãˆã‚‹ã¨ã¯ãŠã‚‚ã†ã‘ã©ã€å¿µã®ãŸã‚ページé·ç§»å®Œäº†ã¾ã§ã¯ã‚ャッシュを無効化ã—ã¦ãŠã。 + * ã‚ャッシュ有効時å‘ã‘ã«excludeを使ã„ãŸã„å ´åˆã¯ã€pageCacheControllerã«ä¸¦åˆ—ã«çªã£è¾¼ã‚€ã®ã§ã¯ãªãã€ä¸‹ã«è¿½è¨˜ã™ã‚‹ã“㨠+ */ +const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined); +const clearCacheRequested = ref(false); + +globalEvents.on('requestClearPageCache', () => { + if (_DEV_) console.log('clear page cache requested'); + if (!clearCacheRequested.value) { + clearCacheRequested.value = true; + } +}); + +// #endregion + onBeforeUnmount(() => { router.removeListener('change', onChange); }); diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts index 90d5f6eede42d9f2da81da34c7b2587e43d274bb..46faec8d3e4cfa01ed2a13c230ac270685ca4901 100644 --- a/packages/frontend/src/events.ts +++ b/packages/frontend/src/events.ts @@ -4,6 +4,10 @@ */ import { EventEmitter } from 'eventemitter3'; +import * as Misskey from 'misskey-js'; -// TODO: 型付㑠-export const globalEvents = new EventEmitter(); +export const globalEvents = new EventEmitter<{ + themeChanged: () => void; + clientNotification: (notification: Misskey.entities.Notification) => void; + requestClearPageCache: () => void; +}>(); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 7cce77cdf7d2ee9c4ec139f2442afc4085e7e741..010fdbb6d84d8b6434399de0455da34306350700 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -128,9 +128,10 @@ export function promiseDialog<T extends Promise<any>>( let popupIdCount = 0; export const popups = ref([]) as Ref<{ - id: any; - component: any; + id: number; + component: Component; props: Record<string, any>; + events: Record<string, any>; }[]>; const zIndexes = { @@ -144,7 +145,18 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number { return zIndexes[priority]; } -export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events = {}, disposeEvent?: string) { +// InstanceType<typeof Component>['$emit'] ã ã¨ã‚¤ãƒ³ã‚¿ãƒ¼ã‚»ã‚¯ã‚·ãƒ§ãƒ³åž‹ãŒè¿”ã£ã¦ã㦠+// 使ã„物ã«ãªã‚‰ãªã„ã®ã§ã€ä»£ã‚ã‚Šã« ['$props'] ã‹ã‚‰è‰²ã€…çœãã“ã¨ã§ emit ã®åž‹ã‚’生æˆã™ã‚‹ +// FIXME: 何故㋠*.ts ファイルã‹ã‚‰ã ã¨åž‹ãŒã†ã¾ãå–ã‚Œãªã„?ã“ã¨ãŒã‚ã‚‹ã®ã‚’ãªã‚“ã¨ã‹ã—ãŸã„ +type ComponentEmit<T> = T extends new () => { $props: infer Props } + ? EmitsExtractor<Props> + : never; + +type EmitsExtractor<T> = { + [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K]; +}; + +export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events: ComponentEmit<T> = {} as ComponentEmit<T>, disposeEvent?: keyof ComponentEmit<T>) { markRaw(component); const id = ++popupIdCount; diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue index 080a81767b5f8e6f0bff31661279d7595e473310..b39c82515236cac322a98957cac8c24a3d311eb4 100644 --- a/packages/frontend/src/pages/admin/bot-protection.vue +++ b/packages/frontend/src/pages/admin/bot-protection.vue @@ -31,15 +31,15 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template v-else-if="provider === 'mcaptcha'"> <MkInput v-model="mcaptchaSiteKey"> - <template #prefix><i class="ti ti-key"></i></template> + <template #prefix><i class="ph-key ph-bold ph-lg"></i></template> <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template> </MkInput> <MkInput v-model="mcaptchaSecretKey"> - <template #prefix><i class="ti ti-key"></i></template> + <template #prefix><i class="ph-key ph-bold ph-lg"></i></template> <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template> </MkInput> <MkInput v-model="mcaptchaInstanceUrl"> - <template #prefix><i class="ti ti-link"></i></template> + <template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template> <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template> </MkInput> <FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl"> diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index 4238589f188e2a4c31b9ed7b36fb268364597f10..d0fce6407b5d07da1053170571031b077afaaecf 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -158,9 +158,9 @@ function save() { themeColor: themeColor.value === '' ? null : themeColor.value, defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value, defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value, - infoImageUrl: infoImageUrl.value, - notFoundImageUrl: notFoundImageUrl.value, - serverErrorImageUrl: serverErrorImageUrl.value, + infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value, + notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value, + serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value, manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)), }).then(() => { fetchInstance(); diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index e8b0e306fcbda5110b0b1155a6d272739d01883a..965793bf4f18c7943b198e91059aa3d93ea49688 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -49,6 +49,11 @@ SPDX-License-Identifier: AGPL-3.0-only <template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template> </MkTextarea> + <MkTextarea v-model="prohibitedWords"> + <template #label>{{ i18n.ts.prohibitedWords }}</template> + <template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template> + </MkTextarea> + <MkTextarea v-model="hiddenTags"> <template #label>{{ i18n.ts.hiddenTags }}</template> <template #caption>{{ i18n.ts.hiddenTagsDescription }}</template> @@ -87,6 +92,7 @@ const emailRequiredForSignup = ref<boolean>(false); const approvalRequiredForSignup = ref<boolean>(false); const bubbleTimelineEnabled = ref<boolean>(false); const sensitiveWords = ref<string>(''); +const prohibitedWords = ref<string>(''); const hiddenTags = ref<string>(''); const preservedUsernames = ref<string>(''); const bubbleTimeline = ref<string>(''); @@ -99,6 +105,7 @@ async function init() { emailRequiredForSignup.value = meta.emailRequiredForSignup; approvalRequiredForSignup.value = meta.approvalRequiredForSignup; sensitiveWords.value = meta.sensitiveWords.join('\n'); + prohibitedWords.value = meta.prohibitedWords.join('\n'); hiddenTags.value = meta.hiddenTags.join('\n'); preservedUsernames.value = meta.preservedUsernames.join('\n'); tosUrl.value = meta.tosUrl; @@ -115,6 +122,7 @@ function save() { tosUrl: tosUrl.value, privacyPolicyUrl: privacyPolicyUrl.value, sensitiveWords: sensitiveWords.value.split('\n'), + prohibitedWords: prohibitedWords.value.split('\n'), hiddenTags: hiddenTags.value.split('\n'), preservedUsernames: preservedUsernames.value.split('\n'), bubbleInstances: bubbleTimeline.value.split('\n'), diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 7190dd436da090f848a1ea35f0cf657cf08d4c18..b1083636db8de5be92a987df3b8504b939129d17 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -42,14 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>Use TrueMail API</template> </MkSwitch> <MkInput v-model="truemailInstance"> - <template #prefix><i class="ti ti-key"></i></template> + <template #prefix><i class="ph-key ph-bold ph-lg"></i></template> <template #label>TrueMail API Instance</template> </MkInput> <MkInput v-model="truemailAuthKey"> - <template #prefix><i class="ti ti-key"></i></template> + <template #prefix><i class="ph-key ph-bold ph-lg"></i></template> <template #label>TrueMail API Auth Key</template> </MkInput> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton primary @click="save"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton> </div> </MkFolder> diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 6d805862e2a16f9e8b93e92d81fb6e94b06ee4e6..513b6560974261331ab8090820e7e6736dc99ead 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -893,7 +893,6 @@ function getGameImageDriveFile() { formData.append('file', blob); formData.append('name', `bubble-game-${Date.now()}.png`); formData.append('isSensitive', 'false'); - formData.append('comment', 'null'); formData.append('i', $i.token); if (defaultStore.state.uploadFolder) { formData.append('folderId', defaultStore.state.uploadFolder); diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 4a4953bbc296664c367b7950e13115ebda47d936..e8b29e9ba35a30c445f45568ca110111eb18abe1 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch> <MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch> <MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch> - <MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton> + <MkButton @click="refreshMetadata"><i class="ph-arrows-clockwise ph-bold ph-lg"></i> Refresh metadata</MkButton> </div> </FormSection> diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index fb812589b4cf84f4d60d49bd310cd6aa4caba129..634a9503916f31e75cdcbfafe138d428e12dc2cc 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.gamePreviews"> <MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> <div :class="$style.gamePreviewPlayers"> - <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/> <span style="margin: 0 1em;">vs</span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/> - <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span> </div> <div :class="$style.gamePreviewFooter"> <span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> @@ -63,13 +63,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.gamePreviews"> <MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> <div :class="$style.gamePreviewPlayers"> - <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/> <span style="margin: 0 1em;">vs</span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/> - <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span> </div> <div :class="$style.gamePreviewFooter"> <span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index 40bb823ac65be67b6c01f1fadd92f898e1f5b5c3..383841d02fac2381b245caac06feb842325f8a7c 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -172,7 +172,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); const setDefaultEmoji = () => setDefault(pinnedEmojis); function previewReaction(ev: MouseEvent) { - reactionPicker.show(getHTMLElement(ev)); + reactionPicker.show(getHTMLElement(ev), null); } function previewEmoji(ev: MouseEvent) { diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index abf929e30b5c9880562d79fd186d55352951156b..37fca4f8ae88a49ab12fa0e16f31d32fe504e2b6 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -132,6 +132,7 @@ import { langmap } from '@/scripts/langmap.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { defaultStore } from '@/store.js'; +import { globalEvents } from '@/events.js'; import MkInfo from '@/components/MkInfo.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -158,7 +159,7 @@ const profile = reactive({ lang: $i.lang, isBot: $i.isBot ?? false, isCat: $i.isCat ?? false, - speakAsCat: $i.speakAsCat, + speakAsCat: $i.speakAsCat ?? false, }); watch(() => profile, () => { @@ -190,6 +191,7 @@ function saveFields() { os.apiWithDialog('i/update', { fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })), }); + globalEvents.emit('requestClearPageCache'); } function save() { @@ -217,6 +219,7 @@ function save() { isCat: !!profile.isCat, speakAsCat: !!profile.speakAsCat, }); + globalEvents.emit('requestClearPageCache'); claimAchievement('profileFilled'); if (profile.name === 'syuilo' || profile.name === 'ã—ã‚…ã„ã‚') { claimAchievement('setNameToSyuilo'); @@ -248,6 +251,7 @@ function changeAvatar(ev) { }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; + globalEvents.emit('requestClearPageCache'); claimAchievement('profileFilled'); }); } @@ -278,6 +282,7 @@ function changeBanner(ev) { }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; + globalEvents.emit('requestClearPageCache'); }); }, }, { @@ -288,6 +293,7 @@ function changeBanner(ev) { }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; + globalEvents.emit('requestClearPageCache'); }, }], ev.currentTarget ?? ev.target); } else { @@ -312,6 +318,7 @@ function changeBanner(ev) { }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; + globalEvents.emit('requestClearPageCache'); }); } } @@ -342,6 +349,7 @@ function changeBackground(ev) { }); $i.backgroundId = i.backgroundId; $i.backgroundUrl = i.backgroundUrl; + globalEvents.emit('requestClearPageCache'); }); }, }, { @@ -352,6 +360,7 @@ function changeBackground(ev) { }); $i.backgroundId = i.backgroundId; $i.backgroundUrl = i.backgroundUrl; + globalEvents.emit('requestClearPageCache'); }, }], ev.currentTarget ?? ev.target); } else { @@ -376,6 +385,7 @@ function changeBackground(ev) { }); $i.backgroundId = i.backgroundId; $i.backgroundUrl = i.backgroundUrl; + globalEvents.emit('requestClearPageCache'); }); } } diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index cb9c714441a55d21b56ecb98ffe913adb04b4da9..f01ebc1377726a142e22f2f37d2af3259ae80ade 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -88,6 +88,18 @@ import { uniqueBy } from '@/scripts/array.js'; import { fetchThemes, getThemes } from '@/theme-store.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; +import { unisonReload } from '@/scripts/unison-reload.js'; +import * as os from '@/os.js'; + +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; + + unisonReload(); +} const installedThemes = ref(getThemes()); const builtinThemes = getBuiltinThemesRef(); @@ -124,6 +136,7 @@ const lightThemeId = computed({ } }, }); + const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); const wallpaper = ref(miLocalStorage.getItem('wallpaper')); @@ -141,7 +154,7 @@ watch(wallpaper, () => { } else { miLocalStorage.setItem('wallpaper', wallpaper.value); } - location.reload(); + reloadAsk(); }); onActivated(() => { diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts index a2ef5d50d1ef8dd2e7c3c1150f612176e22c8db0..1e67d96c71c95dc08656e1627442ec5dae6aa43d 100644 --- a/packages/frontend/src/pages/user/home.stories.impl.ts +++ b/packages/frontend/src/pages/user/home.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; -import { rest } from 'msw'; +import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../../.storybook/fakes.js'; import { commonHandlers } from '../../../.storybook/mocks.js'; import home_ from './home.vue'; @@ -39,12 +39,13 @@ export const Default = { msw: { handlers: [ ...commonHandlers, - rest.post('/api/users/notes', (req, res, ctx) => { - return res(ctx.json([])); + http.post('/api/users/notes', () => { + return HttpResponse.json([]); }), - rest.get('/api/charts/user/notes', (req, res, ctx) => { - const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300); - return res(ctx.json({ + http.get('/api/charts/user/notes', ({ request }) => { + const url = new URL(request.url); + const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300); + return HttpResponse.json({ total: Array.from({ length }, () => 0), inc: Array.from({ length }, () => 0), dec: Array.from({ length }, () => 0), @@ -54,11 +55,12 @@ export const Default = { renote: Array.from({ length }, () => 0), withFile: Array.from({ length }, () => 0), }, - })); + }); }), - rest.get('/api/charts/user/pv', (req, res, ctx) => { - const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300); - return res(ctx.json({ + http.get('/api/charts/user/pv', ({ request }) => { + const url = new URL(request.url); + const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300); + return HttpResponse.json({ upv: { user: Array.from({ length }, () => 0), visitor: Array.from({ length }, () => 0), @@ -67,7 +69,7 @@ export const Default = { user: Array.from({ length }, () => 0), visitor: Array.from({ length }, () => 0), }, - })); + }); }), ], }, diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index 68c36ca1b4b6d63f036c4a762042b56f6014e91c..199addaefd5cbedb90ac52f89ea19d350a2b829b 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -13,6 +13,7 @@ import { get, set } from '@/scripts/idb-proxy.js'; import { defaultStore } from '@/store.js'; import { useStream } from '@/stream.js'; import { deepClone } from '@/scripts/clone.js'; +import { deepMerge } from '@/scripts/merge.js'; type StateDef = Record<string, { where: 'account' | 'device' | 'deviceAccount'; @@ -84,29 +85,9 @@ export class Storage<T extends StateDef> { return typeof value === 'object' && value !== null && !Array.isArray(value); } - /** - * valueã«ãªã„ã‚ーをdefã‹ã‚‰ã‚‚らã†ï¼ˆå†å¸°çš„)\ - * nullã¯ãã®ã¾ã¾ã€undefinedã¯defã®å€¤ - **/ - private mergeObject<X>(value: X, def: X): X { - if (this.isPureObject(value) && this.isPureObject(def)) { - const result = structuredClone(value) as X; - for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { - if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { - result[k] = v; - } else if (this.isPureObject(v) && this.isPureObject(result[k])) { - const child = structuredClone(result[k]) as X[keyof X] & Record<string | number | symbol, unknown>; - result[k] = this.mergeObject<typeof v>(child, v); - } - } - return result; - } - return value; - } - private mergeState<X>(value: X, def: X): X { if (this.isPureObject(value) && this.isPureObject(def)) { - const merged = this.mergeObject(value, def); + const merged = deepMerge(value, def); if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); @@ -258,7 +239,7 @@ export class Storage<T extends StateDef> { /** * 特定ã®ã‚ーã®ã€ç°¡æ˜“çš„ãªgetter/setterを作りã¾ã™ - * 主ã«vueå ´ã§è¨å®šã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã®modelã¨ã—ã¦ä½¿ã†ç”¨ + * 主ã«vue上ã§è¨å®šã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã®modelã¨ã—ã¦ä½¿ã†ç”¨ */ public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): { get: () => T[K]['default']; diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts index 5adb3f606f4356070c67a8b031e08c2f4391bfff..c6a520e9132bb76aa1d0e0b8edccc788c942466c 100644 --- a/packages/frontend/src/router/main.ts +++ b/packages/frontend/src/router/main.ts @@ -80,6 +80,10 @@ class MainRouterProxy implements IRouter { return this.supplier().resolve(path); } + init(): void { + this.supplier().init(); + } + eventNames(): Array<EventEmitter.EventNames<RouterEvent>> { return this.supplier().eventNames(); } diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9d2a5bfc6bff9b5e10481ecd66368fbdc206ef6 --- /dev/null +++ b/packages/frontend/src/scripts/check-reaction-permissions.ts @@ -0,0 +1,8 @@ +import * as Misskey from 'misskey-js'; + +export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean { + const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? []; + return !(emoji.localOnly && note.user.host !== me.host) + && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote')) + && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id))); +} diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts index ac38faefaa272d0d14d146a21ef62d7d26a1ed74..6d3a1c8c79765b587433d79b31f5fee6e95e63e5 100644 --- a/packages/frontend/src/scripts/clone.ts +++ b/packages/frontend/src/scripts/clone.ts @@ -8,13 +8,13 @@ // ã‚ã¨ã€Vue Refã‚’IndexedDBã«ä¿å˜ã—よã†ã¨ã—ã¦structredCloneを使ã£ãŸã‚‰ã‚¨ãƒ©ãƒ¼ã«ãªã£ãŸ // https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045 -type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[]; +export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[]; export function deepClone<T extends Cloneable>(x: T): T { if (typeof x === 'object') { if (x === null) return x; if (Array.isArray(x)) return x.map(deepClone) as T; - const obj = {} as Record<string, Cloneable>; + const obj = {} as Record<string | number | symbol, Cloneable>; for (const [k, v] of Object.entries(x)) { obj[k] = v === undefined ? undefined : deepClone(v); } diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts index bc05ec94d54cc83c346aacdcc6e066f6dc9b8fcf..2733897bab109620214a26f4757e31f4af418a70 100644 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ b/packages/frontend/src/scripts/code-highlighter.ts @@ -1,9 +1,51 @@ +import { bundledThemesInfo } from 'shiki'; import { getHighlighterCore, loadWasm } from 'shiki/core'; import darkPlus from 'shiki/themes/dark-plus.mjs'; -import type { Highlighter, LanguageRegistration } from 'shiki'; +import { unique } from './array.js'; +import { deepClone } from './clone.js'; +import { deepMerge } from './merge.js'; +import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki'; +import { ColdDeviceStorage } from '@/store.js'; +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; let _highlighter: Highlighter | null = null; +export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>; +export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>; +export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> { + const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme')); + + if (theme.base) { + const base = [lightTheme, darkTheme].find(x => x.id === theme.base); + if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter); + } + + if (theme.codeHighlighter) { + let _res: ThemeRegistration = {}; + if (theme.codeHighlighter.base === '_none_') { + _res = deepClone(theme.codeHighlighter.overrides); + } else { + const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus; + _res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base); + } + if (_res.name == null) { + _res.name = theme.id; + } + _res.type = mode; + + if (getName) { + return _res.name; + } + return _res; + } + + if (getName) { + return 'dark-plus'; + } + return darkPlus; +} + export async function getHighlighter(): Promise<Highlighter> { if (!_highlighter) { return await initHighlighter(); @@ -16,17 +58,34 @@ export async function initHighlighter() { await loadWasm(import('shiki/onig.wasm?init')); + // テーマã®é‡è¤‡ã‚’消㙠+ const themes = unique([ + darkPlus, + ...(await Promise.all([getTheme('light'), getTheme('dark')])), + ]); + const highlighter = await getHighlighterCore({ - themes: [darkPlus], + themes, langs: [ import('shiki/langs/javascript.mjs'), - { - aliases: ['is', 'ais'], - ...aiScriptGrammar.default, - } as unknown as LanguageRegistration, + aiScriptGrammar.default as unknown as LanguageRegistration, ], }); + ColdDeviceStorage.watch('lightTheme', async () => { + const newTheme = await getTheme('light'); + if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) { + highlighter.loadTheme(newTheme); + } + }); + + ColdDeviceStorage.watch('darkTheme', async () => { + const newTheme = await getTheme('dark'); + if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) { + highlighter.loadTheme(newTheme); + } + }); + _highlighter = highlighter; return highlighter; diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts new file mode 100644 index 0000000000000000000000000000000000000000..60097051fab66e003c00e86e90c5164eeea75575 --- /dev/null +++ b/packages/frontend/src/scripts/merge.ts @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { deepClone } from './clone.js'; +import type { Cloneable } from './clone.js'; + +function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * valueã«ãªã„ã‚ーをdefã‹ã‚‰ã‚‚らã†ï¼ˆå†å¸°çš„)\ + * nullã¯ãã®ã¾ã¾ã€undefinedã¯defã®å€¤ + **/ +export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: X, def: X): X { + if (isPureObject(value) && isPureObject(def)) { + const result = deepClone(value as Cloneable) as X; + for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { + if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { + result[k] = v; + } else if (isPureObject(v) && isPureObject(result[k])) { + const child = deepClone(result[k] as Cloneable) as X[keyof X] & Record<string | number | symbol, unknown>; + result[k] = deepMerge<typeof v>(child, v); + } + } + return result; + } + return value; +} diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts index a13351b53656ee0d59396cb89725fb3f83147264..193ac838a2583d4d364a7906b296f71b21640984 100644 --- a/packages/frontend/src/scripts/reaction-picker.ts +++ b/packages/frontend/src/scripts/reaction-picker.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import * as Misskey from 'misskey-js'; import { defineAsyncComponent, Ref, ref } from 'vue'; import { popup } from '@/os.js'; import { defaultStore } from '@/store.js'; @@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js'; class ReactionPicker { private src: Ref<HTMLElement | null> = ref(null); private manualShowing = ref(false); + private targetNote: Ref<Misskey.entities.Note | null> = ref(null); private onChosen?: (reaction: string) => void; private onClosed?: () => void; @@ -23,6 +25,7 @@ class ReactionPicker { src: this.src, pinnedEmojis: reactionsRef, asReactionPicker: true, + targetNote: this.targetNote, manualShowing: this.manualShowing, }, { done: reaction => { @@ -38,8 +41,9 @@ class ReactionPicker { }); } - public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { + public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { this.src.value = src; + this.targetNote.value = targetNote; this.manualShowing.value = true; this.onChosen = onChosen; this.onClosed = onClosed; diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index a174f51756481b148991856eeef3f9b9a4163beb..05ccd3dc380895c70fc7290d8a8d42ba1b7d7c69 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -6,6 +6,7 @@ import { ref } from 'vue'; import tinycolor from 'tinycolor2'; import { deepClone } from './clone.js'; +import type { BuiltinTheme } from 'shiki'; import { globalEvents } from '@/events.js'; import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; @@ -18,6 +19,13 @@ export type Theme = { desc?: string; base?: 'dark' | 'light'; props: Record<string, string>; + codeHighlighter?: { + base: BuiltinTheme; + overrides?: Record<string, any>; + } | { + base: '_none_'; + overrides: Record<string, any>; + }; }; export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); @@ -57,7 +65,7 @@ export const getBuiltinThemesRef = () => { const themeFontFaceName = 'sharkey-theme-font-face'; -let timeout = null; +let timeout: number | null = null; export function applyTheme(theme: Theme, persist = true) { if (timeout) window.clearTimeout(timeout); diff --git a/packages/frontend/src/scripts/touch.ts b/packages/frontend/src/scripts/touch.ts index 05f379e4aa602ab80a9b58a479b737b2cda83e06..4fd7d500c4eecbcfa5e0f225e6be71e921cd4366 100644 --- a/packages/frontend/src/scripts/touch.ts +++ b/packages/frontend/src/scripts/touch.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { ref } from 'vue'; import { deviceKind } from '@/scripts/device-kind.js'; const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0; @@ -16,3 +17,6 @@ if (isTouchSupported && !isTouchUsing) { isTouchUsing = true; }, { passive: true }); } + +/** (MkHorizontalSwipe) 横スワイプä¸ã‹ï¼Ÿ */ +export const isHorizontalSwipeSwiping = ref(false); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 4dad6ce406049ae1a8d504974e27073b48266db6..d695caa95f0275ce1496f6559a4eb283007a111d 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -7,6 +7,7 @@ import { markRaw, ref } from 'vue'; import * as Misskey from 'misskey-js'; import { miLocalStorage } from './local-storage.js'; import type { SoundType } from '@/scripts/sound.js'; +import type { BuiltinTheme as ShikiBuiltinTheme } from 'shiki'; import { Storage } from '@/pizzax.js'; import { hemisphere } from '@/scripts/intl-const.js'; diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5 index 8544572718f6faa53e9ee75323e7c15369c43793..7b70aa1e0941cb7e5184e7b8f7a6c142c55f14ea 100644 --- a/packages/frontend/src/themes/_dark.json5 +++ b/packages/frontend/src/themes/_dark.json5 @@ -95,4 +95,8 @@ X16: ':alpha<0.7<@panel', X17: ':alpha<0.8<@bg', }, + + codeHighlighter: { + base: 'one-dark-pro', + }, } diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5 index 2f3783310fa276c873c17385ddc0fc72b0cd1ba6..d797aec734954d47773f8f782ca1b9169064e15b 100644 --- a/packages/frontend/src/themes/_light.json5 +++ b/packages/frontend/src/themes/_light.json5 @@ -95,4 +95,8 @@ X16: ':alpha<0.7<@panel', X17: ':alpha<0.8<@bg', }, + + codeHighlighter: { + base: 'catppuccin-latte', + }, } diff --git a/packages/frontend/test/url-preview.test.ts b/packages/frontend/test/url-preview.test.ts index 6cf8317c07c75263c9e3b1ce6844fdfadad24604..b7587754c65f7955603c0fe7b365ffd84735d898 100644 --- a/packages/frontend/test/url-preview.test.ts +++ b/packages/frontend/test/url-preview.test.ts @@ -116,6 +116,34 @@ describe('MkUrlPreview', () => { assert.strictEqual(iframe?.allow, 'fullscreen;web-share'); }); + test('A Summaly proxy response without allow falls back to the default', async () => { + const iframe = await renderAndOpenPreview({ + url: 'https://example.local', + player: { + url: 'https://example.local/player', + width: null, + height: null, + allow: undefined as any, + }, + }); + assert.exists(iframe, 'iframe should exist'); + assert.strictEqual(iframe?.allow, 'autoplay;encrypted-media;fullscreen'); + }); + + test('Filtering the allow list from the Summaly proxy', async () => { + const iframe = await renderAndOpenPreview({ + url: 'https://example.local', + player: { + url: 'https://example.local/player', + width: null, + height: null, + allow: ['autoplay', 'camera', 'fullscreen'], + }, + }); + assert.exists(iframe, 'iframe should exist'); + assert.strictEqual(iframe?.allow, 'autoplay;fullscreen'); + }); + test('Having a player width should keep the fixed aspect ratio', async () => { const iframe = await renderAndOpenPreview({ url: 'https://example.local', diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts index 7e72359167e1a0e9236e5dec0e745ef1509fa4a7..f091e599a921e9088a136926d43e6c945c5fa6d0 100644 --- a/packages/misskey-js/generator/src/generator.ts +++ b/packages/misskey-js/generator/src/generator.ts @@ -4,22 +4,6 @@ import { toPascal } from 'ts-case-convert'; import OpenAPIParser from '@readme/openapi-parser'; import openapiTS from 'openapi-typescript'; -function generateVersionHeaderComment(openApiDocs: OpenAPIV3_1.Document): string { - const contents = { - version: openApiDocs.info.version, - generatedAt: new Date().toISOString(), - }; - - const lines: string[] = []; - lines.push('/*'); - for (const [key, value] of Object.entries(contents)) { - lines.push(` * ${key}: ${value}`); - } - lines.push(' */'); - - return lines.join('\n'); -} - async function generateBaseTypes( openApiDocs: OpenAPIV3_1.Document, openApiJsonPath: string, @@ -36,9 +20,6 @@ async function generateBaseTypes( } lines.push(''); - lines.push(generateVersionHeaderComment(openApiDocs)); - lines.push(''); - const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true }); lines.push(generatedTypes); lines.push(''); @@ -59,8 +40,6 @@ async function generateSchemaEntities( const schemaNames = Object.keys(schemas); const typeAliasLines: string[] = []; - typeAliasLines.push(generateVersionHeaderComment(openApiDocs)); - typeAliasLines.push(''); typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`); typeAliasLines.push( ...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`), @@ -119,9 +98,6 @@ async function generateEndpoints( const entitiesOutputLine: string[] = []; - entitiesOutputLine.push(generateVersionHeaderComment(openApiDocs)); - entitiesOutputLine.push(''); - entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`); entitiesOutputLine.push(''); @@ -139,9 +115,6 @@ async function generateEndpoints( const endpointOutputLine: string[] = []; - endpointOutputLine.push(generateVersionHeaderComment(openApiDocs)); - endpointOutputLine.push(''); - endpointOutputLine.push('import type {'); endpointOutputLine.push( ...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','), @@ -187,9 +160,6 @@ async function generateApiClientJSDoc( const endpointOutputLine: string[] = []; - endpointOutputLine.push(generateVersionHeaderComment(openApiDocs)); - endpointOutputLine.push(''); - endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`); endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`); endpointOutputLine.push(''); diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 03952c7ac150b4326fabca3bb32662c22db53ff6..26b682f1c4e7340bde685adcb076ff73aeeeb8f7 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.2.0-beta.8", + "version": "2024.2.0-beta.11", "description": "Misskey SDK for JavaScript", "types": "./built/dts/index.d.ts", "exports": { @@ -39,7 +39,7 @@ "@misskey-dev/eslint-plugin": "1.0.0", "@swc/jest": "0.2.31", "@types/jest": "29.5.11", - "@types/node": "20.11.10", + "@types/node": "20.11.17", "@typescript-eslint/eslint-plugin": "6.18.1", "@typescript-eslint/parser": "6.18.1", "eslint": "8.56.0", diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 205fba69a7a4c031cf4ec39f6fe38e53eddf230d..2d9d78228ed4d860b4d3ec617ccb4039e77ab433 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1,8 +1,3 @@ -/* - * version: 2024.2.0-beta2 - * generatedAt: 2024-02-03T19:17:05.681Z - */ - import type { SwitchCaseResponseType } from '../api.js'; import type { Endpoints } from './endpoint.js'; diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 28fc82c60981069289635f6bff7669733e8d7e7d..3f95d3cf4be0dd2cbff4a93d050252fd04c6f87f 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,8 +1,3 @@ -/* - * version: 2024.2.0-beta2 - * generatedAt: 2024-02-03T19:17:05.679Z - */ - import type { EmptyRequest, EmptyResponse, diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 17471a27f0de6a4c37449c2a75551a0073877df6..3e96fcc61c5d2110a47c2943120227142045ba18 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -1,8 +1,3 @@ -/* - * version: 2024.2.0-beta2 - * generatedAt: 2024-02-03T19:17:05.678Z - */ - import { operations } from './types.js'; export type EmptyRequest = Record<string, unknown> | undefined; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 5d87df588cee046f1c8a9e0a14e6d18591957651..8d594e61bb45cce026b617b93b2dd0b2b1b8eb16 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -1,8 +1,3 @@ -/* - * version: 2024.2.0-beta2 - * generatedAt: 2024-02-03T19:17:05.676Z - */ - import { components } from './types.js'; export type Error = components['schemas']['Error']; export type UserLite = components['schemas']['UserLite']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 54765e86f25ab1a6ec734ea478834a28940bce4b..dee7caba99225f0a26bb3d3cb9b79c7e2b9f60aa 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -1,11 +1,6 @@ /* eslint @typescript-eslint/naming-convention: 0 */ /* eslint @typescript-eslint/no-explicit-any: 0 */ -/* - * version: 2024.2.0-beta2 - * generatedAt: 2024-02-03T19:17:05.578Z - */ - /** * This file was auto-generated by openapi-typescript. * Do not make direct changes to the file. @@ -4556,6 +4551,7 @@ export type components = { name: string; category: string | null; url: string; + localOnly?: boolean; isSensitive?: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; }; @@ -4797,6 +4793,7 @@ export type operations = { hiddenTags: string[]; blockedHosts: string[]; sensitiveWords: string[]; + prohibitedWords: string[]; bannedEmailDomains?: string[]; preservedUsernames: string[]; bubbleInstances: string[]; @@ -8818,6 +8815,7 @@ export type operations = { hiddenTags?: string[] | null; blockedHosts?: string[] | null; sensitiveWords?: string[] | null; + prohibitedWords?: string[] | null; themeColor?: string | null; mascotImageUrl?: string | null; bannerUrl?: string | null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 913f0ec2e1b1fe758ab4ef5fa78a29417e14e8bb..2c15690f933eea74b39bdf22c7379af1841385d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,8 +116,8 @@ importers: specifier: 1.7.0 version: 1.7.0 '@simplewebauthn/server': - specifier: 9.0.1 - version: 9.0.1 + specifier: 9.0.2 + version: 9.0.2 '@sinonjs/fake-timers': specifier: 11.2.2 version: 11.2.2 @@ -161,14 +161,14 @@ importers: specifier: 1.20.2 version: 1.20.2 bullmq: - specifier: 5.1.5 - version: 5.1.5 + specifier: 5.1.9 + version: 5.1.9 cacheable-lookup: specifier: 7.0.0 version: 7.0.0 cbor: - specifier: 9.0.1 - version: 9.0.1 + specifier: 9.0.2 + version: 9.0.2 chalk: specifier: 5.3.0 version: 5.3.0 @@ -559,8 +559,8 @@ importers: specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 20.11.10 - version: 20.11.10 + specifier: 20.11.17 + version: 20.11.17 '@types/node-fetch': specifier: 3.0.3 version: 3.0.3 @@ -656,7 +656,7 @@ importers: version: 9.0.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.11.10) + version: 29.7.0(@types/node@20.11.17) jest-mock: specifier: 29.7.0 version: 29.7.0 @@ -707,13 +707,13 @@ importers: version: 15.0.0 '@vitejs/plugin-vue': specifier: 5.0.3 - version: 5.0.3(vite@5.0.12)(vue@3.4.15) + version: 5.0.3(vite@5.1.0)(vue@3.4.15) '@vue/compiler-sfc': specifier: 3.4.15 version: 3.4.15 aiscript-vscode: - specifier: github:aiscript-dev/aiscript-vscode#v0.0.6 - version: github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd + specifier: github:aiscript-dev/aiscript-vscode#v0.1.2 + version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07 astring: specifier: 1.8.6 version: 1.8.6 @@ -838,8 +838,8 @@ importers: specifier: 1.7.2 version: 1.7.2(vue@3.4.15) vite: - specifier: 5.0.12 - version: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + specifier: 5.1.0 + version: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) vue: specifier: 3.4.15 version: 3.4.15(typescript@5.3.3) @@ -891,7 +891,7 @@ importers: version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) '@storybook/react-vite': specifier: 7.6.10 - version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.0.12) + version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.1.0) '@storybook/testing-library': specifier: 0.2.2 version: 0.2.2 @@ -906,7 +906,7 @@ importers: version: 7.6.10(vue@3.4.15) '@storybook/vue3-vite': specifier: 7.6.10 - version: 7.6.10(typescript@5.3.3)(vite@5.0.12)(vue@3.4.15) + version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15) '@testing-library/vue': specifier: 8.0.1 version: 8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15) @@ -923,8 +923,8 @@ importers: specifier: 4.0.6 version: 4.0.6 '@types/node': - specifier: 20.11.10 - version: 20.11.10 + specifier: 20.11.17 + version: 20.11.17 '@types/punycode': specifier: 2.1.3 version: 2.1.3 @@ -962,8 +962,8 @@ importers: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 13.6.3 - version: 13.6.3 + specifier: 13.6.4 + version: 13.6.4 eslint: specifier: 8.56.0 version: 8.56.0 @@ -986,17 +986,17 @@ importers: specifier: 4.0.5 version: 4.0.5 msw: - specifier: 2.1.2 - version: 2.1.2(typescript@5.3.3) + specifier: 2.1.7 + version: 2.1.7(typescript@5.3.3) msw-storybook-addon: - specifier: 1.10.0 - version: 1.10.0(msw@2.1.2) + specifier: 2.0.0-beta.1 + version: 2.0.0-beta.1(msw@2.1.7) nodemon: specifier: 3.0.3 version: 3.0.3 prettier: - specifier: 3.2.4 - version: 3.2.4 + specifier: 3.2.5 + version: 3.2.5 react: specifier: 18.2.0 version: 18.2.0 @@ -1105,7 +1105,7 @@ importers: version: 9.0.0(eslint@8.54.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.11.10) + version: 29.7.0(@types/node@20.11.17) jest-worker: specifier: ^29.7.0 version: 29.7.0 @@ -1185,7 +1185,7 @@ importers: devDependencies: '@microsoft/api-extractor': specifier: 7.39.1 - version: 7.39.1(@types/node@20.11.10) + version: 7.39.1(@types/node@20.11.17) '@misskey-dev/eslint-plugin': specifier: 1.0.0 version: 1.0.0(@typescript-eslint/eslint-plugin@6.18.1)(@typescript-eslint/parser@6.18.1)(eslint-plugin-import@2.29.1)(eslint@8.56.0) @@ -1196,8 +1196,8 @@ importers: specifier: 29.5.11 version: 29.5.11 '@types/node': - specifier: 20.11.10 - version: 20.11.10 + specifier: 20.11.17 + version: 20.11.17 '@typescript-eslint/eslint-plugin': specifier: 6.18.1 version: 6.18.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0)(typescript@5.3.3) @@ -1209,7 +1209,7 @@ importers: version: 8.56.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.11.10) + version: 29.7.0(@types/node@20.11.17) jest-fetch-mock: specifier: 3.0.3 version: 3.0.3 @@ -1993,7 +1993,7 @@ packages: '@babel/traverse': 7.23.4 '@babel/types': 7.23.4 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2085,7 +2085,7 @@ packages: '@babel/core': 7.23.3 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -2281,6 +2281,14 @@ packages: hasBin: true dependencies: '@babel/types': 7.23.4 + dev: true + + /@babel/parser@7.23.9: + resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.4 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3): resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} @@ -3261,13 +3269,6 @@ packages: dependencies: regenerator-runtime: 0.13.11 - /@babel/runtime@7.23.2: - resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - dev: true - /@babel/runtime@7.23.4: resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==} engines: {node: '>=6.9.0'} @@ -3295,7 +3296,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.4 '@babel/types': 7.23.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3356,12 +3357,6 @@ packages: cookie: 0.5.0 dev: true - /@bundled-es-modules/js-levenshtein@2.0.1: - resolution: {integrity: sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==} - dependencies: - js-levenshtein: 1.1.6 - dev: true - /@bundled-es-modules/statuses@1.0.1: resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} dependencies: @@ -4003,7 +3998,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.23.0 ignore: 5.3.0 @@ -4020,7 +4015,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.23.0 ignore: 5.3.0 @@ -4261,7 +4256,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4317,7 +4312,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -4338,14 +4333,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.10) + jest-config: 29.7.0(@types/node@20.11.17) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4380,7 +4375,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 jest-mock: 29.7.0 dev: true @@ -4406,7 +4401,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.10 + '@types/node': 20.11.17 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -4439,7 +4434,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.20 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -4532,7 +4527,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -4544,11 +4539,11 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@types/yargs': 17.0.32 chalk: 4.1.2 - /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.0.12): + /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.1.0): resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==} peerDependencies: typescript: '>= 4.3.x' @@ -4562,7 +4557,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.3.3) typescript: 5.3.3 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) dev: true /@jridgewell/gen-mapping@0.3.3: @@ -4655,24 +4650,24 @@ packages: react: 18.2.0 dev: true - /@microsoft/api-extractor-model@7.28.4(@types/node@20.11.10): + /@microsoft/api-extractor-model@7.28.4(@types/node@20.11.17): resolution: {integrity: sha512-vucgyPmgHrJ/D4/xQywAmjTmSfxAx2/aDmD6TkIoLu51FdsAfuWRbijWA48AePy60OO+l+mmy9p2P/CEeBZqig==} dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.63.0(@types/node@20.11.10) + '@rushstack/node-core-library': 3.63.0(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' dev: true - /@microsoft/api-extractor@7.39.1(@types/node@20.11.10): + /@microsoft/api-extractor@7.39.1(@types/node@20.11.17): resolution: {integrity: sha512-V0HtCufWa8hZZvSmlEzQZfINcJkHAU/bmpyJQj6w+zpI87EkR8DuBOW6RWrO9c7mUYFZoDaNgUTyKo83ytv+QQ==} hasBin: true dependencies: - '@microsoft/api-extractor-model': 7.28.4(@types/node@20.11.10) + '@microsoft/api-extractor-model': 7.28.4(@types/node@20.11.17) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.63.0(@types/node@20.11.10) + '@rushstack/node-core-library': 3.63.0(@types/node@20.11.17) '@rushstack/rig-package': 0.5.1 '@rushstack/ts-command-line': 4.17.1 colors: 1.2.5 @@ -4817,8 +4812,8 @@ packages: engines: {node: '>=18'} dev: true - /@mswjs/interceptors@0.25.14: - resolution: {integrity: sha512-2dnIxl+obqIqjoPXTFldhe6pcdOrqiz+GcLaQQ6hmL02OldAF7nIC+rUgTWm+iF6lvmyCVhFFqbgbapNhR8eag==} + /@mswjs/interceptors@0.25.16: + resolution: {integrity: sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==} engines: {node: '>=18'} dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -5751,7 +5746,7 @@ packages: requiresBuild: true optional: true - /@rushstack/node-core-library@3.63.0(@types/node@20.11.10): + /@rushstack/node-core-library@3.63.0(@types/node@20.11.17): resolution: {integrity: sha512-Q7B3dVpBQF1v+mUfxNcNZh5uHVR8ntcnkN5GYjbBLrxUYHBGKbnCM+OdcN+hzCpFlLBH6Ob0dEHhZ0spQwf24A==} peerDependencies: '@types/node': '*' @@ -5759,7 +5754,7 @@ packages: '@types/node': optional: true dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 colors: 1.2.5 fs-extra: 7.0.1 import-lazy: 4.0.0 @@ -5803,8 +5798,8 @@ packages: resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} dev: true - /@simplewebauthn/server@9.0.1: - resolution: {integrity: sha512-XnilMoBygy2BOZjIHPxby+7ENx5ChN2wXfhd14mOgO/XitYMqdphTo/kwgxEI4/Je3lELK1h/eLDJqM2fIKS1w==} + /@simplewebauthn/server@9.0.2: + resolution: {integrity: sha512-aaWA+qVOU4byk5IDb/l+M1+7dmrAJhTb4ISJHucpsgRQcMMEes76tbGIqO2JQuA7N50tc/OBrnGKBjoKYG1kSw==} engines: {node: '>=16.0.0'} dependencies: '@hexagon/base64': 1.1.27 @@ -5813,15 +5808,15 @@ packages: '@peculiar/asn1-rsa': 2.3.8 '@peculiar/asn1-schema': 2.3.8 '@peculiar/asn1-x509': 2.3.8 - '@simplewebauthn/types': 9.0.0 + '@simplewebauthn/types': 9.0.1 cbor-x: 1.5.4 cross-fetch: 4.0.0 transitivePeerDependencies: - encoding dev: false - /@simplewebauthn/types@9.0.0: - resolution: {integrity: sha512-Lo6LLNQee66D//KueYy9AyX7oiQ7BBKJgdLzP3l0HJDrV4GRSzSAii8AtigBGOeNc8hOQsF/D8itItyuZX9djA==} + /@simplewebauthn/types@9.0.1: + resolution: {integrity: sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==} dev: false /@simplewebauthn/typescript-types@8.3.4: @@ -6579,7 +6574,7 @@ packages: - supports-color dev: true - /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.0.12): + /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.1.0): resolution: {integrity: sha512-qxe19axiNJVdIKj943e1ucAmADwU42fTGgMSdBzzrvfH3pSOmx2057aIxRzd8YtBRnj327eeqpgCHYIDTunMYQ==} peerDependencies: '@preact/preset-vite': '*' @@ -6611,7 +6606,7 @@ packages: magic-string: 0.30.5 rollup: 3.29.4 typescript: 5.3.3 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - encoding - supports-color @@ -6968,7 +6963,7 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.0.12): + /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.1.0): resolution: {integrity: sha512-YE2+J1wy8nO+c6Nv/hBMu91Edew3K184L1KSnfoZV8vtq2074k1Me/8pfe0QNuq631AncpfCYNb37yBAXQ/80w==} engines: {node: '>=16'} peerDependencies: @@ -6976,16 +6971,16 @@ packages: react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.0.12) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.1.0) '@rollup/pluginutils': 5.1.0(rollup@4.9.6) - '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.0.12) + '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0) '@storybook/react': 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) - '@vitejs/plugin-react': 3.1.0(vite@5.0.12) + '@vitejs/plugin-react': 3.1.0(vite@5.1.0) magic-string: 0.30.5 react: 18.2.0 react-docgen: 7.0.1 react-dom: 18.2.0(react@18.2.0) - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -7100,18 +7095,18 @@ packages: file-system-cache: 2.3.0 dev: true - /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.0.12)(vue@3.4.15): + /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15): resolution: {integrity: sha512-5f0Rh4PTVEeAI86ybihfN+rHGXXLNiRsoGKinpJSb7hkfsq/L7u3sVCXJwH/qsG+rUJlZyHs3kfa4/Kgyyi3Mg==} engines: {node: ^14.18 || >=16} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: - '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.0.12) + '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0) '@storybook/core-server': 7.6.10 '@storybook/vue3': 7.6.10(vue@3.4.15) - '@vitejs/plugin-vue': 4.5.2(vite@5.0.12)(vue@3.4.15) + '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.15) magic-string: 0.30.5 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) vue-docgen-api: 4.64.1(vue@3.4.15) transitivePeerDependencies: - '@preact/preset-vite' @@ -7581,7 +7576,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.23.4 - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.23.4 '@types/aria-query': 5.0.1 aria-query: 5.1.3 chalk: 4.1.2 @@ -7609,7 +7604,7 @@ packages: optional: true dependencies: '@adobe/css-tools': 4.3.1 - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.23.4 '@types/jest': 28.1.3 aria-query: 5.1.3 chalk: 3.0.0 @@ -7636,7 +7631,7 @@ packages: '@vue/compiler-sfc': '>= 3' vue: '>= 3' dependencies: - '@babel/runtime': 7.23.2 + '@babel/runtime': 7.23.4 '@testing-library/dom': 9.3.3 '@vue/compiler-sfc': 3.4.15 '@vue/test-utils': 2.4.1(vue@3.4.15) @@ -7672,7 +7667,7 @@ packages: /@types/accepts@1.3.7: resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/archiver@6.0.2: @@ -7726,7 +7721,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/braces@3.0.1: @@ -7738,14 +7733,14 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@types/responselike': 1.0.0 dev: false /@types/cbor@6.0.0: resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==} dependencies: - cbor: 9.0.1 + cbor: 9.0.2 dev: true /@types/chai-subset@1.3.5: @@ -7771,7 +7766,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/content-disposition@0.5.8: @@ -7789,7 +7784,7 @@ packages: /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/detect-port@1.3.2: @@ -7841,7 +7836,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -7862,7 +7857,7 @@ packages: /@types/fluent-ffmpeg@2.1.24: resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/form-data@2.5.0: @@ -7876,13 +7871,13 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/http-cache-semantics@4.0.4: @@ -7891,7 +7886,7 @@ packages: /@types/http-link-header@1.0.5: resolution: {integrity: sha512-AxhIKR8UbyoqCTNp9rRepkktHuUOw3DjfOfDCaO9kwI8AYzjhxyrvZq4+mRw/2daD3hYDknrtSeV6SsPwmc71w==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/istanbul-lib-coverage@2.0.4: @@ -7944,10 +7939,6 @@ packages: pretty-format: 29.7.0 dev: true - /@types/js-levenshtein@1.1.3: - resolution: {integrity: sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==} - dev: true - /@types/js-yaml@4.0.9: resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} dev: true @@ -7955,7 +7946,7 @@ packages: /@types/jsdom@21.1.6: resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@types/tough-cookie': 4.0.2 parse5: 7.1.2 dev: true @@ -7979,7 +7970,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: false /@types/lodash@4.14.191: @@ -8023,7 +8014,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 form-data: 3.0.1 dev: true @@ -8037,8 +8028,8 @@ packages: resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==} dev: true - /@types/node@20.11.10: - resolution: {integrity: sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==} + /@types/node@20.11.17: + resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} dependencies: undici-types: 5.26.5 @@ -8057,7 +8048,7 @@ packages: /@types/nodemailer@6.4.14: resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/normalize-package-data@2.4.1: @@ -8074,13 +8065,13 @@ packages: resolution: {integrity: sha512-Ali0fUUn+zgr4Yy/pCTFbuiaiJpq7l7OQwFnxYVchNbNGIx0c4Wkcdje6WO89I91RAaYF+gVc1pOaizA4YKZmA==} dependencies: '@types/express': 4.17.17 - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/oauth@0.9.4: resolution: {integrity: sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 /@types/object-assign-deep@0.4.3: resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==} @@ -8093,7 +8084,7 @@ packages: /@types/pg@8.11.0: resolution: {integrity: sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 pg-protocol: 1.6.0 pg-types: 4.0.1 dev: true @@ -8117,7 +8108,7 @@ packages: /@types/qrcode@1.5.5: resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/qs@6.9.7: @@ -8147,7 +8138,7 @@ packages: /@types/readdir-glob@1.1.1: resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/rename@1.0.7: @@ -8161,7 +8152,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: false /@types/sanitize-html@2.9.5: @@ -8186,7 +8177,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/serviceworker@0.0.67: @@ -8264,19 +8255,19 @@ packages: /@types/vary@1.1.3: resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/web-push@3.6.3: resolution: {integrity: sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -8300,7 +8291,7 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true optional: true @@ -8321,7 +8312,7 @@ packages: '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.3.0 @@ -8350,7 +8341,7 @@ packages: '@typescript-eslint/type-utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6) '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.54.0 graphemer: 1.4.0 ignore: 5.3.0 @@ -8379,7 +8370,7 @@ packages: '@typescript-eslint/type-utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.18.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.3.0 @@ -8405,7 +8396,7 @@ packages: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8426,7 +8417,7 @@ packages: '@typescript-eslint/types': 6.12.0 '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.54.0 typescript: 5.1.6 transitivePeerDependencies: @@ -8447,7 +8438,7 @@ packages: '@typescript-eslint/types': 6.18.1 '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.18.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8490,7 +8481,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 ts-api-utils: 1.0.3(typescript@5.3.3) typescript: 5.3.3 @@ -8510,7 +8501,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6) '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.54.0 ts-api-utils: 1.0.3(typescript@5.1.6) typescript: 5.1.6 @@ -8530,7 +8521,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 ts-api-utils: 1.0.3(typescript@5.3.3) typescript: 5.3.3 @@ -8564,7 +8555,7 @@ packages: dependencies: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8585,7 +8576,7 @@ packages: dependencies: '@typescript-eslint/types': 6.12.0 '@typescript-eslint/visitor-keys': 6.12.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8606,7 +8597,7 @@ packages: dependencies: '@typescript-eslint/types': 6.18.1 '@typescript-eslint/visitor-keys': 6.18.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -8702,7 +8693,7 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitejs/plugin-react@3.1.0(vite@5.0.12): + /@vitejs/plugin-react@3.1.0(vite@5.1.0): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -8713,30 +8704,30 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.3) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color dev: true - /@vitejs/plugin-vue@4.5.2(vite@5.0.12)(vue@3.4.15): + /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.15): resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 || ^5.0.0 vue: ^3.2.25 dependencies: - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) vue: 3.4.15(typescript@5.3.3) dev: true - /@vitejs/plugin-vue@5.0.3(vite@5.0.12)(vue@3.4.15): + /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.15): resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 vue: ^3.2.25 dependencies: - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) vue: 3.4.15(typescript@5.3.3) dev: false @@ -8818,23 +8809,24 @@ packages: path-browserify: 1.0.1 dev: true - /@vue/compiler-core@3.3.12: - resolution: {integrity: sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w==} + /@vue/compiler-core@3.4.15: + resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==} dependencies: - '@babel/parser': 7.23.6 - '@vue/shared': 3.3.12 + '@babel/parser': 7.23.9 + '@vue/shared': 3.4.15 + entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.0.2 - dev: true - /@vue/compiler-core@3.4.15: - resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==} + /@vue/compiler-core@3.4.18: + resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==} dependencies: - '@babel/parser': 7.23.6 - '@vue/shared': 3.4.15 + '@babel/parser': 7.23.9 + '@vue/shared': 3.4.18 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.0.2 + dev: true /@vue/compiler-core@3.4.3: resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==} @@ -8846,29 +8838,29 @@ packages: source-map-js: 1.0.2 dev: true - /@vue/compiler-dom@3.3.12: - resolution: {integrity: sha512-RdJU9oEYaoPKUdGXCy0l+i4clesdDeLmbvRlszoc9iagsnBnMmQtYfCPVQ5BHB6o7K4SCucDdJM2Dh3oXB0D6g==} - dependencies: - '@vue/compiler-core': 3.3.12 - '@vue/shared': 3.3.12 - dev: true - /@vue/compiler-dom@3.4.15: resolution: {integrity: sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==} dependencies: '@vue/compiler-core': 3.4.15 '@vue/shared': 3.4.15 + /@vue/compiler-dom@3.4.18: + resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==} + dependencies: + '@vue/compiler-core': 3.4.18 + '@vue/shared': 3.4.18 + dev: true + /@vue/compiler-sfc@3.4.15: resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==} dependencies: - '@babel/parser': 7.23.6 + '@babel/parser': 7.23.9 '@vue/compiler-core': 3.4.15 '@vue/compiler-dom': 3.4.15 '@vue/compiler-ssr': 3.4.15 '@vue/shared': 3.4.15 estree-walker: 2.0.2 - magic-string: 0.30.5 + magic-string: 0.30.7 postcss: 8.4.33 source-map-js: 1.0.2 @@ -8888,8 +8880,8 @@ packages: dependencies: '@volar/language-core': 1.11.1 '@volar/source-map': 1.11.1 - '@vue/compiler-dom': 3.3.12 - '@vue/shared': 3.3.12 + '@vue/compiler-dom': 3.4.18 + '@vue/shared': 3.4.18 computeds: 0.0.1 minimatch: 9.0.3 muggle-string: 0.3.1 @@ -8925,13 +8917,13 @@ packages: '@vue/shared': 3.4.15 vue: 3.4.15(typescript@5.3.3) - /@vue/shared@3.3.12: - resolution: {integrity: sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==} - dev: true - /@vue/shared@3.4.15: resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==} + /@vue/shared@3.4.18: + resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==} + dev: true + /@vue/shared@3.4.3: resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==} dev: true @@ -9052,7 +9044,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -9060,7 +9052,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -9456,7 +9448,7 @@ packages: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) fastq: 1.15.0 transitivePeerDependencies: - supports-color @@ -9880,8 +9872,8 @@ packages: dependencies: node-gyp-build: 4.6.0 - /bullmq@5.1.5: - resolution: {integrity: sha512-Rc9QGHrj/wJ8RMENKa839o1pJmdicg7KBTfmVU8YqYuEK2JcMSJaKMg2XrAi7sdYSawgOJgC/kiW9fCGYEj6Yg==} + /bullmq@5.1.9: + resolution: {integrity: sha512-9MfcQxYyfkG8kxpIxRsRXWYlTRQ1o8xWqgdoFR5pLClVTjtMI8qeDO5basRQLZPfp/uiPtv+gpzJ3OTNrm2ZNg==} dependencies: cron-parser: 4.8.1 glob: 8.1.0 @@ -10060,8 +10052,8 @@ packages: cbor-extract: 2.1.1 dev: false - /cbor@9.0.1: - resolution: {integrity: sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==} + /cbor@9.0.2: + resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==} engines: {node: '>=16'} dependencies: nofilter: 3.1.0 @@ -10600,7 +10592,7 @@ packages: readable-stream: 3.6.2 dev: false - /create-jest@29.7.0(@types/node@20.11.10): + /create-jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -10609,7 +10601,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.10) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -10856,6 +10848,56 @@ packages: yauzl: 2.10.0 dev: true + /cypress@13.6.4: + resolution: {integrity: sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==} + engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} + hasBin: true + requiresBuild: true + dependencies: + '@cypress/request': 3.0.0 + '@cypress/xvfb': 1.2.4(supports-color@8.1.1) + '@types/sinonjs__fake-timers': 8.1.1 + '@types/sizzle': 2.3.3 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.3.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.3 + commander: 6.2.1 + common-tags: 1.8.2 + dayjs: 1.11.10 + debug: 4.3.4(supports-color@8.1.1) + enquirer: 2.3.6 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1(supports-color@8.1.1) + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + is-ci: 3.0.1 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0(enquirer@2.3.6) + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.8 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + process: 0.11.10 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.5.4 + supports-color: 8.1.1 + tmp: 0.2.1 + untildify: 4.0.0 + yauzl: 2.10.0 + dev: true + /dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -10920,7 +10962,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 - dev: true /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -10933,6 +10974,7 @@ packages: dependencies: ms: 2.1.2 supports-color: 8.1.1 + dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -11145,7 +11187,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -11462,7 +11504,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -11778,7 +11820,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11825,7 +11867,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11872,7 +11914,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12524,7 +12566,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -13173,7 +13215,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13212,7 +13254,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -13222,7 +13264,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13231,7 +13273,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13391,7 +13433,7 @@ packages: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -13815,7 +13857,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -13869,7 +13911,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -13890,7 +13932,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.11.10): + /jest-cli@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13904,10 +13946,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.10) + create-jest: 29.7.0(@types/node@20.11.17) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.10) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.6.2 @@ -13918,7 +13960,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.11.10): + /jest-config@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -13933,7 +13975,7 @@ packages: '@babel/core': 7.23.3 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 babel-jest: 29.7.0(@babel/core@7.23.3) chalk: 4.1.2 ci-info: 3.9.0 @@ -14012,7 +14054,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -14041,7 +14083,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.11.10 + '@types/node': 20.11.17 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -14100,7 +14142,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.11.10 + '@types/node': 20.11.17 dev: true /jest-mock@29.7.0: @@ -14108,7 +14150,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 jest-util: 29.7.0 dev: true @@ -14163,7 +14205,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -14194,7 +14236,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -14246,7 +14288,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -14270,7 +14312,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.10 + '@types/node': 20.11.17 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -14289,13 +14331,13 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.11.10): + /jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -14308,7 +14350,7 @@ packages: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.10) + jest-cli: 29.7.0(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -14355,11 +14397,6 @@ packages: nopt: 6.0.0 dev: true - /js-levenshtein@1.1.6: - resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} - engines: {node: '>=0.10.0'} - dev: true - /js-stringify@1.0.2: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} @@ -14880,6 +14917,12 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + /mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} dev: false @@ -15298,17 +15341,17 @@ packages: msgpackr-extract: 3.0.2 dev: false - /msw-storybook-addon@1.10.0(msw@2.1.2): - resolution: {integrity: sha512-soCTMTf7DnLeaMnFHPrtVgbyeFTJALVvnDHpzzXpJad+HOzJgQdwU4EAzVfDs1q+X5cVEgxOdAhSMC7ljvnSXg==} + /msw-storybook-addon@2.0.0-beta.1(msw@2.1.7): + resolution: {integrity: sha512-DRyIAMK3waEfC+pKTyiIq68OZfiZ4WZGUVAn6J4YwCRpDdoCvLzzoC2spN0Jgegx4dEmJ7589ATnS14NxqeBig==} peerDependencies: - msw: '>=0.35.0 <2.0.0' + msw: ^2.0.0 dependencies: is-node-process: 1.2.0 - msw: 2.1.2(typescript@5.3.3) + msw: 2.1.7(typescript@5.3.3) dev: true - /msw@2.1.2(typescript@5.3.3): - resolution: {integrity: sha512-7OKbeZNFQTCPFe++o+zZHMkQRIUi4D/5N1dAD3lOlaV+2Wpv3Srp3VFrFeH+2ftZJbb4jAiC0caZoW1efr80KQ==} + /msw@2.1.7(typescript@5.3.3): + resolution: {integrity: sha512-yTIYqEMqDSrdbVMrfmqP6rTKQsnIbglTvVmAHDWwNegyXPXRcV+RjsaFEqubRS266gwWCDLm9YdOkWSKLdDvJQ==} engines: {node: '>=18'} hasBin: true requiresBuild: true @@ -15319,13 +15362,11 @@ packages: optional: true dependencies: '@bundled-es-modules/cookie': 2.0.0 - '@bundled-es-modules/js-levenshtein': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@mswjs/cookies': 1.1.0 - '@mswjs/interceptors': 0.25.14 + '@mswjs/interceptors': 0.25.16 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 - '@types/js-levenshtein': 1.1.3 '@types/statuses': 2.0.4 chalk: 4.1.2 chokidar: 3.5.3 @@ -15333,7 +15374,6 @@ packages: headers-polyfill: 4.0.2 inquirer: 8.2.5 is-node-process: 1.2.0 - js-levenshtein: 1.1.6 outvariant: 1.4.2 path-to-regexp: 6.2.1 strict-event-emitter: 0.5.1 @@ -16680,6 +16720,14 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -16763,8 +16811,8 @@ packages: hasBin: true dev: true - /prettier@3.2.4: - resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} engines: {node: '>=14'} hasBin: true dev: true @@ -17044,7 +17092,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -18013,7 +18061,7 @@ packages: dependencies: '@hapi/hoek': 10.0.1 '@hapi/wreck': 18.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) joi: 17.7.0 transitivePeerDependencies: - supports-color @@ -18213,7 +18261,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -18366,7 +18414,7 @@ packages: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -19008,7 +19056,7 @@ packages: '@babel/core': 7.23.3 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.10) + jest: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -19271,7 +19319,7 @@ packages: chalk: 4.1.2 cli-highlight: 2.1.11 dayjs: 1.11.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) dotenv: 16.0.3 glob: 10.3.10 ioredis: 5.3.2 @@ -19605,17 +19653,17 @@ packages: core-util-is: 1.0.2 extsprintf: 1.3.0 - /vite-node@0.34.6(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0): + /vite-node@0.34.6(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) mlly: 1.5.0 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -19631,8 +19679,8 @@ packages: resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} dev: true - /vite@5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0): - resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} + /vite@5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0): + resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -19659,9 +19707,9 @@ packages: terser: optional: true dependencies: - '@types/node': 20.11.10 + '@types/node': 20.11.17 esbuild: 0.19.11 - postcss: 8.4.33 + postcss: 8.4.35 rollup: 4.9.6 sass: 1.70.0 terser: 5.27.0 @@ -19713,7 +19761,7 @@ packages: dependencies: '@types/chai': 4.3.11 '@types/chai-subset': 1.3.5 - '@types/node': 20.11.10 + '@types/node': 20.11.17 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -19723,7 +19771,7 @@ packages: acorn-walk: 8.3.2 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) happy-dom: 10.0.3 local-pkg: 0.4.3 magic-string: 0.30.5 @@ -19733,8 +19781,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.7.0 - vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) - vite-node: 0.34.6(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0) + vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) + vite-node: 0.34.6(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -19750,6 +19798,42 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + /vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + dev: false + + /vscode-languageclient@9.0.1: + resolution: {integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==} + engines: {vscode: ^1.82.0} + dependencies: + minimatch: 5.1.6 + semver: 7.5.4 + vscode-languageserver-protocol: 3.17.5 + dev: false + + /vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + dev: false + + /vscode-languageserver-textdocument@1.0.11: + resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==} + dev: false + + /vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + dev: false + + /vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + dependencies: + vscode-languageserver-protocol: 3.17.5 + dev: false + /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: true @@ -19805,7 +19889,7 @@ packages: peerDependencies: eslint: '>=6.0.0' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -20329,11 +20413,27 @@ packages: readable-stream: 3.6.0 dev: false - github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd: - resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/b5a8aa0ad927831a0b867d1c183460a14e6c48cd} + '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz': + resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz} + name: '@aiscript-dev/aiscript-languageserver' + version: 0.1.5 + hasBin: true + dependencies: + seedrandom: 3.0.5 + stringz: 2.1.0 + uuid: 9.0.1 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.11 + dev: false + + github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07: + resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/793211d40243c8775f6b85f015c221c82cbffb07} name: aiscript-vscode - version: 0.0.6 + version: 0.1.2 engines: {vscode: ^1.83.0} + dependencies: + '@aiscript-dev/aiscript-languageserver': '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz' + vscode-languageclient: 9.0.1 dev: false github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.6.10)(@storybook/components@7.6.10)(@storybook/core-events@7.6.10)(@storybook/manager-api@7.6.10)(@storybook/preview-api@7.6.10)(@storybook/theming@7.6.10)(@storybook/types@7.6.10)(react-dom@18.2.0)(react@18.2.0):