Merge branch 'develop' of https://codeberg.org/calckey/calckey into keyboard-accessability
@ -27,9 +27,9 @@
|
||||
- Notable differences:
|
||||
- Improved UI/UX (especially on mobile)
|
||||
- Improved notifications
|
||||
- Fediverse account migration
|
||||
- Improved instance security
|
||||
- Improved accessibility
|
||||
- Improved threads
|
||||
- Recommended Instances timeline
|
||||
- OCR image captioning
|
||||
- New and improved Groups
|
||||
|
@ -675,3 +675,48 @@ useGlobalSetting: Fes servir els ajustos globals
|
||||
useGlobalSettingDesc: Si s'activa, es faran servir els ajustos de notificacions del
|
||||
teu compte. Si es desactiva , es poden fer configuracions individuals.
|
||||
other: Altres
|
||||
menu: Menú
|
||||
addItem: Afegeix un element
|
||||
divider: Divisor
|
||||
relays: Relés
|
||||
addRelay: Afegeix un Relé
|
||||
inboxUrl: Adreça de la safata d'entrada
|
||||
addedRelays: Relés afegits
|
||||
serviceworkerInfo: Ha de estar activat per les notificacions push.
|
||||
poll: Enquesta
|
||||
deletedNote: Article eliminat
|
||||
disablePlayer: Tancar el reproductor de vídeo
|
||||
fileIdOrUrl: ID o adreça URL del fitxer
|
||||
behavior: Comportament
|
||||
regenerateLoginTokenDescription: Regenera el token que es fa servir de manera interna
|
||||
durant l'inici de sessió. Normalment això no és necessari. Si es torna a genera
|
||||
el token, es tancarà la sessió a tots els dispositius.
|
||||
setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais.
|
||||
reportAbuseOf: Informa sobre {name}
|
||||
sample: Exemple
|
||||
abuseReports: Informes
|
||||
reportAbuse: Informe
|
||||
reporter: Informador
|
||||
reporterOrigin: Origen d'el informador
|
||||
forwardReport: Envia l'informe a una instancia remota
|
||||
abuseReported: El teu informe ha sigut enviat. Moltes gràcies.
|
||||
reporteeOrigin: Origen de l'informe
|
||||
send: Enviar
|
||||
abuseMarkAsResolved: Marcar l'informe com a resolt
|
||||
visibility: Visibilitat
|
||||
useCw: Amaga el contingut
|
||||
enablePlayer: Obre el reproductor de vídeo
|
||||
yourAccountSuspendedDescription: Aquest compte ha sigut suspesa per no seguir els
|
||||
termes de servei del servidor o quelcom similar. Contacte amb l'administrador si
|
||||
vols conèixer la raó amb més detall. Si us plau no facis un compte nou.
|
||||
invisibleNote: Article ocult
|
||||
enableInfiniteScroll: Carregar més de forma automàtica
|
||||
fillAbuseReportDescription: Si us plau omple els detalls sobre aquest informe. Si
|
||||
es sobre un article en concret, si us plau inclou l'adreça URL.
|
||||
forwardReportIsAnonymous: Com a informador a l'instància remota no es mostrarà el
|
||||
teu compte, si no un compte anònim.
|
||||
openInNewTab: Obrir en una pestanya nova
|
||||
openInSideView: Obrir a la vista lateral
|
||||
defaultNavigationBehaviour: Navegació per defecte
|
||||
editTheseSettingsMayBreakAccount: Si edites aquestes configuracions pots fer mal bé
|
||||
el teu compte.
|
||||
|
@ -1042,7 +1042,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."
|
||||
|
182
locales/fi.yml
@ -3,7 +3,7 @@ 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 +41,183 @@ 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.
|
||||
_lang_: Suomi
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "calckey",
|
||||
"version": "13.2.0-dev38",
|
||||
"version": "13.2.0-dev41",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 21 KiB |
BIN
packages/backend/assets/inverse wordmark.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 20 KiB |
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"
|
||||
`);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
"@bull-board/api": "^4.6.4",
|
||||
"@bull-board/koa": "^4.6.4",
|
||||
"@bull-board/ui": "^4.6.4",
|
||||
"@calckey/megalodon": "5.1.24",
|
||||
"@calckey/megalodon": "5.2.0",
|
||||
"@discordapp/twemoji": "14.0.2",
|
||||
"@elastic/elasticsearch": "7.17.0",
|
||||
"@koa/cors": "3.4.3",
|
||||
|
@ -89,6 +89,11 @@ export type Source = {
|
||||
authKey?: string;
|
||||
isPro?: boolean;
|
||||
};
|
||||
libreTranslate: {
|
||||
managed?: boolean;
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
email: {
|
||||
managed?: boolean;
|
||||
address?: string;
|
||||
|
@ -386,6 +386,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,
|
||||
|
@ -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") {
|
||||
|
@ -512,7 +512,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,
|
||||
@ -564,6 +565,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,
|
||||
};
|
||||
|
@ -124,6 +124,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 },
|
||||
@ -515,6 +517,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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -20,9 +20,12 @@ export default defineComponent({
|
||||
},
|
||||
computed: {
|
||||
compiledFormula(): any {
|
||||
return katex.renderToString(this.formula, {
|
||||
const katexString = katex.renderToString(this.formula, {
|
||||
throwOnError: false,
|
||||
} as any);
|
||||
return this.block
|
||||
? `<div style="text-align:center">${katexString}</div>`
|
||||
: katexString;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -89,7 +89,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tail" :class="{ collapsed }">
|
||||
<div class="tail">
|
||||
<header>
|
||||
<span v-if="notification.type === 'pollEnded'">{{
|
||||
i18n.ts._notification.pollEnded
|
||||
@ -112,11 +112,11 @@
|
||||
v-if="notification.type === 'reaction'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<i class="ph-quotes ph-fill ph-lg"></i>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -142,10 +142,10 @@
|
||||
v-if="notification.type === 'reply'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -155,10 +155,10 @@
|
||||
v-if="notification.type === 'mention'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -168,10 +168,10 @@
|
||||
v-if="notification.type === 'quote'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -181,11 +181,11 @@
|
||||
v-if="notification.type === 'pollVote'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<i class="ph-quotes ph-fill ph-lg"></i>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -196,11 +196,11 @@
|
||||
v-if="notification.type === 'pollEnded'"
|
||||
class="text"
|
||||
:to="notePage(notification.note)"
|
||||
:title="summary"
|
||||
:title="getNoteSummary(notification.note)"
|
||||
>
|
||||
<i class="ph-quotes ph-fill ph-lg"></i>
|
||||
<Mfm
|
||||
:text="summary"
|
||||
:text="getNoteSummary(notification.note)"
|
||||
:plain="true"
|
||||
:nowrap="!full"
|
||||
:custom-emojis="notification.note.emojis"
|
||||
@ -264,7 +264,6 @@
|
||||
<span v-if="notification.type === 'app'" class="text">
|
||||
<Mfm :text="notification.body" :nowrap="!full" />
|
||||
</span>
|
||||
<xShowMoreButton v-if="isLong" v-model="collapsed"></xShowMoreButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -275,7 +274,6 @@ import * as misskey from "calckey-js";
|
||||
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||
import XReactionTooltip from "@/components/MkReactionTooltip.vue";
|
||||
import XShowMoreButton from "./MkShowMoreButton.vue";
|
||||
import { getNoteSummary } from "@/scripts/get-note-summary";
|
||||
import { notePage } from "@/filters/note";
|
||||
import { userPage } from "@/filters/user";
|
||||
@ -301,19 +299,12 @@ const props = withDefaults(
|
||||
const elRef = ref<HTMLElement>(null);
|
||||
const reactionRef = ref(null);
|
||||
|
||||
const summary = getNoteSummary(props.notification.note);
|
||||
|
||||
const showEmojiReactions =
|
||||
defaultStore.state.enableEmojiReactions ||
|
||||
defaultStore.state.showEmojisInReactionNotifications;
|
||||
const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReaction)
|
||||
? instance.defaultReaction
|
||||
: "⭐";
|
||||
const isLong = (summary.split("\n").length > 3 || summary.length > 200);
|
||||
const collapsed = $ref(isLong);
|
||||
|
||||
|
||||
|
||||
|
||||
let readObserver: IntersectionObserver | undefined;
|
||||
let connection;
|
||||
@ -495,7 +486,6 @@ useTooltip(reactionRef, (showing) => {
|
||||
}
|
||||
|
||||
> .tail {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@ -536,17 +526,6 @@ useTooltip(reactionRef, (showing) => {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
&.collapsed > .text {
|
||||
display: block;
|
||||
position: relative;
|
||||
max-height: calc(4em + 50px);
|
||||
overflow: hidden;
|
||||
mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||
-webkit-mask: linear-gradient(
|
||||
black calc(100% - 64px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,68 +0,0 @@
|
||||
<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>
|
@ -120,7 +120,6 @@ import XNoteSimple from "@/components/MkNoteSimple.vue";
|
||||
import XMediaList from "@/components/MkMediaList.vue";
|
||||
import XPoll from "@/components/MkPoll.vue";
|
||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||
import XShowMoreButton from "./MkShowMoreButton.vue";
|
||||
import XCwButton from "@/components/MkCwButton.vue";
|
||||
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
|
||||
import { i18n } from "@/i18n";
|
||||
@ -145,7 +144,6 @@ const isLong =
|
||||
props.note.text != null &&
|
||||
(props.note.text.split("\n").length > 9 || props.note.text.length > 500);
|
||||
const collapsed = $ref(props.note.cw == null && isLong);
|
||||
|
||||
const urls = props.note.text
|
||||
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
||||
: null;
|
||||
@ -274,6 +272,43 @@ function focusFooter(ev) {
|
||||
top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,20 @@
|
||||
:custom-emojis="user.emojis"
|
||||
/>
|
||||
</div>
|
||||
<XShowMoreButton v-if="isLong" v-model="collapsed"></XShowMoreButton>
|
||||
<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>
|
||||
<div v-if="user.fields.length > 0" class="fields">
|
||||
<dl
|
||||
v-for="(field, i) in user.fields"
|
||||
@ -115,7 +128,6 @@ import * as Acct from "calckey-js/built/acct";
|
||||
import type * as misskey from "calckey-js";
|
||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||
import { userPage } from "@/filters/user";
|
||||
import XShowMoreButton from "./MkShowMoreButton.vue";
|
||||
import * as os from "@/os";
|
||||
import { $i } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
|
@ -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();
|
||||
|
@ -87,8 +87,8 @@ importers:
|
||||
specifier: ^4.6.4
|
||||
version: 4.10.2
|
||||
'@calckey/megalodon':
|
||||
specifier: 5.1.24
|
||||
version: 5.1.24
|
||||
specifier: 5.2.0
|
||||
version: 5.2.0
|
||||
'@discordapp/twemoji':
|
||||
specifier: 14.0.2
|
||||
version: 14.0.2
|
||||
@ -1384,8 +1384,8 @@ packages:
|
||||
'@bull-board/api': 4.10.2
|
||||
dev: false
|
||||
|
||||
/@calckey/megalodon@5.1.24:
|
||||
resolution: {integrity: sha512-VRd6x8MFQ2pMF0rnGF67/GVxgp/92CV7lg2XT1wnPAfQZ1NTsjwlDQX3HewEW3fSG/r7Nzh5WbIBXC8WMWKs9g==}
|
||||
/@calckey/megalodon@5.2.0:
|
||||
resolution: {integrity: sha512-9MEjzKJPyd7o5bHGGlNq4oE1tMt22GUJ8o8tZXcXSpXlrSDb2rSwumirM1KXUWTW8G6NGi1leCM59gOBGLko3w==}
|
||||
engines: {node: '>=15.0.0'}
|
||||
dependencies:
|
||||
'@types/oauth': 0.9.1
|
||||
@ -5910,8 +5910,8 @@ packages:
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/core-js@3.30.0:
|
||||
resolution: {integrity: sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==}
|
||||
/core-js@3.30.1:
|
||||
resolution: {integrity: sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
@ -15413,7 +15413,7 @@ packages:
|
||||
name: plyr
|
||||
version: 3.7.0
|
||||
dependencies:
|
||||
core-js: 3.30.0
|
||||
core-js: 3.30.1
|
||||
custom-event-polyfill: 1.0.7
|
||||
loadjs: 4.2.0
|
||||
rangetouch: 2.0.1
|
||||
|