Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d8434a206f | ||
![]() |
83159600ea | ||
![]() |
12b82aca5f | ||
![]() |
73842166ee | ||
![]() |
30594dde18 | ||
![]() |
7948018e6a | ||
![]() |
8fb8d7c10c | ||
![]() |
7ca0af9e7e | ||
![]() |
ac2bace764 | ||
![]() |
d97924890d | ||
![]() |
6b4f57781a | ||
![]() |
c525394989 | ||
![]() |
8753f9ef06 | ||
![]() |
35fd0a7fc2 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -12,8 +12,26 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 202x.x.x (Unreleased)
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
|
- Enhance: ActivityPubをサポートしているウェブリンクを展開できるように
|
||||||
|
|
||||||
|
## 2023.12.2
|
||||||
|
|
||||||
|
### General
|
||||||
|
- v2023.12.1でDockerを利用してサーバーを起動できない問題を修正
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Enhance: 検索画面においてEnterキー押下で検索できるように
|
||||||
|
|
||||||
## 2023.12.1
|
## 2023.12.1
|
||||||
|
|
||||||
|
### Note
|
||||||
|
- アクセストークンの権限が再整理されたため、一部のAPIが古いAPIトークンでは動作しなくなりました。\
|
||||||
|
権限不足になる場合には権限を再設定して再生成してください。
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Enhance: ローカリゼーションの更新
|
- Enhance: ローカリゼーションの更新
|
||||||
- Fix: 自分のdirect noteがuser list timelineに追加されない
|
- Fix: 自分のdirect noteがuser list timelineに追加されない
|
||||||
|
2
COPYING
2
COPYING
@@ -1,5 +1,5 @@
|
|||||||
Unless otherwise stated this repository is
|
Unless otherwise stated this repository is
|
||||||
Copyright © 2014-2023 syuilo and contributers
|
Copyright © 2014-2023 syuilo and contributors
|
||||||
|
|
||||||
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
||||||
|
|
||||||
|
@@ -51,6 +51,7 @@ WORKDIR /misskey
|
|||||||
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
|
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
|
||||||
COPY --link ["scripts", "./scripts"]
|
COPY --link ["scripts", "./scripts"]
|
||||||
COPY --link ["packages/backend/package.json", "./packages/backend/"]
|
COPY --link ["packages/backend/package.json", "./packages/backend/"]
|
||||||
|
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||||
pnpm i --frozen-lockfile --aggregate-output
|
pnpm i --frozen-lockfile --aggregate-output
|
||||||
@@ -77,7 +78,9 @@ WORKDIR /misskey
|
|||||||
|
|
||||||
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
|
COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules
|
||||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||||
|
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
|
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
|
||||||
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||||
COPY --chown=misskey:misskey . ./
|
COPY --chown=misskey:misskey . ./
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
_lang_: "বাংলা"
|
_lang_: "বাংলা"
|
||||||
headlineMisskey: "নোট ব্যাবহার করে সংযুক্ত নেটওয়ার্ক"
|
headlineMisskey: "নোট ব্যাবহার করে সংযুক্ত নেটওয়ার্ক"
|
||||||
introMisskey: "স্বাগতম! মিসকি একটি ওপেন সোর্স, ডিসেন্ট্রালাইজড মাইক্রোব্লগিং পরিষেবা। \n\"নোট\" তৈরির মাধ্যমে যা ঘটছে তা সবার সাথে শেয়ার করুন 📡\n\"রিঅ্যাকশন\" গুলির মাধ্যমে যেকোনো নোট সম্পর্কে আপনার অনুভূতি ব্যাক্ত করতে পারেন 👍\nএকটি নতুন দুনিয়া ঘুরে দেখুন 🚀\n"
|
introMisskey: "স্বাগতম! মিসকি একটি ওপেন সোর্স, ডিসেন্ট্রালাইজড মাইক্রোব্লগিং পরিষেবা। \n\"নোট\" তৈরির মাধ্যমে যা ঘটছে তা সবার সাথে শেয়ার করুন 📡\n\"রিঅ্যাকশন\" গুলির মাধ্যমে যেকোনো নোট সম্পর্কে আপনার অনুভূতি ব্যাক্ত করতে পারেন 👍\nএকটি নতুন দুনিয়া ঘুরে দেখুন 🚀\n"
|
||||||
|
poweredByMisskeyDescription: "{name} হল ওপেন সোর্স প্ল্যাটফর্ম <b>Misskey</b>-এর সার্ভারগুলির একটি৷"
|
||||||
monthAndDay: "{day}/{month}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "খুঁজুন"
|
search: "খুঁজুন"
|
||||||
notifications: "বিজ্ঞপ্তি"
|
notifications: "বিজ্ঞপ্তি"
|
||||||
@@ -12,12 +13,14 @@ fetchingAsApObject: "ফেডিভার্স থেকে খবর আন
|
|||||||
ok: "ঠিক"
|
ok: "ঠিক"
|
||||||
gotIt: "বুঝেছি"
|
gotIt: "বুঝেছি"
|
||||||
cancel: "বাতিল"
|
cancel: "বাতিল"
|
||||||
|
noThankYou: "না, ধন্যবাদ"
|
||||||
enterUsername: "ইউজারনেম লিখুন"
|
enterUsername: "ইউজারনেম লিখুন"
|
||||||
renotedBy: "{user} রিনোট করেছেন"
|
renotedBy: "{user} রিনোট করেছেন"
|
||||||
noNotes: "কোন নোট নেই"
|
noNotes: "কোন নোট নেই"
|
||||||
noNotifications: "কোনো বিজ্ঞপ্তি নেই"
|
noNotifications: "কোনো বিজ্ঞপ্তি নেই"
|
||||||
instance: "ইন্সট্যান্স"
|
instance: "ইন্সট্যান্স"
|
||||||
settings: "সেটিংস"
|
settings: "সেটিংস"
|
||||||
|
notificationSettings: "বিজ্ঞপ্তির সেটিংস"
|
||||||
basicSettings: "সাধারণ সেটিংস"
|
basicSettings: "সাধারণ সেটিংস"
|
||||||
otherSettings: "অন্যান্য সেটিংস"
|
otherSettings: "অন্যান্য সেটিংস"
|
||||||
openInWindow: "নতুন উইন্ডোতে খুলা"
|
openInWindow: "নতুন উইন্ডোতে খুলা"
|
||||||
@@ -42,12 +45,20 @@ pin: "পিন করা"
|
|||||||
unpin: "পিন সরান"
|
unpin: "পিন সরান"
|
||||||
copyContent: "বিষয়বস্তু কপি করুন"
|
copyContent: "বিষয়বস্তু কপি করুন"
|
||||||
copyLink: "লিঙ্ক কপি করুন"
|
copyLink: "লিঙ্ক কপি করুন"
|
||||||
|
copyLinkRenote: "রিনোট লিঙ্ক কপি করুন"
|
||||||
delete: "মুছুন"
|
delete: "মুছুন"
|
||||||
deleteAndEdit: "মুছুন এবং সম্পাদনা করুন"
|
deleteAndEdit: "মুছুন এবং সম্পাদনা করুন"
|
||||||
deleteAndEditConfirm: "আপনি কি এই নোটটি মুছে এটি সম্পাদনা করার বিষয়ে নিশ্চিত? আপনি এটির সমস্ত রিঅ্যাকশন, রিনোট এবং জবাব হারাবেন।"
|
deleteAndEditConfirm: "আপনি কি এই নোটটি মুছে এটি সম্পাদনা করার বিষয়ে নিশ্চিত? আপনি এটির সমস্ত রিঅ্যাকশন, রিনোট এবং জবাব হারাবেন।"
|
||||||
addToList: "লিস্ট এ যোগ করুন"
|
addToList: "লিস্ট এ যোগ করুন"
|
||||||
|
addToAntenna: "অ্যান্টেনা এ যোগ করুন"
|
||||||
sendMessage: "একটি বার্তা পাঠান"
|
sendMessage: "একটি বার্তা পাঠান"
|
||||||
|
copyRSS: "RSS কপি করুন"
|
||||||
copyUsername: "ব্যবহারকারীর নাম কপি করুন"
|
copyUsername: "ব্যবহারকারীর নাম কপি করুন"
|
||||||
|
copyUserId: "ব্যবহারকারীর ID কপি করুন"
|
||||||
|
copyNoteId: "নোটের ID কপি করুন"
|
||||||
|
copyFileId: "ফাইল ID কপি করুন"
|
||||||
|
copyFolderId: "ফোল্ডার ID কপি করুন"
|
||||||
|
copyProfileUrl: "প্রোফাইল URL কপি করুন"
|
||||||
searchUser: "ব্যবহারকারী খুঁজুন..."
|
searchUser: "ব্যবহারকারী খুঁজুন..."
|
||||||
reply: "জবাব"
|
reply: "জবাব"
|
||||||
loadMore: "আরও দেখুন"
|
loadMore: "আরও দেখুন"
|
||||||
@@ -100,6 +111,8 @@ renoted: "রিনোট করা হয়েছে"
|
|||||||
cantRenote: "এই নোটটি রিনোট করা যাবে না।"
|
cantRenote: "এই নোটটি রিনোট করা যাবে না।"
|
||||||
cantReRenote: "রিনোটকে রিনোট করা যাবে না।"
|
cantReRenote: "রিনোটকে রিনোট করা যাবে না।"
|
||||||
quote: "উদ্ধৃতি"
|
quote: "উদ্ধৃতি"
|
||||||
|
inChannelRenote: "চ্যানেলে রিনোট"
|
||||||
|
inChannelQuote: "চ্যানেলে উদ্ধৃতি"
|
||||||
pinnedNote: "পিন করা নোট"
|
pinnedNote: "পিন করা নোট"
|
||||||
pinned: "পিন করা"
|
pinned: "পিন করা"
|
||||||
you: "আপনি"
|
you: "আপনি"
|
||||||
@@ -108,6 +121,10 @@ sensitive: "সংবেদনশীল বিষয়বস্তু"
|
|||||||
add: "যুক্ত করুন"
|
add: "যুক্ত করুন"
|
||||||
reaction: "প্রতিক্রিয়া"
|
reaction: "প্রতিক্রিয়া"
|
||||||
reactions: "প্রতিক্রিয়া"
|
reactions: "প্রতিক্রিয়া"
|
||||||
|
emojiPicker: "ইমোজি পিকার"
|
||||||
|
pinnedEmojisForReactionSettingDescription: "রিঅ্যাকশন দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।"
|
||||||
|
pinnedEmojisSettingDescription: "ইমোজি ইনপুট দেয়ার সময় আপনি ইমোজিটিকে পিন করা এবং প্রদর্শিত হওয়ার জন্য সেট করতে পারেন।"
|
||||||
|
emojiPickerDisplay: "পিকার ডিসপ্লে"
|
||||||
reactionSettingDescription2: "পুনরায় সাজাতে টেনে আনুন, মুছতে ক্লিক করুন, যোগ করতে + টিপুন।"
|
reactionSettingDescription2: "পুনরায় সাজাতে টেনে আনুন, মুছতে ক্লিক করুন, যোগ করতে + টিপুন।"
|
||||||
rememberNoteVisibility: "নোটের দৃশ্যমান্যতার সেটিংস মনে রাখুন"
|
rememberNoteVisibility: "নোটের দৃশ্যমান্যতার সেটিংস মনে রাখুন"
|
||||||
attachCancel: "অ্যাটাচমেন্ট সরান "
|
attachCancel: "অ্যাটাচমেন্ট সরান "
|
||||||
@@ -1034,6 +1051,7 @@ _2fa:
|
|||||||
step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
|
step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
|
||||||
step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
|
step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
|
||||||
securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
|
securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
|
||||||
|
renewTOTPCancel: "না, ধন্যবাদ"
|
||||||
_permissions:
|
_permissions:
|
||||||
"read:account": "অ্যাকাউন্টের তথ্য দেখুন"
|
"read:account": "অ্যাকাউন্টের তথ্য দেখুন"
|
||||||
"write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন"
|
"write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন"
|
||||||
|
@@ -121,6 +121,10 @@ sensitive: "Konten sensitif"
|
|||||||
add: "Tambahkan"
|
add: "Tambahkan"
|
||||||
reaction: "Reaksi"
|
reaction: "Reaksi"
|
||||||
reactions: "Reaksi"
|
reactions: "Reaksi"
|
||||||
|
emojiPicker: "Emoji Picker"
|
||||||
|
pinnedEmojisForReactionSettingDescription: "Atur sematan emoji pada reaksi"
|
||||||
|
pinnedEmojisSettingDescription: "Atur sematan emoji pada masukan emoji"
|
||||||
|
emojiPickerDisplay: "Tampilan Emoji Picker"
|
||||||
reactionSettingDescription2: "Geser untuk memindah urutan emoji, klik untuk menghapus, tekan \"+\" untuk menambahkan"
|
reactionSettingDescription2: "Geser untuk memindah urutan emoji, klik untuk menghapus, tekan \"+\" untuk menambahkan"
|
||||||
rememberNoteVisibility: "Ingat pengaturan visibilitas catatan"
|
rememberNoteVisibility: "Ingat pengaturan visibilitas catatan"
|
||||||
attachCancel: "Hapus lampiran"
|
attachCancel: "Hapus lampiran"
|
||||||
@@ -641,6 +645,7 @@ smtpSecure: "Gunakan SSL/TLS implisit untuk koneksi SMTP"
|
|||||||
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
|
smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
|
||||||
testEmail: "Tes pengiriman surel"
|
testEmail: "Tes pengiriman surel"
|
||||||
wordMute: "Bisukan kata"
|
wordMute: "Bisukan kata"
|
||||||
|
hardWordMute: "Pembisuan kata keras"
|
||||||
regexpError: "Kesalahan ekspresi reguler"
|
regexpError: "Kesalahan ekspresi reguler"
|
||||||
regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
|
regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
|
||||||
instanceMute: "Bisukan instansi"
|
instanceMute: "Bisukan instansi"
|
||||||
@@ -1154,6 +1159,7 @@ tosAndPrivacyPolicy: "Syarat dan Ketentuan serta Kebijakan Privasi"
|
|||||||
avatarDecorations: "Dekorasi avatar"
|
avatarDecorations: "Dekorasi avatar"
|
||||||
attach: "Lampirkan"
|
attach: "Lampirkan"
|
||||||
detach: "Hapus"
|
detach: "Hapus"
|
||||||
|
detachAll: "Lepas Semua"
|
||||||
angle: "Sudut"
|
angle: "Sudut"
|
||||||
flip: "Balik"
|
flip: "Balik"
|
||||||
showAvatarDecorations: "Tampilkan dekorasi avatar"
|
showAvatarDecorations: "Tampilkan dekorasi avatar"
|
||||||
@@ -1168,6 +1174,7 @@ doReaction: "Tambahkan reaksi"
|
|||||||
code: "Kode"
|
code: "Kode"
|
||||||
reloadRequiredToApplySettings: "Muat ulang diperlukan untuk menerapkan pengaturan."
|
reloadRequiredToApplySettings: "Muat ulang diperlukan untuk menerapkan pengaturan."
|
||||||
remainingN: "Sisa : {n}"
|
remainingN: "Sisa : {n}"
|
||||||
|
decorate: "Dekor"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "Hanya pengguna yang telah ada"
|
forExistingUsers: "Hanya pengguna yang telah ada"
|
||||||
forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
|
forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
|
||||||
@@ -1215,6 +1222,7 @@ _initialTutorial:
|
|||||||
followers: "Perlihatkan ke pengikut saja. Hanya pengikut yang dapat melihat postinganmu dan tidak dapat direnote oleh siapapun."
|
followers: "Perlihatkan ke pengikut saja. Hanya pengikut yang dapat melihat postinganmu dan tidak dapat direnote oleh siapapun."
|
||||||
direct: "Hanya perlihatkan ke pengguna spesifik dan penerima akan diberi tahu. Dapat juga digunakan sebagai alternatif dari pesan langsung."
|
direct: "Hanya perlihatkan ke pengguna spesifik dan penerima akan diberi tahu. Dapat juga digunakan sebagai alternatif dari pesan langsung."
|
||||||
_cw:
|
_cw:
|
||||||
|
title: "Peringatan Konten (CW)"
|
||||||
_exampleNote:
|
_exampleNote:
|
||||||
cw: "Peringatan: Bikin Lapar!"
|
cw: "Peringatan: Bikin Lapar!"
|
||||||
note: "Baru aja makan donat berlapis coklat 🍩😋"
|
note: "Baru aja makan donat berlapis coklat 🍩😋"
|
||||||
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
@@ -609,6 +609,7 @@ export interface Locale {
|
|||||||
"enablePlayer": string;
|
"enablePlayer": string;
|
||||||
"disablePlayer": string;
|
"disablePlayer": string;
|
||||||
"expandTweet": string;
|
"expandTweet": string;
|
||||||
|
"expandNote": string;
|
||||||
"themeEditor": string;
|
"themeEditor": string;
|
||||||
"description": string;
|
"description": string;
|
||||||
"describeFile": string;
|
"describeFile": string;
|
||||||
|
@@ -606,6 +606,7 @@ useCw: "内容を隠す"
|
|||||||
enablePlayer: "プレイヤーを開く"
|
enablePlayer: "プレイヤーを開く"
|
||||||
disablePlayer: "プレイヤーを閉じる"
|
disablePlayer: "プレイヤーを閉じる"
|
||||||
expandTweet: "ポストを展開する"
|
expandTweet: "ポストを展開する"
|
||||||
|
expandNote: "ノートを展開する"
|
||||||
themeEditor: "テーマエディター"
|
themeEditor: "テーマエディター"
|
||||||
description: "説明"
|
description: "説明"
|
||||||
describeFile: "キャプションを付ける"
|
describeFile: "キャプションを付ける"
|
||||||
|
@@ -260,6 +260,7 @@ removed: "뭉캣십니다"
|
|||||||
removeAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?"
|
removeAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?"
|
||||||
deleteAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?"
|
deleteAreYouSure: "‘{x}’(얼)럴 뭉캡니꺼?"
|
||||||
resetAreYouSure: "아시로 데돌립니꺼?"
|
resetAreYouSure: "아시로 데돌립니꺼?"
|
||||||
|
areYouSure: "갠찮십니꺼?"
|
||||||
saved: "저장햇십니다"
|
saved: "저장햇십니다"
|
||||||
messaging: "대화"
|
messaging: "대화"
|
||||||
upload: "올리기"
|
upload: "올리기"
|
||||||
@@ -458,6 +459,7 @@ noMessagesYet: "아직 대화가 없십니다"
|
|||||||
newMessageExists: "새 메시지가 있십니다"
|
newMessageExists: "새 메시지가 있십니다"
|
||||||
onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니다"
|
onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니다"
|
||||||
invitations: "초대하기"
|
invitations: "초대하기"
|
||||||
|
invitationCode: "초대장"
|
||||||
checking: "학인하고 잇십니다"
|
checking: "학인하고 잇십니다"
|
||||||
passwordMatched: "맞십니다"
|
passwordMatched: "맞십니다"
|
||||||
passwordNotMatched: "안 맞십니다"
|
passwordNotMatched: "안 맞십니다"
|
||||||
@@ -564,6 +566,11 @@ removeAllFollowing: "팔로잉 말캉 무루기"
|
|||||||
removeAllFollowingDescription: "{host} 서버랑 걸어놓은 모든 팔로잉을 무룹니다. 고 서버가 아예 없어지삐맀든가, 그런 경우에 하이소."
|
removeAllFollowingDescription: "{host} 서버랑 걸어놓은 모든 팔로잉을 무룹니다. 고 서버가 아예 없어지삐맀든가, 그런 경우에 하이소."
|
||||||
userSuspended: "요 게정은... 얼어 있십니다."
|
userSuspended: "요 게정은... 얼어 있십니다."
|
||||||
userSilenced: "요 게정은... 수ᇚ혀 있십니다."
|
userSilenced: "요 게정은... 수ᇚ혀 있십니다."
|
||||||
|
relays: "릴레이"
|
||||||
|
addRelay: "릴레이 옇기"
|
||||||
|
addedRelays: "옇은 릴레이"
|
||||||
|
enableInfiniteScroll: "알아서 더 보기"
|
||||||
|
author: "맨던 사람"
|
||||||
manage: "간리"
|
manage: "간리"
|
||||||
emailServer: "전자우펜 서버"
|
emailServer: "전자우펜 서버"
|
||||||
email: "전자우펜"
|
email: "전자우펜"
|
||||||
@@ -572,6 +579,8 @@ smtpHost: "호스트 이럼"
|
|||||||
smtpPort: "포트"
|
smtpPort: "포트"
|
||||||
smtpUser: "사용자 이럼"
|
smtpUser: "사용자 이럼"
|
||||||
smtpPass: "비밀번호"
|
smtpPass: "비밀번호"
|
||||||
|
display: "보기"
|
||||||
|
create: "맨걸기"
|
||||||
abuseReports: "신고하기"
|
abuseReports: "신고하기"
|
||||||
reportAbuse: "신고하기"
|
reportAbuse: "신고하기"
|
||||||
reportAbuseRenote: "리노트 신고하기"
|
reportAbuseRenote: "리노트 신고하기"
|
||||||
@@ -583,6 +592,7 @@ forwardReport: "웬겍 서버에 신고 보내기"
|
|||||||
random: "무작이"
|
random: "무작이"
|
||||||
system: "시스템"
|
system: "시스템"
|
||||||
clip: "클립 맨걸기"
|
clip: "클립 맨걸기"
|
||||||
|
createNew: "새로 맨걸기"
|
||||||
notesCount: "노트 수"
|
notesCount: "노트 수"
|
||||||
renotesCount: "리노트한 수"
|
renotesCount: "리노트한 수"
|
||||||
renotedCount: "리노트덴 수"
|
renotedCount: "리노트덴 수"
|
||||||
@@ -608,6 +618,7 @@ tools: "도구"
|
|||||||
like: "좋네예!"
|
like: "좋네예!"
|
||||||
unlike: "좋네예 무루기"
|
unlike: "좋네예 무루기"
|
||||||
numberOfLikes: "좋네예 수"
|
numberOfLikes: "좋네예 수"
|
||||||
|
show: "보기"
|
||||||
roles: "옉할"
|
roles: "옉할"
|
||||||
role: "옉할"
|
role: "옉할"
|
||||||
noRole: "옉할이 없십니다"
|
noRole: "옉할이 없십니다"
|
||||||
@@ -637,6 +648,8 @@ _gallery:
|
|||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
title: "새 팔로워가 잇십니다"
|
title: "새 팔로워가 잇십니다"
|
||||||
|
_serverDisconnectedBehavior:
|
||||||
|
reload: "알아서 새로곤침"
|
||||||
_channel:
|
_channel:
|
||||||
removeBanner: "배너 뭉캐기"
|
removeBanner: "배너 뭉캐기"
|
||||||
_theme:
|
_theme:
|
||||||
|
@@ -425,9 +425,9 @@ setupOf2fa: "2단계 인증 설정"
|
|||||||
totp: "인증 앱"
|
totp: "인증 앱"
|
||||||
totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
|
totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
|
||||||
moderator: "모더레이터"
|
moderator: "모더레이터"
|
||||||
moderation: "모더레이션"
|
moderation: "조정"
|
||||||
moderationNote: "모더레이션 노트"
|
moderationNote: "조정 기록"
|
||||||
addModerationNote: "모더레이션 노트 추가하기"
|
addModerationNote: "조정 기록 추가하기"
|
||||||
moderationLogs: "모더레이션 로그"
|
moderationLogs: "모더레이션 로그"
|
||||||
nUsersMentioned: "{n}명이 언급함"
|
nUsersMentioned: "{n}명이 언급함"
|
||||||
securityKeyAndPasskey: "보안 키 또는 패스 키"
|
securityKeyAndPasskey: "보안 키 또는 패스 키"
|
||||||
@@ -513,7 +513,7 @@ dayOverDayChanges: "어제보다"
|
|||||||
appearance: "모양"
|
appearance: "모양"
|
||||||
clientSettings: "클라이언트 설정"
|
clientSettings: "클라이언트 설정"
|
||||||
accountSettings: "계정 설정"
|
accountSettings: "계정 설정"
|
||||||
promotion: "프로모션"
|
promotion: "홍보"
|
||||||
promote: "프로모션하기"
|
promote: "프로모션하기"
|
||||||
numberOfDays: "며칠동안"
|
numberOfDays: "며칠동안"
|
||||||
hideThisNote: "이 노트를 숨기기"
|
hideThisNote: "이 노트를 숨기기"
|
||||||
@@ -863,8 +863,8 @@ devMode: "개발자 모드"
|
|||||||
keepCw: "CW 유지하기"
|
keepCw: "CW 유지하기"
|
||||||
pubSub: "Pub/Sub 계정"
|
pubSub: "Pub/Sub 계정"
|
||||||
lastCommunication: "마지막 통신"
|
lastCommunication: "마지막 통신"
|
||||||
resolved: "해결됨"
|
resolved: "처리함"
|
||||||
unresolved: "해결되지 않음"
|
unresolved: "처리되지 않음"
|
||||||
breakFollow: "팔로워 해제"
|
breakFollow: "팔로워 해제"
|
||||||
breakFollowConfirm: "팔로우를 해제하시겠습니까?"
|
breakFollowConfirm: "팔로우를 해제하시겠습니까?"
|
||||||
itsOn: "켜져 있습니다"
|
itsOn: "켜져 있습니다"
|
||||||
@@ -1181,6 +1181,8 @@ remainingN: "나머지: {n}"
|
|||||||
overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?"
|
overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?"
|
||||||
seasonalScreenEffect: "계절에 따른 효과 보이기"
|
seasonalScreenEffect: "계절에 따른 효과 보이기"
|
||||||
decorate: "장식하기"
|
decorate: "장식하기"
|
||||||
|
addMfmFunction: "장식 추가하기"
|
||||||
|
enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "기존 유저에게만 알림"
|
forExistingUsers: "기존 유저에게만 알림"
|
||||||
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
|
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
|
||||||
@@ -1557,7 +1559,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: "수동"
|
||||||
@@ -1628,7 +1630,7 @@ _role:
|
|||||||
or: "다음을 하나라도 만족"
|
or: "다음을 하나라도 만족"
|
||||||
not: "다음을 만족하지 않음"
|
not: "다음을 만족하지 않음"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
|
description: "기계 학습으로 민감한 미디어를 알아서 찾아내어 조정에 참고하도록 합니다. 서버가 부하를 다소 받습니다."
|
||||||
sensitivity: "탐지 민감도"
|
sensitivity: "탐지 민감도"
|
||||||
sensitivityDescription: "민감도가 낮을수록 안전한 미디어가 잘못 탐지될 확률이 줄어들며, 높을수록 민감한 미디어가 탐지되지 않을 확률이 줄어듭니다."
|
sensitivityDescription: "민감도가 낮을수록 안전한 미디어가 잘못 탐지될 확률이 줄어들며, 높을수록 민감한 미디어가 탐지되지 않을 확률이 줄어듭니다."
|
||||||
setSensitiveFlagAutomatically: "자동으로 NSFW로 설정하기"
|
setSensitiveFlagAutomatically: "자동으로 NSFW로 설정하기"
|
||||||
@@ -1933,6 +1935,55 @@ _permissions:
|
|||||||
"write:flash": "Play를 조작합니다"
|
"write:flash": "Play를 조작합니다"
|
||||||
"read:flash-likes": "Play의 좋아요를 봅니다"
|
"read:flash-likes": "Play의 좋아요를 봅니다"
|
||||||
"write:flash-likes": "Play의 좋아요를 조작합니다"
|
"write:flash-likes": "Play의 좋아요를 조작합니다"
|
||||||
|
"read:admin:abuse-user-reports": "사용자 신고 보기"
|
||||||
|
"write:admin:delete-account": "사용자 계정 삭제하기"
|
||||||
|
"write:admin:delete-all-files-of-a-user": "모든 사용자 파일 삭제하기"
|
||||||
|
"read:admin:index-stats": "데이터베이스 색인 정보 보기"
|
||||||
|
"read:admin:table-stats": "데이터베이스 테이블 정보 보기"
|
||||||
|
"read:admin:user-ips": "사용자 IP 주소 보기"
|
||||||
|
"read:admin:meta": "인스턴스 메타데이터 보기"
|
||||||
|
"write:admin:reset-password": "사용자 비밀번호 재설정하기"
|
||||||
|
"write:admin:resolve-abuse-user-report": "사용자 신고 처리하기"
|
||||||
|
"write:admin:send-email": "이메일 보내기"
|
||||||
|
"read:admin:server-info": "서버 정보 보기"
|
||||||
|
"read:admin:show-moderation-log": "조정 기록 보기"
|
||||||
|
"read:admin:show-user": "사용자 개인정보 보기"
|
||||||
|
"read:admin:show-users": "사용자 개인정보 보기"
|
||||||
|
"write:admin:suspend-user": "사용자 정지하기"
|
||||||
|
"write:admin:unset-user-avatar": "사용자 아바타 삭제하기"
|
||||||
|
"write:admin:unset-user-banner": "사용자 배너 삭제하기"
|
||||||
|
"write:admin:unsuspend-user": "사용자 정지 해제하기"
|
||||||
|
"write:admin:meta": "인스턴스 메타데이터 수정하기"
|
||||||
|
"write:admin:user-note": "조정 기록 수정하기"
|
||||||
|
"write:admin:roles": "역할 수정하기"
|
||||||
|
"read:admin:roles": "역할 보기"
|
||||||
|
"write:admin:relays": "릴레이 수정하기"
|
||||||
|
"read:admin:relays": "릴레이 보기"
|
||||||
|
"write:admin:invite-codes": "초대 코드 수정하기"
|
||||||
|
"read:admin:invite-codes": "초대 코드 보기"
|
||||||
|
"write:admin:announcements": "공지사항 수정하기"
|
||||||
|
"read:admin:announcements": "공지사항 보기"
|
||||||
|
"write:admin:avatar-decorations": "아바타 꾸미기 수정하기"
|
||||||
|
"read:admin:avatar-decorations": "아바타 꾸미기 보기"
|
||||||
|
"write:admin:federation": "연합 정보 수정하기"
|
||||||
|
"write:admin:account": "사용자 계정 수정하기"
|
||||||
|
"read:admin:account": "사용자 정보 보기"
|
||||||
|
"write:admin:emoji": "이모지 수정하기"
|
||||||
|
"read:admin:emoji": "이모지 보기"
|
||||||
|
"write:admin:queue": "작업 대기열 수정하기"
|
||||||
|
"read:admin:queue": "작업 대기열 정보 보기"
|
||||||
|
"write:admin:promo": "홍보 기록 수정하기"
|
||||||
|
"write:admin:drive": "사용자 드라이브 수정하기"
|
||||||
|
"read:admin:drive": "사용자 드라이브 정보 보기"
|
||||||
|
"read:admin:stream": "관리자용 Websocket API 사용하기"
|
||||||
|
"write:admin:ad": "광고 수정하기"
|
||||||
|
"read:admin:ad": "광고 보기"
|
||||||
|
"write:invite-codes": "초대 코드 만들기"
|
||||||
|
"read:invite-codes": "초대 코드 불러오기"
|
||||||
|
"write:clip-favorite": "클립의 좋아요 수정하기"
|
||||||
|
"read:clip-favorite": "클립의 좋아요 보기"
|
||||||
|
"read:federation": "연합 정보 불러오기"
|
||||||
|
"write:report-abuse": "위반 내용 신고하기"
|
||||||
_auth:
|
_auth:
|
||||||
shareAccessTitle: "어플리케이션의 접근 허가"
|
shareAccessTitle: "어플리케이션의 접근 허가"
|
||||||
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
|
||||||
@@ -2267,21 +2318,21 @@ _moderationLogTypes:
|
|||||||
updateCustomEmoji: "커스텀 이모지 수정"
|
updateCustomEmoji: "커스텀 이모지 수정"
|
||||||
deleteCustomEmoji: "커스텀 이모지 삭제"
|
deleteCustomEmoji: "커스텀 이모지 삭제"
|
||||||
updateServerSettings: "서버 설정 갱신"
|
updateServerSettings: "서버 설정 갱신"
|
||||||
updateUserNote: "모더레이션 노트 갱신"
|
updateUserNote: "조정 기록 갱신"
|
||||||
deleteDriveFile: "파일 삭제"
|
deleteDriveFile: "파일 삭제"
|
||||||
deleteNote: "노트 삭제"
|
deleteNote: "노트 삭제"
|
||||||
createGlobalAnnouncement: "전역 공지사항 생성"
|
createGlobalAnnouncement: "모든 공지사항 만들기"
|
||||||
createUserAnnouncement: "유저 공지사항 생성"
|
createUserAnnouncement: "사용자 공지사항 만들기"
|
||||||
updateGlobalAnnouncement: "전역 공지사항 수정"
|
updateGlobalAnnouncement: "모든 공지사항 수정"
|
||||||
updateUserAnnouncement: "유저 공지사항 수정"
|
updateUserAnnouncement: "사용자 공지사항 수정"
|
||||||
deleteGlobalAnnouncement: "전역 공지사항 삭제"
|
deleteGlobalAnnouncement: "모든 공지사항 삭제"
|
||||||
deleteUserAnnouncement: "유저 공지사항 삭제"
|
deleteUserAnnouncement: "사용자 공지사항 삭제"
|
||||||
resetPassword: "비밀번호 재설정"
|
resetPassword: "비밀번호 재설정"
|
||||||
suspendRemoteInstance: "리모트 서버를 정지"
|
suspendRemoteInstance: "리모트 서버를 정지"
|
||||||
unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
|
unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
|
||||||
markSensitiveDriveFile: "파일에 열람주의를 설정"
|
markSensitiveDriveFile: "파일에 열람주의를 설정"
|
||||||
unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
|
unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
|
||||||
resolveAbuseReport: "신고 해결"
|
resolveAbuseReport: "신고 처리"
|
||||||
createInvitation: "초대 코드 생성"
|
createInvitation: "초대 코드 생성"
|
||||||
createAd: "광고 생성"
|
createAd: "광고 생성"
|
||||||
deleteAd: "광고 삭제"
|
deleteAd: "광고 삭제"
|
||||||
|
@@ -1181,6 +1181,8 @@ remainingN: "剩餘:{n}"
|
|||||||
overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
|
overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
|
||||||
seasonalScreenEffect: "隨季節變換畫面的呈現"
|
seasonalScreenEffect: "隨季節變換畫面的呈現"
|
||||||
decorate: "設置頭像裝飾"
|
decorate: "設置頭像裝飾"
|
||||||
|
addMfmFunction: "插入MFM功能語法"
|
||||||
|
enableQuickAddMfmFunction: "顯示高級MFM選擇器"
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "僅限既有的使用者"
|
forExistingUsers: "僅限既有的使用者"
|
||||||
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
|
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.12.1",
|
"version": "2023.12.2",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"build-assets": "node ./scripts/build-assets.mjs",
|
"build-assets": "node ./scripts/build-assets.mjs",
|
||||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
"build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build",
|
"build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SupportTrueMailApi1703658526000 {
|
||||||
|
name = 'SupportTrueMailApi1703658526000'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`);
|
||||||
|
}
|
||||||
|
}
|
@@ -156,7 +156,7 @@ export class EmailService {
|
|||||||
@bindThis
|
@bindThis
|
||||||
public async validateEmailForAccount(emailAddress: string): Promise<{
|
public async validateEmailForAccount(emailAddress: string): Promise<{
|
||||||
available: boolean;
|
available: boolean;
|
||||||
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned';
|
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
|
||||||
}> {
|
}> {
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
|
||||||
@@ -173,6 +173,8 @@ export class EmailService {
|
|||||||
if (meta.enableActiveEmailValidation) {
|
if (meta.enableActiveEmailValidation) {
|
||||||
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
|
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
|
||||||
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
|
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
|
||||||
|
} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
|
||||||
|
validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
|
||||||
} else {
|
} else {
|
||||||
validated = await validateEmail({
|
validated = await validateEmail({
|
||||||
email: emailAddress,
|
email: emailAddress,
|
||||||
@@ -201,6 +203,8 @@ export class EmailService {
|
|||||||
validated.reason === 'disposable' ? 'disposable' :
|
validated.reason === 'disposable' ? 'disposable' :
|
||||||
validated.reason === 'mx' ? 'mx' :
|
validated.reason === 'mx' ? 'mx' :
|
||||||
validated.reason === 'smtp' ? 'smtp' :
|
validated.reason === 'smtp' ? 'smtp' :
|
||||||
|
validated.reason === 'network' ? 'network' :
|
||||||
|
validated.reason === 'blacklist' ? 'blacklist' :
|
||||||
null,
|
null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -265,4 +269,67 @@ export class EmailService {
|
|||||||
reason: null,
|
reason: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async trueMail<T>(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{
|
||||||
|
valid: boolean;
|
||||||
|
reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null;
|
||||||
|
}> {
|
||||||
|
const endpoint = truemailInstance + '?email=' + emailAddress;
|
||||||
|
try {
|
||||||
|
const res = await this.httpRequestService.send(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: truemailAuthKey
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = (await res.json()) as {
|
||||||
|
email: string;
|
||||||
|
success: boolean;
|
||||||
|
errors?: {
|
||||||
|
list_match?: string;
|
||||||
|
regex?: string;
|
||||||
|
mx?: string;
|
||||||
|
smtp?: string;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: 'format',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (json.errors?.smtp) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: 'smtp',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (json.errors?.mx) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: 'mx',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!json.success) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: json.errors?.list_match as T || 'blacklist',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
reason: null,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: 'network',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -457,6 +457,23 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public verifymailAuthKey: string | null;
|
public verifymailAuthKey: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public enableTruemailApi: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public truemailInstance: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public truemailAuthKey: string | null;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
|
@@ -284,6 +284,18 @@ export const meta = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
enableTruemailApi: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
truemailInstance: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
truemailAuthKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
enableChartsForRemoteUser: {
|
enableChartsForRemoteUser: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
@@ -520,6 +532,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
||||||
enableVerifymailApi: instance.enableVerifymailApi,
|
enableVerifymailApi: instance.enableVerifymailApi,
|
||||||
verifymailAuthKey: instance.verifymailAuthKey,
|
verifymailAuthKey: instance.verifymailAuthKey,
|
||||||
|
enableTruemailApi: instance.enableTruemailApi,
|
||||||
|
truemailInstance: instance.truemailInstance,
|
||||||
|
truemailAuthKey: instance.truemailAuthKey,
|
||||||
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
|
enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
|
||||||
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
|
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
|
||||||
enableServerMachineStats: instance.enableServerMachineStats,
|
enableServerMachineStats: instance.enableServerMachineStats,
|
||||||
|
@@ -116,6 +116,9 @@ export const paramDef = {
|
|||||||
enableActiveEmailValidation: { type: 'boolean' },
|
enableActiveEmailValidation: { type: 'boolean' },
|
||||||
enableVerifymailApi: { type: 'boolean' },
|
enableVerifymailApi: { type: 'boolean' },
|
||||||
verifymailAuthKey: { type: 'string', nullable: true },
|
verifymailAuthKey: { type: 'string', nullable: true },
|
||||||
|
enableTruemailApi: { type: 'boolean' },
|
||||||
|
truemailInstance: { type: 'string', nullable: true },
|
||||||
|
truemailAuthKey: { type: 'string', nullable: true },
|
||||||
enableChartsForRemoteUser: { type: 'boolean' },
|
enableChartsForRemoteUser: { type: 'boolean' },
|
||||||
enableChartsForFederatedInstances: { type: 'boolean' },
|
enableChartsForFederatedInstances: { type: 'boolean' },
|
||||||
enableServerMachineStats: { type: 'boolean' },
|
enableServerMachineStats: { type: 'boolean' },
|
||||||
@@ -470,6 +473,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.enableTruemailApi !== undefined) {
|
||||||
|
set.enableTruemailApi = ps.enableTruemailApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.truemailInstance !== undefined) {
|
||||||
|
if (ps.truemailInstance === '') {
|
||||||
|
set.truemailInstance = null;
|
||||||
|
} else {
|
||||||
|
set.truemailInstance = ps.truemailInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.truemailAuthKey !== undefined) {
|
||||||
|
if (ps.truemailAuthKey === '') {
|
||||||
|
set.truemailAuthKey = null;
|
||||||
|
} else {
|
||||||
|
set.truemailAuthKey = ps.truemailAuthKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.enableChartsForRemoteUser !== undefined) {
|
if (ps.enableChartsForRemoteUser !== undefined) {
|
||||||
set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;
|
set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;
|
||||||
}
|
}
|
||||||
|
@@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/>
|
<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/>
|
||||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
|
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
|
||||||
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
|
<MkNoteSimple v-if="appearNote.renote" :class="$style.quote" :note="appearNote.renote" :quoted="true"/>
|
||||||
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
|
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
|
||||||
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
|
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -801,14 +801,7 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quote {
|
.quote {
|
||||||
padding: 8px 0;
|
margin: 8px 0;
|
||||||
}
|
|
||||||
|
|
||||||
.quoteNote {
|
|
||||||
padding: 16px;
|
|
||||||
border: dashed 1px var(--renote);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: clip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel {
|
.channel {
|
||||||
@@ -947,12 +940,6 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (max-width: 250px) {
|
|
||||||
.quoteNote {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div :class="[$style.root, quoted ? $style.quoted : null]">
|
||||||
<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
|
<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
|
||||||
<div :class="$style.main">
|
<div :class="$style.main">
|
||||||
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
|
||||||
@@ -30,6 +30,8 @@ import MkCwButton from '@/components/MkCwButton.vue';
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
pinned?: boolean;
|
||||||
|
quoted?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
@@ -78,12 +80,23 @@ const showContent = ref(false);
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quoted {
|
||||||
|
padding: 16px;
|
||||||
|
border: dashed 1px var(--renote);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
@container (min-width: 250px) {
|
@container (min-width: 250px) {
|
||||||
.avatar {
|
.avatar {
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quoted {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (min-width: 350px) {
|
@container (min-width: 350px) {
|
||||||
|
@@ -752,7 +752,17 @@ async function post(ev?: MouseEvent) {
|
|||||||
|
|
||||||
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
|
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
|
||||||
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
|
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
|
||||||
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
|
if (!postData.text) {
|
||||||
|
postData.text = hashtags_;
|
||||||
|
} else {
|
||||||
|
const postTextLines = postData.text.split('\n');
|
||||||
|
if (postTextLines[postTextLines.length - 1].trim() === '') {
|
||||||
|
postTextLines[postTextLines.length - 1] += hashtags_;
|
||||||
|
} else {
|
||||||
|
postTextLines[postTextLines.length - 1] += ' ' + hashtags_;
|
||||||
|
}
|
||||||
|
postData.text = postTextLines.join('\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// plugin
|
// plugin
|
||||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="player.url && playerEnabled">
|
<div v-if="player.url && playerEnabled">
|
||||||
<div
|
<div
|
||||||
:class="$style.player"
|
:class="$style.player"
|
||||||
:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`"
|
:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`"
|
||||||
@@ -25,9 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<i class="ti ti-x"></i> {{ i18n.ts.disablePlayer }}
|
<i class="ti ti-x"></i> {{ i18n.ts.disablePlayer }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-else-if="tweetId && tweetExpanded">
|
<div v-else-if="postExpanded">
|
||||||
<div ref="twitter">
|
<div v-if="tweetId" ref="twitter">
|
||||||
<iframe
|
<iframe
|
||||||
ref="tweet"
|
ref="tweet"
|
||||||
allow="fullscreen;web-share"
|
allow="fullscreen;web-share"
|
||||||
@@ -37,12 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"
|
:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
<MkNoteSimple v-else-if="note" :note="note" :quoted="true"/>
|
||||||
<div :class="$style.action">
|
<div :class="$style.action">
|
||||||
<MkButton :small="true" inline @click="tweetExpanded = false">
|
<MkButton :small="true" inline @click="postExpanded = false">
|
||||||
<i class="ti ti-x"></i> {{ i18n.ts.close }}
|
<i v-if="tweetId" class="ti ti-x"></i> {{ i18n.ts.close }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
|
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
|
||||||
<div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
|
<div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
|
||||||
@@ -66,10 +67,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</component>
|
</component>
|
||||||
<template v-if="showActions">
|
<template v-if="showActions">
|
||||||
<div v-if="tweetId" :class="$style.action">
|
<div v-if="tweetId" :class="$style.action">
|
||||||
<MkButton :small="true" inline @click="tweetExpanded = true">
|
<MkButton :small="true" inline @click="postExpanded = true">
|
||||||
<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }}
|
<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="noteUrl || note" :class="$style.action">
|
||||||
|
<MkButton :small="true" inline @click="resolveNote()">
|
||||||
|
{{ i18n.ts.expandNote }}
|
||||||
|
</MkButton>
|
||||||
|
</div>
|
||||||
<div v-if="!playerEnabled && player.url" :class="$style.action">
|
<div v-if="!playerEnabled && player.url" :class="$style.action">
|
||||||
<MkButton :small="true" inline @click="playerEnabled = true">
|
<MkButton :small="true" inline @click="playerEnabled = true">
|
||||||
<i class="ti ti-player-play"></i> {{ i18n.ts.enablePlayer }}
|
<i class="ti ti-player-play"></i> {{ i18n.ts.enablePlayer }}
|
||||||
@@ -85,11 +91,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
|
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
|
||||||
import type { summaly } from 'summaly';
|
import type { summaly } from 'summaly';
|
||||||
|
import type * as Misskey from 'misskey-js';
|
||||||
import { url as local } from '@/config.js';
|
import { url as local } from '@/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
import { versatileLang } from '@/scripts/intl-const.js';
|
import { versatileLang } from '@/scripts/intl-const.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
@@ -126,7 +134,9 @@ const player = ref({
|
|||||||
} as SummalyResult['player']);
|
} as SummalyResult['player']);
|
||||||
const playerEnabled = ref(false);
|
const playerEnabled = ref(false);
|
||||||
const tweetId = ref<string | null>(null);
|
const tweetId = ref<string | null>(null);
|
||||||
const tweetExpanded = ref(props.detail);
|
const noteUrl = ref<string | null>(null);
|
||||||
|
const note = ref<Misskey.entities.Note | null>(null);
|
||||||
|
const postExpanded = ref(props.detail);
|
||||||
const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
|
const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
|
||||||
const tweetHeight = ref(150);
|
const tweetHeight = ref(150);
|
||||||
const unknownUrl = ref(false);
|
const unknownUrl = ref(false);
|
||||||
@@ -172,9 +182,40 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
|
|||||||
sitename.value = info.sitename;
|
sitename.value = info.sitename;
|
||||||
player.value = info.player;
|
player.value = info.player;
|
||||||
sensitive.value = info.sensitive ?? false;
|
sensitive.value = info.sensitive ?? false;
|
||||||
|
noteUrl.value = info.activityPub;
|
||||||
|
if (postExpanded.value) {
|
||||||
|
resolveNote();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function adjustTweetHeight(message: any) {
|
async function resolveNote(): Promise<void> {
|
||||||
|
if (note.value) {
|
||||||
|
// Reuse the data
|
||||||
|
postExpanded.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!noteUrl.value) {
|
||||||
|
// Note does not exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fetching.value = true;
|
||||||
|
const result = await os.api('ap/show', { uri: noteUrl.value });
|
||||||
|
if (result.type === 'Note') {
|
||||||
|
note.value = result.object;
|
||||||
|
postExpanded.value = true;
|
||||||
|
} else {
|
||||||
|
postExpanded.value = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Prevent repeated resolving
|
||||||
|
noteUrl.value = null;
|
||||||
|
fetching.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustTweetHeight(message: any): void {
|
||||||
if (message.origin !== 'https://platform.twitter.com') return;
|
if (message.origin !== 'https://platform.twitter.com') return;
|
||||||
const embed = message.data?.['twttr.embed'];
|
const embed = message.data?.['twttr.embed'];
|
||||||
if (embed?.method !== 'twttr.private.resize') return;
|
if (embed?.method !== 'twttr.private.resize') return;
|
||||||
|
@@ -80,6 +80,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>Verifymail.io API Auth Key</template>
|
<template #label>Verifymail.io API Auth Key</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
<MkSwitch v-model="enableTruemailApi" @update:modelValue="save">
|
||||||
|
<template #label>Use TrueMail API</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkInput v-model="truemailInstance" @update:modelValue="save">
|
||||||
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
|
<template #label>TrueMail API Instance</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="truemailAuthKey" @update:modelValue="save">
|
||||||
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
|
<template #label>TrueMail API Auth Key</template>
|
||||||
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
@@ -153,6 +164,9 @@ const enableIpLogging = ref<boolean>(false);
|
|||||||
const enableActiveEmailValidation = ref<boolean>(false);
|
const enableActiveEmailValidation = ref<boolean>(false);
|
||||||
const enableVerifymailApi = ref<boolean>(false);
|
const enableVerifymailApi = ref<boolean>(false);
|
||||||
const verifymailAuthKey = ref<string | null>(null);
|
const verifymailAuthKey = ref<string | null>(null);
|
||||||
|
const enableTruemailApi = ref<boolean>(false);
|
||||||
|
const truemailInstance = ref<string | null>(null);
|
||||||
|
const truemailAuthKey = ref<string | null>(null);
|
||||||
const bannedEmailDomains = ref<string>('');
|
const bannedEmailDomains = ref<string>('');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
@@ -194,6 +208,9 @@ function save() {
|
|||||||
enableActiveEmailValidation: enableActiveEmailValidation.value,
|
enableActiveEmailValidation: enableActiveEmailValidation.value,
|
||||||
enableVerifymailApi: enableVerifymailApi.value,
|
enableVerifymailApi: enableVerifymailApi.value,
|
||||||
verifymailAuthKey: verifymailAuthKey.value,
|
verifymailAuthKey: verifymailAuthKey.value,
|
||||||
|
enableTruemailApi: enableTruemailApi.value,
|
||||||
|
truemailInstance: truemailInstance.value,
|
||||||
|
truemailAuthKey: truemailAuthKey.value,
|
||||||
bannedEmailDomains: bannedEmailDomains.value.split('\n'),
|
bannedEmailDomains: bannedEmailDomains.value.split('\n'),
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
|
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div v-else-if="tab === 'search'">
|
<div v-else-if="tab === 'search'">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div>
|
<div>
|
||||||
<MkInput v-model="searchQuery">
|
<MkInput v-model="searchQuery" @enter="search()">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
||||||
|
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'search'">
|
<div v-if="tab === 'search'">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkRadios v-model="searchType" @update:modelValue="search()">
|
<MkRadios v-model="searchType" @update:modelValue="search()">
|
||||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkRadios v-model="searchOrigin" @update:modelValue="search()">
|
<MkRadios v-model="searchOrigin" @update:modelValue="search()">
|
||||||
|
@@ -3,10 +3,11 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, test, assert, afterEach } from 'vitest';
|
import { describe, test, assert, afterEach, beforeAll, vi } from 'vitest';
|
||||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||||
import './init';
|
import './init';
|
||||||
import type { summaly } from 'summaly';
|
import type { summaly } from 'summaly';
|
||||||
|
import type * as misskey from 'misskey-js';
|
||||||
import { components } from '@/components/index.js';
|
import { components } from '@/components/index.js';
|
||||||
import { directives } from '@/directives/index.js';
|
import { directives } from '@/directives/index.js';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
@@ -47,13 +48,18 @@ describe('MkUrlPreview', () => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAndOpenPreview = async (summary: Partial<SummalyResult>): Promise<HTMLIFrameElement | null> => {
|
const renderAndOpenPreview = async (summary: Partial<SummalyResult>): Promise<RenderResult> => {
|
||||||
const mkUrlPreview = await renderPreviewBy(summary);
|
const mkUrlPreview = await renderPreviewBy(summary);
|
||||||
const buttons = mkUrlPreview.getAllByRole('button');
|
const buttons = mkUrlPreview.getAllByRole('button');
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
// Wait for the click event to be fired
|
// Wait for the click event to be fired
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
||||||
|
return mkUrlPreview;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderAndOpenPreviewInIFrame = async (summary: Partial<SummalyResult>): Promise<HTMLIFrameElement | null> => {
|
||||||
|
const mkUrlPreview = await renderAndOpenPreview(summary);
|
||||||
return mkUrlPreview.container.querySelector('iframe');
|
return mkUrlPreview.container.querySelector('iframe');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,7 +91,7 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Having a player should setup the iframe', async () => {
|
test('Having a player should setup the iframe', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://example.local',
|
url: 'https://example.local',
|
||||||
player: {
|
player: {
|
||||||
url: 'https://example.local/player',
|
url: 'https://example.local/player',
|
||||||
@@ -103,7 +109,7 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Having a player with `allow` field should set permissions', async () => {
|
test('Having a player with `allow` field should set permissions', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://example.local',
|
url: 'https://example.local',
|
||||||
player: {
|
player: {
|
||||||
url: 'https://example.local/player',
|
url: 'https://example.local/player',
|
||||||
@@ -117,7 +123,7 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Having a player width should keep the fixed aspect ratio', async () => {
|
test('Having a player width should keep the fixed aspect ratio', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://example.local',
|
url: 'https://example.local',
|
||||||
player: {
|
player: {
|
||||||
url: 'https://example.local/player',
|
url: 'https://example.local/player',
|
||||||
@@ -131,7 +137,7 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Having a player width should keep the fixed height', async () => {
|
test('Having a player width should keep the fixed height', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://example.local',
|
url: 'https://example.local',
|
||||||
player: {
|
player: {
|
||||||
url: 'https://example.local/player',
|
url: 'https://example.local/player',
|
||||||
@@ -145,7 +151,7 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Loading a tweet in iframe', async () => {
|
test('Loading a tweet in iframe', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://twitter.com/i/web/status/1685072521782325249',
|
url: 'https://twitter.com/i/web/status/1685072521782325249',
|
||||||
});
|
});
|
||||||
assert.exists(iframe, 'iframe should exist');
|
assert.exists(iframe, 'iframe should exist');
|
||||||
@@ -154,11 +160,48 @@ describe('MkUrlPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Loading a post in iframe', async () => {
|
test('Loading a post in iframe', async () => {
|
||||||
const iframe = await renderAndOpenPreview({
|
const iframe = await renderAndOpenPreviewInIFrame({
|
||||||
url: 'https://x.com/i/web/status/1685072521782325249',
|
url: 'https://x.com/i/web/status/1685072521782325249',
|
||||||
});
|
});
|
||||||
assert.exists(iframe, 'iframe should exist');
|
assert.exists(iframe, 'iframe should exist');
|
||||||
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
|
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
|
||||||
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin');
|
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ActivityPub notes', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Preview a note', async () => {
|
||||||
|
vi.mock('@/os', () => {
|
||||||
|
return {
|
||||||
|
api(endpoint: string): unknown {
|
||||||
|
if (endpoint === 'ap/show') {
|
||||||
|
return {
|
||||||
|
type: 'Note',
|
||||||
|
object: {
|
||||||
|
text: 'Mizuki',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
user: {},
|
||||||
|
files: [] as misskey.entities.DriveFile[],
|
||||||
|
} as misskey.entities.Note,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected api call ${endpoint}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = 'https://example.local';
|
||||||
|
const renderResult = await renderAndOpenPreview({
|
||||||
|
url,
|
||||||
|
description: 'Misskey',
|
||||||
|
activityPub: url,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.notExists(renderResult.queryByText('Misskey'), 'Original description should disappear');
|
||||||
|
assert.exists(renderResult.queryByText('Mizuki'), 'ActivityPub fetch result should appear');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user