Merge branch 'develop' into beta
This commit is contained in:
commit
bd4b969489
91 changed files with 2853 additions and 529 deletions
|
@ -1,7 +1,8 @@
|
|||
pipeline:
|
||||
testCommit:
|
||||
image: node:latest
|
||||
image: node:alpine
|
||||
commands:
|
||||
- apk add --no-cache cargo python3 make g++
|
||||
- cp .config/ci.yml .config/default.yml
|
||||
- corepack enable
|
||||
- corepack prepare pnpm@latest --activate
|
||||
|
|
1378
locales/ca-ES.yml
1378
locales/ca-ES.yml
File diff suppressed because it is too large
Load diff
|
@ -68,8 +68,8 @@ import: "Import"
|
|||
export: "Export"
|
||||
files: "Files"
|
||||
download: "Download"
|
||||
driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? Posts\
|
||||
\ with this file attached will also be deleted."
|
||||
driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? It\
|
||||
\ will be removed from all posts that contain it as an attachment."
|
||||
unfollowConfirm: "Are you sure that you want to unfollow {name}?"
|
||||
exportRequested: "You've requested an export. This may take a while. It will be added\
|
||||
\ to your Drive once completed."
|
||||
|
@ -197,6 +197,7 @@ perHour: "Per Hour"
|
|||
perDay: "Per Day"
|
||||
stopActivityDelivery: "Stop sending activities"
|
||||
blockThisInstance: "Block this instance"
|
||||
silenceThisInstance: "Silence this instance"
|
||||
operations: "Operations"
|
||||
software: "Software"
|
||||
version: "Version"
|
||||
|
@ -218,10 +219,13 @@ clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote
|
|||
blockedInstances: "Blocked Instances"
|
||||
blockedInstancesDescription: "List the hostnames of the instances that you want to\
|
||||
\ block. Listed instances will no longer be able to communicate with this instance."
|
||||
silencedInstances: "Silenced Instances"
|
||||
silencedInstancesDescription: "List the hostnames of the instances that you want to\
|
||||
\ silence. Accounts in the listed instances are treated as \"Silenced\", can only make follow requests, and cannot mention local accounts if not followed. This will not affect the blocked instances."
|
||||
hiddenTags: "Hidden Hashtags"
|
||||
hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\
|
||||
\ to hide from trending and explore. Hidden hashtags are still discoverable via\
|
||||
\ other means."
|
||||
\ other means. Blocked instances are not affected even if listed here."
|
||||
muteAndBlock: "Mutes and Blocks"
|
||||
mutedUsers: "Muted users"
|
||||
blockedUsers: "Blocked users"
|
||||
|
@ -240,6 +244,7 @@ noCustomEmojis: "There are no emoji"
|
|||
noJobs: "There are no jobs"
|
||||
federating: "Federating"
|
||||
blocked: "Blocked"
|
||||
silenced: "Silenced"
|
||||
suspended: "Suspended"
|
||||
all: "All"
|
||||
subscribing: "Subscribing"
|
||||
|
@ -829,7 +834,7 @@ active: "Active"
|
|||
offline: "Offline"
|
||||
notRecommended: "Not recommended"
|
||||
botProtection: "Bot Protection"
|
||||
instanceBlocking: "Blocked Instances"
|
||||
instanceBlocking: "Federation Block/Silence"
|
||||
selectAccount: "Select account"
|
||||
switchAccount: "Switch account"
|
||||
enabled: "Enabled"
|
||||
|
@ -1042,7 +1047,7 @@ moveFromLabel: "Account you're moving from:"
|
|||
moveFromDescription: "This will set an alias of your old account so that you can move\
|
||||
\ from that account to this current one. Do this BEFORE moving from your older account.\
|
||||
\ Please enter the tag of the account formatted like @person@instance.com"
|
||||
migrationConfirm: "Are you absolutely sure you want to migrate your acccount to {account}?\
|
||||
migrationConfirm: "Are you absolutely sure you want to migrate your account to {account}?\
|
||||
\ Once you do this, you won't be able to reverse it, and you won't be able to use\
|
||||
\ your account normally again.\nAlso, please ensure that you've set this current\
|
||||
\ account as the account you're moving from."
|
||||
|
@ -1197,7 +1202,7 @@ _mfm:
|
|||
inlineMath: "Math (Inline)"
|
||||
inlineMathDescription: "Display math formulas (KaTeX) in-line"
|
||||
blockMath: "Math (Block)"
|
||||
blockMathDescription: "Display multi-line math formulas (KaTeX) in a block"
|
||||
blockMathDescription: "Display math formulas (KaTeX) in a block"
|
||||
quote: "Quote"
|
||||
quoteDescription: "Displays content as a quote."
|
||||
emoji: "Custom Emoji"
|
||||
|
|
182
locales/fi.yml
182
locales/fi.yml
|
@ -1,9 +1,10 @@
|
|||
_lang_: "Suomi"
|
||||
username: Käyttäjänimi
|
||||
fetchingAsApObject: Hae Fedeversestä
|
||||
gotIt: Selvä!
|
||||
cancel: Peruuta
|
||||
enterUsername: Anna käyttäjänimi
|
||||
renotedBy: Buustannut {käyttäjä}
|
||||
renotedBy: Buustannut {user}
|
||||
noNotes: Ei lähetyksiä
|
||||
noNotifications: Ei ilmoituksia
|
||||
instance: Instanssi
|
||||
|
@ -41,3 +42,182 @@ favorite: Lisää kirjanmerkkeihin
|
|||
copyContent: Kopioi sisältö
|
||||
deleteAndEdit: Poista ja muokkaa
|
||||
copyLink: Kopioi linkki
|
||||
makeFollowManuallyApprove: Seuraajapyyntö vaatii hyväksymistä
|
||||
follow: Seuraa
|
||||
pinned: Kiinnitä profiiliin
|
||||
followRequestPending: Seuraajapyyntö odottaa
|
||||
you: Sinä
|
||||
unrenote: Peruuta buustaus
|
||||
reaction: Reaktiot
|
||||
reactionSettingDescription2: Vedä uudelleenjärjestelläksesi, napsauta poistaaksesi,
|
||||
paina "+" lisätäksesi.
|
||||
attachCancel: Poista liite
|
||||
enterFileName: Anna tiedostonimi
|
||||
mute: Hiljennä
|
||||
unmute: Poista hiljennys
|
||||
headlineMisskey: Avoimen lähdekoodin, hajautettu sosiaalisen median alusta, joka on
|
||||
ikuisesti ilmainen! 🚀
|
||||
monthAndDay: '{day}/{month}'
|
||||
deleteAndEditConfirm: Oletko varma, että haluat poistaa tämän lähetyksen ja muokata
|
||||
sitä? Menetät kaikki reaktiot, buustaukset ja vastaukset lähetyksestäsi.
|
||||
addToList: Lisää listaan
|
||||
sendMessage: Lähetä viesti
|
||||
reply: Vastaa
|
||||
loadMore: Lataa enemmän
|
||||
showMore: Näytä enemmän
|
||||
receiveFollowRequest: Seuraajapyyntö vastaanotettu
|
||||
followRequestAccepted: Seuraajapyyntö hyväksytty
|
||||
mentions: Maininnat
|
||||
importAndExport: Tuo/Vie Tietosisältö
|
||||
import: Tuo
|
||||
export: Vie
|
||||
files: Tiedostot
|
||||
download: Lataa
|
||||
unfollowConfirm: Oletko varma, ettet halua seurata enää käyttäjää {name}?
|
||||
noLists: Sinulla ei ole listoja
|
||||
note: Lähetys
|
||||
notes: Lähetykset
|
||||
following: Seuraa
|
||||
createList: Luo lista
|
||||
manageLists: Hallitse listoja
|
||||
error: Virhe
|
||||
somethingHappened: On tapahtunut virhe
|
||||
retry: Yritä uudelleen
|
||||
pageLoadError: Virhe ladattaessa sivua.
|
||||
serverIsDead: Tämä palvelin ei vastaa. Yritä hetken kuluttua uudelleen.
|
||||
youShouldUpgradeClient: Nähdäksesi tämän sivun, virkistä päivittääksesi asiakasohjelmasi.
|
||||
privacy: Tietosuoja
|
||||
defaultNoteVisibility: Oletusnäkyvyys
|
||||
followRequest: Seuraajapyyntö
|
||||
followRequests: Seuraajapyynnöt
|
||||
unfollow: Poista seuraaminen
|
||||
enterEmoji: Syötä emoji
|
||||
renote: Buustaa
|
||||
renoted: Buustattu.
|
||||
cantRenote: Tätä lähetystä ei voi buustata.
|
||||
cantReRenote: Buustausta ei voi buustata.
|
||||
quote: Lainaus
|
||||
pinnedNote: Lukittu lähetys
|
||||
clickToShow: Napsauta nähdäksesi
|
||||
sensitive: Herkkää sisältöä (NSFW)
|
||||
add: Lisää
|
||||
enableEmojiReactions: Ota käyttöön emoji-reaktiot
|
||||
showEmojisInReactionNotifications: Näytä emojit reaktioilmoituksissa
|
||||
reactionSetting: Reaktiot näytettäväksi reaktiovalitsimessa
|
||||
rememberNoteVisibility: Muista lähetyksen näkyvyysasetukset
|
||||
markAsSensitive: Merkitse herkäksi sisällöksi (NSFW)
|
||||
unmarkAsSensitive: Poista merkintä herkkää sisältöä (NSFW)
|
||||
renoteMute: Hiljennä buustit
|
||||
renoteUnmute: Poista buustien hiljennys
|
||||
block: Estä
|
||||
unblock: Poista esto
|
||||
unsuspend: Poista keskeytys
|
||||
suspend: Keskeytys
|
||||
blockConfirm: Oletko varma, että haluat estää tämän tilin?
|
||||
unblockConfirm: Oletko varma, että haluat poistaa tämän tilin eston?
|
||||
selectAntenna: Valitse antenni
|
||||
selectWidget: Valitse vimpain
|
||||
editWidgets: Muokkaa vimpaimia
|
||||
editWidgetsExit: Valmis
|
||||
emoji: Emoji
|
||||
emojis: Emojit
|
||||
emojiName: Emojin nimi
|
||||
emojiUrl: Emojin URL-linkki
|
||||
cacheRemoteFiles: Taltioi etätiedostot välimuistiin
|
||||
flagAsBot: Merkitse tili botiksi
|
||||
flagAsBotDescription: Ota tämä vaihtoehto käyttöön, jos tätä tiliä ohjaa ohjelma.
|
||||
Jos se on käytössä, se toimii lippuna muille kehittäjille, jotta estetään loputtomat
|
||||
vuorovaikutusketjut muiden bottien kanssa ja säädetään Calckeyn sisäiset järjestelmät
|
||||
käsittelemään tätä tiliä botina.
|
||||
flagAsCat: Oletko kissa? 🐱
|
||||
flagAsCatDescription: Saat kissan korvat ja puhut kuin kissa!
|
||||
flagSpeakAsCat: Puhu kuin kissa
|
||||
flagShowTimelineReplies: Näytä vastaukset aikajanalla
|
||||
addAccount: Lisää tili
|
||||
loginFailed: Kirjautuminen epäonnistui
|
||||
showOnRemote: Katsele etäinstanssilla
|
||||
general: Yleistä
|
||||
accountMoved: 'Käyttäjä on muuttanut uuteen tiliin:'
|
||||
wallpaper: Taustakuva
|
||||
setWallpaper: Aseta taustakuva
|
||||
searchWith: 'Etsi: {q}'
|
||||
youHaveNoLists: Sinulla ei ole listoja
|
||||
followConfirm: Oletko varma, että haluat seurata käyttäjää {name}?
|
||||
host: Isäntä
|
||||
selectUser: Valitse käyttäjä
|
||||
annotation: Kommentit
|
||||
registeredAt: Rekisteröity
|
||||
latestRequestReceivedAt: Viimeisin pyyntö vastaanotettu
|
||||
latestRequestSentAt: Viimeisin pyyntö lähetetty
|
||||
storageUsage: Tallennustilan käyttö
|
||||
charts: Kaaviot
|
||||
stopActivityDelivery: Lopeta toimintojen lähettäminen
|
||||
blockThisInstance: Estä tämä instanssi
|
||||
operations: Toiminnot
|
||||
metadata: Metatieto
|
||||
monitor: Seuranta
|
||||
jobQueue: Työjono
|
||||
cpuAndMemory: Prosessori ja muisti
|
||||
network: Verkko
|
||||
disk: Levy
|
||||
clearCachedFiles: Tyhjennä välimuisti
|
||||
clearCachedFilesConfirm: Oletko varma, että haluat tyhjentää kaikki välimuistiin tallennetut
|
||||
etätiedostot?
|
||||
blockedInstances: Estetyt instanssit
|
||||
hiddenTags: Piilotetut asiatunnisteet
|
||||
mention: Maininta
|
||||
copyUsername: Kopioi käyttäjänimi
|
||||
searchUser: Etsi käyttäjää
|
||||
showLess: Sulje
|
||||
youGotNewFollower: seurasi sinua
|
||||
directNotes: Yksityisviestit
|
||||
driveFileDeleteConfirm: Oletko varma, että haluat poistaa tiedoston " {name}"? Lähetykset,
|
||||
jotka sisältyvät tiedostoon, poistuvat myös.
|
||||
importRequested: Olet pyytänyt viemistä. Tämä voi viedä hetken.
|
||||
exportRequested: Olet pyytänyt tuomista. Tämä voi viedä hetken. Se lisätään asemaan
|
||||
kun tuonti valmistuu.
|
||||
lists: Listat
|
||||
followers: Seuraajat
|
||||
followsYou: Seuraa sinua
|
||||
pageLoadErrorDescription: Tämä yleensä johtuu verkkovirheistä tai selaimen välimuistista.
|
||||
Kokeile tyhjentämällä välimuisti ja yritä sitten hetken kuluttua uudelleen.
|
||||
enterListName: Anna listalle nimi
|
||||
withNFiles: '{n} tiedosto(t)'
|
||||
instanceInfo: Instanssin tiedot
|
||||
clearQueue: Tyhjennä jono
|
||||
suspendConfirm: Oletko varma, että haluat keskeyttää tämän tilin?
|
||||
unsuspendConfirm: Oletko varma, että haluat poistaa tämän tilin keskeytyksen?
|
||||
selectList: Valitse lista
|
||||
customEmojis: Kustomoitu Emoji
|
||||
addEmoji: Lisää
|
||||
settingGuide: Suositellut asetukset
|
||||
cacheRemoteFilesDescription: Kun tämä asetus ei ole käytössä, etätiedostot on ladattu
|
||||
suoraan etäinstanssilta. Asetuksen poistaminen käytöstä vähentää tallennustilan
|
||||
käyttöä, mutta lisää verkkoliikennettä kun pienoiskuvat eivät muodostu.
|
||||
flagSpeakAsCatDescription: Lähetyksesi nyanifioidaan, kun olet kissatilassa
|
||||
flagShowTimelineRepliesDescription: Näyttää käyttäjien vastaukset muiden käyttäjien
|
||||
lähetyksiin aikajanalla, jos se on päällä.
|
||||
autoAcceptFollowed: Automaattisesti hyväksy seuraamispyynnöt käyttäjiltä, joita seuraat
|
||||
perHour: Tunnissa
|
||||
removeWallpaper: Poista taustakuva
|
||||
recipient: Vastaanottaja(t)
|
||||
federation: Federaatio
|
||||
software: Ohjelmisto
|
||||
proxyAccount: Proxy-tili
|
||||
proxyAccountDescription: Välitystili (Proxy-tili) on tili, joka toimii käyttäjien
|
||||
etäseuraajana tietyin edellytyksin. Kun käyttäjä esimerkiksi lisää etäkäyttäjän
|
||||
luetteloon, etäkäyttäjän toimintaa ei toimiteta instanssiin, jos yksikään paikallinen
|
||||
käyttäjä ei seuraa kyseistä käyttäjää, joten välitystili seuraa sen sijaan.
|
||||
latestStatus: Viimeisin tila
|
||||
selectInstance: Valitse instanssi
|
||||
instances: Instanssit
|
||||
perDay: Päivässä
|
||||
version: Versio
|
||||
statistics: Tilastot
|
||||
clearQueueConfirmTitle: Oletko varma, että haluat tyhjentää jonon?
|
||||
introMisskey: Tervetuloa! Calckey on avoimen lähdekoodin, hajautettu sosiaalisen median
|
||||
alusta, joka on ikuisesti ilmainen! 🚀
|
||||
clearQueueConfirmText: Mitkään välittämättömät lähetykset, jotka ovat jonossa, eivät
|
||||
federoidu. Yleensä tätä toimintoa ei tarvita.
|
||||
blockedInstancesDescription: Lista instanssien isäntänimistä, jotka haluat estää.
|
||||
Listatut instanssit eivät kykene kommunikoimaan enää tämän instanssin kanssa.
|
||||
|
|
|
@ -183,6 +183,7 @@ perHour: "1時間ごと"
|
|||
perDay: "1日ごと"
|
||||
stopActivityDelivery: "アクティビティの配送を停止"
|
||||
blockThisInstance: "このインスタンスをブロック"
|
||||
silenceThisInstance: "このインスタンスをサイレンス"
|
||||
operations: "操作"
|
||||
software: "ソフトウェア"
|
||||
version: "バージョン"
|
||||
|
@ -202,6 +203,8 @@ clearCachedFiles: "キャッシュをクリア"
|
|||
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
|
||||
blockedInstances: "ブロックしたインスタンス"
|
||||
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。"
|
||||
silencedInstances: "サイレンスしたインスタンス"
|
||||
silencedInstancesDescription: "サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
|
||||
muteAndBlock: "ミュートとブロック"
|
||||
mutedUsers: "ミュートしたユーザー"
|
||||
blockedUsers: "ブロックしたユーザー"
|
||||
|
@ -220,6 +223,7 @@ noCustomEmojis: "絵文字はありません"
|
|||
noJobs: "ジョブはありません"
|
||||
federating: "連合中"
|
||||
blocked: "ブロック中"
|
||||
silenced: "サイレンス中"
|
||||
suspended: "配信停止"
|
||||
all: "全て"
|
||||
subscribing: "購読中"
|
||||
|
@ -768,7 +772,7 @@ active: "アクティブ"
|
|||
offline: "オフライン"
|
||||
notRecommended: "非推奨"
|
||||
botProtection: "Botプロテクション"
|
||||
instanceBlocking: "インスタンスブロック"
|
||||
instanceBlocking: "連合ブロック・サイレンス"
|
||||
selectAccount: "アカウントを選択"
|
||||
switchAccount: "アカウントを切り替え"
|
||||
enabled: "有効"
|
||||
|
@ -1079,7 +1083,7 @@ _mfm:
|
|||
inlineMath: "数式(インライン)"
|
||||
inlineMathDescription: "数式(KaTeX)をインラインで表示します。"
|
||||
blockMath: "数式(ブロック)"
|
||||
blockMathDescription: "複数行の数式(KaTeX)をブロックで表示します。"
|
||||
blockMathDescription: "数式(KaTeX)をブロックで表示します。"
|
||||
quote: "引用"
|
||||
quoteDescription: "内容が引用であることを示せます。"
|
||||
emoji: "カスタム絵文字"
|
||||
|
@ -1120,6 +1124,7 @@ _mfm:
|
|||
rotateDescription: "指定した角度で回転させます。"
|
||||
plain: "プレーン"
|
||||
plainDescription: "内側の構文を全て無効にします。"
|
||||
position: 位置
|
||||
_instanceTicker:
|
||||
none: "表示しない"
|
||||
remote: "リモートユーザーに表示"
|
||||
|
@ -1128,7 +1133,7 @@ _serverDisconnectedBehavior:
|
|||
reload: "自動でリロード"
|
||||
dialog: "ダイアログで警告"
|
||||
quiet: "控えめに警告"
|
||||
nothing: "何も起こらない"
|
||||
nothing: "何もしない"
|
||||
_channel:
|
||||
create: "チャンネルを作成"
|
||||
edit: "チャンネルを編集"
|
||||
|
|
|
@ -1009,9 +1009,9 @@ _mfm:
|
|||
blockCode: "代码(块)"
|
||||
blockCodeDescription: "语法高亮显示整块程序代码。"
|
||||
inlineMath: "数学公式(内嵌)"
|
||||
inlineMathDescription: "显示内嵌的KaTex公式。"
|
||||
inlineMathDescription: "显示内嵌的KaTeX公式。"
|
||||
blockMath: "数学公式(块)"
|
||||
blockMathDescription: "显示整块的多行KaTex数学公式。"
|
||||
blockMathDescription: "显示整块的KaTeX数学公式。"
|
||||
quote: "引用"
|
||||
quoteDescription: "可以用来表示引用的内容。"
|
||||
emoji: "自定义表情符号"
|
||||
|
|
|
@ -1012,9 +1012,9 @@ _mfm:
|
|||
blockCode: "程式碼(區塊)"
|
||||
blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。"
|
||||
inlineMath: "數學公式(內嵌)"
|
||||
inlineMathDescription: "顯示內嵌的KaTex數學公式。"
|
||||
inlineMathDescription: "顯示內嵌的KaTeX數學公式。"
|
||||
blockMath: "數學公式(方塊)"
|
||||
blockMathDescription: "以區塊顯示複數行的KaTex數學式。"
|
||||
blockMathDescription: "以區塊顯示KaTeX數學式。"
|
||||
quote: "引用"
|
||||
quoteDescription: "可以用來表示引用的内容。"
|
||||
emoji: "自訂表情符號"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "calckey",
|
||||
"version": "13.2.0-beta9h",
|
||||
"version": "14.0.0-rc",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 473 KiB |
23
packages/backend/migration/1682777547198-LibreTranslate.js
Normal file
23
packages/backend/migration/1682777547198-LibreTranslate.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export class LibreTranslate1682777547198 {
|
||||
name = "LibreTranslate1682777547198";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "meta"
|
||||
ADD "libreTranslateApiUrl" character varying(512)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "meta"
|
||||
ADD "libreTranslateApiKey" character varying(128)
|
||||
`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey"
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "meta" DROP COLUMN "libreTranslateApiUrl"
|
||||
`);
|
||||
}
|
||||
}
|
13
packages/backend/migration/1682891890317-InstanceSilence.js
Normal file
13
packages/backend/migration/1682891890317-InstanceSilence.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export class InstanceSilence1682891890317 {
|
||||
name = "InstanceSilence1682891890317";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "meta" ADD "silencedHosts" character varying(256) array NOT NULL DEFAULT '{}'`,
|
||||
);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`);
|
||||
}
|
||||
}
|
|
@ -89,6 +89,11 @@ export type Source = {
|
|||
authKey?: string;
|
||||
isPro?: boolean;
|
||||
};
|
||||
libreTranslate: {
|
||||
managed?: boolean;
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
email: {
|
||||
managed?: boolean;
|
||||
address?: string;
|
||||
|
|
|
@ -18,3 +18,21 @@ export async function shouldBlockInstance(
|
|||
(blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a specific host (punycoded) should be limited.
|
||||
*
|
||||
* @param host punycoded instance host
|
||||
* @param meta a resolved Meta table
|
||||
* @returns whether the given host should be limited
|
||||
*/
|
||||
export async function shouldSilenceInstance(
|
||||
host: Instance["host"],
|
||||
meta?: Meta,
|
||||
): Promise<boolean> {
|
||||
const { silencedHosts } = meta ?? (await fetchMeta());
|
||||
return silencedHosts.some(
|
||||
(silencedHost) =>
|
||||
host === silencedHost || host.endsWith(`.${silencedHost}`),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -97,6 +97,11 @@ export class Meta {
|
|||
})
|
||||
public blockedHosts: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}',
|
||||
})
|
||||
public silencedHosts: string[];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
@ -386,6 +391,18 @@ export class Meta {
|
|||
})
|
||||
public deeplIsPro: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
nullable: true,
|
||||
})
|
||||
public libreTranslateApiUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true,
|
||||
})
|
||||
public libreTranslateApiKey: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
nullable: true,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { db } from "@/db/postgre.js";
|
||||
import { Instance } from "@/models/entities/instance.js";
|
||||
import type { Packed } from "@/misc/schema.js";
|
||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
||||
import {
|
||||
shouldBlockInstance,
|
||||
shouldSilenceInstance,
|
||||
} from "@/misc/should-block-instance.js";
|
||||
|
||||
export const InstanceRepository = db.getRepository(Instance).extend({
|
||||
async pack(instance: Instance): Promise<Packed<"FederationInstance">> {
|
||||
const meta = await fetchMeta();
|
||||
return {
|
||||
id: instance.id,
|
||||
caughtAt: instance.caughtAt.toISOString(),
|
||||
|
@ -22,6 +23,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({
|
|||
isNotResponding: instance.isNotResponding,
|
||||
isSuspended: instance.isSuspended,
|
||||
isBlocked: await shouldBlockInstance(instance.host),
|
||||
isSilenced: await shouldSilenceInstance(instance.host),
|
||||
softwareName: instance.softwareName,
|
||||
softwareVersion: instance.softwareVersion,
|
||||
openRegistrations: instance.openRegistrations,
|
||||
|
|
|
@ -68,6 +68,11 @@ export const packedFederationInstanceSchema = {
|
|||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
isSilenced: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
softwareName: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
|
|
|
@ -10,7 +10,13 @@ import { renderPerson } from "@/remote/activitypub/renderer/person.js";
|
|||
import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
|
||||
import { inbox as processInbox } from "@/queue/index.js";
|
||||
import { isSelfHost, toPuny } from "@/misc/convert-host.js";
|
||||
import { Notes, Users, Emojis, NoteReactions } from "@/models/index.js";
|
||||
import {
|
||||
Notes,
|
||||
Users,
|
||||
Emojis,
|
||||
NoteReactions,
|
||||
FollowRequests,
|
||||
} from "@/models/index.js";
|
||||
import type { ILocalUser, User } from "@/models/entities/user.js";
|
||||
import { renderLike } from "@/remote/activitypub/renderer/like.js";
|
||||
import { getUserKeypair } from "@/misc/keypair-store.js";
|
||||
|
@ -330,22 +336,68 @@ router.get("/likes/:like", async (ctx) => {
|
|||
});
|
||||
|
||||
// follow
|
||||
router.get("/follows/:follower/:followee", async (ctx) => {
|
||||
router.get(
|
||||
"/follows/:follower/:followee",
|
||||
async (ctx: Router.RouterContext) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
// This may be used before the follow is completed, so we do not
|
||||
// check if the following exists.
|
||||
|
||||
const [follower, followee] = await Promise.all([
|
||||
Users.findOneBy({
|
||||
id: ctx.params.follower,
|
||||
host: IsNull(),
|
||||
}),
|
||||
Users.findOneBy({
|
||||
id: ctx.params.followee,
|
||||
host: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (follower == null || followee == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = renderActivity(renderFollow(follower, followee));
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
|
||||
} else {
|
||||
ctx.set("Cache-Control", "public, max-age=180");
|
||||
}
|
||||
setResponseType(ctx);
|
||||
},
|
||||
);
|
||||
|
||||
// follow request
|
||||
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
|
||||
const verify = await checkFetch(ctx.req);
|
||||
if (verify !== 200) {
|
||||
ctx.status = verify;
|
||||
return;
|
||||
}
|
||||
// This may be used before the follow is completed, so we do not
|
||||
// check if the following exists.
|
||||
|
||||
const followRequest = await FollowRequests.findOneBy({
|
||||
id: ctx.params.followRequestId,
|
||||
});
|
||||
|
||||
if (followRequest == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
const [follower, followee] = await Promise.all([
|
||||
Users.findOneBy({
|
||||
id: ctx.params.follower,
|
||||
id: followRequest.followerId,
|
||||
host: IsNull(),
|
||||
}),
|
||||
Users.findOneBy({
|
||||
id: ctx.params.followee,
|
||||
id: followRequest.followeeId,
|
||||
host: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
|
@ -355,13 +407,13 @@ router.get("/follows/:follower/:followee", async (ctx) => {
|
|||
return;
|
||||
}
|
||||
|
||||
ctx.body = renderActivity(renderFollow(follower, followee));
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
|
||||
} else {
|
||||
ctx.set("Cache-Control", "public, max-age=180");
|
||||
}
|
||||
ctx.body = renderActivity(renderFollow(follower, followee));
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
|
|
|
@ -30,6 +30,17 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
set.deeplIsPro = config.deepl.isPro;
|
||||
}
|
||||
}
|
||||
if (
|
||||
config.libreTranslate.managed != null &&
|
||||
config.libreTranslate.managed === true
|
||||
) {
|
||||
if (typeof config.libreTranslate.apiUrl === "string") {
|
||||
set.libreTranslateApiUrl = config.libreTranslate.apiUrl;
|
||||
}
|
||||
if (typeof config.libreTranslate.apiKey === "string") {
|
||||
set.libreTranslateApiKey = config.libreTranslate.apiKey;
|
||||
}
|
||||
}
|
||||
if (config.email.managed != null && config.email.managed === true) {
|
||||
set.enableEmail = true;
|
||||
if (typeof config.email.address === "string") {
|
||||
|
|
|
@ -259,6 +259,16 @@ export const meta = {
|
|||
nullable: false,
|
||||
},
|
||||
},
|
||||
silencedHosts: {
|
||||
type: "array",
|
||||
optional: true,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
},
|
||||
allowedHosts: {
|
||||
type: "array",
|
||||
optional: true,
|
||||
|
@ -512,7 +522,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
enableGithubIntegration: instance.enableGithubIntegration,
|
||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
translatorAvailable:
|
||||
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
|
||||
pinnedPages: instance.pinnedPages,
|
||||
pinnedClipId: instance.pinnedClipId,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
|
@ -523,6 +534,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
customSplashIcons: instance.customSplashIcons,
|
||||
hiddenTags: instance.hiddenTags,
|
||||
blockedHosts: instance.blockedHosts,
|
||||
silencedHosts: instance.silencedHosts,
|
||||
allowedHosts: instance.allowedHosts,
|
||||
privateMode: instance.privateMode,
|
||||
secureMode: instance.secureMode,
|
||||
|
@ -564,6 +576,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
||||
deeplAuthKey: instance.deeplAuthKey,
|
||||
deeplIsPro: instance.deeplIsPro,
|
||||
libreTranslateApiUrl: instance.libreTranslateApiUrl,
|
||||
libreTranslateApiKey: instance.libreTranslateApiKey,
|
||||
enableIpLogging: instance.enableIpLogging,
|
||||
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
||||
};
|
||||
|
|
|
@ -61,6 +61,13 @@ export const paramDef = {
|
|||
type: "string",
|
||||
},
|
||||
},
|
||||
silencedHosts: {
|
||||
type: "array",
|
||||
nullable: true,
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
allowedHosts: {
|
||||
type: "array",
|
||||
nullable: true,
|
||||
|
@ -124,6 +131,8 @@ export const paramDef = {
|
|||
summalyProxy: { type: "string", nullable: true },
|
||||
deeplAuthKey: { type: "string", nullable: true },
|
||||
deeplIsPro: { type: "boolean" },
|
||||
libreTranslateApiUrl: { type: "string", nullable: true },
|
||||
libreTranslateApiKey: { type: "string", nullable: true },
|
||||
enableTwitterIntegration: { type: "boolean" },
|
||||
twitterConsumerKey: { type: "string", nullable: true },
|
||||
twitterConsumerSecret: { type: "string", nullable: true },
|
||||
|
@ -217,6 +226,15 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.silencedHosts)) {
|
||||
let lastValue = "";
|
||||
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
|
||||
const lv = lastValue;
|
||||
lastValue = h;
|
||||
return h !== "" && h !== lv;
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.themeColor !== undefined) {
|
||||
set.themeColor = ps.themeColor;
|
||||
}
|
||||
|
@ -515,6 +533,22 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
set.deeplIsPro = ps.deeplIsPro;
|
||||
}
|
||||
|
||||
if (ps.libreTranslateApiUrl !== undefined) {
|
||||
if (ps.libreTranslateApiUrl === "") {
|
||||
set.libreTranslateApiUrl = null;
|
||||
} else {
|
||||
set.libreTranslateApiUrl = ps.libreTranslateApiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.libreTranslateApiKey !== undefined) {
|
||||
if (ps.libreTranslateApiKey === "") {
|
||||
set.libreTranslateApiKey = null;
|
||||
} else {
|
||||
set.libreTranslateApiKey = ps.libreTranslateApiKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.enableIpLogging !== undefined) {
|
||||
set.enableIpLogging = ps.enableIpLogging;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ export const paramDef = {
|
|||
notResponding: { type: "boolean", nullable: true },
|
||||
suspended: { type: "boolean", nullable: true },
|
||||
federating: { type: "boolean", nullable: true },
|
||||
silenced: { type: "boolean", nullable: true },
|
||||
subscribing: { type: "boolean", nullable: true },
|
||||
publishing: { type: "boolean", nullable: true },
|
||||
limit: { type: "integer", minimum: 1, maximum: 100, default: 30 },
|
||||
|
@ -115,6 +116,22 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (typeof ps.silenced === "boolean") {
|
||||
const meta = await fetchMeta(true);
|
||||
if (ps.silenced) {
|
||||
if (meta.silencedHosts.length === 0) {
|
||||
return [];
|
||||
}
|
||||
query.andWhere("instance.host IN (:...silences)", {
|
||||
silences: meta.silencedHosts,
|
||||
});
|
||||
} else if (meta.silencedHosts.length > 0) {
|
||||
query.andWhere("instance.host NOT IN (:...silences)", {
|
||||
silences: meta.silencedHosts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof ps.notResponding === "boolean") {
|
||||
if (ps.notResponding) {
|
||||
query.andWhere("instance.isNotResponding = TRUE");
|
||||
|
|
|
@ -482,7 +482,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
enableServiceWorker: instance.enableServiceWorker,
|
||||
|
||||
translatorAvailable: instance.deeplAuthKey != null,
|
||||
translatorAvailable:
|
||||
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
|
||||
defaultReaction: instance.defaultReaction,
|
||||
|
||||
...(ps.detail
|
||||
|
|
|
@ -51,15 +51,54 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
|
||||
const instance = await fetchMeta();
|
||||
|
||||
if (instance.deeplAuthKey == null) {
|
||||
if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
|
||||
return 204; // TODO: 良い感じのエラー返す
|
||||
}
|
||||
|
||||
let targetLang = ps.targetLang;
|
||||
if (targetLang.includes("-")) targetLang = targetLang.split("-")[0];
|
||||
|
||||
if (instance.libreTranslateApiUrl != null) {
|
||||
const jsonBody = {
|
||||
q: note.text,
|
||||
source: "auto",
|
||||
target: targetLang,
|
||||
format: "text",
|
||||
api_key: instance.libreTranslateApiKey ?? "",
|
||||
};
|
||||
|
||||
const url = new URL(instance.libreTranslateApiUrl);
|
||||
if (url.pathname.endsWith("/")) {
|
||||
url.pathname = url.pathname.slice(0, -1);
|
||||
}
|
||||
if (!url.pathname.endsWith("/translate")) {
|
||||
url.pathname += "/translate";
|
||||
}
|
||||
const res = await fetch(url.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(jsonBody),
|
||||
agent: getAgentByUrl,
|
||||
});
|
||||
|
||||
const json = (await res.json()) as {
|
||||
detectedLanguage?: {
|
||||
confidence: number;
|
||||
language: string;
|
||||
};
|
||||
translatedText: string;
|
||||
};
|
||||
|
||||
return {
|
||||
sourceLang: json.detectedLanguage?.language,
|
||||
text: json.translatedText,
|
||||
};
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append("auth_key", instance.deeplAuthKey);
|
||||
params.append("auth_key", instance.deeplAuthKey ?? "");
|
||||
params.append("text", note.text);
|
||||
params.append("target_lang", targetLang);
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
convertId,
|
||||
IdConvertType as IdType,
|
||||
} from "../../../native-utils/built/index.js";
|
||||
import { convertAttachment } from "./mastodon/converters.js";
|
||||
|
||||
// re-export native rust id conversion (function and enum)
|
||||
export { IdType, convertId };
|
||||
|
@ -93,7 +94,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
|
|||
return;
|
||||
}
|
||||
const data = await client.uploadMedia(multipartData);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertAttachment(data.data as Entity.Attachment);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -112,7 +113,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => {
|
|||
return;
|
||||
}
|
||||
const data = await client.uploadMedia(multipartData);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertAttachment(data.data as Entity.Attachment);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
|
|
@ -8,6 +8,8 @@ import { apiTimelineMastodon } from "./endpoints/timeline.js";
|
|||
import { apiNotificationsMastodon } from "./endpoints/notifications.js";
|
||||
import { apiSearchMastodon } from "./endpoints/search.js";
|
||||
import { getInstance } from "./endpoints/meta.js";
|
||||
import { convertAnnouncement, convertFilter } from "./converters.js";
|
||||
import { convertId, IdType } from "../index.js";
|
||||
|
||||
export function getClient(
|
||||
BASE_URL: string,
|
||||
|
@ -68,7 +70,9 @@ export function apiMastodonCompatible(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getInstanceAnnouncements();
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.map((announcement) =>
|
||||
convertAnnouncement(announcement),
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -83,7 +87,9 @@ export function apiMastodonCompatible(router: Router): void {
|
|||
const accessTokens = ctx.request.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.dismissInstanceAnnouncement(ctx.params.id);
|
||||
const data = await client.dismissInstanceAnnouncement(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
@ -100,7 +106,7 @@ export function apiMastodonCompatible(router: Router): void {
|
|||
// displayed without being logged in
|
||||
try {
|
||||
const data = await client.getFilters();
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.map((filter) => convertFilter(filter));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
|
61
packages/backend/src/server/api/mastodon/converters.ts
Normal file
61
packages/backend/src/server/api/mastodon/converters.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { Entity } from "@calckey/megalodon";
|
||||
import { convertId, IdType } from "../index.js";
|
||||
|
||||
function simpleConvert(data: any) {
|
||||
data.id = convertId(data.id, IdType.MastodonId);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function convertAccount(account: Entity.Account) {
|
||||
return simpleConvert(account);
|
||||
}
|
||||
export function convertAnnouncement(announcement: Entity.Announcement) {
|
||||
return simpleConvert(announcement);
|
||||
}
|
||||
export function convertAttachment(attachment: Entity.Attachment) {
|
||||
return simpleConvert(attachment);
|
||||
}
|
||||
export function convertFilter(filter: Entity.Filter) {
|
||||
return simpleConvert(filter);
|
||||
}
|
||||
export function convertList(list: Entity.List) {
|
||||
return simpleConvert(list);
|
||||
}
|
||||
|
||||
export function convertNotification(notification: Entity.Notification) {
|
||||
notification.account = convertAccount(notification.account);
|
||||
notification.id = convertId(notification.id, IdType.MastodonId);
|
||||
if (notification.status)
|
||||
notification.status = convertStatus(notification.status);
|
||||
return notification;
|
||||
}
|
||||
|
||||
export function convertPoll(poll: Entity.Poll) {
|
||||
return simpleConvert(poll);
|
||||
}
|
||||
export function convertRelationship(relationship: Entity.Relationship) {
|
||||
return simpleConvert(relationship);
|
||||
}
|
||||
|
||||
export function convertStatus(status: Entity.Status) {
|
||||
status.account = convertAccount(status.account);
|
||||
status.id = convertId(status.id, IdType.MastodonId);
|
||||
if (status.in_reply_to_account_id)
|
||||
status.in_reply_to_account_id = convertId(
|
||||
status.in_reply_to_account_id,
|
||||
IdType.MastodonId,
|
||||
);
|
||||
if (status.in_reply_to_id)
|
||||
status.in_reply_to_id = convertId(status.in_reply_to_id, IdType.MastodonId);
|
||||
status.media_attachments = status.media_attachments.map((attachment) =>
|
||||
convertAttachment(attachment),
|
||||
);
|
||||
status.mentions = status.mentions.map((mention) => ({
|
||||
...mention,
|
||||
id: convertId(mention.id, IdType.MastodonId),
|
||||
}));
|
||||
if (status.poll) status.poll = convertPoll(status.poll);
|
||||
if (status.reblog) status.reblog = convertStatus(status.reblog);
|
||||
|
||||
return status;
|
||||
}
|
|
@ -3,8 +3,14 @@ import { resolveUser } from "@/remote/resolve-user.js";
|
|||
import Router from "@koa/router";
|
||||
import { FindOptionsWhere, IsNull } from "typeorm";
|
||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||
import { argsToBools, limitToInt } from "./timeline.js";
|
||||
import { argsToBools, convertTimelinesArgsId, limitToInt } from "./timeline.js";
|
||||
import { convertId, IdType } from "../../index.js";
|
||||
import {
|
||||
convertAccount,
|
||||
convertList,
|
||||
convertRelationship,
|
||||
convertStatus,
|
||||
} from "../converters.js";
|
||||
|
||||
const relationshipModel = {
|
||||
id: "",
|
||||
|
@ -62,9 +68,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.updateCredentials(
|
||||
(ctx.request as any).body as any,
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertAccount(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -81,9 +85,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
(ctx.request.query as any).acct,
|
||||
"accounts",
|
||||
);
|
||||
let resp = data.data.accounts[0];
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertAccount(data.data.accounts[0]);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -115,11 +117,9 @@ export function apiAccountMastodon(router: Router): void {
|
|||
}
|
||||
|
||||
const data = await client.getRelationships(reqIds);
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
ctx.body = data.data.map((relationship) =>
|
||||
convertRelationship(relationship),
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
let data = e.response.data;
|
||||
|
@ -136,9 +136,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
try {
|
||||
const calcId = convertId(ctx.params.id, IdType.CalckeyId);
|
||||
const data = await client.getAccount(calcId);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertAccount(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -155,27 +153,9 @@ export function apiAccountMastodon(router: Router): void {
|
|||
try {
|
||||
const data = await client.getAccountStatuses(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
argsToBools(limitToInt(ctx.query as any)),
|
||||
convertTimelinesArgsId(argsToBools(limitToInt(ctx.query as any))),
|
||||
);
|
||||
let resp = data.data;
|
||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
||||
resp[statIdx].in_reply_to_account_id = resp[statIdx]
|
||||
.in_reply_to_account_id
|
||||
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
|
||||
: null;
|
||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
|
||||
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
|
||||
: null;
|
||||
let mentions = resp[statIdx].mentions;
|
||||
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
|
||||
resp[statIdx].mentions[mtnIdx].id = convertId(
|
||||
mentions[mtnIdx].id,
|
||||
IdType.MastodonId,
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.body = resp;
|
||||
ctx.body = data.data.map((status) => convertStatus(status));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -193,13 +173,9 @@ export function apiAccountMastodon(router: Router): void {
|
|||
try {
|
||||
const data = await client.getAccountFollowers(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
limitToInt(ctx.query as any),
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -217,13 +193,9 @@ export function apiAccountMastodon(router: Router): void {
|
|||
try {
|
||||
const data = await client.getAccountFollowing(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
limitToInt(ctx.query as any),
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -239,8 +211,10 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getAccountLists(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getAccountLists(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data.map((list) => convertList(list));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -259,9 +233,8 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.followAccount(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let acct = data.data;
|
||||
let acct = convertRelationship(data.data);
|
||||
acct.following = true;
|
||||
acct.id = convertId(acct.id, IdType.MastodonId);
|
||||
ctx.body = acct;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
@ -281,8 +254,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.unfollowAccount(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let acct = data.data;
|
||||
acct.id = convertId(acct.id, IdType.MastodonId);
|
||||
let acct = convertRelationship(data.data);
|
||||
acct.following = false;
|
||||
ctx.body = acct;
|
||||
} catch (e: any) {
|
||||
|
@ -303,9 +275,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.blockAccount(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -324,9 +294,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.unblockAccount(
|
||||
convertId(ctx.params.id, IdType.MastodonId),
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -346,9 +314,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
(ctx.request as any).body as any,
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -367,9 +333,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.unmuteAccount(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -383,28 +347,10 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = (await client.getBookmarks(
|
||||
limitToInt(ctx.query as any),
|
||||
)) as any;
|
||||
let resp = data.data;
|
||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
||||
resp[statIdx].in_reply_to_account_id = resp[statIdx]
|
||||
.in_reply_to_account_id
|
||||
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
|
||||
: null;
|
||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
|
||||
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
|
||||
: null;
|
||||
let mentions = resp[statIdx].mentions;
|
||||
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
|
||||
resp[statIdx].mentions[mtnIdx].id = convertId(
|
||||
mentions[mtnIdx].id,
|
||||
IdType.MastodonId,
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.body = resp;
|
||||
const data = await client.getBookmarks(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
ctx.body = data.data.map((status) => convertStatus(status));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -417,26 +363,10 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getFavourites(limitToInt(ctx.query as any));
|
||||
let resp = data.data;
|
||||
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
|
||||
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
|
||||
resp[statIdx].in_reply_to_account_id = resp[statIdx]
|
||||
.in_reply_to_account_id
|
||||
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
|
||||
: null;
|
||||
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
|
||||
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
|
||||
: null;
|
||||
let mentions = resp[statIdx].mentions;
|
||||
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
|
||||
resp[statIdx].mentions[mtnIdx].id = convertId(
|
||||
mentions[mtnIdx].id,
|
||||
IdType.MastodonId,
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.body = resp;
|
||||
const data = await client.getFavourites(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
ctx.body = data.data.map((status) => convertStatus(status));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -449,12 +379,10 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getMutes(limitToInt(ctx.query as any));
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
const data = await client.getMutes(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -467,12 +395,10 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getBlocks(limitToInt(ctx.query as any));
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
const data = await client.getBlocks(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -488,11 +414,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.getFollowRequests(
|
||||
((ctx.query as any) || { limit: 20 }).limit,
|
||||
);
|
||||
let resp = data.data;
|
||||
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
|
||||
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
|
||||
}
|
||||
ctx.body = resp;
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -510,9 +432,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.acceptFollowRequest(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -531,9 +451,7 @@ export function apiAccountMastodon(router: Router): void {
|
|||
const data = await client.rejectFollowRequest(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
let resp = data.data;
|
||||
resp.id = convertId(resp.id, IdType.MastodonId);
|
||||
ctx.body = resp;
|
||||
ctx.body = convertRelationship(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import megalodon, { MegalodonInterface } from "@calckey/megalodon";
|
||||
import Router from "@koa/router";
|
||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||
import { IdType, convertId } from "../../index.js";
|
||||
import { convertFilter } from "../converters.js";
|
||||
|
||||
export function apiFilterMastodon(router: Router): void {
|
||||
router.get("/v1/filters", async (ctx) => {
|
||||
|
@ -10,7 +12,7 @@ export function apiFilterMastodon(router: Router): void {
|
|||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.getFilters();
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.map((filter) => convertFilter(filter));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -24,8 +26,10 @@ export function apiFilterMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.getFilter(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getFilter(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertFilter(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -40,7 +44,7 @@ export function apiFilterMastodon(router: Router): void {
|
|||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.createFilter(body.phrase, body.context, body);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertFilter(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -55,11 +59,11 @@ export function apiFilterMastodon(router: Router): void {
|
|||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.updateFilter(
|
||||
ctx.params.id,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
body.phrase,
|
||||
body.context,
|
||||
);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertFilter(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -73,7 +77,9 @@ export function apiFilterMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.deleteFilter(ctx.params.id);
|
||||
const data = await client.deleteFilter(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import megalodon, { MegalodonInterface } from "@calckey/megalodon";
|
||||
import Router from "@koa/router";
|
||||
import { koaBody } from "koa-body";
|
||||
import { convertId, IdType } from "../../index.js";
|
||||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||
import { toTextWithReaction } from "./timeline.js";
|
||||
import { convertTimelinesArgsId, toTextWithReaction } from "./timeline.js";
|
||||
import { convertNotification } from "../converters.js";
|
||||
function toLimitToInt(q: any) {
|
||||
if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
|
||||
return q;
|
||||
|
@ -15,9 +17,12 @@ export function apiNotificationsMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.getNotifications(toLimitToInt(ctx.query));
|
||||
const data = await client.getNotifications(
|
||||
convertTimelinesArgsId(toLimitToInt(ctx.query)),
|
||||
);
|
||||
const notfs = data.data;
|
||||
const ret = notfs.map((n) => {
|
||||
n = convertNotification(n);
|
||||
if (n.type !== "follow" && n.type !== "follow_request") {
|
||||
if (n.type === "reaction") n.type = "favourite";
|
||||
n.status = toTextWithReaction(
|
||||
|
@ -43,8 +48,10 @@ export function apiNotificationsMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const dataRaw = await client.getNotification(ctx.params.id);
|
||||
const data = dataRaw.data;
|
||||
const dataRaw = await client.getNotification(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
const data = convertNotification(dataRaw.data);
|
||||
if (data.type !== "follow" && data.type !== "follow_request") {
|
||||
if (data.type === "reaction") data.type = "favourite";
|
||||
ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
|
||||
|
@ -79,7 +86,9 @@ export function apiNotificationsMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const data = await client.dismissNotification(ctx.params.id);
|
||||
const data = await client.dismissNotification(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
|
|
@ -3,7 +3,8 @@ import Router from "@koa/router";
|
|||
import { getClient } from "../ApiMastodonCompatibleService.js";
|
||||
import axios from "axios";
|
||||
import { Converter } from "@calckey/megalodon";
|
||||
import { limitToInt } from "./timeline.js";
|
||||
import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
|
||||
import { convertAccount, convertStatus } from "../converters.js";
|
||||
|
||||
export function apiSearchMastodon(router: Router): void {
|
||||
router.get("/v1/search", async (ctx) => {
|
||||
|
@ -12,7 +13,7 @@ export function apiSearchMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const body: any = ctx.request.body;
|
||||
try {
|
||||
const query: any = limitToInt(ctx.query);
|
||||
const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
|
||||
const type = query.type || "";
|
||||
const data = await client.search(query.q, type, query);
|
||||
ctx.body = data.data;
|
||||
|
@ -27,18 +28,20 @@ export function apiSearchMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const query: any = limitToInt(ctx.query);
|
||||
const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
|
||||
const type = query.type;
|
||||
if (type) {
|
||||
const data = await client.search(query.q, type, query);
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.accounts.map((account) => convertAccount(account));
|
||||
} else {
|
||||
const acct = await client.search(query.q, "accounts", query);
|
||||
const stat = await client.search(query.q, "statuses", query);
|
||||
const tags = await client.search(query.q, "hashtags", query);
|
||||
ctx.body = {
|
||||
accounts: acct.data.accounts,
|
||||
statuses: stat.data.statuses,
|
||||
accounts: acct.data.accounts.map((account) =>
|
||||
convertAccount(account),
|
||||
),
|
||||
statuses: stat.data.statuses.map((status) => convertStatus(status)),
|
||||
hashtags: tags.data.hashtags,
|
||||
};
|
||||
}
|
||||
|
@ -57,7 +60,7 @@ export function apiSearchMastodon(router: Router): void {
|
|||
ctx.request.hostname,
|
||||
accessTokens,
|
||||
);
|
||||
ctx.body = data;
|
||||
ctx.body = data.map((status) => convertStatus(status));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -69,12 +72,16 @@ export function apiSearchMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
try {
|
||||
const query: any = ctx.query;
|
||||
const data = await getFeaturedUser(
|
||||
let data = await getFeaturedUser(
|
||||
BASE_URL,
|
||||
ctx.request.hostname,
|
||||
accessTokens,
|
||||
query.limit || 20,
|
||||
);
|
||||
data = data.map((suggestion) => {
|
||||
suggestion.account = convertAccount(suggestion.account);
|
||||
return suggestion;
|
||||
});
|
||||
console.log(data);
|
||||
ctx.body = data;
|
||||
} catch (e: any) {
|
||||
|
|
|
@ -4,7 +4,14 @@ import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
|
|||
import axios from "axios";
|
||||
import querystring from "node:querystring";
|
||||
import qs from "qs";
|
||||
import { limitToInt } from "./timeline.js";
|
||||
import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
|
||||
import { convertId, IdType } from "../../index.js";
|
||||
import {
|
||||
convertAccount,
|
||||
convertAttachment,
|
||||
convertPoll,
|
||||
convertStatus,
|
||||
} from "../converters.js";
|
||||
|
||||
function normalizeQuery(data: any) {
|
||||
const str = querystring.stringify(data);
|
||||
|
@ -18,6 +25,8 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
let body: any = ctx.request.body;
|
||||
if (body.in_reply_to_id)
|
||||
body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.CalckeyId);
|
||||
if (
|
||||
(!body.poll && body["poll[options][]"]) ||
|
||||
(!body.media_ids && body["media_ids[]"])
|
||||
|
@ -54,7 +63,7 @@ export function apiStatusMastodon(router: Router): void {
|
|||
body.sensitive =
|
||||
typeof sensitive === "string" ? sensitive === "true" : sensitive;
|
||||
const data = await client.postStatus(text, body);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -66,8 +75,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -79,7 +90,9 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.deleteStatus(ctx.params.id);
|
||||
const data = await client.deleteStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e.response.data, request.params.id);
|
||||
|
@ -100,10 +113,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const id = ctx.params.id;
|
||||
const id = convertId(ctx.params.id, IdType.CalckeyId);
|
||||
const data = await client.getStatusContext(
|
||||
id,
|
||||
limitToInt(ctx.query as any),
|
||||
convertTimelinesArgsId(limitToInt(ctx.query as any)),
|
||||
);
|
||||
const status = await client.getStatus(id);
|
||||
let reqInstance = axios.create({
|
||||
|
@ -126,6 +139,12 @@ export function apiStatusMastodon(router: Router): void {
|
|||
text,
|
||||
),
|
||||
);
|
||||
data.data.ancestors = data.data.ancestors.map((status) =>
|
||||
convertStatus(status),
|
||||
);
|
||||
data.data.descendants = data.data.descendants.map((status) =>
|
||||
convertStatus(status),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
@ -141,8 +160,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getStatusRebloggedBy(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getStatusRebloggedBy(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -165,11 +186,11 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const react = await getFirstReaction(BASE_URL, accessTokens);
|
||||
try {
|
||||
const a = (await client.createEmojiReaction(
|
||||
ctx.params.id,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
react,
|
||||
)) as any;
|
||||
//const data = await client.favouriteStatus(ctx.params.id) as any;
|
||||
ctx.body = a.data;
|
||||
ctx.body = convertStatus(a.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -186,8 +207,11 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
const react = await getFirstReaction(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.deleteEmojiReaction(ctx.params.id, react);
|
||||
ctx.body = data.data;
|
||||
const data = await client.deleteEmojiReaction(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
react,
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -203,8 +227,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.reblogStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.reblogStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -220,8 +246,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.unreblogStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.unreblogStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -237,8 +265,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.bookmarkStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.bookmarkStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -254,8 +284,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = (await client.unbookmarkStatus(ctx.params.id)) as any;
|
||||
ctx.body = data.data;
|
||||
const data = await client.unbookmarkStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -271,8 +303,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.pinStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.pinStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -288,8 +322,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.unpinStatus(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.unpinStatus(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertStatus(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -302,8 +338,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getMedia(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getMedia(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertAttachment(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -316,10 +354,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.updateMedia(
|
||||
ctx.params.id,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
ctx.request.body as any,
|
||||
);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertAttachment(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -331,8 +369,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getPoll(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getPoll(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertPoll(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
@ -347,10 +387,10 @@ export function apiStatusMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.votePoll(
|
||||
ctx.params.id,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
(ctx.request.body as any).choices,
|
||||
);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertPoll(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
ctx.status = 401;
|
||||
|
|
|
@ -4,6 +4,8 @@ import { getClient } from "../ApiMastodonCompatibleService.js";
|
|||
import { statusModel } from "./status.js";
|
||||
import Autolinker from "autolinker";
|
||||
import { ParsedUrlQuery } from "querystring";
|
||||
import { convertAccount, convertList, convertStatus } from "../converters.js";
|
||||
import { convertId, IdType } from "../../index.js";
|
||||
|
||||
export function limitToInt(q: ParsedUrlQuery) {
|
||||
let object: any = q;
|
||||
|
@ -29,6 +31,16 @@ export function argsToBools(q: ParsedUrlQuery) {
|
|||
return q;
|
||||
}
|
||||
|
||||
export function convertTimelinesArgsId(q: ParsedUrlQuery) {
|
||||
if (typeof q.min_id === "string")
|
||||
q.min_id = convertId(q.min_id, IdType.CalckeyId);
|
||||
if (typeof q.max_id === "string")
|
||||
q.max_id = convertId(q.max_id, IdType.CalckeyId);
|
||||
if (typeof q.since_id === "string")
|
||||
q.since_id = convertId(q.since_id, IdType.CalckeyId);
|
||||
return q;
|
||||
}
|
||||
|
||||
export function toTextWithReaction(status: Entity.Status[], host: string) {
|
||||
return status.map((t) => {
|
||||
if (!t) return statusModel(null, null, [], "no content");
|
||||
|
@ -97,9 +109,14 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
try {
|
||||
const query: any = ctx.query;
|
||||
const data = query.local
|
||||
? await client.getLocalTimeline(argsToBools(limitToInt(query)))
|
||||
: await client.getPublicTimeline(argsToBools(limitToInt(query)));
|
||||
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
||||
? await client.getLocalTimeline(
|
||||
convertTimelinesArgsId(argsToBools(limitToInt(query))),
|
||||
)
|
||||
: await client.getPublicTimeline(
|
||||
convertTimelinesArgsId(argsToBools(limitToInt(query))),
|
||||
);
|
||||
let resp = data.data.map((status) => convertStatus(status));
|
||||
ctx.body = toTextWithReaction(resp, ctx.hostname);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -116,9 +133,10 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
try {
|
||||
const data = await client.getTagTimeline(
|
||||
ctx.params.hashtag,
|
||||
argsToBools(limitToInt(ctx.query)),
|
||||
convertTimelinesArgsId(argsToBools(limitToInt(ctx.query))),
|
||||
);
|
||||
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
||||
let resp = data.data.map((status) => convertStatus(status));
|
||||
ctx.body = toTextWithReaction(resp, ctx.hostname);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -132,8 +150,11 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getHomeTimeline(limitToInt(ctx.query));
|
||||
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
||||
const data = await client.getHomeTimeline(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query)),
|
||||
);
|
||||
let resp = data.data.map((status) => convertStatus(status));
|
||||
ctx.body = toTextWithReaction(resp, ctx.hostname);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -149,10 +170,11 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getListTimeline(
|
||||
ctx.params.listId,
|
||||
limitToInt(ctx.query),
|
||||
convertId(ctx.params.listId, IdType.CalckeyId),
|
||||
convertTimelinesArgsId(limitToInt(ctx.query)),
|
||||
);
|
||||
ctx.body = toTextWithReaction(data.data, ctx.hostname);
|
||||
let resp = data.data.map((status) => convertStatus(status));
|
||||
ctx.body = toTextWithReaction(resp, ctx.hostname);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -166,7 +188,9 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getConversationTimeline(limitToInt(ctx.query));
|
||||
const data = await client.getConversationTimeline(
|
||||
convertTimelinesArgsId(limitToInt(ctx.query)),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
@ -181,7 +205,7 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getLists();
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.map((list) => convertList(list));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -196,8 +220,10 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getList(ctx.params.id);
|
||||
ctx.body = data.data;
|
||||
const data = await client.getList(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = convertList(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -212,7 +238,7 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.createList((ctx.request.body as any).title);
|
||||
ctx.body = data.data;
|
||||
ctx.body = convertList(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -227,8 +253,11 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.updateList(ctx.params.id, (ctx.request.body as any).title);
|
||||
ctx.body = data.data;
|
||||
const data = await client.updateList(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
(ctx.request.body as any).title,
|
||||
);
|
||||
ctx.body = convertList(data.data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -244,7 +273,9 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const accessTokens = ctx.headers.authorization;
|
||||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.deleteList(ctx.params.id);
|
||||
const data = await client.deleteList(
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
@ -262,10 +293,10 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.getAccountsInList(
|
||||
ctx.params.id,
|
||||
ctx.query as any,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
convertTimelinesArgsId(ctx.query as any),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
ctx.body = data.data.map((account) => convertAccount(account));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
console.error(e.response.data);
|
||||
|
@ -282,8 +313,10 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.addAccountsToList(
|
||||
ctx.params.id,
|
||||
(ctx.query as any).account_ids,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
(ctx.query.account_ids as string[]).map((id) =>
|
||||
convertId(id, IdType.CalckeyId),
|
||||
),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
|
@ -302,8 +335,10 @@ export function apiTimelineMastodon(router: Router): void {
|
|||
const client = getClient(BASE_URL, accessTokens);
|
||||
try {
|
||||
const data = await client.deleteAccountsFromList(
|
||||
ctx.params.id,
|
||||
(ctx.query as any).account_ids,
|
||||
convertId(ctx.params.id, IdType.CalckeyId),
|
||||
(ctx.query.account_ids as string[]).map((id) =>
|
||||
convertId(id, IdType.CalckeyId),
|
||||
),
|
||||
);
|
||||
ctx.body = data.data;
|
||||
} catch (e: any) {
|
||||
|
|
|
@ -55,7 +55,7 @@ export default async (ctx: Koa.Context) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const available = await validateEmailForAccount(emailAddress);
|
||||
const { available } = await validateEmailForAccount(emailAddress);
|
||||
if (!available) {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
|
|
|
@ -399,28 +399,33 @@ router.get("/notes/:note", async (ctx, next) => {
|
|||
visibility: In(["public", "home"]),
|
||||
});
|
||||
|
||||
if (note) {
|
||||
const _note = await Notes.pack(note);
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: note.userId });
|
||||
const meta = await fetchMeta();
|
||||
await ctx.render("note", {
|
||||
note: _note,
|
||||
profile,
|
||||
avatarUrl: await Users.getAvatarUrl(
|
||||
await Users.findOneByOrFail({ id: note.userId }),
|
||||
),
|
||||
// TODO: Let locale changeable by instance setting
|
||||
summary: getNoteSummary(_note),
|
||||
instanceName: meta.name || "Calckey",
|
||||
icon: meta.iconUrl,
|
||||
privateMode: meta.privateMode,
|
||||
themeColor: meta.themeColor,
|
||||
});
|
||||
try {
|
||||
if (note) {
|
||||
const _note = await Notes.pack(note);
|
||||
|
||||
ctx.set("Cache-Control", "public, max-age=15");
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: note.userId,
|
||||
});
|
||||
const meta = await fetchMeta();
|
||||
await ctx.render("note", {
|
||||
note: _note,
|
||||
profile,
|
||||
avatarUrl: await Users.getAvatarUrl(
|
||||
await Users.findOneByOrFail({ id: note.userId }),
|
||||
),
|
||||
// TODO: Let locale changeable by instance setting
|
||||
summary: getNoteSummary(_note),
|
||||
instanceName: meta.name || "Calckey",
|
||||
icon: meta.iconUrl,
|
||||
privateMode: meta.privateMode,
|
||||
themeColor: meta.themeColor,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
ctx.set("Cache-Control", "public, max-age=15");
|
||||
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
|
|
@ -6,11 +6,13 @@ import {
|
|||
NoteThreadMutings,
|
||||
UserProfiles,
|
||||
Users,
|
||||
Followings,
|
||||
} from "@/models/index.js";
|
||||
import { genId } from "@/misc/gen-id.js";
|
||||
import type { User } from "@/models/entities/user.js";
|
||||
import type { Notification } from "@/models/entities/notification.js";
|
||||
import { sendEmailNotification } from "./send-email-notification.js";
|
||||
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||
|
||||
export async function createNotification(
|
||||
notifieeId: User["id"],
|
||||
|
@ -21,6 +23,26 @@ export async function createNotification(
|
|||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
data.notifierId &&
|
||||
["mention", "reply", "renote", "quote", "reaction"].includes(type)
|
||||
) {
|
||||
const notifier = await Users.findOneBy({ id: data.notifierId });
|
||||
// suppress if the notifier does not exist or is silenced.
|
||||
if (!notifier) return null;
|
||||
|
||||
// suppress if the notifier is silenced or in a silenced instance, and not followed by the notifiee.
|
||||
if (
|
||||
(notifier.isSilenced ||
|
||||
(Users.isRemoteUser(notifier) &&
|
||||
(await shouldSilenceInstance(notifier.host)))) &&
|
||||
!(await Followings.exist({
|
||||
where: { followerId: notifieeId, followeeId: data.notifierId },
|
||||
}))
|
||||
)
|
||||
return null;
|
||||
}
|
||||
|
||||
const profile = await UserProfiles.findOneBy({ userId: notifieeId });
|
||||
|
||||
const isMuted = profile?.mutingNotificationTypes.includes(type);
|
||||
|
|
|
@ -27,6 +27,7 @@ import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js
|
|||
import type { Packed } from "@/misc/schema.js";
|
||||
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
||||
import { webhookDeliver } from "@/queue/index.js";
|
||||
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||
|
||||
const logger = new Logger("following/create");
|
||||
|
||||
|
@ -226,13 +227,19 @@ export default async function (
|
|||
});
|
||||
|
||||
// フォロー対象が鍵アカウントである or
|
||||
// The follower is silenced, or
|
||||
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
||||
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
|
||||
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or
|
||||
// The follower is remote, the followee is local, and the follower is in a silenced instance.
|
||||
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
||||
if (
|
||||
followee.isLocked ||
|
||||
follower.isSilenced ||
|
||||
(followeeProfile.carefulBot && follower.isBot) ||
|
||||
(Users.isLocalUser(follower) && Users.isRemoteUser(followee))
|
||||
(Users.isLocalUser(follower) && Users.isRemoteUser(followee)) ||
|
||||
(Users.isRemoteUser(follower) &&
|
||||
Users.isLocalUser(followee) &&
|
||||
(await shouldSilenceInstance(follower.host)))
|
||||
) {
|
||||
let autoAccept = false;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import type { User } from "@/models/entities/user.js";
|
|||
import { Blockings, FollowRequests, Users } from "@/models/index.js";
|
||||
import { genId } from "@/misc/gen-id.js";
|
||||
import { createNotification } from "../../create-notification.js";
|
||||
import config from "@/config/index.js";
|
||||
|
||||
export default async function (
|
||||
follower: {
|
||||
|
@ -79,7 +80,13 @@ export default async function (
|
|||
}
|
||||
|
||||
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
|
||||
const content = renderActivity(renderFollow(follower, followee));
|
||||
const content = renderActivity(
|
||||
renderFollow(
|
||||
follower,
|
||||
followee,
|
||||
requestId ?? `${config.url}/follows/${followRequest.id}`,
|
||||
),
|
||||
);
|
||||
deliver(follower, content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
} from "@/models/index.js";
|
||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||
import type { App } from "@/models/entities/app.js";
|
||||
import { Not, In } from "typeorm";
|
||||
import { Not, In, IsNull } from "typeorm";
|
||||
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
|
||||
import { genId } from "@/misc/gen-id.js";
|
||||
import {
|
||||
|
@ -66,6 +66,7 @@ import { Cache } from "@/misc/cache.js";
|
|||
import type { UserProfile } from "@/models/entities/user-profile.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
||||
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||
|
||||
const mutedWordsCache = new Cache<
|
||||
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
|
||||
|
@ -166,6 +167,7 @@ export default async (
|
|||
data: Option,
|
||||
silent = false,
|
||||
) =>
|
||||
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
||||
new Promise<Note>(async (res, rej) => {
|
||||
// If you reply outside the channel, match the scope of the target.
|
||||
// TODO (I think it's a process that could be done on the client side, but it's server side for now.)
|
||||
|
@ -203,6 +205,15 @@ export default async (
|
|||
data.visibility = "home";
|
||||
}
|
||||
|
||||
// Enforce home visibility if the user is in a silenced instance.
|
||||
if (
|
||||
data.visibility === "public" &&
|
||||
Users.isRemoteUser(user) &&
|
||||
(await shouldSilenceInstance(user.host))
|
||||
) {
|
||||
data.visibility = "home";
|
||||
}
|
||||
|
||||
// Reject if the target of the renote is a public range other than "Home or Entire".
|
||||
if (
|
||||
data.renote &&
|
||||
|
|
|
@ -118,7 +118,7 @@ export default async (
|
|||
userId: user.id,
|
||||
});
|
||||
|
||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
||||
// Create notification if the reaction target is a local user.
|
||||
if (note.userHost === null) {
|
||||
createNotification(note.userId, "reaction", {
|
||||
notifierId: user.id,
|
||||
|
@ -143,7 +143,7 @@ export default async (
|
|||
}
|
||||
});
|
||||
|
||||
//#region 配信
|
||||
//#region deliver
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
const content = renderActivity(await renderLike(record, note));
|
||||
const dm = new DeliverManager(user, content);
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
node_modules
|
||||
/built
|
||||
/coverage
|
||||
/.eslintrc.js
|
||||
/jest.config.ts
|
||||
/test
|
||||
/test-d
|
|
@ -1,65 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
rules: {
|
||||
indent: [
|
||||
"error",
|
||||
"tab",
|
||||
{
|
||||
SwitchCase: 1,
|
||||
MemberExpression: "off",
|
||||
flatTernaryExpressions: true,
|
||||
ArrayExpression: "first",
|
||||
ObjectExpression: "first",
|
||||
},
|
||||
],
|
||||
"eol-last": ["error", "always"],
|
||||
semi: ["error", "always"],
|
||||
quotes: ["error", "single"],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"keyword-spacing": [
|
||||
"error",
|
||||
{
|
||||
before: true,
|
||||
after: true,
|
||||
},
|
||||
],
|
||||
"key-spacing": [
|
||||
"error",
|
||||
{
|
||||
beforeColon: false,
|
||||
afterColon: true,
|
||||
},
|
||||
],
|
||||
"space-infix-ops": ["error"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"nonblock-statement-body-position": ["error", "beside"],
|
||||
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||
"no-multiple-empty-lines": ["error", { max: 1 }],
|
||||
"no-multi-spaces": ["error"],
|
||||
"no-var": ["error"],
|
||||
"prefer-arrow-callback": ["error"],
|
||||
"no-throw-literal": ["error"],
|
||||
"no-param-reassign": ["warn"],
|
||||
"no-constant-condition": ["warn"],
|
||||
"no-empty-pattern": ["warn"],
|
||||
"@typescript-eslint/no-unnecessary-condition": ["error"],
|
||||
"@typescript-eslint/no-inferrable-types": ["warn"],
|
||||
"@typescript-eslint/no-non-null-assertion": ["warn"],
|
||||
"@typescript-eslint/explicit-function-return-type": ["warn"],
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{
|
||||
checksVoidReturn: false,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
},
|
||||
};
|
|
@ -9,9 +9,8 @@
|
|||
"tsd": "tsd",
|
||||
"api": "pnpm api-extractor run --local --verbose",
|
||||
"api-prod": "pnpm api-extractor run --verbose",
|
||||
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "pnpm typecheck && pnpm eslint",
|
||||
"lint": "pnpm typecheck && pnpm rome check \"src/*.ts\"",
|
||||
"jest": "jest --coverage --detectOpenHandles",
|
||||
"test": "pnpm jest && pnpm tsd"
|
||||
},
|
||||
|
|
|
@ -55,6 +55,7 @@ export type Endpoints = {
|
|||
"admin/get-table-stats": { req: TODO; res: TODO };
|
||||
"admin/invite": { req: TODO; res: TODO };
|
||||
"admin/logs": { req: TODO; res: TODO };
|
||||
"admin/meta": { req: TODO; res: TODO };
|
||||
"admin/reset-password": { req: TODO; res: TODO };
|
||||
"admin/resolve-abuse-user-report": { req: TODO; res: TODO };
|
||||
"admin/resync-chart": { req: TODO; res: TODO };
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"autosize": "5.0.2",
|
||||
"blurhash": "1.1.5",
|
||||
"broadcast-channel": "4.19.1",
|
||||
"browser-image-resizer": "https://github.com/misskey-dev/browser-image-resizer.git",
|
||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
|
||||
"calckey-js": "workspace:*",
|
||||
"chart.js": "4.1.1",
|
||||
"chartjs-adapter-date-fns": "2.0.1",
|
||||
|
|
|
@ -28,7 +28,7 @@ const emit = defineEmits<{
|
|||
(ev: "update:modelValue", v: boolean): void;
|
||||
}>();
|
||||
|
||||
const el = ref<HTMLElement>();
|
||||
const el = ref<HTMLElement>();
|
||||
|
||||
const label = computed(() => {
|
||||
return concat([
|
||||
|
@ -52,7 +52,7 @@ function focus() {
|
|||
}
|
||||
|
||||
defineExpose({
|
||||
focus
|
||||
focus,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -73,9 +73,46 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
}
|
||||
&:hover > span, &:focus > span {
|
||||
&:hover > span,
|
||||
&:focus > span {
|
||||
background: var(--cwFg) !important;
|
||||
color: var(--cwBg) !important;
|
||||
}
|
||||
|
||||
&.fade {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 0.4em 1em;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
&:hover {
|
||||
> span {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.showLess {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
bottom: var(--stickyBottom);
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 7px 7px var(--bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -174,7 +174,7 @@ import { deviceKind } from "@/scripts/device-kind";
|
|||
import { emojiCategories, instance } from "@/instance";
|
||||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import { FocusTrap } from 'focus-trap-vue';
|
||||
import { FocusTrap } from "focus-trap-vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
{
|
||||
yellow: instance.isNotResponding,
|
||||
red: instance.isBlocked,
|
||||
purple: instance.isSilenced,
|
||||
gray: instance.isSuspended,
|
||||
},
|
||||
]"
|
||||
|
@ -23,13 +24,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as misskey from "calckey-js";
|
||||
import * as calckey from "calckey-js";
|
||||
import MkMiniChart from "@/components/MkMiniChart.vue";
|
||||
import * as os from "@/os";
|
||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||
|
||||
const props = defineProps<{
|
||||
instance: misskey.entities.Instance;
|
||||
instance: calckey.entities.Instance;
|
||||
}>();
|
||||
|
||||
let chartValues = $ref<number[] | null>(null);
|
||||
|
@ -135,6 +136,21 @@ function getInstanceIcon(instance): string {
|
|||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
&:global(.purple) {
|
||||
--c: rgba(196, 0, 255, 0.15);
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
var(--c) 16.67%,
|
||||
transparent 16.67%,
|
||||
transparent 50%,
|
||||
var(--c) 50%,
|
||||
var(--c) 66.67%,
|
||||
transparent 66.67%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
&:global(.gray) {
|
||||
--c: var(--bg);
|
||||
background-image: linear-gradient(
|
||||
|
|
|
@ -139,7 +139,8 @@ function close() {
|
|||
height: 100px;
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover, &:focus-visible {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
color: var(--accent);
|
||||
background: var(--accentedBg);
|
||||
text-decoration: none;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div ref="el" class="sfhdhdhr" tabindex="-1">
|
||||
<MkMenu
|
||||
ref="menu"
|
||||
:items="items"
|
||||
:align="align"
|
||||
:width="width"
|
||||
:as-drawer="false"
|
||||
@close="onChildClosed"
|
||||
/>
|
||||
</div>
|
||||
<MkMenu
|
||||
ref="menu"
|
||||
:items="items"
|
||||
:align="align"
|
||||
:width="width"
|
||||
:as-drawer="false"
|
||||
@close="onChildClosed"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<FocusTrap v-bind:active="isActive">
|
||||
<div tabindex="-1" v-focus>
|
||||
<FocusTrap :active="false" ref="focusTrap">
|
||||
<div tabindex="-1">
|
||||
<div
|
||||
ref="itemsEl"
|
||||
class="rrevdjwt _popup _shadow"
|
||||
|
@ -14,7 +14,9 @@
|
|||
<template v-for="(item, i) in items2">
|
||||
<div v-if="item === null" class="divider"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item">
|
||||
<span :style="item.textStyle || ''">{{ item.text }}</span>
|
||||
<span :style="item.textStyle || ''">{{
|
||||
item.text
|
||||
}}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="item.type === 'pending'"
|
||||
|
@ -48,7 +50,9 @@
|
|||
class="avatar"
|
||||
disableLink
|
||||
/>
|
||||
<span :style="item.textStyle || ''">{{ item.text }}</span>
|
||||
<span :style="item.textStyle || ''">{{
|
||||
item.text
|
||||
}}</span>
|
||||
<span v-if="item.indicate" class="indicator"
|
||||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
|
@ -75,7 +79,9 @@
|
|||
:class="icon"
|
||||
></i>
|
||||
</span>
|
||||
<span :style="item.textStyle || ''">{{ item.text }}</span>
|
||||
<span :style="item.textStyle || ''">{{
|
||||
item.text
|
||||
}}</span>
|
||||
<span v-if="item.indicate" class="indicator"
|
||||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
|
@ -89,9 +95,11 @@
|
|||
@mouseenter.passive="onItemMouseEnter(item)"
|
||||
@mouseleave.passive="onItemMouseLeave(item)"
|
||||
>
|
||||
<MkAvatar :user="item.user" class="avatar" disableLink /><MkUserName
|
||||
<MkAvatar
|
||||
:user="item.user"
|
||||
/>
|
||||
class="avatar"
|
||||
disableLink
|
||||
/><MkUserName :user="item.user" />
|
||||
<span v-if="item.indicate" class="indicator"
|
||||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
|
@ -129,9 +137,13 @@
|
|||
:class="icon"
|
||||
></i>
|
||||
</span>
|
||||
<span :style="item.textStyle || ''">{{ item.text }}</span>
|
||||
<span :style="item.textStyle || ''">{{
|
||||
item.text
|
||||
}}</span>
|
||||
<span class="caret"
|
||||
><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i
|
||||
><i
|
||||
class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
<button
|
||||
|
@ -161,7 +173,9 @@
|
|||
class="avatar"
|
||||
disableLink
|
||||
/>
|
||||
<span :style="item.textStyle || ''">{{ item.text }}</span>
|
||||
<span :style="item.textStyle || ''">{{
|
||||
item.text
|
||||
}}</span>
|
||||
<span v-if="item.indicate" class="indicator"
|
||||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
|
@ -203,9 +217,10 @@ import FormSwitch from "@/components/form/switch.vue";
|
|||
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
import { FocusTrap } from 'focus-trap-vue';
|
||||
import { FocusTrap } from "focus-trap-vue";
|
||||
|
||||
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
|
||||
const focusTrap = ref();
|
||||
|
||||
const props = defineProps<{
|
||||
items: MenuItem[];
|
||||
|
@ -316,6 +331,8 @@ function focusDown() {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
focusTrap.value.activate();
|
||||
|
||||
if (props.viaKeyboard) {
|
||||
nextTick(() => {
|
||||
focusNext(itemsEl.children[0], true, false);
|
||||
|
@ -380,7 +397,8 @@ onBeforeUnmount(() => {
|
|||
transform: translateY(0em);
|
||||
}
|
||||
|
||||
&:not(:disabled):hover, &:focus-visible {
|
||||
&:not(:disabled):hover,
|
||||
&:focus-visible {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
|
||||
|
|
|
@ -26,13 +26,16 @@
|
|||
$style.root,
|
||||
{
|
||||
[$style.drawer]: type === 'drawer',
|
||||
[$style.dialog]: type === 'dialog' || type === 'dialog:top',
|
||||
[$style.dialog]:
|
||||
type === 'dialog' || type === 'dialog:top',
|
||||
[$style.popup]: type === 'popup',
|
||||
},
|
||||
]"
|
||||
:style="{
|
||||
zIndex,
|
||||
pointerEvents: (manualShowing != null ? manualShowing : showing)
|
||||
pointerEvents: (
|
||||
manualShowing != null ? manualShowing : showing
|
||||
)
|
||||
? 'auto'
|
||||
: 'none',
|
||||
'--transformOrigin': transformOrigin,
|
||||
|
@ -76,7 +79,7 @@ import * as os from "@/os";
|
|||
import { isTouchUsing } from "@/scripts/touch";
|
||||
import { defaultStore } from "@/store";
|
||||
import { deviceKind } from "@/scripts/device-kind";
|
||||
import { FocusTrap } from 'focus-trap-vue';
|
||||
import { FocusTrap } from "focus-trap-vue";
|
||||
|
||||
function getFixedContainer(el: Element | null): Element | null {
|
||||
if (el == null || el.tagName === "BODY") return null;
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
import { FocusTrap } from 'focus-trap-vue';
|
||||
import { FocusTrap } from "focus-trap-vue";
|
||||
import MkModal from "./MkModal.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
|
|
|
@ -279,7 +279,7 @@ const isRenote =
|
|||
note.poll == null;
|
||||
|
||||
const el = ref<HTMLElement>();
|
||||
const footerEl = ref<HTMLElement>();
|
||||
const footerEl = ref<HTMLElement>();
|
||||
const menuButton = ref<HTMLElement>();
|
||||
const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||
|
|
|
@ -462,15 +462,26 @@ if (
|
|||
props.reply &&
|
||||
["home", "followers", "specified"].includes(props.reply.visibility)
|
||||
) {
|
||||
visibility = props.reply.visibility;
|
||||
if (props.reply.visibility === "specified") {
|
||||
os.api("users/show", {
|
||||
userIds: props.reply.visibleUserIds.filter(
|
||||
(uid) => uid !== $i.id && uid !== props.reply.userId
|
||||
),
|
||||
}).then((users) => {
|
||||
users.forEach(pushVisibleUser);
|
||||
});
|
||||
if (props.reply.visibility === "home" && visibility === "followers") {
|
||||
visibility = "followers";
|
||||
} else if (
|
||||
["home", "followers"].includes(props.reply.visibility) &&
|
||||
visibility === "specified"
|
||||
) {
|
||||
visibility = "specified";
|
||||
} else {
|
||||
visibility = props.reply.visibility;
|
||||
}
|
||||
if (visibility === "specified") {
|
||||
if (props.reply.visibleUserIds) {
|
||||
os.api("users/show", {
|
||||
userIds: props.reply.visibleUserIds.filter(
|
||||
(uid) => uid !== $i.id && uid !== props.reply.userId
|
||||
),
|
||||
}).then((users) => {
|
||||
users.forEach(pushVisibleUser);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.reply.userId !== $i.id) {
|
||||
os.api("users/show", { userId: props.reply.userId }).then(
|
||||
|
|
60
packages/client/src/components/MkShowMoreButton.vue
Normal file
60
packages/client/src/components/MkShowMoreButton.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<button v-if="modelValue" class="fade _button" @click.stop="toggle">
|
||||
<span>{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
<button v-if="!modelValue" class="showLess _button" @click.stop="toggle">
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: "update:modelValue", v: boolean): void;
|
||||
}>();
|
||||
|
||||
const toggle = () => {
|
||||
emit("update:modelValue", !props.modelValue);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.fade {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 0.4em 1em;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
|
||||
}
|
||||
&:hover {
|
||||
> span {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
.showLess {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
position: sticky;
|
||||
bottom: var(--stickyBottom);
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
background: var(--panel);
|
||||
padding: 6px 10px;
|
||||
font-size: 0.8em;
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 0 7px 7px var(--bg);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -35,10 +35,19 @@
|
|||
class="content"
|
||||
:class="{ collapsed, isLong, showContent: note.cw && !showContent }"
|
||||
>
|
||||
<XCwButton ref="cwButton" v-if="note.cw && !showContent" v-model="showContent" :note="note" v-on:keydown="focusFooter" />
|
||||
<div
|
||||
<XCwButton
|
||||
ref="cwButton"
|
||||
v-if="note.cw && !showContent"
|
||||
v-model="showContent"
|
||||
:note="note"
|
||||
v-on:keydown="focusFooter"
|
||||
/>
|
||||
<div
|
||||
class="body"
|
||||
v-bind="{ 'aria-label': !showContent ? '' : null, 'tabindex': !showContent ? '-1' : null }"
|
||||
v-bind="{
|
||||
'aria-label': !showContent ? '' : null,
|
||||
tabindex: !showContent ? '-1' : null,
|
||||
}"
|
||||
>
|
||||
<span v-if="note.deletedAt" style="opacity: 0.5"
|
||||
>({{ i18n.ts.deleted }})</span
|
||||
|
@ -106,27 +115,17 @@
|
|||
v-on:focus="cwButton?.focus()"
|
||||
></div>
|
||||
</div>
|
||||
<button
|
||||
v-if="isLong && collapsed"
|
||||
class="fade _button"
|
||||
@click.stop="collapsed = false"
|
||||
>
|
||||
<span>{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="isLong && !collapsed"
|
||||
class="showLess _button"
|
||||
@click.stop="collapsed = true"
|
||||
>
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
<XShowMoreButton
|
||||
v-if="isLong"
|
||||
v-model="collapsed"
|
||||
></XShowMoreButton>
|
||||
<XCwButton v-if="note.cw" v-model="showContent" :note="note" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
import * as misskey from "calckey-js";
|
||||
import * as mfm from "mfm-js";
|
||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
||||
|
@ -150,7 +149,7 @@ const emit = defineEmits<{
|
|||
(ev: "focusfooter"): void;
|
||||
}>();
|
||||
|
||||
const cwButton = ref<HTMLElement>();
|
||||
const cwButton = ref<HTMLElement>();
|
||||
const isLong =
|
||||
!props.detailedView &&
|
||||
props.note.cw == null &&
|
||||
|
@ -163,7 +162,6 @@ const urls = props.note.text
|
|||
|
||||
let showContent = $ref(false);
|
||||
|
||||
|
||||
function focusFooter(ev) {
|
||||
if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
|
||||
emit("focusfooter");
|
||||
|
|
|
@ -96,7 +96,8 @@ export default defineComponent({
|
|||
font-size: 0.9em;
|
||||
margin-bottom: 0.3rem;
|
||||
|
||||
&:hover, &:focus-visible {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
text-decoration: none;
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,10 @@
|
|||
/></MkA>
|
||||
<p class="username"><MkAcct :user="user" /></p>
|
||||
</div>
|
||||
<div class="description" :class="{ collapsed: isLong && collapsed }">
|
||||
<div
|
||||
class="description"
|
||||
:class="{ collapsed: isLong && collapsed }"
|
||||
>
|
||||
<Mfm
|
||||
v-if="user.description"
|
||||
:text="user.description"
|
||||
|
@ -55,20 +58,10 @@
|
|||
:custom-emojis="user.emojis"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-if="isLong && collapsed"
|
||||
class="fade _button"
|
||||
@click.stop="collapsed = false"
|
||||
>
|
||||
<span>{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="isLong && !collapsed"
|
||||
class="showLess _button"
|
||||
@click.stop="collapsed = true"
|
||||
>
|
||||
<span>{{ i18n.ts.showLess }}</span>
|
||||
</button>
|
||||
<XShowMoreButton
|
||||
v-if="isLong"
|
||||
v-model="collapsed"
|
||||
></XShowMoreButton>
|
||||
<div v-if="user.fields.length > 0" class="fields">
|
||||
<dl
|
||||
v-for="(field, i) in user.fields"
|
||||
|
@ -149,14 +142,15 @@ let user = $ref<misskey.entities.UserDetailed | null>(null);
|
|||
let top = $ref(0);
|
||||
let left = $ref(0);
|
||||
|
||||
|
||||
let isLong = $ref(false);
|
||||
let collapsed = $ref(!isLong);
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof props.q === "object") {
|
||||
user = props.q;
|
||||
isLong = (user.description.split("\n").length > 9 || user.description.length > 400);
|
||||
isLong =
|
||||
user.description.split("\n").length > 9 ||
|
||||
user.description.length > 400;
|
||||
} else {
|
||||
const query = props.q.startsWith("@")
|
||||
? Acct.parse(props.q.substr(1))
|
||||
|
@ -165,10 +159,11 @@ onMounted(() => {
|
|||
os.api("users/show", query).then((res) => {
|
||||
if (!props.showing) return;
|
||||
user = res;
|
||||
isLong = (user.description.split("\n").length > 9 || user.description.length > 400);
|
||||
isLong =
|
||||
user.description.split("\n").length > 9 ||
|
||||
user.description.length > 400;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const rect = props.source.getBoundingClientRect();
|
||||
const x =
|
||||
|
@ -313,7 +308,7 @@ onMounted(() => {
|
|||
|
||||
> .fields {
|
||||
padding: 0 16px;
|
||||
font-size: .8em;
|
||||
font-size: 0.8em;
|
||||
margin-top: 1em;
|
||||
|
||||
> .field {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
v-bind="Object.fromEntries(currentPageProps)"
|
||||
tabindex="-1"
|
||||
v-focus
|
||||
style="outline: none;"
|
||||
style="outline: none"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default {
|
||||
mounted: (el) => el.focus()
|
||||
}
|
||||
mounted: (el) => el.focus(),
|
||||
};
|
||||
|
|
|
@ -87,23 +87,11 @@ export default {
|
|||
self.hideTimer = window.setTimeout(self.close, delay);
|
||||
}
|
||||
|
||||
el.addEventListener(
|
||||
start, showTooltip,
|
||||
{ passive: true },
|
||||
);
|
||||
el.addEventListener(
|
||||
"focusin", showTooltip,
|
||||
{ passive: true },
|
||||
);
|
||||
el.addEventListener(start, showTooltip, { passive: true });
|
||||
el.addEventListener("focusin", showTooltip, { passive: true });
|
||||
|
||||
el.addEventListener(
|
||||
end, hideTooltip,
|
||||
{ passive: true },
|
||||
);
|
||||
el.addEventListener(
|
||||
"focusout", hideTooltip,
|
||||
{ passive: true },
|
||||
);
|
||||
el.addEventListener(end, hideTooltip, { passive: true });
|
||||
el.addEventListener("focusout", hideTooltip, { passive: true });
|
||||
|
||||
el.addEventListener("click", () => {
|
||||
window.clearTimeout(self.showTimer);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<option value="publishing">{{ i18n.ts.publishing }}</option>
|
||||
<option value="suspended">{{ i18n.ts.suspended }}</option>
|
||||
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
||||
<option value="silenced">{{ i18n.ts.silenced }}</option>
|
||||
<option value="notResponding">
|
||||
{{ i18n.ts.notResponding }}
|
||||
</option>
|
||||
|
@ -105,13 +106,11 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import MkButton from "@/components/MkButton.vue";
|
||||
import MkInput from "@/components/form/input.vue";
|
||||
import MkSelect from "@/components/form/select.vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
|
||||
import FormSplit from "@/components/form/split.vue";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
let host = $ref("");
|
||||
|
@ -134,6 +133,8 @@ const pagination = {
|
|||
? { suspended: true }
|
||||
: state === "blocked"
|
||||
? { blocked: true }
|
||||
: state === "silenced"
|
||||
? { silenced: true }
|
||||
: state === "notResponding"
|
||||
? { notResponding: true }
|
||||
: {}),
|
||||
|
@ -143,6 +144,7 @@ const pagination = {
|
|||
function getStatus(instance) {
|
||||
if (instance.isSuspended) return "Suspended";
|
||||
if (instance.isBlocked) return "Blocked";
|
||||
if (instance.isSilenced) return "Silenced";
|
||||
if (instance.isNotResponding) return "Error";
|
||||
return "Alive";
|
||||
}
|
||||
|
|
|
@ -313,7 +313,9 @@ onUnmounted(() => {
|
|||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover, &:focus-visible, &.active {
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<MkStickyContainer>
|
||||
<template #header
|
||||
><MkPageHeader
|
||||
v-model:tab="tab"
|
||||
:actions="headerActions"
|
||||
:tabs="headerTabs"
|
||||
:display-back-button="true"
|
||||
|
|
|
@ -7,13 +7,31 @@
|
|||
:display-back-button="true"
|
||||
/></template>
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<MkTab v-model="tab" class="_formBlock">
|
||||
<option value="block">{{ i18n.ts.blockedInstances }}</option>
|
||||
<option value="silence">{{ i18n.ts.silencedInstances }}</option>
|
||||
</MkTab>
|
||||
<FormSuspense :p="init">
|
||||
<FormTextarea v-model="blockedHosts" class="_formBlock">
|
||||
<FormTextarea
|
||||
v-if="tab === 'block'"
|
||||
v-model="blockedHosts"
|
||||
class="_formBlock"
|
||||
>
|
||||
<span>{{ i18n.ts.blockedInstances }}</span>
|
||||
<template #caption>{{
|
||||
i18n.ts.blockedInstancesDescription
|
||||
}}</template>
|
||||
</FormTextarea>
|
||||
<FormTextarea
|
||||
v-else-if="tab === 'silence'"
|
||||
v-model="silencedHosts"
|
||||
class="_formBlock"
|
||||
>
|
||||
<span>{{ i18n.ts.silencedInstances }}</span>
|
||||
<template #caption>{{
|
||||
i18n.ts.silencedInstancesDescription
|
||||
}}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormButton primary class="_formBlock" @click="save"
|
||||
><i class="ph-floppy-disk-back ph-bold ph-lg"></i>
|
||||
|
@ -29,21 +47,28 @@ import {} from "vue";
|
|||
import FormButton from "@/components/MkButton.vue";
|
||||
import FormTextarea from "@/components/form/textarea.vue";
|
||||
import FormSuspense from "@/components/form/suspense.vue";
|
||||
import MkTab from "@/components/MkTab.vue";
|
||||
import * as os from "@/os";
|
||||
import { fetchInstance } from "@/instance";
|
||||
import { i18n } from "@/i18n";
|
||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||
|
||||
let blockedHosts: string = $ref("");
|
||||
let silencedHosts: string = $ref("");
|
||||
let tab = $ref("block");
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api("admin/meta");
|
||||
blockedHosts = meta.blockedHosts.join("\n");
|
||||
if (meta) {
|
||||
blockedHosts = meta.blockedHosts.join("\n");
|
||||
silencedHosts = meta.silencedHosts.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog("admin/update-meta", {
|
||||
blockedHosts: blockedHosts.split("\n").map((h) => h.trim()) || [],
|
||||
silencedHosts: silencedHosts.split("\n").map((h) => h.trim()) || [],
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
|
|
|
@ -12,7 +12,12 @@
|
|||
class="user"
|
||||
:to="`/user-info/${user.id}`"
|
||||
>
|
||||
<MkAvatar :user="user" class="avatar" indicator disableLink />
|
||||
<MkAvatar
|
||||
:user="user"
|
||||
class="avatar"
|
||||
indicator
|
||||
disableLink
|
||||
/>
|
||||
</MkA>
|
||||
</div>
|
||||
</Transition>
|
||||
|
|
|
@ -371,6 +371,34 @@
|
|||
<template #label>Pro account</template>
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>Libre Translate</template>
|
||||
|
||||
<FormInput
|
||||
v-model="libreTranslateApiUrl"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #prefix
|
||||
><i class="ph-link ph-bold ph-lg"></i
|
||||
></template>
|
||||
<template #label
|
||||
>Libre Translate API URL</template
|
||||
>
|
||||
</FormInput>
|
||||
|
||||
<FormInput
|
||||
v-model="libreTranslateApiKey"
|
||||
class="_formBlock"
|
||||
>
|
||||
<template #prefix
|
||||
><i class="ph-key ph-bold ph-lg"></i
|
||||
></template>
|
||||
<template #label
|
||||
>Libre Translate API Key</template
|
||||
>
|
||||
</FormInput>
|
||||
</FormSection>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
|
@ -422,6 +450,8 @@ let swPublicKey: any = $ref(null);
|
|||
let swPrivateKey: any = $ref(null);
|
||||
let deeplAuthKey: string = $ref("");
|
||||
let deeplIsPro: boolean = $ref(false);
|
||||
let libreTranslateApiUrl: string = $ref("");
|
||||
let libreTranslateApiKey: string = $ref("");
|
||||
let defaultReaction: string = $ref("");
|
||||
let defaultReactionCustom: string = $ref("");
|
||||
|
||||
|
@ -456,6 +486,8 @@ async function init() {
|
|||
swPrivateKey = meta.swPrivateKey;
|
||||
deeplAuthKey = meta.deeplAuthKey;
|
||||
deeplIsPro = meta.deeplIsPro;
|
||||
libreTranslateApiUrl = meta.libreTranslateApiUrl;
|
||||
libreTranslateApiKey = meta.libreTranslateApiKey;
|
||||
defaultReaction = ["⭐", "👍", "❤️"].includes(meta.defaultReaction)
|
||||
? meta.defaultReaction
|
||||
: "custom";
|
||||
|
@ -498,6 +530,8 @@ function save() {
|
|||
swPrivateKey,
|
||||
deeplAuthKey,
|
||||
deeplIsPro,
|
||||
libreTranslateApiUrl,
|
||||
libreTranslateApiKey,
|
||||
defaultReaction,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
|
|
|
@ -98,6 +98,14 @@
|
|||
@update:modelValue="toggleBlock"
|
||||
>{{ i18n.ts.blockThisInstance }}</FormSwitch
|
||||
>
|
||||
<FormSwitch
|
||||
v-model="isSilenced"
|
||||
class="_formBlock"
|
||||
@update:modelValue="toggleSilence"
|
||||
>{{
|
||||
i18n.ts.silenceThisInstance
|
||||
}}</FormSwitch
|
||||
>
|
||||
</FormSuspense>
|
||||
<MkButton @click="refreshMetadata"
|
||||
><i
|
||||
|
@ -329,7 +337,7 @@
|
|||
import { watch } from "vue";
|
||||
import { Virtual } from "swiper";
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type * as misskey from "calckey-js";
|
||||
import type * as calckey from "calckey-js";
|
||||
import MkChart from "@/components/MkChart.vue";
|
||||
import MkObjectView from "@/components/MkObjectView.vue";
|
||||
import FormLink from "@/components/form/link.vue";
|
||||
|
@ -352,11 +360,13 @@ import "swiper/scss";
|
|||
import "swiper/scss/virtual";
|
||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||
|
||||
type AugmentedInstanceMetadata = misskey.entities.DetailedInstanceMetadata & {
|
||||
type AugmentedInstanceMetadata = calckey.entities.DetailedInstanceMetadata & {
|
||||
blockedHosts: string[];
|
||||
silencedHosts: string[];
|
||||
};
|
||||
type AugmentedInstance = misskey.entities.Instance & {
|
||||
type AugmentedInstance = calckey.entities.Instance & {
|
||||
isBlocked: boolean;
|
||||
isSilenced: boolean;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -373,6 +383,7 @@ let meta = $ref<AugmentedInstanceMetadata | null>(null);
|
|||
let instance = $ref<AugmentedInstance | null>(null);
|
||||
let suspended = $ref(false);
|
||||
let isBlocked = $ref(false);
|
||||
let isSilenced = $ref(false);
|
||||
let faviconUrl = $ref(null);
|
||||
|
||||
const usersPagination = {
|
||||
|
@ -386,16 +397,14 @@ const usersPagination = {
|
|||
offsetMode: true,
|
||||
};
|
||||
|
||||
async function init() {
|
||||
meta = await os.api("admin/meta");
|
||||
}
|
||||
|
||||
async function fetch() {
|
||||
meta = (await os.api("admin/meta")) as AugmentedInstanceMetadata;
|
||||
instance = (await os.api("federation/show-instance", {
|
||||
host: props.host,
|
||||
})) as AugmentedInstance;
|
||||
suspended = instance.isSuspended;
|
||||
isBlocked = instance.isBlocked;
|
||||
isSilenced = instance.isSilenced;
|
||||
faviconUrl =
|
||||
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
|
||||
getProxiedImageUrlNullable(instance.iconUrl, "preview");
|
||||
|
@ -417,6 +426,22 @@ async function toggleBlock() {
|
|||
});
|
||||
}
|
||||
|
||||
async function toggleSilence() {
|
||||
if (meta == null) return;
|
||||
if (!instance) {
|
||||
throw new Error(`Instance info not loaded`);
|
||||
}
|
||||
let silencedHosts: string[];
|
||||
if (isSilenced) {
|
||||
silencedHosts = meta.silencedHosts.concat([instance.host]);
|
||||
} else {
|
||||
silencedHosts = meta.silencedHosts.filter((x) => x !== instance!.host);
|
||||
}
|
||||
await os.api("admin/update-meta", {
|
||||
silencedHosts,
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleSuspend(v) {
|
||||
await os.api("admin/federation/update-instance", {
|
||||
host: instance.host,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader /></template>
|
||||
<MkSpacer :content-max="800">
|
||||
<div class="mwysmxbg">
|
||||
<div :class="$style.root">
|
||||
<div>{{ i18n.ts._mfm.intro }}</div>
|
||||
<br />
|
||||
<div class="section _block">
|
||||
|
@ -137,6 +137,18 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.blockMath }}</div>
|
||||
<div class="content">
|
||||
<p>{{ i18n.ts._mfm.blockMathDescription }}</p>
|
||||
<div class="preview">
|
||||
<Mfm :text="preview_blockMath" />
|
||||
<MkTextarea v-model="preview_blockMath"
|
||||
><template #label>MFM</template></MkTextarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- deprecated
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.search }}</div>
|
||||
|
@ -427,8 +439,11 @@ let preview_blockCode = $ref(
|
|||
'```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```'
|
||||
);
|
||||
let preview_inlineMath = $ref("\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)");
|
||||
let preview_blockMath = $ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]");
|
||||
let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`);
|
||||
let preview_search = $ref(`${i18n.ts._mfm.dummy} 検索`);
|
||||
let preview_search = $ref(
|
||||
`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]\n${i18n.ts._mfm.dummy} 検索`
|
||||
);
|
||||
let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=5s 🍮]");
|
||||
let preview_tada = $ref("$[tada 🍮] $[tada.speed=5s 🍮]");
|
||||
let preview_jump = $ref("$[jump 🍮] $[jump.speed=5s 🍮]");
|
||||
|
@ -450,9 +465,15 @@ let preview_x4 = $ref("$[x4 🍮]");
|
|||
let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`);
|
||||
let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=5s 🍮]");
|
||||
let preview_sparkle = $ref("$[sparkle 🍮]");
|
||||
let preview_rotate = $ref("$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]");
|
||||
let preview_position = $ref("$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]");
|
||||
let preview_scale = $ref("$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]");
|
||||
let preview_rotate = $ref(
|
||||
"$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"
|
||||
);
|
||||
let preview_position = $ref(
|
||||
"$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]"
|
||||
);
|
||||
let preview_scale = $ref(
|
||||
"$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]"
|
||||
);
|
||||
let preview_fg = $ref("$[fg.color=ff0000 Text color]");
|
||||
let preview_bg = $ref("$[bg.color=ff0000 Background color]");
|
||||
let preview_plain = $ref(
|
||||
|
@ -465,8 +486,8 @@ definePageMetadata({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mwysmxbg {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
background: var(--bg);
|
||||
|
||||
> .section {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '080a01c5-377d-4fbb-88cc-6bb5d04977ea',
|
||||
base: 'dark',
|
||||
name: 'Mi Astro Dark',
|
||||
name: 'Astro Dark',
|
||||
author: 'syuilo',
|
||||
props: {
|
||||
bg: '#232125',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '504debaf-4912-6a4c-5059-1db08a76b737',
|
||||
|
||||
name: 'Mi Botanical Dark',
|
||||
name: 'Botanical Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'ffcd3328-5c57-4ca3-9dac-4580cbf7742f',
|
||||
base: 'dark',
|
||||
name: 'Catppuccin frappe',
|
||||
name: 'Catppuccin Frappe',
|
||||
props: {
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(255, 255, 255, 0.05)',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'd413f41f-a489-48be-9e20-3532ffbb4363',
|
||||
base: 'dark',
|
||||
name: 'Catppuccin mocha',
|
||||
name: 'Catppuccin Mocha',
|
||||
props: {
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(255, 255, 255, 0.05)',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '679b3b87-a4e9-4789-8696-b56c15cc33b0',
|
||||
|
||||
name: 'Mi Cherry Dark',
|
||||
name: 'Cherry Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '32a637ef-b47a-4775-bb7b-bacbb823f865',
|
||||
|
||||
name: 'Mi Future Dark',
|
||||
name: 'Future Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '02816013-8107-440f-877e-865083ffe194',
|
||||
|
||||
name: 'Mi Green+Lime Dark',
|
||||
name: 'Mi Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
id: 'dc489603-27b5-424a-9b25-1ff6aec9824a',
|
||||
|
||||
name: 'Mi Green+Orange Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
||||
props: {
|
||||
accent: '#e97f00',
|
||||
bg: '#0C1210',
|
||||
fg: '#dee7e4',
|
||||
fgHighlighted: '#fff',
|
||||
fgOnAccent: '#192320',
|
||||
divider: '#e7fffb24',
|
||||
panel: '#192320',
|
||||
panelHeaderBg: '@panel',
|
||||
panelHeaderDivider: '@divider',
|
||||
popup: '#293330',
|
||||
renote: '@accent',
|
||||
mentionMe: '#b4e900',
|
||||
link: '#24d7ce',
|
||||
},
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '66e7e5a9-cd43-42cd-837d-12f47841fa34',
|
||||
|
||||
name: 'Mi Ice Dark',
|
||||
name: 'Ice Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'c503d768-7c70-4db2-a4e6-08264304bc8d',
|
||||
|
||||
name: 'Mi Persimmon Dark',
|
||||
name: 'Persimmon Dark',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'dark',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '7a5bc13b-df8f-4d44-8e94-4452f0c634bb',
|
||||
base: 'dark',
|
||||
name: 'Mi U0 Dark',
|
||||
name: 'U0 Dark',
|
||||
props: {
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(255, 255, 255, 0.05)',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '0ff48d43-aab3-46e7-ab12-8492110d2e2b',
|
||||
|
||||
name: 'Mi Apricot Light',
|
||||
name: 'Apricot Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
94
packages/client/src/themes/l-catppuccin-latte.json5
Normal file
94
packages/client/src/themes/l-catppuccin-latte.json5
Normal file
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
id: "169661d2-5a17-4dfc-b71b-9938cbbbed3e",
|
||||
base: "light",
|
||||
name: "Catppuccin Latte",
|
||||
props: {
|
||||
X2: ":darken<2<@panel",
|
||||
X3: "rgba(255, 255, 255, 0.05)",
|
||||
X4: "rgba(255, 255, 255, 0.1)",
|
||||
X5: "rgba(255, 255, 255, 0.05)",
|
||||
X6: "rgba(255, 255, 255, 0.15)",
|
||||
X7: "rgba(255, 255, 255, 0.05)",
|
||||
X8: ":lighten<5<@accent",
|
||||
X9: ":darken<5<@accent",
|
||||
bg: "#dce0e8",
|
||||
fg: "#4c4f69",
|
||||
X10: ":alpha<0.4<@accent",
|
||||
X11: "rgba(0, 0, 0, 0.3)",
|
||||
X12: "rgba(255, 255, 255, 0.1)",
|
||||
X13: "rgba(255, 255, 255, 0.15)",
|
||||
X14: ":alpha<0.5<@navBg",
|
||||
X15: ":alpha<0<@panel",
|
||||
X16: ":alpha<0.7<@panel",
|
||||
X17: ":alpha<0.8<@bg",
|
||||
cwBg: "#bcc0cc",
|
||||
cwFg: "#5c5f77",
|
||||
link: "#1e66f5",
|
||||
warn: "#fe640b",
|
||||
badge: "#1e66f5",
|
||||
error: "#d20f39",
|
||||
focus: ":alpha<0.3<@accent",
|
||||
navBg: "@panel",
|
||||
navFg: "@fg",
|
||||
panel: ":lighten<3<@bg",
|
||||
popup: ":lighten<3<@panel",
|
||||
accent: "#8839ef",
|
||||
header: ":alpha<0.7<@panel",
|
||||
infoBg: "#ccd0da",
|
||||
infoFg: "#6c6f85",
|
||||
renote: "#1e66f5",
|
||||
shadow: "rgba(0, 0, 0, 0.3)",
|
||||
divider: "rgba(255, 255, 255, 0.1)",
|
||||
hashtag: "#209fb5",
|
||||
mention: "@accent",
|
||||
modalBg: "rgba(0, 0, 0, 0.5)",
|
||||
success: "#40a02b",
|
||||
buttonBg: "rgba(255, 255, 255, 0.05)",
|
||||
switchBg: "rgba(255, 255, 255, 0.15)",
|
||||
acrylicBg: ":alpha<0.5<@bg",
|
||||
cwHoverBg: "#acb0be",
|
||||
indicator: "@accent",
|
||||
mentionMe: "@mention",
|
||||
messageBg: "@bg",
|
||||
navActive: "@accent",
|
||||
accentedBg: ":alpha<0.15<@accent",
|
||||
codeNumber: "#40a02b",
|
||||
codeString: "#fe640b",
|
||||
fgOnAccent: "#eff1f5",
|
||||
infoWarnBg: "#ccd0da",
|
||||
infoWarnFg: "#5c5f77",
|
||||
navHoverFg: ":lighten<17<@fg",
|
||||
swutchOnBg: "@accentedBg",
|
||||
swutchOnFg: "@accent",
|
||||
codeBoolean: "@accent",
|
||||
dateLabelFg: "@fg",
|
||||
deckDivider: "#9ca0b0",
|
||||
inputBorder: "rgba(255, 255, 255, 0.1)",
|
||||
panelBorder: "solid 1px var(--divider)",
|
||||
swutchOffBg: "rgba(255, 255, 255, 0.1)",
|
||||
swutchOffFg: "@fg",
|
||||
accentDarken: ":darken<10<@accent",
|
||||
acrylicPanel: ":alpha<0.5<@panel",
|
||||
navIndicator: "@indicator",
|
||||
windowHeader: ":alpha<0.85<@panel",
|
||||
accentLighten: ":lighten<10<@accent",
|
||||
buttonHoverBg: "rgba(255, 255, 255, 0.1)",
|
||||
driveFolderBg: ":alpha<0.3<@accent",
|
||||
fgHighlighted: ":lighten<3<@fg",
|
||||
fgTransparent: ":alpha<0.5<@fg",
|
||||
panelHeaderBg: ":lighten<3<@panel",
|
||||
panelHeaderFg: "@fg",
|
||||
buttonGradateA: "@accent",
|
||||
buttonGradateB: ":hue<20<@accent",
|
||||
htmlThemeColor: "@bg",
|
||||
panelHighlight: ":lighten<3<@panel",
|
||||
listItemHoverBg: "rgba(255, 255, 255, 0.03)",
|
||||
scrollbarHandle: "rgba(255, 255, 255, 0.2)",
|
||||
inputBorderHover: "rgba(255, 255, 255, 0.2)",
|
||||
wallpaperOverlay: "rgba(0, 0, 0, 0.5)",
|
||||
fgTransparentWeak: ":alpha<0.75<@fg",
|
||||
panelHeaderDivider: "rgba(0, 0, 0, 0)",
|
||||
scrollbarHandleHover: "rgba(255, 255, 255, 0.4)",
|
||||
},
|
||||
author: "somebody ¯_(ツ)_/¯",
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'ac168876-f737-4074-a3fc-a370c732ef48',
|
||||
|
||||
name: 'Mi Cherry Light',
|
||||
name: 'Cherry Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '6ed80faa-74f0-42c2-98e4-a64d9e138eab',
|
||||
|
||||
name: 'Mi Coffee Light',
|
||||
name: 'Coffee Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'a58a0abb-ff8c-476a-8dec-0ad7837e7e96',
|
||||
|
||||
name: 'Mi Rainy Light',
|
||||
name: 'Rainy Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '213273e5-7d20-d5f0-6e36-1b6a4f67115c',
|
||||
|
||||
name: 'Mi Sushi Light',
|
||||
name: 'Sushi Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: 'e2c940b5-6e9a-4c03-b738-261c720c426d',
|
||||
base: 'light',
|
||||
name: 'Mi U0 Light',
|
||||
name: 'U0 Light',
|
||||
props: {
|
||||
X2: ':darken<2<@panel',
|
||||
X3: 'rgba(255, 255, 255, 0.05)',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
id: '6128c2a9-5c54-43fe-a47d-17942356470b',
|
||||
|
||||
name: 'Mi Vivid Light',
|
||||
name: 'Vivid Light',
|
||||
author: 'syuilo',
|
||||
|
||||
base: 'light',
|
||||
|
|
|
@ -440,7 +440,7 @@ function more(ev: MouseEvent) {
|
|||
color: var(--navActive);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
"@jovikowi@calckey.social",
|
||||
"@padraig@calckey.social",
|
||||
"@pancakes@cats.city",
|
||||
"@theresmiling@calckey.social",
|
||||
"@AlderForrest@calckey.social",
|
||||
"@kristian@calckey.social",
|
||||
"Interkosmos Link"
|
||||
]
|
||||
}
|
||||
|
|
165
pnpm-lock.yaml
165
pnpm-lock.yaml
|
@ -24,7 +24,7 @@ importers:
|
|||
version: 7.2.0
|
||||
focus-trap-vue:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(focus-trap@7.2.0)(vue@3.2.45)
|
||||
version: 4.0.1(focus-trap@7.2.0)(vue@3.2.47)
|
||||
js-yaml:
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0
|
||||
|
@ -726,8 +726,8 @@ importers:
|
|||
specifier: 4.19.1
|
||||
version: 4.19.1
|
||||
browser-image-resizer:
|
||||
specifier: https://github.com/misskey-dev/browser-image-resizer.git
|
||||
version: github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c
|
||||
specifier: github:misskey-dev/browser-image-resizer
|
||||
version: github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e
|
||||
calckey-js:
|
||||
specifier: workspace:*
|
||||
version: link:../calckey-js
|
||||
|
@ -1071,6 +1071,11 @@ packages:
|
|||
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
/@babel/helper-string-parser@7.21.5:
|
||||
resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: false
|
||||
|
||||
/@babel/helper-validator-identifier@7.19.1:
|
||||
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -1114,6 +1119,14 @@ packages:
|
|||
'@babel/types': 7.21.4
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.21.5:
|
||||
resolution: {integrity: sha512-J+IxH2IsxV4HbnTrSWgMAQj0UEo61hDA4Ny8h8PCX0MLXiibqHbqIOVneqdocemSBc22VpBKxt4J6FQzy9HarQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.21.5
|
||||
dev: false
|
||||
|
||||
/@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4):
|
||||
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
|
||||
peerDependencies:
|
||||
|
@ -1300,6 +1313,15 @@ packages:
|
|||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/types@7.21.5:
|
||||
resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.21.5
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
to-fast-properties: 2.0.0
|
||||
dev: false
|
||||
|
||||
/@bcoe/v8-coverage@0.2.3:
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
dev: true
|
||||
|
@ -3809,12 +3831,30 @@ packages:
|
|||
'@vue/shared': 3.2.45
|
||||
estree-walker: 2.0.2
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-core@3.2.47:
|
||||
resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.5
|
||||
'@vue/shared': 3.2.47
|
||||
estree-walker: 2.0.2
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-dom@3.2.45:
|
||||
resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.2.45
|
||||
'@vue/shared': 3.2.45
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-dom@3.2.47:
|
||||
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-sfc@2.7.14:
|
||||
resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
|
||||
|
@ -3837,12 +3877,36 @@ packages:
|
|||
magic-string: 0.25.9
|
||||
postcss: 8.4.21
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-sfc@3.2.47:
|
||||
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.5
|
||||
'@vue/compiler-core': 3.2.47
|
||||
'@vue/compiler-dom': 3.2.47
|
||||
'@vue/compiler-ssr': 3.2.47
|
||||
'@vue/reactivity-transform': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.25.9
|
||||
postcss: 8.4.23
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-ssr@3.2.45:
|
||||
resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.2.45
|
||||
'@vue/shared': 3.2.45
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-ssr@3.2.47:
|
||||
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
dev: false
|
||||
|
||||
/@vue/reactivity-transform@3.2.45:
|
||||
resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
|
||||
|
@ -3852,17 +3916,43 @@ packages:
|
|||
'@vue/shared': 3.2.45
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.25.9
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity-transform@3.2.47:
|
||||
resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.5
|
||||
'@vue/compiler-core': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.25.9
|
||||
dev: false
|
||||
|
||||
/@vue/reactivity@3.2.45:
|
||||
resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
|
||||
dependencies:
|
||||
'@vue/shared': 3.2.45
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity@3.2.47:
|
||||
resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==}
|
||||
dependencies:
|
||||
'@vue/shared': 3.2.47
|
||||
dev: false
|
||||
|
||||
/@vue/runtime-core@3.2.45:
|
||||
resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.2.45
|
||||
'@vue/shared': 3.2.45
|
||||
dev: true
|
||||
|
||||
/@vue/runtime-core@3.2.47:
|
||||
resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==}
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
dev: false
|
||||
|
||||
/@vue/runtime-dom@3.2.45:
|
||||
resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
|
||||
|
@ -3870,6 +3960,15 @@ packages:
|
|||
'@vue/runtime-core': 3.2.45
|
||||
'@vue/shared': 3.2.45
|
||||
csstype: 2.6.21
|
||||
dev: true
|
||||
|
||||
/@vue/runtime-dom@3.2.47:
|
||||
resolution: {integrity: sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==}
|
||||
dependencies:
|
||||
'@vue/runtime-core': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
csstype: 2.6.21
|
||||
dev: false
|
||||
|
||||
/@vue/server-renderer@3.2.45(vue@3.2.45):
|
||||
resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
|
||||
|
@ -3879,9 +3978,25 @@ packages:
|
|||
'@vue/compiler-ssr': 3.2.45
|
||||
'@vue/shared': 3.2.45
|
||||
vue: 3.2.45
|
||||
dev: true
|
||||
|
||||
/@vue/server-renderer@3.2.47(vue@3.2.47):
|
||||
resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==}
|
||||
peerDependencies:
|
||||
vue: 3.2.47
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.2.47
|
||||
'@vue/shared': 3.2.47
|
||||
vue: 3.2.47
|
||||
dev: false
|
||||
|
||||
/@vue/shared@3.2.45:
|
||||
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
|
||||
dev: true
|
||||
|
||||
/@vue/shared@3.2.47:
|
||||
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
|
||||
dev: false
|
||||
|
||||
/@webassemblyjs/ast@1.11.1:
|
||||
resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
|
||||
|
@ -7439,14 +7554,14 @@ packages:
|
|||
readable-stream: 2.3.7
|
||||
dev: true
|
||||
|
||||
/focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.45):
|
||||
/focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.47):
|
||||
resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
|
||||
peerDependencies:
|
||||
focus-trap: ^7.0.0
|
||||
vue: ^3.0.0
|
||||
dependencies:
|
||||
focus-trap: 7.2.0
|
||||
vue: 3.2.45
|
||||
vue: 3.2.47
|
||||
dev: false
|
||||
|
||||
/focus-trap@7.2.0:
|
||||
|
@ -7606,7 +7721,7 @@ packages:
|
|||
resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
graceful-fs: 4.2.10
|
||||
through2: 2.0.5
|
||||
dev: true
|
||||
|
||||
|
@ -9695,7 +9810,7 @@ packages:
|
|||
/jsonfile@4.0.0:
|
||||
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
graceful-fs: 4.2.10
|
||||
|
||||
/jsonfile@5.0.0:
|
||||
resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
|
||||
|
@ -9709,7 +9824,7 @@ packages:
|
|||
dependencies:
|
||||
universalify: 2.0.0
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
graceful-fs: 4.2.10
|
||||
dev: true
|
||||
|
||||
/jsonld@6.0.0:
|
||||
|
@ -10832,6 +10947,12 @@ packages:
|
|||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
/nanoid@3.3.6:
|
||||
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/nanomatch@1.2.13:
|
||||
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -11953,6 +12074,15 @@ packages:
|
|||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/postcss@8.4.23:
|
||||
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.6
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
dev: false
|
||||
|
||||
/postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -14675,7 +14805,7 @@ packages:
|
|||
dependencies:
|
||||
append-buffer: 1.0.2
|
||||
convert-source-map: 1.9.0
|
||||
graceful-fs: 4.2.11
|
||||
graceful-fs: 4.2.10
|
||||
normalize-path: 2.1.1
|
||||
now-and-later: 2.0.1
|
||||
remove-bom-buffer: 3.0.0
|
||||
|
@ -14788,6 +14918,17 @@ packages:
|
|||
'@vue/runtime-dom': 3.2.45
|
||||
'@vue/server-renderer': 3.2.45(vue@3.2.45)
|
||||
'@vue/shared': 3.2.45
|
||||
dev: true
|
||||
|
||||
/vue@3.2.47:
|
||||
resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.2.47
|
||||
'@vue/compiler-sfc': 3.2.47
|
||||
'@vue/runtime-dom': 3.2.47
|
||||
'@vue/server-renderer': 3.2.47(vue@3.2.47)
|
||||
'@vue/shared': 3.2.47
|
||||
dev: false
|
||||
|
||||
/vuedraggable@4.1.0(vue@3.2.45):
|
||||
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
|
||||
|
@ -15402,10 +15543,10 @@ packages:
|
|||
resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==}
|
||||
dev: false
|
||||
|
||||
github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c:
|
||||
resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/0380d12c8e736788ea7f4e6e985175521ea7b23c}
|
||||
github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e:
|
||||
resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/56f504427ad7f6500e141a6d9f3aee42023d7f3e}
|
||||
name: browser-image-resizer
|
||||
version: 2.2.1-misskey.3
|
||||
version: 2.2.1-misskey.4
|
||||
dev: true
|
||||
|
||||
github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658:
|
||||
|
|
Loading…
Reference in a new issue