diff --git a/CHANGELOG.md b/CHANGELOG.md index 714269535..00d69dbd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ You should also include the user name that made the change. --> +## 13.5.0 (2023/02/08) + +### Changes +- perf(client): do not render custom emojis in user names + +### Improvements +- Client: disableShowingAnimatedImagesのデフォルト値をprefers-reduced-motionにする +- enhance(client): tweak medialist style + +### Bugfixes +- fix docker health check +- Client: MkEmojiPickerでもChromeで検索ダイアログで変換確定するとそのまま検索されてしまうのを修正 +- fix(mfm): default degree not used in rotate +- fix(server): validate urls from ap to improve security + ## 13.4.0 (2023/02/05) ### Improvements diff --git a/Dockerfile b/Dockerfile index 89a8d38f8..0bfd24bd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \ && apt-get update \ && apt-get install -yqq --no-install-recommends \ - build-essential wget ca-certificates \ - && wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq \ - && chmod +x /usr/bin/yq + build-essential RUN corepack enable @@ -44,7 +42,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \ && apt-get update \ && apt-get install -y --no-install-recommends \ - ffmpeg tini \ + ffmpeg tini curl \ && corepack enable \ && groupadd -g "${GID}" misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ @@ -54,7 +52,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ USER misskey WORKDIR /misskey -COPY --from=builder /usr/bin/yq /usr/bin/yq COPY --chown=misskey:misskey --from=builder /misskey/node_modules ./node_modules COPY --chown=misskey:misskey --from=builder /misskey/built ./built COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules diff --git a/healthcheck.sh b/healthcheck.sh index f8e598b28..e97a3f063 100644 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -1,4 +1,4 @@ #!/bin/bash -PORT=$(yq '.port' /misskey/.config/default.yml) +PORT=$(grep '^port:' /misskey/.config/default.yml | awk 'NR==1{print $2; exit}') curl -s -S -o /dev/null "http://localhost:${PORT}" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 66af4654c..701fb7eef 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1044,7 +1044,7 @@ _achievements: flavor: "Grazie per aver usato Misskey!" _noteClipped1: title: "Devo clippare!" - description: "Ho raccolto in Clip la prima Nota" + description: "Hai raccolto la tua prima Nota in una Clip" _noteFavorited1: title: "Guarda le stelle" description: "Aggiungi una Nota ai preferiti per la prima volta" @@ -1080,7 +1080,7 @@ _achievements: title: "Follow me!" description: "Hai ottenuto 10 profili Follower" _followers50: - title: "Follower a frotte" + title: "Un gregge di Follower" description: "Hai ottenuto 50 Follower" _followers100: title: "Popolare" @@ -1108,7 +1108,7 @@ _achievements: title: "Caccia al tesoro" description: "Hai trovato un tesoro nascosto" _client30min: - title: "Piccola pausa" + title: "Piccola grande pausa" description: "Hai passato più di 30 minuti su Misskey" _noteDeletedWithin1min: title: "Ooops!" @@ -1134,7 +1134,7 @@ _achievements: title: "Hello, world!" description: "Hai scritto «Hello world» nel blocco appunti" _open3windows: - title: "Finestrato" + title: "Apri le finestre!" description: "Hai aperto almeno 3 finestre contemporaneamente" _driveFolderCircularReference: title: "Riferimento circolare" @@ -1170,7 +1170,7 @@ _achievements: _cookieClicked: title: "Clicca il biscotto" description: "Hai giocato a cliccare il cookie" - flavor: "Hai autorizzato i cookie?" + flavor: "È il sito giusto?" _brainDiver: title: "Brain Diver" description: "Pubblica un link a Brain Diver" @@ -1195,6 +1195,9 @@ _role: baseRole: "Ruolo di base" useBaseValue: "Eredita dal ruolo base" chooseRoleToAssign: "Seleziona il ruolo da assegnare" + iconUrl: "URL dell'icona" + asBadge: "Mostra come badge" + descriptionOfAsBadge: "Se indicato, accanto al nome utente viene visualizzata l'icona del ruolo." canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo" descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori." priority: "Priorità" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index a46ddcc10..ae938835e 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -1,2 +1,72 @@ --- _lang_: "ພາສາລາວ" +headlineMisskey: "ເຊື່ອມຕໍ່ເຄືອຂ່າຍໂດຍຫມາຍເຫດ" +introMisskey: "ຍິນດີຕ້ອນຮັບ! Misskey ເປັນແຫຼ່ງເປີດ, ການບໍລິການ microblogging ກະຈາຍ\nສ້າງ \"ບັນທຶກ\" ເພື່ອແບ່ງປັນຄວາມຄິດຂອງທ່ານກັບທຸກໆຄົນທີ່ຢູ່ອ້ອມຮອບທ່ານ 📡\nດ້ວຍ \"ປະຕິກິລິຍາ\", ທ່ານຍັງສາມາດສະແດງຄວາມຮູ້ສຶກຂອງທ່ານຢ່າງໄວວາກ່ຽວກັບບັນທຶກຂອງທຸກໆຄົນ 👍\nມາສຳຫຼວດໂລກໃໝ່! 🚀" +poweredByMisskeyDescription: "{name} ແມ່ນສ່ວນໜຶ່ງຂອງການບໍລິການທີ່ຂັບເຄື່ອນໂດຍແພລດຟອມ open source. <b>Misskey</b> (ເອີ້ນວ່າ \"Misskey instance\")" +monthAndDay: "{ເດືອນ}/{ມື້}" +search: "ຄົ້ນຫາ" +notifications: "ການແຈ້ງເຕືອນ" +username: "ຊື່ຜູ້ໃຊ້" +password: "ລະຫັດຜ່ານ" +forgotPassword: "ລືມລະຫັດຜ່ານ" +fetchingAsApObject: "ກຳລັງດຶງຂໍ້ມູນຈາກ fediverse..." +ok: "ຕົກລົງ" +gotIt: "ເຂົ້າໃຈແລ້ວ!" +cancel: "ຍົກເລີກ" +noThankYou: "ບໍ່ແມ່ນຕອນນີ້" +enterUsername: "ປ້ອນຊື່ຜູ້ໃຊ້" +renotedBy: "Renoted ໂດຍ {ຜູ້ໃຊ້}" +noNotes: "ບໍ່ມີຫມາຍເຫດ" +noNotifications: "ບໍ່ມີການແຈ້ງເຕືອນ" +instance: "ອີນສະແຕນ" +settings: "ກຳນົດຄ່າ" +basicSettings: "ການຕັ້ງຄ່າພື້ນຖານ" +otherSettings: "ການຕັ້ງຄ່າອື່ນໆ" +openInWindow: "ເປີດຢູ່ໃນປ່ອງຢ້ຽມ" +profile: "ໂພຼຟາຍ" +timeline: "ເສັ້ນກຳນົດເວລາ" +noAccountDescription: "ຜູ້ໃຊ້ນີ້ຍັງບໍ່ໄດ້ຂຽນໃນຊີວະປະຫວັດຂອງເຂົາເຈົ້າເທື່ອ" +login: "ເຂົ້າສູ່ລະບົບ" +loggingIn: "ກຳລັງເຂົ້າສູ່ລະບົບ..." +logout: "ອອກຈາກລະບົບ" +signup: "ລົງທະບຽນ" +uploading: "ການອັບໂຫຼດ..." +save: "ບັນທຶກ" +users: "ຜູ້ໃຊ້ຕ່າງໆ" +addUser: "ເພີ່ມຜູ້ໃຊ້" +favorite: "ເພີ່ມໃສ່ລາຍການທີ່ມັກ" +favorites: "ລາຍການທີ່ມັກ" +unfavorite: "ລຶບອອກຈາກລາຍການທີ່ມັກ" +favorited: "ເພີ່ມໃສ່ລາຍການທີ່ມັກແລ້ວ" +alreadyFavorited: "ເພີ່ມເຂົ້າໃນລາຍການທີ່ມັກແລ້ວ." +cantFavorite: "ບໍ່ສາມາດເພີ່ມໃສ່ລາຍການທີ່ມັກໄດ້." +pin: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌" +unpin: "ຖອດປັກໝຸດອອກຈາກໂປຣໄຟລ໌" +copyContent: "ຄັດລອກເນື້ອຫາ" +copyLink: "ສຳເນົາລິ້ງ" +delete: "ລຶບ" +deleteAndEdit: "ລົບແລະແກ້ໄຂ" +deleteAndEditConfirm: "ເຈົ້າແນ່ໃຈບໍ່? ທີ່ທ່ານຕ້ອງການທີ່ຈະລຶບບັນທຶກນີ້ແລະແກ້ໄຂມັນ ທ່ານອາດຈະສູນເສຍການໂຕ້ຕອບ, ບັນທຶກ, ແລະການຕອບກັບທັງໝົດ" +addToList: "ເພີ່ມໃສ່ລາຍຊື່" +sendMessage: "ສົ່ງຂໍ້ຄວາມ" +pinned: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌" +instances: "ອີນສະແຕນ" +remove: "ລຶບ" +smtpUser: "ຊື່ຜູ້ໃຊ້" +smtpPass: "ລະຫັດຜ່ານ" +user: "ຜູ້ໃຊ້ຕ່າງໆ" +searchByGoogle: "ຄົ້ນຫາ" +_mfm: + search: "ຄົ້ນຫາ" +_sfx: + notification: "ການແຈ້ງເຕືອນ" +_widgets: + profile: "ໂພຼຟາຍ" + notifications: "ການແຈ້ງເຕືອນ" + timeline: "ເສັ້ນກຳນົດເວລາ" +_profile: + username: "ຊື່ຜູ້ໃຊ້" +_deck: + _columns: + notifications: "ການແຈ້ງເຕືອນ" + tl: "ເສັ້ນກຳນົດເວລາ" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 8087090f5..98ac29151 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1147,7 +1147,7 @@ _achievements: description: "คุณได้คลิกที่นี่" _justPlainLucky: title: "แค่ลัคกี้ธรรมดา" - description: "มีโอกาสที่จะได้รับด้วยความน่าจะเป็นไปได้ 0.01% ทุก ๆ 10 วินาที" + description: "มีโอกาสที่จะได้รับด้วยความน่าจะเป็นไปได้ 0.005% ทุก ๆ 10 วินาที" _setNameToSyuilo: title: "พระเจ้าคอมเพล็กซ์" description: "ตั้งชื่อของคุณเป็น \"syuilo\"" @@ -1182,7 +1182,7 @@ _role: description: "คำอธิบายบทบาท" permission: "สิทธิ์ตามบทบาท" descriptionOfPermission: "<b>ผู้ดูแลกลั่นกรองเนื้อหา</b> สามารถดำเนินการดูแลขั้นพื้นฐานได้นะ\n<b>ผู้ดูแลระบบ</b> สามารถเปลี่ยนการตั้งค่าทั้งหมดของอินสแตนซ์ได้นะ" - assignTarget: "กำหนดเป้าหมาย" + assignTarget: "มอบหมาย" descriptionOfAssignTarget: "<b>แมนนวล</b> เพื่อเปลี่ยนผู้ที่เป็นส่วนหนึ่งของบทบาทนี้และใครที่ไม่ใช่ด้วยตนเอง\n<b>เงื่อนไข</b> เพื่อให้ผู้ใช้ได้รับการกำหนดและนำออกจากบทบาทนี้โดยอัตโนมัติตามเงื่อนไขชุดหนึ่ง" manual: "ปรับเอง" conditional: "มีเงื่อนไข" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 52f3f41fc..d38cab9d1 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -1382,8 +1382,8 @@ _tutorial: step1_1: "Ласкаво просимо!" step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів на яких ви підписані." step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки і не підписані на інших." - step2_1: "Перш ніж зробити запис або підписатись на когось, спочатку заповніть свій обліковий запис." - step2_2: "Надання деякої інформації про себе дозволить іншим користувачам підписатись на вас." + step2_1: "Перш ніж зробити запис або підписатись на когось, заповніть свій профіль." + step2_2: "Надання деякої інформації про себе допоможе іншим користувачам вирішити підписатись на вас." step3_1: "Ви успішно налаштували свій обліковий запис?" step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення олівця на екрані." step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку у верхньому правому куті форми." diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 74f3c237f..ed1e7d8bc 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -326,7 +326,7 @@ connectService: "己連結" disconnectService: "己斷開 " enableLocalTimeline: "開啟本地時間軸" enableGlobalTimeline: "啟用全域時間軸" -disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審核員仍可以繼續使用。" +disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審查員仍可以繼續使用。" registration: "註冊" enableRegistration: "開啟新使用者註冊" invite: "邀請" @@ -389,8 +389,8 @@ aboutMisskey: "關於 Misskey" administrator: "管理員" token: "權杖" twoStepAuthentication: "兩階段驗證" -moderator: "審核員" -moderation: "監察" +moderator: "審查員" +moderation: "審查" nUsersMentioned: "提到了{n}" securityKey: "安全金鑰" securityKeyName: "金鑰名稱" @@ -607,7 +607,7 @@ testEmail: "測試郵件發送" wordMute: "被靜音的文字" regexpError: "正規表達式錯誤" regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:" -instanceMute: "實例的靜音" +instanceMute: "被靜音的實例" userSaysSomething: "{name}說了什麼" makeActive: "啟用" display: "檢視" @@ -1181,7 +1181,7 @@ _role: name: "角色名稱" description: "角色描述 " permission: "角色的權限" - descriptionOfPermission: "<b>審核員</b>執行與審核相關的基本操作。\n<b>管理員</b>能變更實例的全部設定。" + descriptionOfPermission: "<b>審查員</b>執行與審查相關的基本操作。\n<b>管理員</b>能變更實例的全部設定" assignTarget: "指派目標" descriptionOfAssignTarget: "<b>手動</b>是以手動管理這個角色包含的人員。\n<b>符合條件</b>是設定條件以自動包含符合條件的使用者。" manual: "手動" @@ -1198,8 +1198,8 @@ _role: iconUrl: "圖示的URL" asBadge: "顯示為徽章" descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。" - canEditMembersByModerator: "允許編輯監察員的成員" - descriptionOfCanEditMembersByModerator: "如果開啟,管理員與監察員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。" + canEditMembersByModerator: "允許編輯審查員的成員" + descriptionOfCanEditMembersByModerator: "如果開啟,管理員與審查員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。" priority: "優先級" _priority: low: "低" @@ -1236,7 +1236,7 @@ _role: or: "~或~" not: "~否" _sensitiveMediaDetection: - description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" + description: "您可以使用機器學習自動檢測敏感媒體並將其用於審查。 伺服器的負荷會稍微增加。" sensitivity: "檢測敏感度" sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。" setSensitiveFlagAutomatically: "設定 NSFW 旗標" diff --git a/package.json b/package.json index 6e0414ec0..10de772e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.4.0", + "version": "13.5.0", "codename": "nasubi", "repository": { "type": "git", @@ -19,7 +19,7 @@ "start": "cd packages/backend && node ./built/boot/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js", "init": "pnpm migrate", - "migrate": "cd packages/backend && pnpm typeorm migration:run -d ormconfig.js", + "migrate": "cd packages/backend && pnpm migrate", "migrateandstart": "pnpm migrate && pnpm start", "gulp": "pnpm exec gulp build", "watch": "pnpm dev", @@ -28,8 +28,8 @@ "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "pnpm cypress run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", - "jest": "cd packages/backend && pnpm cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand", - "jest-and-coverage": "cd packages/backend && pnpm cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand", + "jest": "cd packages/backend && pnpm jest", + "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "test": "pnpm jest", "test-and-coverage": "pnpm jest-and-coverage", "format": "pnpm exec gulp format", diff --git a/packages/backend/ormconfig.js b/packages/backend/ormconfig.js index 32c26f7b6..229e5bf1f 100644 --- a/packages/backend/ormconfig.js +++ b/packages/backend/ormconfig.js @@ -1,6 +1,6 @@ import { DataSource } from 'typeorm'; import { loadConfig } from './built/config.js'; -import { entities } from './built/postgre.js'; +import { entities } from './built/postgres.js'; const config = loadConfig(); diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 8a70129eb..35416209a 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -4,7 +4,7 @@ import { DataSource } from 'typeorm'; import { createRedisConnection } from '@/redis.js'; import { DI } from './di-symbols.js'; import { loadConfig } from './config.js'; -import { createPostgreDataSource } from './postgre.js'; +import { createPostgresDataSource } from './postgres.js'; import { RepositoryModule } from './models/RepositoryModule.js'; import type { Provider, OnApplicationShutdown } from '@nestjs/common'; @@ -18,7 +18,7 @@ const $config: Provider = { const $db: Provider = { provide: DI.db, useFactory: async (config) => { - const db = createPostgreDataSource(config); + const db = createPostgresDataSource(config); return await db.initialize(); }, inject: [DI.config], diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 63f031944..3ac796fb2 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -150,17 +150,9 @@ export class CustomEmojiService { if (note.renote) { emojis = emojis.concat(note.renote.emojis .map(e => this.parseEmojiStr(e, note.renote!.userHost))); - if (note.renote.user) { - emojis = emojis.concat(note.renote.user.emojis - .map(e => this.parseEmojiStr(e, note.renote!.userHost))); - } } const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis; emojis = emojis.concat(customReactions); - if (note.user) { - emojis = emojis.concat(note.user.emojis - .map(e => this.parseEmojiStr(e, note.userHost))); - } } return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[]; } diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index d01817b0d..928ef1ae7 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -48,6 +48,10 @@ export class ApImageService { throw new Error('invalid image: url not privided'); } + if (!image.url.startsWith('https://')) { + throw new Error('invalid image: unexpected shcema of url: ' + image.url); + } + this.logger.info(`Creating the Image: ${image.url}`); const instance = await this.metaService.fetch(); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index c9192f53b..813415e6f 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -1,8 +1,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import { DI } from '@/di-symbols.js'; -import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; -import type { UsersRepository } from '@/models/index.js'; +import type { MessagingMessagesRepository, PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; @@ -18,6 +17,7 @@ import { PollService } from '@/core/PollService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; import { MessagingService } from '@/core/MessagingService.js'; +import { bindThis } from '@/decorators.js'; import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { ApLoggerService } from '../ApLoggerService.js'; @@ -32,7 +32,6 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ApNoteService { @@ -133,6 +132,16 @@ export class ApNoteService { const note: IPost = object; this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); + + if (note.id && !note.id.startsWith('https://')) { + throw new Error('unexpected shcema of note.id: ' + note.id); + } + + const url = getOneApHrefNullable(note.url); + + if (url && !url.startsWith('https://')) { + throw new Error('unexpected shcema of note url: ' + url); + } this.logger.info(`Creating the Note: ${note.id}`); @@ -307,7 +316,7 @@ export class ApNoteService { apEmojis, poll, uri: note.id, - url: getOneApHrefNullable(note.url), + url: url, }, silent); } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 2325bbe09..76f820cda 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -252,6 +252,12 @@ export class ApPersonService implements OnModuleInit { const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + const url = getOneApHrefNullable(person.url); + + if (url && !url.startsWith('https://')) { + throw new Error('unexpected shcema of person url: ' + url); + } + // Create user let user: IRemoteUser; try { @@ -283,7 +289,7 @@ export class ApPersonService implements OnModuleInit { await transactionalEntityManager.save(new UserProfile({ userId: user.id, description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, - url: getOneApHrefNullable(person.url), + url: url, fields, birthday: bday ? bday[0] : null, location: person['vcard:Address'] ?? null, @@ -425,6 +431,12 @@ export class ApPersonService implements OnModuleInit { const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); + const url = getOneApHrefNullable(person.url); + + if (url && !url.startsWith('https://')) { + throw new Error('unexpected shcema of person url: ' + url); + } + const updates = { lastFetchedAt: new Date(), inbox: person.inbox, @@ -459,7 +471,7 @@ export class ApPersonService implements OnModuleInit { } await this.userProfilesRepository.update({ userId: exist.id }, { - url: getOneApHrefNullable(person.url), + url: url, fields, description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, birthday: bday ? bday[0] : null, diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index efc196f74..9dd115d45 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -20,6 +20,7 @@ type PackOptions = { withUser?: boolean, }; import { bindThis } from '@/decorators.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; @Injectable() export class DriveFileEntityService { @@ -82,7 +83,9 @@ export class DriveFileEntityService { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { - return proxiedUrl(file.uri); + if (!(mode === 'static' && file.type.startsWith('video'))) { + return proxiedUrl(file.uri); + } } // リモートかつ期限切れはローカルプロキシを試みる @@ -91,20 +94,19 @@ export class DriveFileEntityService { if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 const url = `${this.config.url}/files/${key}`; - if (mode === 'avatar') return proxiedUrl(url); + if (mode === 'avatar') return proxiedUrl(file.uri); return url; } } - const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/avif', 'image/svg+xml'].includes(file.type); - - if (mode === 'static') { - return file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null); - } - const url = file.webpublicUrl ?? file.url; - if (mode === 'avatar') return proxiedUrl(url); + if (mode === 'static') { + return file.thumbnailUrl ?? (isMimeImage(file.type, 'sharp-convertible-image') ? proxiedUrl(url) : null); + } + if (mode === 'avatar') { + return proxiedUrl(url); + } return url; } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index eea9d5567..09b69d509 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -413,7 +413,6 @@ export class UserEntityService implements OnModuleInit { faviconUrl: instance.faviconUrl, themeColor: instance.themeColor, } : undefined) : undefined, - emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), // パフォーマンス上の理由でローカルユーザーのみ badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.map(r => ({ @@ -464,6 +463,7 @@ export class UserEntityService implements OnModuleInit { isModerator: role.isModerator, isAdministrator: role.isAdministrator, }))), + emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), } : {}), ...(opts.detail && isMe ? { diff --git a/packages/backend/src/postgre.ts b/packages/backend/src/postgres.ts similarity index 99% rename from packages/backend/src/postgre.ts rename to packages/backend/src/postgres.ts index c55cb78a6..33b924e77 100644 --- a/packages/backend/src/postgre.ts +++ b/packages/backend/src/postgres.ts @@ -197,7 +197,7 @@ export const entities = [ const log = process.env.NODE_ENV !== 'production'; -export function createPostgreDataSource(config: Config) { +export function createPostgresDataSource(config: Config) { return new DataSource({ type: 'postgres', host: config.db.host, diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index c8fd41e1d..50988939a 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -11,7 +11,7 @@ import FormData from 'form-data'; import { DataSource } from 'typeorm'; import got, { RequestError } from 'got'; import loadConfig from '../src/config/load.js'; -import { entities } from '../src/postgre.js'; +import { entities } from '@/postgres.js'; import type * as misskey from 'misskey-js'; const _filename = fileURLToPath(import.meta.url); diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index 0e18a5a83..701dd8bb3 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -4,7 +4,7 @@ <MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`"> <MkAvatar class="avatar" :user="report.targetUser" indicator/> <div class="names"> - <MkUserName class="name" :user="report.targetUser"/> + <span class="name _nowrap">{{ report.targetUser.name ?? report.targetUser.username }}</span> <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> </div> </MkA> diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index e523b988b..595e393e0 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -4,7 +4,7 @@ <li v-for="user in users" tabindex="-1" :class="$style.item" @click="complete(type, user)" @keydown="onKeydown"> <img :class="$style.avatar" :src="user.avatarUrl"/> <span :class="$style.userName"> - <MkUserName :key="user.id" :user="user"/> + <span :key="user.id" class="_nowrap">{{ user.name ?? user.username }}</span> </span> <span>@{{ acct(user) }}</span> </li> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 2063ab4de..c7556ec36 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -1,6 +1,6 @@ <template> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> - <input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()"> + <input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter"> <div ref="emojisEl" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0" class="body"> @@ -327,6 +327,11 @@ function paste(event: ClipboardEvent): void { } } +function onEnter(ev: KeyboardEvent) { + if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; + done(); +} + function done(query?: string): boolean | void { if (query == null) query = q.value; if (query == null || typeof query !== 'string') return; diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 718ce80e0..c4af1ee8d 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -6,15 +6,14 @@ <span>{{ $ts.clickToShow }}</span> </div> <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> - <audio - ref="audioEl" - class="audio" - :src="media.url" - :title="media.name" - controls - preload="metadata" - @volumechange="volumechange" - /> + <VuePlyr :options="{ volume: 0.5 }"> + <audio controls preload="metadata"> + <source + :src="media.url" + :type="media.type" + /> + </audio> + </VuePlyr> </div> <a v-else class="download" @@ -31,7 +30,9 @@ <script lang="ts" setup> import { onMounted } from 'vue'; import * as misskey from 'misskey-js'; +import VuePlyr from 'vue-plyr'; import { ColdDeviceStorage } from '@/store'; +import 'vue-plyr/dist/vue-plyr.css'; const props = withDefaults(defineProps<{ media: misskey.entities.DriveFile; @@ -55,7 +56,11 @@ onMounted(() => { width: 100%; border-radius: 4px; margin-top: 4px; - overflow: hidden; + overflow: clip; + + --plyr-color-main: var(--accent); + --plyr-audio-controls-background: var(--bg); + --plyr-audio-controls-color: var(--accentLighten); > .download, > .sensitive { @@ -93,10 +98,8 @@ onMounted(() => { } > .audio { - .audio { - display: block; - width: 100%; - } + border-radius: 8px; + overflow: clip; } } </style> diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index f263ae0ce..562cd3d63 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -1,11 +1,11 @@ <template> -<div class="hoawjimk"> +<div> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> - <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container"> - <div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length"> + <div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container"> + <div ref="gallery" :class="[$style.medias, count <= 4 ? $style['n' + count] : $style.nMany]"> <template v-for="media in mediaList.filter(media => previewable(media))"> - <XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/> - <XImage v-else-if="media.type.startsWith('image')" :key="media.id" class="image" :data-id="media.id" :image="media" :raw="raw"/> + <XVideo v-if="media.type.startsWith('video')" :key="media.id" :class="$style.media" :video="media"/> + <XImage v-else-if="media.type.startsWith('image')" :key="media.id" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/> </template> </div> </div> @@ -32,6 +32,7 @@ const props = defineProps<{ const gallery = ref(null); const pswpZIndex = os.claimZIndex('middle'); +const count = $computed(() => props.mediaList.filter(media => previewable(media)).length); onMounted(() => { const lightbox = new PhotoSwipeLightbox({ @@ -122,82 +123,61 @@ const previewable = (file: misskey.entities.DriveFile): boolean => { }; </script> -<style lang="scss" scoped> -.hoawjimk { - > .gird-container { - position: relative; - width: 100%; - margin-top: 4px; +<style lang="scss" module> +.container { + position: relative; + width: 100%; + margin-top: 4px; +} - &:before { - content: ''; - display: block; - padding-top: 56.25% // 16:9; +.medias { + display: grid; + grid-gap: 8px; + + &.n1 { + aspect-ratio: 16/9; + grid-template-rows: 1fr; + } + + &.n2 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + } + + &.n3 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 0.5fr; + grid-template-rows: 1fr 1fr; + + > .media:nth-child(1) { + grid-row: 1 / 3; } - > div { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: grid; - grid-gap: 8px; - - > * { - overflow: hidden; - border-radius: 6px; - } - - &[data-count="1"] { - grid-template-rows: 1fr; - } - - &[data-count="2"] { - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr; - } - - &[data-count="3"] { - grid-template-columns: 1fr 0.5fr; - grid-template-rows: 1fr 1fr; - - > *:nth-child(1) { - grid-row: 1 / 3; - } - - > *:nth-child(3) { - grid-column: 2 / 3; - grid-row: 2 / 3; - } - } - - &[data-count="4"] { - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; - } - - > *:nth-child(1) { - grid-column: 1 / 2; - grid-row: 1 / 2; - } - - > *:nth-child(2) { - grid-column: 2 / 3; - grid-row: 1 / 2; - } - - > *:nth-child(3) { - grid-column: 1 / 2; - grid-row: 2 / 3; - } - - > *:nth-child(4) { - grid-column: 2 / 3; - grid-row: 2 / 3; - } + > .media:nth-child(3) { + grid-column: 2 / 3; + grid-row: 2 / 3; } } + + &.n4 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + } + + &.nMany { + grid-template-columns: 1fr 1fr; + + > .media { + aspect-ratio: 16/9; + } + } +} + +.media { + overflow: hidden; // clipにするとバグる + border-radius: 8px; } </style> diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 2c24c16f5..979c3eed2 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -6,7 +6,7 @@ </div> </div> <div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu"> - <vue-plyr> + <VuePlyr :options="{ volume: 0.5 }"> <video controls :data-poster="video.thumbnailUrl" @@ -17,7 +17,7 @@ :type="video.type" /> </video> - </vue-plyr> + </VuePlyr> <i class="ti ti-eye-off" @click="hide = true"></i> </div> </template> diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index eee77a947..2185a3ed0 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -27,7 +27,7 @@ <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> </a> <button v-else-if="item.type === 'user'" :tabindex="i" class="_button" :class="[$style.item, { [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> - <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/> + <MkAvatar :user="item.user" :class="$style.avatar"/><span class="_nowrap">{{ item.user.name ?? item.user.username }}</span> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> </button> <span v-else-if="item.type === 'switch'" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 351861ac1..a77158c86 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -17,7 +17,7 @@ <I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText"> <template #user> <MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> + <span class="_nowrap">{{ note.user.name ?? note.user.username }}</span> </MkA> </template> </I18n> @@ -108,7 +108,7 @@ <I18n :src="i18n.ts.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> - <MkUserName :user="appearNote.user"/> + <span class="_nowrap">{{ appearNote.user.name ?? appearNote.user.username }}</span> </MkA> </template> </I18n> diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 0da06c4f1..b632a7739 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -16,7 +16,7 @@ <I18n :src="i18n.ts.renotedBy" tag="span"> <template #user> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> + <span class="_nowrap">{{ note.user.name ?? note.user.username }}</span> </MkA> </template> </I18n> @@ -39,7 +39,7 @@ <div class="body"> <div class="top"> <MkA v-user-preview="appearNote.user.id" class="name" :to="userPage(appearNote.user)"> - <MkUserName :nowrap="false" :user="appearNote.user"/> + <span class="_nowrap">{{ appearNote.user.name ?? appearNote.user.username }}</span> </MkA> <span v-if="appearNote.user.isBot" class="is-bot">bot</span> <div class="info"> @@ -125,7 +125,7 @@ <I18n :src="i18n.ts.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> - <MkUserName :user="appearNote.user"/> + <span class="_nowrap">{{ appearNote.user.name ?? appearNote.user.username }}</span> </MkA> </template> </I18n> diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 6b43f1466..d86a37ed5 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -1,7 +1,7 @@ <template> <header :class="$style.root"> <MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> + <span class="_nowrap">{{ note.user.name ?? note.user.username }}</span> </MkA> <div v-if="note.user.isBot" :class="$style.isBot">bot</div> <div :class="$style.username"><MkAcct :user="note.user"/></div> diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 1cc01386b..d3862e2ac 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -3,7 +3,7 @@ <MkAvatar :class="$style.avatar" :user="$i" link preview/> <div :class="$style.main"> <div :class="$style.header"> - <MkUserName :user="$i"/> + <span class="_nowrap">{{ $i.name ?? $i.username }}</span> </div> <div> <div :class="$style.content"> diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index e7a951dd2..fd055dfb5 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -31,7 +31,7 @@ <header :class="$style.header"> <span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> - <MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> + <MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><span class="_nowrap">{{ notification.user.name ?? notification.user.username }}</span></MkA> <span v-else>{{ notification.header }}</span> <MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/> </header> diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index b4210be91..b0d7ebf3e 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -8,7 +8,7 @@ <div :class="$style.users"> <div v-for="u in users" :key="u.id" :class="$style.user"> <MkAvatar :class="$style.avatar" :user="u"/> - <MkUserName :user="u" :nowrap="true"/> + <span class="_nowrap">{{ u.name ?? u.username }}</span> </div> <div v-if="users.length > 10">+{{ count - 10 }}</div> </div> diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index 457504e6c..e2b9b2a15 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -2,7 +2,7 @@ <div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }]"> <MkAvatar class="avatar" :user="user" indicator/> <div class="body"> - <span class="name"><MkUserName class="name" :user="user"/></span> + <span class="name _nowrap">{{ user.name ?? user.username }}</span> <span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span> </div> <MkMiniChart v-if="chartValues" class="chart" :src="chartValues"/> diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 1486423b3..4459fd1a5 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -3,8 +3,9 @@ <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> <MkAvatar class="avatar" :user="user" indicator/> <div class="title"> - <MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> - <p class="username"><MkAcct :user="user"/></p> + <MkA class="name _nowrap" :to="userPage(user)"{{ user.name ?? user.username }}</mk-a> + <p class="username"><MkAcct :user="user"/></p> + </mka> </div> <span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> <div class="description"> diff --git a/packages/frontend/src/components/MkUserPreview.vue b/packages/frontend/src/components/MkUserPreview.vue index f68fdd64d..c9519a29a 100644 --- a/packages/frontend/src/components/MkUserPreview.vue +++ b/packages/frontend/src/components/MkUserPreview.vue @@ -7,7 +7,7 @@ </div> <MkAvatar class="avatar" :user="user" indicator/> <div class="title"> - <MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> + <MkA class="name _nowrap" :to="userPage(user)">{{ user.name ?? user.username }}</MkA> <p class="username"><MkAcct :user="user"/></p> </div> <div class="description"> diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index c17b97f28..dd976a4b3 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -27,7 +27,7 @@ <div v-for="user in users" :key="user.id" class="_button" :class="[$style.user, { [$style.selected]: selected && selected.id === user.id }]" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" :class="$style.avatar" indicator/> <div :class="$style.userBody"> - <MkUserName :user="user" :class="$style.userName"/> + <span :class="$style.userName" class="_nowrap">{{ user.name ?? user.username }}</span> <MkAcct :user="user" :class="$style.userAcct"/> </div> </div> @@ -41,7 +41,7 @@ <div v-for="user in recentUsers" :key="user.id" class="_button" :class="[$style.user, { [$style.selected]: selected && selected.id === user.id }]" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" :class="$style.avatar" indicator/> <div :class="$style.userBody"> - <MkUserName :user="user" :class="$style.userName"/> + <span :class="$style.userName" class="_nowrap">{{ user.name ?? user.username }}</span> <MkAcct :user="user" :class="$style.userAcct"/> </div> </div> diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue index d0f95fced..c2c7271fc 100644 --- a/packages/frontend/src/components/MkUsersTooltip.vue +++ b/packages/frontend/src/components/MkUsersTooltip.vue @@ -3,7 +3,7 @@ <div :class="$style.root"> <div v-for="u in users" :key="u.id" :class="$style.user"> <MkAvatar :class="$style.avatar" :user="u"/> - <MkUserName :class="$style.name" :user="u" :nowrap="true"/> + <span :class="$style.name" class="_nowrap">{{ u.name ?? u.username }}</span> </div> <div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div> </div> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue index 28a0d1c98..f49dc7ddf 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue @@ -1,5 +1,5 @@ <template> -<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" :class="[$style.root, { [$style.nowrap]: nowrap }]"/> +<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" :class="[$style.root, { '_nowrap': nowrap }]"/> </template> <script lang="ts" setup> @@ -160,12 +160,5 @@ const props = withDefaults(defineProps<{ <style lang="scss" module> .root { white-space: pre-wrap; - - &.nowrap { - white-space: pre; - word-wrap: normal; // https://codeday.me/jp/qa/20190424/690106.html - overflow: hidden; - text-overflow: ellipsis; - } } </style> diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index ab66502e0..3dcb19bd8 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -9,7 +9,7 @@ <i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i> <div :class="$style.title"> - <MkUserName v-if="metadata.userName" :user="metadata.userName" :nowrap="true"/> + <span v-if="metadata.userName" class="_nowrap">{{ metadata.userName.name ?? metadata.userName.username }}</span> <div v-else-if="metadata.title">{{ metadata.title }}</div> <div v-if="!narrow && metadata.subtitle" :class="$style.subtitle"> {{ metadata.subtitle }} diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue deleted file mode 100644 index 4186a4a4f..000000000 --- a/packages/frontend/src/components/global/MkUserName.vue +++ /dev/null @@ -1,15 +0,0 @@ -<template> -<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emoji-urls="user.emojis"/> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import * as misskey from 'misskey-js'; - -const props = withDefaults(defineProps<{ - user: misskey.entities.User; - nowrap?: boolean; -}>(), { - nowrap: true, -}); -</script> diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 560870f84..4ddebe782 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -6,7 +6,6 @@ import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; import MkEmoji from './global/MkEmoji.vue'; import MkCustomEmoji from './global/MkCustomEmoji.vue'; -import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; import MkTime from './global/MkTime.vue'; import MkUrl from './global/MkUrl.vue'; @@ -28,7 +27,6 @@ export default function(app: App) { app.component('MkAvatar', MkAvatar); app.component('MkEmoji', MkEmoji); app.component('MkCustomEmoji', MkCustomEmoji); - app.component('MkUserName', MkUserName); app.component('MkEllipsis', MkEllipsis); app.component('MkTime', MkTime); app.component('MkUrl', MkUrl); @@ -50,7 +48,6 @@ declare module '@vue/runtime-core' { MkAvatar: typeof MkAvatar; MkEmoji: typeof MkEmoji; MkCustomEmoji: typeof MkCustomEmoji; - MkUserName: typeof MkUserName; MkEllipsis: typeof MkEllipsis; MkTime: typeof MkTime; MkUrl: typeof MkUrl; diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts index 7cfb19aeb..683c9014a 100644 --- a/packages/frontend/src/components/mfm.ts +++ b/packages/frontend/src/components/mfm.ts @@ -195,7 +195,7 @@ export default defineComponent({ return h(MkSparkle, {}, genEl(token.children)); } case 'rotate': { - const degrees = parseFloat(token.props.args.deg) ?? '90'; + const degrees = parseFloat(token.props.args.deg ?? '90'); style = `transform: rotate(${degrees}deg); transform-origin: center center;`; break; } diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index cd9cec0d4..89419f79c 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -8,7 +8,7 @@ <Mfm :text="clip.description" :is-note="false" :i="$i"/> </div> <div class="user"> - <MkAvatar :user="clip.user" class="avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/> + <MkAvatar :user="clip.user" class="avatar" indicator link preview/> <span class="_nowrap">{{ clip.user.name ?? clip.user.username }}</span> </div> </div> diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index 835dd0b54..e8a59d71e 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -15,7 +15,7 @@ <MkAvatar class="avatar" :user="req.follower" indicator link preview/> <div class="body"> <div class="name"> - <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> + <MkA v-user-preview="req.follower.id" class="name _nowrap" :to="userPage(req.follower)">{{ req.follower.name ?? req.follower.username }}</MkA> <p class="acct">@{{ acct(req.follower) }}</p> </div> <div v-if="req.follower.description" class="description" :title="req.follower.description"> diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 31e2727bb..20bc0e3d3 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -30,7 +30,7 @@ <div class="user"> <MkAvatar :user="post.user" class="avatar" link preview/> <div class="name"> - <MkUserName :user="post.user" style="display: block;"/> + <div class="_nowrap">{{ post.user.name ?? post.user.username }}</div> <MkAcct :user="post.user"/> </div> <MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue index 3d11cf13e..0fd1f71d4 100644 --- a/packages/frontend/src/pages/messaging/index.vue +++ b/packages/frontend/src/pages/messaging/index.vue @@ -22,7 +22,7 @@ <MkTime :time="message.createdAt" class="time"/> </header> <header v-else> - <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> + <span class="name">{{ isMe(message) ? message.recipient.name : message.user.name }}</span> <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> <MkTime :time="message.createdAt" class="time"/> </header> diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index f47b4bf90..cb190fb11 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -21,7 +21,7 @@ <div v-for="user in users" :key="user.id" class="user _panel"> <MkAvatar :user="user" class="avatar" indicator link preview/> <div class="body"> - <MkUserName :user="user" class="name"/> + <span class="name _nowrap">{{ user.name ?? user.username }}</span> <MkAcct :user="user" class="acct"/> </div> <div class="action"> diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 86b3fce3c..e28c30f62 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -21,7 +21,7 @@ <b>{{ item.name }}</b> <div v-if="item.description" class="description">{{ item.description }}</div> <div class="user"> - <MkAvatar :user="item.user" class="avatar" indicator link preview/> <MkUserName :user="item.user" :nowrap="false"/> + <MkAvatar :user="item.user" class="avatar" indicator link preview/> <span class="_nowrap">{{ item.user.name ?? item.user.username }}</span> </div> </MkA> </div> diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 202244b34..a4bc17e3b 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -29,7 +29,7 @@ <div class="user"> <MkAvatar :user="page.user" class="avatar" link preview/> <div class="name"> - <MkUserName :user="page.user" style="display: block;"/> + <div class="_nowrap">{{ page.user.name ?? page.user.username }}</div> <MkAcct :user="page.user"/> </div> <MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index a5eaae2ba..8fa30bccb 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -9,9 +9,7 @@ <MkAvatar :user="account" class="avatar"/> </div> <div class="body"> - <div class="name"> - <MkUserName :user="account"/> - </div> + <div class="name _nowrap">{{ account.name ?? account.username }}</div> <div class="acct"> <MkAcct :user="account"/> </div> diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue index 7ba8a3d16..40e5ec83c 100644 --- a/packages/frontend/src/pages/user-info.vue +++ b/packages/frontend/src/pages/user-info.vue @@ -7,7 +7,7 @@ <div class="aeakzknw"> <MkAvatar class="avatar" :user="user" indicator link preview/> <div class="body"> - <span class="name"><MkUserName class="name" :user="user"/></span> + <span class="name _nowrap">{{ user.name ?? user.username }}</span> <span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span> <span class="state"> <span v-if="suspended" class="suspended">Suspended</span> diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 56858a937..e4bb024a8 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -14,7 +14,7 @@ <div ref="bannerEl" class="banner" :style="style"></div> <div class="fade"></div> <div class="title"> - <MkUserName class="name" :user="user" :nowrap="true"/> + <span class="name _nowrap">{{ user.name ?? user.username }}</span> <div class="bottom"> <span class="username"><MkAcct :user="user" :detail="true"/></span> <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> @@ -30,7 +30,7 @@ </div> <MkAvatar class="avatar" :user="user" indicator/> <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> + <span class="name _nowrap">{{ user.name ?? user.username }}</span> <div class="bottom"> <span class="username"><MkAcct :user="user" :detail="true"/></span> <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 8ecf59edb..94892ff52 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -168,7 +168,7 @@ export const defaultStore = markRaw(new Storage('base', { }, disableShowingAnimatedImages: { where: 'device', - default: false, + default: matchMedia('(prefers-reduced-motion)').matches, }, emojiStyle: { where: 'device', diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index aa4efe305..bacdc967d 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -156,6 +156,13 @@ hr { -webkit-touch-callout: none; } +._nowrap { + white-space: pre; + word-wrap: normal; // https://codeday.me/jp/qa/20190424/690106.html + overflow: clip; + text-overflow: ellipsis; +} + ._ghost { @extend ._noSelect; pointer-events: none; diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue index 7e8ac429e..b2a5c8710 100644 --- a/packages/frontend/src/widgets/WidgetProfile.vue +++ b/packages/frontend/src/widgets/WidgetProfile.vue @@ -6,9 +6,7 @@ </div> <div :class="$style.bodyContainer"> <div :class="$style.body"> - <MkA :class="$style.name" :to="userPage($i)"> - <MkUserName :user="$i"/> - </MkA> + <MkA :class="$style.name" :to="userPage($i)" class="_nowrap">{{ $i.name ?? $i.username }}</MkA> <div :class="$style.username"><MkAcct :user="$i" detail/></div> </div> </div>