Merge branch 'develop'

This commit is contained in:
syuilo 2023-02-08 18:03:28 +09:00
commit 03744a25ed
55 changed files with 284 additions and 213 deletions

View file

@ -8,6 +8,21 @@
You should also include the user name that made the change. 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) ## 13.4.0 (2023/02/05)
### Improvements ### Improvements

View file

@ -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 \ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& apt-get update \ && apt-get update \
&& apt-get install -yqq --no-install-recommends \ && apt-get install -yqq --no-install-recommends \
build-essential wget ca-certificates \ build-essential
&& wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq \
&& chmod +x /usr/bin/yq
RUN corepack enable 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 \ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
ffmpeg tini \ ffmpeg tini curl \
&& corepack enable \ && corepack enable \
&& groupadd -g "${GID}" misskey \ && groupadd -g "${GID}" misskey \
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey 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 USER misskey
WORKDIR /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/node_modules ./node_modules
COPY --chown=misskey:misskey --from=builder /misskey/built ./built COPY --chown=misskey:misskey --from=builder /misskey/built ./built
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/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}" curl -s -S -o /dev/null "http://localhost:${PORT}"

View file

@ -1044,7 +1044,7 @@ _achievements:
flavor: "Grazie per aver usato Misskey!" flavor: "Grazie per aver usato Misskey!"
_noteClipped1: _noteClipped1:
title: "Devo clippare!" title: "Devo clippare!"
description: "Ho raccolto in Clip la prima Nota" description: "Hai raccolto la tua prima Nota in una Clip"
_noteFavorited1: _noteFavorited1:
title: "Guarda le stelle" title: "Guarda le stelle"
description: "Aggiungi una Nota ai preferiti per la prima volta" description: "Aggiungi una Nota ai preferiti per la prima volta"
@ -1080,7 +1080,7 @@ _achievements:
title: "Follow me!" title: "Follow me!"
description: "Hai ottenuto 10 profili Follower" description: "Hai ottenuto 10 profili Follower"
_followers50: _followers50:
title: "Follower a frotte" title: "Un gregge di Follower"
description: "Hai ottenuto 50 Follower" description: "Hai ottenuto 50 Follower"
_followers100: _followers100:
title: "Popolare" title: "Popolare"
@ -1108,7 +1108,7 @@ _achievements:
title: "Caccia al tesoro" title: "Caccia al tesoro"
description: "Hai trovato un tesoro nascosto" description: "Hai trovato un tesoro nascosto"
_client30min: _client30min:
title: "Piccola pausa" title: "Piccola grande pausa"
description: "Hai passato più di 30 minuti su Misskey" description: "Hai passato più di 30 minuti su Misskey"
_noteDeletedWithin1min: _noteDeletedWithin1min:
title: "Ooops!" title: "Ooops!"
@ -1134,7 +1134,7 @@ _achievements:
title: "Hello, world!" title: "Hello, world!"
description: "Hai scritto «Hello world» nel blocco appunti" description: "Hai scritto «Hello world» nel blocco appunti"
_open3windows: _open3windows:
title: "Finestrato" title: "Apri le finestre!"
description: "Hai aperto almeno 3 finestre contemporaneamente" description: "Hai aperto almeno 3 finestre contemporaneamente"
_driveFolderCircularReference: _driveFolderCircularReference:
title: "Riferimento circolare" title: "Riferimento circolare"
@ -1170,7 +1170,7 @@ _achievements:
_cookieClicked: _cookieClicked:
title: "Clicca il biscotto" title: "Clicca il biscotto"
description: "Hai giocato a cliccare il cookie" description: "Hai giocato a cliccare il cookie"
flavor: "Hai autorizzato i cookie?" flavor: "È il sito giusto?"
_brainDiver: _brainDiver:
title: "Brain Diver" title: "Brain Diver"
description: "Pubblica un link a Brain Diver" description: "Pubblica un link a Brain Diver"
@ -1195,6 +1195,9 @@ _role:
baseRole: "Ruolo di base" baseRole: "Ruolo di base"
useBaseValue: "Eredita dal ruolo base" useBaseValue: "Eredita dal ruolo base"
chooseRoleToAssign: "Seleziona il ruolo da assegnare" 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" canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo"
descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori." descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori."
priority: "Priorità" priority: "Priorità"

View file

@ -1,2 +1,72 @@
--- ---
_lang_: "ພາສາລາວ" _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: "​ເສັ້ນກຳ​ນົດ​ເວ​ລາ​"

View file

@ -1147,7 +1147,7 @@ _achievements:
description: "คุณได้คลิกที่นี่" description: "คุณได้คลิกที่นี่"
_justPlainLucky: _justPlainLucky:
title: "แค่ลัคกี้ธรรมดา" title: "แค่ลัคกี้ธรรมดา"
description: "มีโอกาสที่จะได้รับด้วยความน่าจะเป็นไปได้ 0.01% ทุก ๆ 10 วินาที" description: "มีโอกาสที่จะได้รับด้วยความน่าจะเป็นไปได้ 0.005% ทุก ๆ 10 วินาที"
_setNameToSyuilo: _setNameToSyuilo:
title: "พระเจ้าคอมเพล็กซ์" title: "พระเจ้าคอมเพล็กซ์"
description: "ตั้งชื่อของคุณเป็น \"syuilo\"" description: "ตั้งชื่อของคุณเป็น \"syuilo\""
@ -1182,7 +1182,7 @@ _role:
description: "คำอธิบายบทบาท" description: "คำอธิบายบทบาท"
permission: "สิทธิ์ตามบทบาท" permission: "สิทธิ์ตามบทบาท"
descriptionOfPermission: "<b>ผู้ดูแลกลั่นกรองเนื้อหา</b> สามารถดำเนินการดูแลขั้นพื้นฐานได้นะ\n<b>ผู้ดูแลระบบ</b> สามารถเปลี่ยนการตั้งค่าทั้งหมดของอินสแตนซ์ได้นะ" descriptionOfPermission: "<b>ผู้ดูแลกลั่นกรองเนื้อหา</b> สามารถดำเนินการดูแลขั้นพื้นฐานได้นะ\n<b>ผู้ดูแลระบบ</b> สามารถเปลี่ยนการตั้งค่าทั้งหมดของอินสแตนซ์ได้นะ"
assignTarget: "กำหนดเป้าหมาย" assignTarget: "มอบหมาย"
descriptionOfAssignTarget: "<b>แมนนวล</b> เพื่อเปลี่ยนผู้ที่เป็นส่วนหนึ่งของบทบาทนี้และใครที่ไม่ใช่ด้วยตนเอง\n<b>เงื่อนไข</b> เพื่อให้ผู้ใช้ได้รับการกำหนดและนำออกจากบทบาทนี้โดยอัตโนมัติตามเงื่อนไขชุดหนึ่ง" descriptionOfAssignTarget: "<b>แมนนวล</b> เพื่อเปลี่ยนผู้ที่เป็นส่วนหนึ่งของบทบาทนี้และใครที่ไม่ใช่ด้วยตนเอง\n<b>เงื่อนไข</b> เพื่อให้ผู้ใช้ได้รับการกำหนดและนำออกจากบทบาทนี้โดยอัตโนมัติตามเงื่อนไขชุดหนึ่ง"
manual: "ปรับเอง" manual: "ปรับเอง"
conditional: "มีเงื่อนไข" conditional: "มีเงื่อนไข"

View file

@ -1382,8 +1382,8 @@ _tutorial:
step1_1: "Ласкаво просимо!" step1_1: "Ласкаво просимо!"
step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів на яких ви підписані." step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів на яких ви підписані."
step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки і не підписані на інших." step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки і не підписані на інших."
step2_1: "Перш ніж зробити запис або підписатись на когось, спочатку заповніть свій обліковий запис." step2_1: "Перш ніж зробити запис або підписатись на когось, заповніть свій профіль."
step2_2: "Надання деякої інформації про себе дозволить іншим користувачам підписатись на вас." step2_2: "Надання деякої інформації про себе допоможе іншим користувачам вирішити підписатись на вас."
step3_1: "Ви успішно налаштували свій обліковий запис?" step3_1: "Ви успішно налаштували свій обліковий запис?"
step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення олівця на екрані." step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення олівця на екрані."
step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку у верхньому правому куті форми." step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку у верхньому правому куті форми."

View file

@ -326,7 +326,7 @@ connectService: "己連結"
disconnectService: "己斷開 " disconnectService: "己斷開 "
enableLocalTimeline: "開啟本地時間軸" enableLocalTimeline: "開啟本地時間軸"
enableGlobalTimeline: "啟用全域時間軸" enableGlobalTimeline: "啟用全域時間軸"
disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審員仍可以繼續使用。" disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審員仍可以繼續使用。"
registration: "註冊" registration: "註冊"
enableRegistration: "開啟新使用者註冊" enableRegistration: "開啟新使用者註冊"
invite: "邀請" invite: "邀請"
@ -389,8 +389,8 @@ aboutMisskey: "關於 Misskey"
administrator: "管理員" administrator: "管理員"
token: "權杖" token: "權杖"
twoStepAuthentication: "兩階段驗證" twoStepAuthentication: "兩階段驗證"
moderator: "審員" moderator: "審員"
moderation: "監察" moderation: "審查"
nUsersMentioned: "提到了{n}" nUsersMentioned: "提到了{n}"
securityKey: "安全金鑰" securityKey: "安全金鑰"
securityKeyName: "金鑰名稱" securityKeyName: "金鑰名稱"
@ -607,7 +607,7 @@ testEmail: "測試郵件發送"
wordMute: "被靜音的文字" wordMute: "被靜音的文字"
regexpError: "正規表達式錯誤" regexpError: "正規表達式錯誤"
regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:" regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:"
instanceMute: "實例的靜音" instanceMute: "被靜音的實例"
userSaysSomething: "{name}說了什麼" userSaysSomething: "{name}說了什麼"
makeActive: "啟用" makeActive: "啟用"
display: "檢視" display: "檢視"
@ -1181,7 +1181,7 @@ _role:
name: "角色名稱" name: "角色名稱"
description: "角色描述 " description: "角色描述 "
permission: "角色的權限" permission: "角色的權限"
descriptionOfPermission: "<b>審核員</b>執行與審核相關的基本操作。\n<b>管理員</b>能變更實例的全部設定" descriptionOfPermission: "<b>審查員</b>執行與審查相關的基本操作。\n<b>管理員</b>能變更實例的全部設定"
assignTarget: "指派目標" assignTarget: "指派目標"
descriptionOfAssignTarget: "<b>手動</b>是以手動管理這個角色包含的人員。\n<b>符合條件</b>是設定條件以自動包含符合條件的使用者。" descriptionOfAssignTarget: "<b>手動</b>是以手動管理這個角色包含的人員。\n<b>符合條件</b>是設定條件以自動包含符合條件的使用者。"
manual: "手動" manual: "手動"
@ -1198,8 +1198,8 @@ _role:
iconUrl: "圖示的URL" iconUrl: "圖示的URL"
asBadge: "顯示為徽章" asBadge: "顯示為徽章"
descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。" descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。"
canEditMembersByModerator: "允許編輯監察員的成員" canEditMembersByModerator: "允許編輯審查員的成員"
descriptionOfCanEditMembersByModerator: "如果開啟,管理員與監察員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。" descriptionOfCanEditMembersByModerator: "如果開啟,管理員與審查員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。"
priority: "優先級" priority: "優先級"
_priority: _priority:
low: "低" low: "低"
@ -1236,7 +1236,7 @@ _role:
or: "~或~" or: "~或~"
not: "~否" not: "~否"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審。 伺服器的負荷會稍微增加。" description: "您可以使用機器學習自動檢測敏感媒體並將其用於審。 伺服器的負荷會稍微增加。"
sensitivity: "檢測敏感度" sensitivity: "檢測敏感度"
sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。" sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。"
setSensitiveFlagAutomatically: "設定 NSFW 旗標" setSensitiveFlagAutomatically: "設定 NSFW 旗標"

View file

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.4.0", "version": "13.5.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
@ -19,7 +19,7 @@
"start": "cd packages/backend && node ./built/boot/index.js", "start": "cd packages/backend && node ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
"init": "pnpm migrate", "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", "migrateandstart": "pnpm migrate && pnpm start",
"gulp": "pnpm exec gulp build", "gulp": "pnpm exec gulp build",
"watch": "pnpm dev", "watch": "pnpm dev",
@ -28,8 +28,8 @@
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run", "cy:run": "pnpm cypress run",
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy: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": "cd packages/backend && pnpm jest",
"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-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm jest", "test": "pnpm jest",
"test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage": "pnpm jest-and-coverage",
"format": "pnpm exec gulp format", "format": "pnpm exec gulp format",

View file

@ -1,6 +1,6 @@
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { loadConfig } from './built/config.js'; import { loadConfig } from './built/config.js';
import { entities } from './built/postgre.js'; import { entities } from './built/postgres.js';
const config = loadConfig(); const config = loadConfig();

View file

@ -4,7 +4,7 @@ import { DataSource } from 'typeorm';
import { createRedisConnection } from '@/redis.js'; import { createRedisConnection } from '@/redis.js';
import { DI } from './di-symbols.js'; import { DI } from './di-symbols.js';
import { loadConfig } from './config.js'; import { loadConfig } from './config.js';
import { createPostgreDataSource } from './postgre.js'; import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js'; import { RepositoryModule } from './models/RepositoryModule.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common'; import type { Provider, OnApplicationShutdown } from '@nestjs/common';
@ -18,7 +18,7 @@ const $config: Provider = {
const $db: Provider = { const $db: Provider = {
provide: DI.db, provide: DI.db,
useFactory: async (config) => { useFactory: async (config) => {
const db = createPostgreDataSource(config); const db = createPostgresDataSource(config);
return await db.initialize(); return await db.initialize();
}, },
inject: [DI.config], inject: [DI.config],

View file

@ -150,17 +150,9 @@ export class CustomEmojiService {
if (note.renote) { if (note.renote) {
emojis = emojis.concat(note.renote.emojis emojis = emojis.concat(note.renote.emojis
.map(e => this.parseEmojiStr(e, note.renote!.userHost))); .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; const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
emojis = emojis.concat(customReactions); 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; }[]; return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[];
} }

View file

@ -48,6 +48,10 @@ export class ApImageService {
throw new Error('invalid image: url not privided'); 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}`); this.logger.info(`Creating the Image: ${image.url}`);
const instance = await this.metaService.fetch(); const instance = await this.metaService.fetch();

View file

@ -1,8 +1,7 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { forwardRef, Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit'; import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MessagingMessagesRepository, PollsRepository, EmojisRepository } from '@/models/index.js'; import type { MessagingMessagesRepository, PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { CacheableRemoteUser } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.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 { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { MessagingService } from '@/core/MessagingService.js'; import { MessagingService } from '@/core/MessagingService.js';
import { bindThis } from '@/decorators.js';
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // eslint-disable-next-line @typescript-eslint/consistent-type-imports
import { ApLoggerService } from '../ApLoggerService.js'; import { ApLoggerService } from '../ApLoggerService.js';
@ -32,7 +32,6 @@ import { ApQuestionService } from './ApQuestionService.js';
import { ApImageService } from './ApImageService.js'; import { ApImageService } from './ApImageService.js';
import type { Resolver } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js';
import type { IObject, IPost } from '../type.js'; import type { IObject, IPost } from '../type.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ApNoteService { export class ApNoteService {
@ -133,6 +132,16 @@ export class ApNoteService {
const note: IPost = object; const note: IPost = object;
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); 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}`); this.logger.info(`Creating the Note: ${note.id}`);
@ -307,7 +316,7 @@ export class ApNoteService {
apEmojis, apEmojis,
poll, poll,
uri: note.id, uri: note.id,
url: getOneApHrefNullable(note.url), url: url,
}, silent); }, silent);
} }

View file

@ -252,6 +252,12 @@ export class ApPersonService implements OnModuleInit {
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); 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 // Create user
let user: IRemoteUser; let user: IRemoteUser;
try { try {
@ -283,7 +289,7 @@ export class ApPersonService implements OnModuleInit {
await transactionalEntityManager.save(new UserProfile({ await transactionalEntityManager.save(new UserProfile({
userId: user.id, userId: user.id,
description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
url: getOneApHrefNullable(person.url), url: url,
fields, fields,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person['vcard:Address'] ?? 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 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 = { const updates = {
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
inbox: person.inbox, inbox: person.inbox,
@ -459,7 +471,7 @@ export class ApPersonService implements OnModuleInit {
} }
await this.userProfilesRepository.update({ userId: exist.id }, { await this.userProfilesRepository.update({ userId: exist.id }, {
url: getOneApHrefNullable(person.url), url: url,
fields, fields,
description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,

View file

@ -20,6 +20,7 @@ type PackOptions = {
withUser?: boolean, withUser?: boolean,
}; };
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
@Injectable() @Injectable()
export class DriveFileEntityService { export class DriveFileEntityService {
@ -82,7 +83,9 @@ export class DriveFileEntityService {
// リモートかつメディアプロキシ // リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { 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('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外
const url = `${this.config.url}/files/${key}`; const url = `${this.config.url}/files/${key}`;
if (mode === 'avatar') return proxiedUrl(url); if (mode === 'avatar') return proxiedUrl(file.uri);
return url; 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; 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; return url;
} }

View file

@ -413,7 +413,6 @@ export class UserEntityService implements OnModuleInit {
faviconUrl: instance.faviconUrl, faviconUrl: instance.faviconUrl,
themeColor: instance.themeColor, themeColor: instance.themeColor,
} : undefined) : undefined, } : undefined) : undefined,
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
onlineStatus: this.getOnlineStatus(user), onlineStatus: this.getOnlineStatus(user),
// パフォーマンス上の理由でローカルユーザーのみ // パフォーマンス上の理由でローカルユーザーのみ
badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.map(r => ({ 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, isModerator: role.isModerator,
isAdministrator: role.isAdministrator, isAdministrator: role.isAdministrator,
}))), }))),
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
} : {}), } : {}),
...(opts.detail && isMe ? { ...(opts.detail && isMe ? {

View file

@ -197,7 +197,7 @@ export const entities = [
const log = process.env.NODE_ENV !== 'production'; const log = process.env.NODE_ENV !== 'production';
export function createPostgreDataSource(config: Config) { export function createPostgresDataSource(config: Config) {
return new DataSource({ return new DataSource({
type: 'postgres', type: 'postgres',
host: config.db.host, host: config.db.host,

View file

@ -11,7 +11,7 @@ import FormData from 'form-data';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import got, { RequestError } from 'got'; import got, { RequestError } from 'got';
import loadConfig from '../src/config/load.js'; 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'; import type * as misskey from 'misskey-js';
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);

View file

@ -4,7 +4,7 @@
<MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`"> <MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`">
<MkAvatar class="avatar" :user="report.targetUser" indicator/> <MkAvatar class="avatar" :user="report.targetUser" indicator/>
<div class="names"> <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;"/> <MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
</div> </div>
</MkA> </MkA>

View file

@ -4,7 +4,7 @@
<li v-for="user in users" tabindex="-1" :class="$style.item" @click="complete(type, user)" @keydown="onKeydown"> <li v-for="user in users" tabindex="-1" :class="$style.item" @click="complete(type, user)" @keydown="onKeydown">
<img :class="$style.avatar" :src="user.avatarUrl"/> <img :class="$style.avatar" :src="user.avatarUrl"/>
<span :class="$style.userName"> <span :class="$style.userName">
<MkUserName :key="user.id" :user="user"/> <span :key="user.id" class="_nowrap">{{ user.name ?? user.username }}</span>
</span> </span>
<span>@{{ acct(user) }}</span> <span>@{{ acct(user) }}</span>
</li> </li>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> <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"> <div ref="emojisEl" class="emojis">
<section class="result"> <section class="result">
<div v-if="searchResultCustom.length > 0" class="body"> <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 { function done(query?: string): boolean | void {
if (query == null) query = q.value; if (query == null) query = q.value;
if (query == null || typeof query !== 'string') return; if (query == null || typeof query !== 'string') return;

View file

@ -6,15 +6,14 @@
<span>{{ $ts.clickToShow }}</span> <span>{{ $ts.clickToShow }}</span>
</div> </div>
<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio">
<audio <VuePlyr :options="{ volume: 0.5 }">
ref="audioEl" <audio controls preload="metadata">
class="audio" <source
:src="media.url" :src="media.url"
:title="media.name" :type="media.type"
controls />
preload="metadata" </audio>
@volumechange="volumechange" </VuePlyr>
/>
</div> </div>
<a <a
v-else class="download" v-else class="download"
@ -31,7 +30,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import VuePlyr from 'vue-plyr';
import { ColdDeviceStorage } from '@/store'; import { ColdDeviceStorage } from '@/store';
import 'vue-plyr/dist/vue-plyr.css';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
media: misskey.entities.DriveFile; media: misskey.entities.DriveFile;
@ -55,7 +56,11 @@ onMounted(() => {
width: 100%; width: 100%;
border-radius: 4px; border-radius: 4px;
margin-top: 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, > .download,
> .sensitive { > .sensitive {
@ -93,10 +98,8 @@ onMounted(() => {
} }
> .audio { > .audio {
.audio { border-radius: 8px;
display: block; overflow: clip;
width: 100%;
}
} }
} }
</style> </style>

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="hoawjimk"> <div>
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <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 v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
<div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length"> <div ref="gallery" :class="[$style.medias, count <= 4 ? $style['n' + count] : $style.nMany]">
<template v-for="media in mediaList.filter(media => previewable(media))"> <template v-for="media in mediaList.filter(media => previewable(media))">
<XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/> <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="image" :data-id="media.id" :image="media" :raw="raw"/> <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> </template>
</div> </div>
</div> </div>
@ -32,6 +32,7 @@ const props = defineProps<{
const gallery = ref(null); const gallery = ref(null);
const pswpZIndex = os.claimZIndex('middle'); const pswpZIndex = os.claimZIndex('middle');
const count = $computed(() => props.mediaList.filter(media => previewable(media)).length);
onMounted(() => { onMounted(() => {
const lightbox = new PhotoSwipeLightbox({ const lightbox = new PhotoSwipeLightbox({
@ -122,82 +123,61 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.hoawjimk { .container {
> .gird-container { position: relative;
position: relative; width: 100%;
width: 100%; margin-top: 4px;
margin-top: 4px; }
&:before { .medias {
content: ''; display: grid;
display: block; grid-gap: 8px;
padding-top: 56.25% // 16:9;
&.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 { > .media:nth-child(3) {
position: absolute; grid-column: 2 / 3;
top: 0; grid-row: 2 / 3;
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;
}
} }
} }
&.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> </style>

View file

@ -6,7 +6,7 @@
</div> </div>
</div> </div>
<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu"> <div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
<vue-plyr> <VuePlyr :options="{ volume: 0.5 }">
<video <video
controls controls
:data-poster="video.thumbnailUrl" :data-poster="video.thumbnailUrl"
@ -17,7 +17,7 @@
:type="video.type" :type="video.type"
/> />
</video> </video>
</vue-plyr> </VuePlyr>
<i class="ti ti-eye-off" @click="hide = true"></i> <i class="ti ti-eye-off" @click="hide = true"></i>
</div> </div>
</template> </template>

View file

@ -27,7 +27,7 @@
<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</a> </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)"> <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> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
</button> </button>
<span v-else-if="item.type === 'switch'" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <span v-else-if="item.type === 'switch'" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">

View file

@ -17,7 +17,7 @@
<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText"> <I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
<template #user> <template #user>
<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.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> </MkA>
</template> </template>
</I18n> </I18n>
@ -108,7 +108,7 @@
<I18n :src="i18n.ts.userSaysSomething" tag="small"> <I18n :src="i18n.ts.userSaysSomething" tag="small">
<template #name> <template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)"> <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> </MkA>
</template> </template>
</I18n> </I18n>

View file

@ -16,7 +16,7 @@
<I18n :src="i18n.ts.renotedBy" tag="span"> <I18n :src="i18n.ts.renotedBy" tag="span">
<template #user> <template #user>
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.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> </MkA>
</template> </template>
</I18n> </I18n>
@ -39,7 +39,7 @@
<div class="body"> <div class="body">
<div class="top"> <div class="top">
<MkA v-user-preview="appearNote.user.id" class="name" :to="userPage(appearNote.user)"> <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> </MkA>
<span v-if="appearNote.user.isBot" class="is-bot">bot</span> <span v-if="appearNote.user.isBot" class="is-bot">bot</span>
<div class="info"> <div class="info">
@ -125,7 +125,7 @@
<I18n :src="i18n.ts.userSaysSomething" tag="small"> <I18n :src="i18n.ts.userSaysSomething" tag="small">
<template #name> <template #name>
<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> <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> </MkA>
</template> </template>
</I18n> </I18n>

View file

@ -1,7 +1,7 @@
<template> <template>
<header :class="$style.root"> <header :class="$style.root">
<MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> <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> </MkA>
<div v-if="note.user.isBot" :class="$style.isBot">bot</div> <div v-if="note.user.isBot" :class="$style.isBot">bot</div>
<div :class="$style.username"><MkAcct :user="note.user"/></div> <div :class="$style.username"><MkAcct :user="note.user"/></div>

View file

@ -3,7 +3,7 @@
<MkAvatar :class="$style.avatar" :user="$i" link preview/> <MkAvatar :class="$style.avatar" :user="$i" link preview/>
<div :class="$style.main"> <div :class="$style.main">
<div :class="$style.header"> <div :class="$style.header">
<MkUserName :user="$i"/> <span class="_nowrap">{{ $i.name ?? $i.username }}</span>
</div> </div>
<div> <div>
<div :class="$style.content"> <div :class="$style.content">

View file

@ -31,7 +31,7 @@
<header :class="$style.header"> <header :class="$style.header">
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span> <span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</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> <span v-else>{{ notification.header }}</span>
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/> <MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
</header> </header>

View file

@ -8,7 +8,7 @@
<div :class="$style.users"> <div :class="$style.users">
<div v-for="u in users" :key="u.id" :class="$style.user"> <div v-for="u in users" :key="u.id" :class="$style.user">
<MkAvatar :class="$style.avatar" :user="u"/> <MkAvatar :class="$style.avatar" :user="u"/>
<MkUserName :user="u" :nowrap="true"/> <span class="_nowrap">{{ u.name ?? u.username }}</span>
</div> </div>
<div v-if="users.length > 10">+{{ count - 10 }}</div> <div v-if="users.length > 10">+{{ count - 10 }}</div>
</div> </div>

View file

@ -2,7 +2,7 @@
<div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }]"> <div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }]">
<MkAvatar class="avatar" :user="user" indicator/> <MkAvatar class="avatar" :user="user" indicator/>
<div class="body"> <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="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
</div> </div>
<MkMiniChart v-if="chartValues" class="chart" :src="chartValues"/> <MkMiniChart v-if="chartValues" class="chart" :src="chartValues"/>

View file

@ -3,8 +3,9 @@
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" indicator/> <MkAvatar class="avatar" :user="user" indicator/>
<div class="title"> <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 }}</mk-a>
<p class="username"><MkAcct :user="user"/></p> <p class="username"><MkAcct :user="user"/></p>
</mka>
</div> </div>
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> <span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
<div class="description"> <div class="description">

View file

@ -7,7 +7,7 @@
</div> </div>
<MkAvatar class="avatar" :user="user" indicator/> <MkAvatar class="avatar" :user="user" indicator/>
<div class="title"> <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> <p class="username"><MkAcct :user="user"/></p>
</div> </div>
<div class="description"> <div class="description">

View file

@ -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()"> <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/> <MkAvatar :user="user" :class="$style.avatar" indicator/>
<div :class="$style.userBody"> <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"/> <MkAcct :user="user" :class="$style.userAcct"/>
</div> </div>
</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()"> <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/> <MkAvatar :user="user" :class="$style.avatar" indicator/>
<div :class="$style.userBody"> <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"/> <MkAcct :user="user" :class="$style.userAcct"/>
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
<div :class="$style.root"> <div :class="$style.root">
<div v-for="u in users" :key="u.id" :class="$style.user"> <div v-for="u in users" :key="u.id" :class="$style.user">
<MkAvatar :class="$style.avatar" :user="u"/> <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>
<div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div> <div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div>
</div> </div>

View file

@ -1,5 +1,5 @@
<template> <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> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -160,12 +160,5 @@ const props = withDefaults(defineProps<{
<style lang="scss" module> <style lang="scss" module>
.root { .root {
white-space: pre-wrap; 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> </style>

View file

@ -9,7 +9,7 @@
<i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i> <i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i>
<div :class="$style.title"> <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-else-if="metadata.title">{{ metadata.title }}</div>
<div v-if="!narrow && metadata.subtitle" :class="$style.subtitle"> <div v-if="!narrow && metadata.subtitle" :class="$style.subtitle">
{{ metadata.subtitle }} {{ metadata.subtitle }}

View file

@ -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>

View file

@ -6,7 +6,6 @@ import MkAcct from './global/MkAcct.vue';
import MkAvatar from './global/MkAvatar.vue'; import MkAvatar from './global/MkAvatar.vue';
import MkEmoji from './global/MkEmoji.vue'; import MkEmoji from './global/MkEmoji.vue';
import MkCustomEmoji from './global/MkCustomEmoji.vue'; import MkCustomEmoji from './global/MkCustomEmoji.vue';
import MkUserName from './global/MkUserName.vue';
import MkEllipsis from './global/MkEllipsis.vue'; import MkEllipsis from './global/MkEllipsis.vue';
import MkTime from './global/MkTime.vue'; import MkTime from './global/MkTime.vue';
import MkUrl from './global/MkUrl.vue'; import MkUrl from './global/MkUrl.vue';
@ -28,7 +27,6 @@ export default function(app: App) {
app.component('MkAvatar', MkAvatar); app.component('MkAvatar', MkAvatar);
app.component('MkEmoji', MkEmoji); app.component('MkEmoji', MkEmoji);
app.component('MkCustomEmoji', MkCustomEmoji); app.component('MkCustomEmoji', MkCustomEmoji);
app.component('MkUserName', MkUserName);
app.component('MkEllipsis', MkEllipsis); app.component('MkEllipsis', MkEllipsis);
app.component('MkTime', MkTime); app.component('MkTime', MkTime);
app.component('MkUrl', MkUrl); app.component('MkUrl', MkUrl);
@ -50,7 +48,6 @@ declare module '@vue/runtime-core' {
MkAvatar: typeof MkAvatar; MkAvatar: typeof MkAvatar;
MkEmoji: typeof MkEmoji; MkEmoji: typeof MkEmoji;
MkCustomEmoji: typeof MkCustomEmoji; MkCustomEmoji: typeof MkCustomEmoji;
MkUserName: typeof MkUserName;
MkEllipsis: typeof MkEllipsis; MkEllipsis: typeof MkEllipsis;
MkTime: typeof MkTime; MkTime: typeof MkTime;
MkUrl: typeof MkUrl; MkUrl: typeof MkUrl;

View file

@ -195,7 +195,7 @@ export default defineComponent({
return h(MkSparkle, {}, genEl(token.children)); return h(MkSparkle, {}, genEl(token.children));
} }
case 'rotate': { 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;`; style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
break; break;
} }

View file

@ -8,7 +8,7 @@
<Mfm :text="clip.description" :is-note="false" :i="$i"/> <Mfm :text="clip.description" :is-note="false" :i="$i"/>
</div> </div>
<div class="user"> <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>
</div> </div>

View file

@ -15,7 +15,7 @@
<MkAvatar class="avatar" :user="req.follower" indicator link preview/> <MkAvatar class="avatar" :user="req.follower" indicator link preview/>
<div class="body"> <div class="body">
<div class="name"> <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> <p class="acct">@{{ acct(req.follower) }}</p>
</div> </div>
<div v-if="req.follower.description" class="description" :title="req.follower.description"> <div v-if="req.follower.description" class="description" :title="req.follower.description">

View file

@ -30,7 +30,7 @@
<div class="user"> <div class="user">
<MkAvatar :user="post.user" class="avatar" link preview/> <MkAvatar :user="post.user" class="avatar" link preview/>
<div class="name"> <div class="name">
<MkUserName :user="post.user" style="display: block;"/> <div class="_nowrap">{{ post.user.name ?? post.user.username }}</div>
<MkAcct :user="post.user"/> <MkAcct :user="post.user"/>
</div> </div>
<MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> <MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>

View file

@ -22,7 +22,7 @@
<MkTime :time="message.createdAt" class="time"/> <MkTime :time="message.createdAt" class="time"/>
</header> </header>
<header v-else> <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> <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
<MkTime :time="message.createdAt" class="time"/> <MkTime :time="message.createdAt" class="time"/>
</header> </header>

View file

@ -21,7 +21,7 @@
<div v-for="user in users" :key="user.id" class="user _panel"> <div v-for="user in users" :key="user.id" class="user _panel">
<MkAvatar :user="user" class="avatar" indicator link preview/> <MkAvatar :user="user" class="avatar" indicator link preview/>
<div class="body"> <div class="body">
<MkUserName :user="user" class="name"/> <span class="name _nowrap">{{ user.name ?? user.username }}</span>
<MkAcct :user="user" class="acct"/> <MkAcct :user="user" class="acct"/>
</div> </div>
<div class="action"> <div class="action">

View file

@ -21,7 +21,7 @@
<b>{{ item.name }}</b> <b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div> <div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user"> <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> </div>
</MkA> </MkA>
</div> </div>

View file

@ -29,7 +29,7 @@
<div class="user"> <div class="user">
<MkAvatar :user="page.user" class="avatar" link preview/> <MkAvatar :user="page.user" class="avatar" link preview/>
<div class="name"> <div class="name">
<MkUserName :user="page.user" style="display: block;"/> <div class="_nowrap">{{ page.user.name ?? page.user.username }}</div>
<MkAcct :user="page.user"/> <MkAcct :user="page.user"/>
</div> </div>
<MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> <MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>

View file

@ -9,9 +9,7 @@
<MkAvatar :user="account" class="avatar"/> <MkAvatar :user="account" class="avatar"/>
</div> </div>
<div class="body"> <div class="body">
<div class="name"> <div class="name _nowrap">{{ account.name ?? account.username }}</div>
<MkUserName :user="account"/>
</div>
<div class="acct"> <div class="acct">
<MkAcct :user="account"/> <MkAcct :user="account"/>
</div> </div>

View file

@ -7,7 +7,7 @@
<div class="aeakzknw"> <div class="aeakzknw">
<MkAvatar class="avatar" :user="user" indicator link preview/> <MkAvatar class="avatar" :user="user" indicator link preview/>
<div class="body"> <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="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
<span class="state"> <span class="state">
<span v-if="suspended" class="suspended">Suspended</span> <span v-if="suspended" class="suspended">Suspended</span>

View file

@ -14,7 +14,7 @@
<div ref="bannerEl" class="banner" :style="style"></div> <div ref="bannerEl" class="banner" :style="style"></div>
<div class="fade"></div> <div class="fade"></div>
<div class="title"> <div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/> <span class="name _nowrap">{{ user.name ?? user.username }}</span>
<div class="bottom"> <div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true"/></span> <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> <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> </div>
<MkAvatar class="avatar" :user="user" indicator/> <MkAvatar class="avatar" :user="user" indicator/>
<div class="title"> <div class="title">
<MkUserName :user="user" :nowrap="false" class="name"/> <span class="name _nowrap">{{ user.name ?? user.username }}</span>
<div class="bottom"> <div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true"/></span> <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> <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span>

View file

@ -168,7 +168,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
disableShowingAnimatedImages: { disableShowingAnimatedImages: {
where: 'device', where: 'device',
default: false, default: matchMedia('(prefers-reduced-motion)').matches,
}, },
emojiStyle: { emojiStyle: {
where: 'device', where: 'device',

View file

@ -156,6 +156,13 @@ hr {
-webkit-touch-callout: none; -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 { ._ghost {
@extend ._noSelect; @extend ._noSelect;
pointer-events: none; pointer-events: none;

View file

@ -6,9 +6,7 @@
</div> </div>
<div :class="$style.bodyContainer"> <div :class="$style.bodyContainer">
<div :class="$style.body"> <div :class="$style.body">
<MkA :class="$style.name" :to="userPage($i)"> <MkA :class="$style.name" :to="userPage($i)" class="_nowrap">{{ $i.name ?? $i.username }}</MkA>
<MkUserName :user="$i"/>
</MkA>
<div :class="$style.username"><MkAcct :user="$i" detail/></div> <div :class="$style.username"><MkAcct :user="$i" detail/></div>
</div> </div>
</div> </div>