Merge branch 'develop' into 'fix/local-user-notes-count'
# Conflicts: # packages/backend/src/services/note/delete.ts
This commit is contained in:
commit
d002741ecc
194 changed files with 1947 additions and 1402 deletions
|
@ -48,7 +48,7 @@ FROM docker.io/node:20-slim
|
|||
WORKDIR /firefish
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && DEBIAN_FRONTEND='noninteractive' apt-get install -y --no-install-recommends zip unzip tini ffmpeg ca-certificates
|
||||
RUN apt-get update && DEBIAN_FRONTEND='noninteractive' apt-get install -y --no-install-recommends zip unzip tini ffmpeg ca-certificates curl
|
||||
|
||||
RUN echo 'deb https://deb.debian.org/debian experimental main' | tee /etc/apt/sources.list
|
||||
RUN apt-get update && DEBIAN_FRONTEND='noninteractive' apt-get --target-release experimental install -y --no-install-recommends libc6
|
||||
|
|
|
@ -48,7 +48,7 @@ If you have access to a server that supports one of the sources below, I recomme
|
|||
|
||||
## Dependencies
|
||||
|
||||
- At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20/v21 recommended)
|
||||
- At least [NodeJS](https://nodejs.org/en/) v18.17.0 (v20/v21 recommended)
|
||||
- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended)
|
||||
- At least [Redis](https://redis.io/) v7
|
||||
- Web Proxy (one of the following)
|
||||
|
|
|
@ -6,8 +6,10 @@ services:
|
|||
container_name: firefish_web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
|
@ -19,6 +21,15 @@ services:
|
|||
- ./custom:/firefish/custom:ro
|
||||
- ./files:/firefish/files
|
||||
- ./.config:/firefish/.config:ro
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:3000 || exit 1
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4096M
|
||||
|
||||
redis:
|
||||
restart: unless-stopped
|
||||
|
@ -28,6 +39,16 @@ services:
|
|||
- calcnet
|
||||
volumes:
|
||||
- ./redis:/data
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 200M
|
||||
|
||||
|
||||
db:
|
||||
restart: unless-stopped
|
||||
|
@ -39,6 +60,15 @@ services:
|
|||
- .config/docker.env
|
||||
volumes:
|
||||
- ./db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: pg_isready --user="$${POSTGRES_USER}" --dbname="$${POSTGRES_DB}"
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 200M
|
||||
|
||||
networks:
|
||||
calcnet:
|
||||
|
|
|
@ -4,6 +4,7 @@ Breaking changes are indicated by the :warning: icon.
|
|||
|
||||
## Unreleased
|
||||
|
||||
- :warning: `followingCount` and `followersCount` in `users/show` will be `null` (instead of 0) if these values are unavailable.
|
||||
- :warning: `admin/search/index-all` is removed since posts are now indexed automatically.
|
||||
- New optional parameters are added to `notes/search` endpoint:
|
||||
- `sinceDate`
|
||||
|
|
|
@ -11,9 +11,9 @@ Critical security updates are indicated by the :warning: icon.
|
|||
- Add langage annotation to post contents (!10687)
|
||||
- Add a toggleable setting to show a warning when you attempt to post files without alt text
|
||||
- Fix bugs
|
||||
- Update documents
|
||||
- Update documents and example config files
|
||||
|
||||
## v20240301
|
||||
## [v20240301](https://firefish.dev/firefish/firefish/-/compare/v20240229...v20240301?from_project_id=7&straight=false)
|
||||
|
||||
- Add a page (`/my/follow-requests/sent`) to check your follow requests that haven't been approved
|
||||
- Add ability to hide replies from certain users in timelines
|
||||
|
@ -32,34 +32,34 @@ Critical security updates are indicated by the :warning: icon.
|
|||
- Disable new user registration
|
||||
- Fix bugs
|
||||
|
||||
## v20240229
|
||||
## [v20240229](https://firefish.dev/firefish/firefish/-/compare/v20240228...v20240229?from_project_id=7&straight=false)
|
||||
|
||||
- Add ability to pull-down-to-refresh timelines in PWA
|
||||
- Make passkey/security key independent of TOTP (!10670)
|
||||
- Fix bugs
|
||||
|
||||
## v20240228
|
||||
## [v20240228](https://firefish.dev/firefish/firefish/-/compare/v20240225...v20240228?from_project_id=7&straight=false)
|
||||
|
||||
- Update "About Firefish" page (!10673)
|
||||
- Fix bugs (!10675 !10676 !10678 !10679)
|
||||
- Remove charts generation to improve performance (#10611)
|
||||
|
||||
## v20240225
|
||||
## [v20240225](https://firefish.dev/firefish/firefish/-/compare/v20240222...v20240225?from_project_id=7&straight=false)
|
||||
|
||||
- Fix bugs
|
||||
- Add syntax highlighting in MFM code blocks in various programming languages
|
||||
|
||||
## v20240222
|
||||
## [v20240222](https://firefish.dev/firefish/firefish/-/compare/v20240221-1...v20240222?from_project_id=7&straight=false)
|
||||
|
||||
- Enhance Mastodon post import feature (!10652)
|
||||
- Minor style change in the web client
|
||||
- Refactoring
|
||||
|
||||
## v20240221-1
|
||||
## [v20240221-1](https://firefish.dev/firefish/firefish/-/compare/v20240221...v20240221-1?from_project_id=7&straight=false)
|
||||
|
||||
- Fix a bug
|
||||
|
||||
## v20240221
|
||||
## [v20240221](https://firefish.dev/firefish/firefish/-/compare/v20240217-1...v20240221?from_project_id=7&straight=false)
|
||||
|
||||
- Add the ability to give regular (non-moderator) users permission to manage custom emojis
|
||||
- Fix a bug that made impossible to update user profiles under some conditions
|
||||
|
@ -67,11 +67,11 @@ Critical security updates are indicated by the :warning: icon.
|
|||
- It's just a paraphrase of DMs without recipients
|
||||
- You can also convert your existing public posts to private posts
|
||||
|
||||
## :warning: v20240217-1
|
||||
## :warning: [v20240217-1](https://firefish.dev/firefish/firefish/-/compare/v20240217...v20240217-1?from_project_id=7&straight=false)
|
||||
|
||||
- Fix a [security issue](https://github.com/misskey-dev/misskey/security/advisories/GHSA-qqrm-9grj-6v32)
|
||||
|
||||
## v20240217
|
||||
## [v20240217](https://firefish.dev/firefish/firefish/-/compare/v20240216...v20240217?from_project_id=7&straight=false)
|
||||
|
||||
- Add ability to specify the search engine used in the search bar MFM
|
||||
- Remove auto NSFW media detection
|
||||
|
@ -80,49 +80,49 @@ Critical security updates are indicated by the :warning: icon.
|
|||
- Change the second tab on the notifications page from "unread" to "reactions"
|
||||
- Add ability to show a huge post button on the posting form
|
||||
- This is a joke feature inspired by https://mstdn.poyo.me/@prime/110668364208741253
|
||||
- Bug fix
|
||||
- Fix bugs
|
||||
- Add `/api/emojis` endpoint (compatible with Misskey v13) for better experiences with Misskey clients
|
||||
- This does not mean we will continue to maintain API compatibility with Misskey. Instead, we plan to improve the compatibility with the Mastodon API.
|
||||
|
||||
## v20240216
|
||||
## [v20240216](https://firefish.dev/firefish/firefish/-/compare/v20240215...v20240216?from_project_id=7&straight=false)
|
||||
|
||||
- Style changes in the web client (a770ef4314e21f17fdce1f19feb3758953b04486 ab39ff5954a392cc6688a02f1723e1702df5e35c 4eefd534d8150e2cd5cf31dddd327edceb5b84dc)
|
||||
- Clicking the "like" button now sends the actual emoji reaction (star, good, heart, etc.) instead of an empty "like"
|
||||
|
||||
## v20240215
|
||||
## [v20240215](https://firefish.dev/firefish/firefish/-/compare/v20240214...v20240215?from_project_id=7&straight=false)
|
||||
|
||||
- Separate settings for displaying rounded avatars for cat and non-cat accounts
|
||||
- Add a toggleable setting to replace the chat button with account menu on mobile
|
||||
- Reduce the size of the container image (!10667)
|
||||
|
||||
## v20240214
|
||||
## [v20240214](https://firefish.dev/firefish/firefish/-/compare/v20240213...v20240214?from_project_id=7&straight=false)
|
||||
|
||||
- Fix container images
|
||||
|
||||
## v20240213
|
||||
## [v20240213](https://firefish.dev/firefish/firefish/-/compare/v20240212...v20240213?from_project_id=7&straight=false)
|
||||
|
||||
- Bug fix
|
||||
- Fix bugs
|
||||
- Refactoring
|
||||
|
||||
## v20240212
|
||||
## [v20240212](https://firefish.dev/firefish/firefish/-/compare/v20240210...v20240212?from_project_id=7&straight=false)
|
||||
|
||||
- Refactoring
|
||||
- Add a toggleable setting to hide follow buttons in a misclickable position
|
||||
- Add a toggleable setting to show preview in posting form by default
|
||||
|
||||
## v20240210
|
||||
## [v20240210](https://firefish.dev/firefish/firefish/-/compare/v20240208...v20240210?from_project_id=7&straight=false)
|
||||
|
||||
- Security update (cf5b42a160ae8a4d94bf3dcea04ce12935ca4f76)
|
||||
- Refactoring
|
||||
|
||||
## v20240208
|
||||
## [v20240208](https://firefish.dev/firefish/firefish/-/compare/v20240206...v20240208?from_project_id=7&straight=false)
|
||||
|
||||
- Bug fix (!10654 !10665)
|
||||
- Fix bugs (!10654 !10665)
|
||||
- Enlarge profile picture by clicking it (!10659)
|
||||
- Support Pleroma chat (!10660)
|
||||
- [Add documentation about downgrading](./docs/downgrade.md)
|
||||
|
||||
## v20240206
|
||||
## [v20240206](https://firefish.dev/firefish/firefish/-/compare/v1.0.5-rc...v20240206?from_project_id=7&straight=false)
|
||||
|
||||
- Many bug fixes
|
||||
- Fix many bugs
|
||||
- Per-post language selector (!10616)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
BEGIN;
|
||||
|
||||
DELETE FROM "migrations" WHERE name IN (
|
||||
'FixMutingIndices1710690239308',
|
||||
'RemoveMentionedUsersColumn1710688552234',
|
||||
'NoteFile1710304584214',
|
||||
'RenameMetaColumns1705944717480',
|
||||
'SeparateHardMuteWordsAndPatterns1706413792769',
|
||||
'IndexAltTextAndCw1708872574733',
|
||||
|
@ -16,6 +19,33 @@ DELETE FROM "migrations" WHERE name IN (
|
|||
'RemoveNativeUtilsMigration1705877093218'
|
||||
);
|
||||
|
||||
-- fix-muting-indices
|
||||
DROP INDEX "IDX_renote_muting_createdAt";
|
||||
DROP INDEX "IDX_renote_muting_muteeId";
|
||||
DROP INDEX "IDX_renote_muting_muterId";
|
||||
DROP INDEX "IDX_reply_muting_createdAt";
|
||||
DROP INDEX "IDX_reply_muting_muteeId";
|
||||
DROP INDEX "IDX_reply_muting_muterId";
|
||||
CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt");
|
||||
CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId");
|
||||
CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId");
|
||||
|
||||
-- remove-mentioned-users-column
|
||||
ALTER TABLE "note" ADD "mentionedRemoteUsers" text NOT NULL DEFAULT '[]'::text;
|
||||
CREATE TABLE "temp_mentions_1710688552234" AS
|
||||
SELECT "id", "url", "uri", "username", "host"
|
||||
FROM "user"
|
||||
JOIN "user_profile" ON "user"."id" = "user_profile". "userId" WHERE "user"."host" IS NOT NULL;
|
||||
CREATE UNIQUE INDEX "temp_mentions_id" ON "temp_mentions_1710688552234" ("id");
|
||||
UPDATE "note" SET "mentionedRemoteUsers" = (
|
||||
SELECT COALESCE(json_agg(row_to_json("data")::jsonb - 'id')::text, '[]') FROM "temp_mentions_1710688552234" AS "data"
|
||||
WHERE "data"."id" = ANY("note"."mentions")
|
||||
);
|
||||
DROP TABLE "temp_mentions_1710688552234";
|
||||
|
||||
-- note-file
|
||||
DROP TABLE "note_file";
|
||||
|
||||
-- rename-meta-columns
|
||||
ALTER TABLE "meta" RENAME COLUMN "tosUrl" TO "ToSUrl";
|
||||
ALTER TABLE "meta" RENAME COLUMN "objectStorageUseSsl" TO "objectStorageUseSSL";
|
||||
|
|
|
@ -4,6 +4,9 @@ The full-text search engine used in Firefish has been changed to [PGroonga](http
|
|||
|
||||
## For systemd/pm2 users
|
||||
|
||||
- Required Node.js version has been bumped from v18.16.0 to v18.17.0.
|
||||
- You need to install PGroonga on your system. Please follow the instructions below.
|
||||
|
||||
### 1. Install PGroonga
|
||||
|
||||
Please execute `psql --version` to check your PostgreSQL major version. This will print a message like this:
|
||||
|
|
|
@ -323,8 +323,8 @@ _2fa:
|
|||
securityKeyInfo: A més de l'autenticació d'empremta digital o PIN, també podeu configurar
|
||||
l'autenticació mitjançant claus de seguretat de maquinari compatibles amb FIDO2
|
||||
per protegir encara més el vostre compte.
|
||||
step4: A partir d'ara, qualsevol intent d'inici de sessió futur demanarà aquest
|
||||
token d'inici de sessió.
|
||||
step4: A partir d'ara, qualsevol intent d'inici de sessió futur demanarà aquesta
|
||||
clau d'inici de sessió.
|
||||
registerSecurityKey: Registrar una clau de seguretat o d'accés
|
||||
step1: En primer lloc, instal·la una aplicació d'autenticació (com ara {a} o {b})
|
||||
al dispositiu.
|
||||
|
@ -2067,9 +2067,8 @@ _relayStatus:
|
|||
deleted: Eliminat
|
||||
editNote: Edita la publicació
|
||||
edited: 'Editat el {date} {time}'
|
||||
signupsDisabled: Actualment, les inscripcions en aquest servidor estan desactivades,
|
||||
però sempre podeu registrar-vos en un altre servidor. Si teniu un codi d'invitació
|
||||
per a aquest servidor, introduïu-lo a continuació.
|
||||
signupsDisabled: Actualment, les inscripcions en aquest servidor estan desactivades.
|
||||
Si teniu un codi d'invitació per a aquest servidor, introduïu-lo a continuació.
|
||||
userSaysSomethingReasonQuote: '{name} ha citat una publicació que conté {reason}'
|
||||
userSaysSomethingReasonReply: '{name} ha respost a una publicació que conté {reason}'
|
||||
userSaysSomethingReasonRenote: '{name} ha impulsat una publicació que conté {reason}'
|
||||
|
@ -2253,7 +2252,34 @@ searchWordsDescription: "Per cercar publicacions, escriu el terme a buscar. Sepa
|
|||
o la ID en aquest camp i fes clic al botó 'Trobar'. Fent clic a 'Cercar' trobarà
|
||||
publicacions que, literalment , continguin la ID/adreça URL."
|
||||
searchPostsWithFiles: Només publicacions amb fitxers
|
||||
searchCwAndAlt: Inclou avisos de contingut i arxius amb descripcions.
|
||||
searchCwAndAlt: Inclou avisos de contingut i arxius amb descripcions
|
||||
searchUsers: Publicat per (opcional)
|
||||
searchRange: Publicat dintre de (opcional)
|
||||
publishTimelines: Publica línies de temps per visitants
|
||||
toPost: Publicà
|
||||
publishTimelinesDescription: Si està activat, les línies de temps Global i Local es
|
||||
mostraran a {url} fins i tot sense estar registrat.
|
||||
noAltTextWarning: Alguns fitxers adjunts no tenen una descripció. T'has s oblidat
|
||||
d'escriure-les?
|
||||
showNoAltTextWarning: Mostra un avís si públiques un fitxer sense descripció
|
||||
toReply: Respon
|
||||
toQuote: Cita
|
||||
toEdit: Edita
|
||||
searchUsersDescription: "Per buscar publicacions concretes d'un usuari/servidor, escriu
|
||||
la ID (@usuari@exemple.com, o @usuari per un usuari local) o nom del domini (exemple.com).\n
|
||||
\nSi escrius 'me' (sense cometes), totes les teves publicacions (incloent-hi publicacions
|
||||
sense llistar, només per a seguidors i secretes) es buscaran.\n\nSi escrius 'local'
|
||||
(sense cometes), el resultat serà filtrat per mostrar només publicacions d'aquest
|
||||
servidor."
|
||||
messagingUnencryptedInfo: Els xats a Firefish no són encriptats d'extrem a extrem.
|
||||
No comparteixis dades sensibles fent servir Firefish.
|
||||
searchRangeDescription: "Si vols filtrar per un període de temps, has de fer servir
|
||||
aquest format: 20220615-20231031\n\nSi no escrius l'any (per exemple 0105-0106 o
|
||||
20231105-0110), serà interpretat com l'any en curs.\n\nInclús pots morir la data
|
||||
de començament o de finalització. Per exemple, -0102 filtrarà els resultats per
|
||||
mostrar només publicacions fetes abans del 2 de gener d'aquest any, i 20231026-
|
||||
filtrarà els resultats per mostrar publicacions fetes després del 26 d'octubre del
|
||||
2023."
|
||||
moderationNote: Nota de moderació
|
||||
ipFirstAcknowledged: Data en què es va veure la adreça IP per primera vegada
|
||||
driveCapacityOverride: Capacitat del disc esgotada
|
||||
|
|
|
@ -2218,3 +2218,4 @@ renotes: Boosts
|
|||
quotes: Zitate
|
||||
moreUrlsDescription: "Die Seiten, welche angepinnt werde sollen, im Hilfe-Menü in
|
||||
der unteren linken Ecke in folgender Notation angeben:\n\"Anzeigename\": https://example.com/"
|
||||
toQuote: Zitat
|
||||
|
|
|
@ -462,7 +462,7 @@ securityKeyName: "Key name"
|
|||
registerSecurityKey: "Register a security key"
|
||||
lastUsed: "Last used"
|
||||
unregister: "Unregister"
|
||||
passwordLessLogin: "Password-less login"
|
||||
passwordLessLogin: "Password-less sign in"
|
||||
resetPassword: "Reset password"
|
||||
newPasswordIs: "The new password is \"{password}\""
|
||||
reduceUiAnimation: "Reduce UI animations"
|
||||
|
@ -527,7 +527,7 @@ disableDrawer: "Don't use drawer-style menus"
|
|||
youHaveNoGroups: "You have no groups"
|
||||
joinOrCreateGroup: "Get invited to a group or create your own."
|
||||
noHistory: "No history available"
|
||||
signinHistory: "Login history"
|
||||
signinHistory: "Sign in history"
|
||||
disableAnimatedMfm: "Disable MFM with animation"
|
||||
doing: "Processing..."
|
||||
category: "Category"
|
||||
|
@ -717,9 +717,9 @@ useGlobalSetting: "Use global settings"
|
|||
useGlobalSettingDesc: "If turned on, your account's notification settings will be
|
||||
used. If turned off, individual configurations can be made."
|
||||
other: "Other"
|
||||
regenerateLoginToken: "Regenerate login token"
|
||||
regenerateLoginTokenDescription: "Regenerates the token used internally during login.
|
||||
Normally this action is not necessary. If regenerated, all devices will be logged
|
||||
regenerateLoginToken: "Regenerate sign in token"
|
||||
regenerateLoginTokenDescription: "Regenerates the token used internally during sign
|
||||
in. Normally this action is not necessary. If regenerated, all devices will be logged
|
||||
out."
|
||||
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
|
||||
fileIdOrUrl: "File ID or URL"
|
||||
|
@ -1001,7 +1001,7 @@ check: "Check"
|
|||
driveCapOverrideLabel: "Change the drive capacity for this user"
|
||||
driveCapOverrideCaption: "Reset the capacity to default by inputting a value of 0
|
||||
or lower."
|
||||
requireAdminForView: "You must log in with an administrator account to view this."
|
||||
requireAdminForView: "You must sign in with an administrator account to view this."
|
||||
isSystemAccount: "This account is created and automatically operated by the system.
|
||||
Please do not moderate, edit, delete, or otherwise tamper with this account, or
|
||||
it may break your server."
|
||||
|
@ -1011,7 +1011,7 @@ document: "Documentation"
|
|||
numberOfPageCache: "Number of cached pages"
|
||||
numberOfPageCacheDescription: "Increasing this number will improve convenience for
|
||||
users but cause more server load as well as more memory to be used."
|
||||
logoutConfirm: "Really log out?"
|
||||
logoutConfirm: "Really sign out?"
|
||||
lastActiveDate: "Last used at"
|
||||
statusbar: "Status bar"
|
||||
pleaseSelect: "Select an option"
|
||||
|
@ -1112,6 +1112,9 @@ signupsDisabled: "Signups on this server are currently disabled. If you have an
|
|||
code for this server, please enter it below."
|
||||
apps: "Apps"
|
||||
sendModMail: "Send Moderation Notice"
|
||||
moderationNote: "Moderation Note"
|
||||
ipFirstAcknowledged: "The date of the first acquisition of the IP address"
|
||||
driveCapacityOverride: "Drive Capacity Override"
|
||||
preventAiLearning: "Prevent AI bot scraping"
|
||||
preventAiLearningDescription: "Request third-party AI language models not to study
|
||||
content you upload, such as posts and images."
|
||||
|
@ -1195,7 +1198,9 @@ searchWordsDescription: "To search for posts, enter the search term. Separate wo
|
|||
search.\nFor example, 'morning night' will find posts that contain both 'morning'
|
||||
and 'night', and 'morning OR night' will find posts that contain either 'morning'
|
||||
or 'night' (or both).\nYou can also combine AND/OR conditions like '(morning OR
|
||||
night) sleepy'.\n\nIf you want to go to a specific user page or post page, enter
|
||||
night) sleepy'.\nIf you want to search for a sequence of words (e.g., a sentence), you
|
||||
must put it in double quotes, not to make it an AND search: \"Today I learned\"\n\n
|
||||
If you want to go to a specific user page or post page, enter
|
||||
the ID or URL in this field and click the 'Lookup' button. Clicking 'Search' will
|
||||
search for posts that literally contain the ID/URL."
|
||||
searchUsers: "Posted by (optional)"
|
||||
|
@ -1214,7 +1219,7 @@ searchRangeDescription: "If you want to filter the time period, enter it in this
|
|||
searchPostsWithFiles: "Only posts with files"
|
||||
searchCwAndAlt: "Include content warnings and file descriptions"
|
||||
publishTimelines: "Publish timelines for visitors"
|
||||
publishTimelinesDescription: "If enabled, the Local and Global timeline will be shown
|
||||
publishTimelinesDescription: "If enabled, the Local and Global timelines will be shown
|
||||
on {url} even when signed out."
|
||||
noAltTextWarning: "Some attached file(s) have no description. Did you forget to write?"
|
||||
showNoAltTextWarning: "Show a warning if you attempt to post files without a description"
|
||||
|
@ -1627,7 +1632,7 @@ _2fa:
|
|||
step2Url: "You can also enter this URL if you're using a desktop program:"
|
||||
step3Title: "Enter an authentication code"
|
||||
step3: "Enter the token provided by your app to finish setup."
|
||||
step4: "From now on, any future login attempts will ask for such a login token."
|
||||
step4: "From now on, any future sign in attempts will ask for such a token."
|
||||
securityKeyNotSupported: "Your browser does not support security keys."
|
||||
securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup
|
||||
authentication via hardware security keys that support FIDO2 to further secure
|
||||
|
@ -2220,3 +2225,5 @@ _iconSets:
|
|||
moreUrls: "Pinned pages"
|
||||
moreUrlsDescription: "Enter the pages you want to pin to the help menu in the lower
|
||||
left corner using this notation:\n\"Display name\": https://example.com/"
|
||||
messagingUnencryptedInfo: "Chats on Firefish are not end-to-end encrypted. Don't share
|
||||
any sensitive infomation over Firefish."
|
||||
|
|
|
@ -666,7 +666,7 @@ useGlobalSettingDesc: "S'il est activé, les paramètres de notification de votr
|
|||
other: "Autre"
|
||||
regenerateLoginToken: "Régénérer le jeton de connexion"
|
||||
regenerateLoginTokenDescription: "Générer un nouveau jeton d'authentification. Cette
|
||||
opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau jeton,
|
||||
opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau jeton,
|
||||
tous les appareils seront déconnectés."
|
||||
setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant
|
||||
par des espaces."
|
||||
|
@ -2038,7 +2038,7 @@ noInstances: Il n'y a aucun serveur
|
|||
showLocalPosts: 'Montrer les notes locales dans :'
|
||||
homeTimeline: Timeline d'Accueil
|
||||
socialTimeline: Timeline Sociale
|
||||
requireAdminForView: Vous avez besoin d'un compte d'administration pour voir cela.
|
||||
requireAdminForView: Vous avez besoin d'un compte d'administration pour voir ceci.
|
||||
isSystemAccount: Ce compte est créé et géré automatiquement par le système. Veuillez
|
||||
ne pas modérer, éditer, supprimer ou altérer d'une autre manière ce compte, ou cela
|
||||
risque de perturber votre serveur.
|
||||
|
@ -2091,9 +2091,8 @@ _experiments:
|
|||
peut entraîner des ralentissements lors du chargement si votre file d'attente
|
||||
est congestionnée.
|
||||
userSaysSomethingReasonQuote: '{name} a cité une publication contenant {reason}'
|
||||
signupsDisabled: Les inscriptions sur ce serveur sont actuellement désactivés, mais
|
||||
vous pouvez toujours vous inscrire sur un autre serveur ! Si vous avez un code d'invitation
|
||||
pour ce serveur, entrez-le ci-dessous s'il vous plait.
|
||||
signupsDisabled: Les inscriptions sur ce serveur sont actuellement désactivés. Si
|
||||
vous avez un code d'invitation pour ce serveur, saisissez-le ci-dessous.
|
||||
apps: Applications
|
||||
userSaysSomethingReasonReply: '{noms} a répondu à une publication contenant {raison}'
|
||||
defaultValueIs: 'défaut : {valeur}'
|
||||
|
@ -2300,3 +2299,16 @@ searchRangeDescription: "Si vous voulez filtrer par période de temps, saisissez
|
|||
résultats de recherche pour afficher seulement les messages effectués avant le 2
|
||||
janvier de cette année, et 20231026- filtrera les résultats pour afficher seulement
|
||||
les messages effectués après le 26 octobre 2023."
|
||||
toReply: Répondre
|
||||
toPost: Publier
|
||||
toQuote: Citer
|
||||
toEdit: Modifier
|
||||
messagingUnencryptedInfo: Les conversations sur Firefish ne sont pas cryptées. Ne
|
||||
partagez aucune information sensible sur Firefish.
|
||||
moderationNote: Note de modération
|
||||
driveCapacityOverride: Limite de stockage personalisée
|
||||
ipFirstAcknowledged: La date de la première acquisition de l'adresse IP
|
||||
noAltTextWarning: Certains fichiers joints n'ont aucune description. Avez-vous oublié
|
||||
de l'écrire ?
|
||||
showNoAltTextWarning: Afficher un avertissement si vous essayez de publier des fichiers
|
||||
sans description
|
||||
|
|
|
@ -415,7 +415,7 @@ securityKeyName: "Nama kunci"
|
|||
registerSecurityKey: "Daftarkan kunci keamanan"
|
||||
lastUsed: "Terakhir digunakan"
|
||||
unregister: "Batalkan pendaftaran"
|
||||
passwordLessLogin: "Setel login tanpa kata sandi"
|
||||
passwordLessLogin: "Masuk tanpa kata sandi"
|
||||
resetPassword: "Atur ulang kata sandi"
|
||||
newPasswordIs: "Kata sandi baru adalah \"{password}\""
|
||||
reduceUiAnimation: "Kurangi animasi antarmuka"
|
||||
|
@ -655,10 +655,10 @@ useGlobalSetting: "Gunakan setelan global"
|
|||
useGlobalSettingDesc: "Jika dinyalakan, setelan pemberitahuan akun kamu akan digunakan.
|
||||
Jika dimatikan, konfigurasi secara individu dapat dibuat."
|
||||
other: "Lainnya"
|
||||
regenerateLoginToken: "Perbarui token login"
|
||||
regenerateLoginToken: "Perbarui token masuk"
|
||||
regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat
|
||||
login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan
|
||||
dilogout."
|
||||
masuk ke akun. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat
|
||||
akan dikeluarkan dari akun."
|
||||
setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya
|
||||
menggunakan spasi."
|
||||
fileIdOrUrl: "File-ID atau URL"
|
||||
|
@ -1296,8 +1296,8 @@ _2fa:
|
|||
step2Url: "Di aplikasi desktop, masukkan URL berikut:"
|
||||
step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan
|
||||
pemasangan."
|
||||
step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi
|
||||
otentikasi kamu."
|
||||
step4: "Mulai sekarang, upaya pemasukan akun apa pun akan meminta token masuk dari
|
||||
aplikasi autentikasi kamu."
|
||||
securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses
|
||||
login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung
|
||||
FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
|
||||
|
@ -1988,9 +1988,8 @@ moveAccountDescription: Proses ini permanen. Pastikan kamu sudah mengatur alias
|
|||
akun ini ke akun barumu sebelum pindah. Silakan masukkan tag akun dengan format
|
||||
seperti @orang@server.com
|
||||
sendModMail: Kirim Pemberitahuan Moderasi
|
||||
signupsDisabled: Pendaftaran ke server ini nonaktif, tapi kamu dapat selalu mendaftar
|
||||
ke server lain! Jika kamu memiliki kode undangan server ini, harap masukkan di bawah
|
||||
ini.
|
||||
signupsDisabled: Pendaftaran ke server ini nonaktifkam. Jika kamu memiliki kode undangan
|
||||
server ini, harap masukkan di bawah ini.
|
||||
enableCustomKaTeXMacro: Aktifkan makro KaTeX khusus
|
||||
isBot: Akun ini akun otomatis
|
||||
customMOTD: MOTD khusus (pesan layar percik)
|
||||
|
@ -2241,7 +2240,7 @@ reloading: Memuat ulang
|
|||
replyMute: Bisukan balasan dalam lini masa
|
||||
searchRange: Dikirim dalam (opsional)
|
||||
searchUsersDescription: "Untuk mencari kiriman oleh pengguna/server tertentu, masukkan
|
||||
ID (@pengguna@contoh.id, atau @pengguna untuk pengguna lokal) atau nama domain (contoh.id).\n
|
||||
ID (@pengguna@contoh.id, atau @pengguna untuk pengguna lokal) atau nama domain (contoh.id)\n
|
||||
\nJika kamu memasukkan 'me' ('aku', tanpa tanda kutip), semua kirimanmu (termasuk
|
||||
kiriman yang tidak terdaftar, khusus pengikut, langsung, dan rahasia) akan dicari.\n
|
||||
\nJika Anda memasukkan 'local' (tanpa tanda kutip), hasilnya akan disaring untuk
|
||||
|
@ -2253,3 +2252,16 @@ searchRangeDescription: "Jika kamu ingin memfilter periode waktu, masukkan dalam
|
|||
pencarian untuk menampilkan hanya kiriman yang dibuat sebelum tanggal 2 Januari
|
||||
tahun ini, dan 20231026- akan memfilter hasil pencarian untuk menampilkan hanya
|
||||
kiriman yang dibuat setelah tanggal 26 Oktober 2023."
|
||||
toPost: Kirim
|
||||
toQuote: Kutip
|
||||
noAltTextWarning: Beberapa berkas yang dilampirkan tidak memiliki deskripsi. Lupa
|
||||
menulis deskripsinya?
|
||||
toEdit: Sunting
|
||||
showNoAltTextWarning: Tampilkan peringatan jika kamu mencoba mengirim berkas tanpa
|
||||
deskripsi
|
||||
toReply: Balas
|
||||
messagingUnencryptedInfo: Percakapan di Firefish tidak terenkripsi secara ujung ke
|
||||
ujung. Jangan bagikan informasi sensitif apa pun melalui Firefish.
|
||||
moderationNote: Catatan Moderasi
|
||||
driveCapacityOverride: Penimpaan Kapasitas Drive
|
||||
ipFirstAcknowledged: Tanggal akuisisi pertama dari alamat IP
|
||||
|
|
|
@ -107,7 +107,7 @@ cantRenote: "この投稿はブーストできません。"
|
|||
cantReRenote: "ブーストをブーストすることはできません。"
|
||||
quote: "引用"
|
||||
quotes: "引用"
|
||||
toQuote: "引用"
|
||||
toQuote: "引用する"
|
||||
pinnedNote: "ピン留めされた投稿"
|
||||
pinned: "ピン留め"
|
||||
you: "あなた"
|
||||
|
@ -416,7 +416,7 @@ securityKeyName: "キーの名前"
|
|||
registerSecurityKey: "セキュリティキーを登録する"
|
||||
lastUsed: "最後の使用"
|
||||
unregister: "登録を解除"
|
||||
passwordLessLogin: "パスワード無しでログイン"
|
||||
passwordLessLogin: "パスワード無しでサインイン"
|
||||
resetPassword: "パスワードをリセット"
|
||||
newPasswordIs: "新しいパスワードは「{password}」です"
|
||||
reduceUiAnimation: "UIのアニメーションを減らす"
|
||||
|
@ -481,7 +481,7 @@ disableDrawer: "メニューをドロワーで表示しない"
|
|||
youHaveNoGroups: "グループがありません"
|
||||
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
signinHistory: "サインイン履歴"
|
||||
disableAnimatedMfm: "動きのあるMFMを無効にする"
|
||||
doing: "やっています"
|
||||
category: "カテゴリ"
|
||||
|
@ -648,8 +648,8 @@ notificationSettingDesc: "表示する通知の種別を選択してください
|
|||
useGlobalSetting: "グローバル設定を使う"
|
||||
useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。"
|
||||
other: "その他"
|
||||
regenerateLoginToken: "ログイントークンを再生成"
|
||||
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
|
||||
regenerateLoginToken: "サインイントークンを再生成"
|
||||
regenerateLoginTokenDescription: "サインインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスからサインアウトされます。"
|
||||
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
|
||||
fileIdOrUrl: "ファイルIDまたはURL"
|
||||
behavior: "動作"
|
||||
|
@ -908,14 +908,14 @@ thereIsUnresolvedAbuseReportWarning: "未対応の通報があります。"
|
|||
check: "チェック"
|
||||
driveCapOverrideLabel: "このユーザーのドライブ容量上限を変更"
|
||||
driveCapOverrideCaption: "0以下を指定すると解除されます。"
|
||||
requireAdminForView: "閲覧するには管理者アカウントでログインしている必要があります。"
|
||||
requireAdminForView: "閲覧するには管理者アカウントでサインインしている必要があります。"
|
||||
isSystemAccount: "システムにより自動で作成・管理されているアカウントです。モデレーション・編集・削除を行うとサーバーの動作が不正になる可能性があるため、操作しないでください。"
|
||||
typeToConfirm: "この操作を行うには {x} と入力してください"
|
||||
deleteAccount: "アカウント削除"
|
||||
document: "ドキュメント"
|
||||
numberOfPageCache: "ページキャッシュ数"
|
||||
numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。"
|
||||
logoutConfirm: "ログアウトしますか?"
|
||||
logoutConfirm: "サインアウトしますか?"
|
||||
lastActiveDate: "最終利用日時"
|
||||
statusbar: "ステータスバー"
|
||||
pleaseSelect: "選択してください"
|
||||
|
@ -1008,7 +1008,8 @@ enableTimelineStreaming: "タイムラインを自動で更新する"
|
|||
searchWords: "検索語句・照会するIDやURL"
|
||||
searchWordsDescription: "投稿を検索するには、ここに検索語句を入力してください。空白区切りでAND検索になり、ORを挟むとOR検索になります。\n
|
||||
例えば「朝 夜」と入力すると「朝」と「夜」が両方含まれた投稿を検索し、「朝 OR 夜」と入力すると「朝」または「夜」(または両方)が含まれた投稿を検索します。\n
|
||||
「(朝 OR 夜) 眠い」のように、AND検索とOR検索を同時に行うこともできます。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID (@user@example.com)
|
||||
「(朝 OR 夜) 眠い」のように、AND検索とOR検索を同時に行うこともできます。\n空白を含む文字列をAND検索ではなくそのまま検索したい場合、\"明日 買うもの\"\
|
||||
\ のように二重引用符 (\") で囲む必要があります。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID (@user@example.com)
|
||||
や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
|
||||
searchUsers: "投稿元(オプション)"
|
||||
searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
|
||||
|
@ -2051,3 +2052,7 @@ makePrivate: "秘密にする"
|
|||
makePrivateConfirm: "リモートサーバーに削除リクエストを送信し、投稿の公開範囲を「秘密」にして他の人から見られないようにします。実行しますか?"
|
||||
sentFollowRequests: 未承認のフォローリクエスト
|
||||
noSentFollowRequests: 未承認のフォローリクエストはありません
|
||||
messagingUnencryptedInfo: FirefishのチャットはE2E暗号化されていません。漏洩してはいけない情報はFirefishで送らないでください。
|
||||
moderationNote: モデレーション用のメモ
|
||||
ipFirstAcknowledged: IPアドレスが最初に取得された日
|
||||
driveCapacityOverride: ドライブ容量の変更
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
---
|
||||
_lang_: "Română"
|
||||
headlineFirefish: "O rețea conectată prin note"
|
||||
introFirefish: "Bine ai venit! Firefish este un serviciu de microblogging open source și decentralizat.\nCreează \"note\" cu care să îți poți împărți gândurile cu oricine din jurul tău. 📡\nCu \"reacții\" îți poți expirma rapid părerea despre notele oricui. 👍\nHai să explorăm o lume nouă! 🚀"
|
||||
introFirefish: "Bine ai venit! Firefish este un serviciu de microblogging open source
|
||||
și decentralizat.\nCreează \"note\" cu care să îți poți împărți gândurile cu oricine
|
||||
din jurul tău. 📡\nCu \"reacții\" îți poți expirma rapid părerea despre notele oricui.
|
||||
👍\nHai să explorăm o lume nouă! 🚀"
|
||||
monthAndDay: "{day}/{month}"
|
||||
search: "Caută"
|
||||
notifications: "Notificări"
|
||||
|
@ -44,7 +46,8 @@ copyContent: "Copiază conținutul"
|
|||
copyLink: "Copiază link-ul"
|
||||
delete: "Şterge"
|
||||
deleteAndEdit: "Șterge și editează"
|
||||
deleteAndEditConfirm: "Ești sigur că vrei să ștergi această notă și să o editezi? Vei pierde reacțiile, re-notele și răspunsurile acesteia."
|
||||
deleteAndEditConfirm: "Ești sigur că vrei să ștergi această notă și să o editezi?
|
||||
Vei pierde reacțiile, re-notele și răspunsurile acesteia."
|
||||
addToList: "Adaugă în listă"
|
||||
sendMessage: "Trimite un mesaj"
|
||||
copyUsername: "Copiază numele de utilizator"
|
||||
|
@ -64,9 +67,11 @@ import: "Importă"
|
|||
export: "Exportă"
|
||||
files: "Fișiere"
|
||||
download: "Descarcă"
|
||||
driveFileDeleteConfirm: "Ești sigur ca vrei să ștergi fișierul \"{name}\"? Notele atașate fișierului vor fi șterse și ele."
|
||||
driveFileDeleteConfirm: "Ești sigur ca vrei să ștergi fișierul \"{name}\"? Notele
|
||||
atașate fișierului vor fi șterse și ele."
|
||||
unfollowConfirm: "Ești sigur ca vrei să nu mai urmărești pe {name}?"
|
||||
exportRequested: "Ai cerut un export. S-ar putea să ia un pic. Va fi adăugat in Drive-ul tău odată completat."
|
||||
exportRequested: "Ai cerut un export. S-ar putea să ia un pic. Va fi adăugat in Drive-ul
|
||||
tău odată completat."
|
||||
importRequested: "Ai cerut un import. S-ar putea să ia un pic."
|
||||
lists: "Liste"
|
||||
noLists: "Nu ai nici o listă"
|
||||
|
@ -81,9 +86,12 @@ error: "Eroare"
|
|||
somethingHappened: "A survenit o eroare"
|
||||
retry: "Reîncearcă"
|
||||
pageLoadError: "A apărut o eroare la încărcarea paginii."
|
||||
pageLoadErrorDescription: "De obicei asta este cauzat de o eroare de rețea sau cache-ul browser-ului. Încearcă să cureți cache-ul și apoi să încerci din nou puțin mai târziu."
|
||||
serverIsDead: "Serverul nu răspunde. Te rugăm să aștepți o perioadă și să încerci din nou."
|
||||
youShouldUpgradeClient: "Pentru a vedea această pagină, te rugăm să îți actualizezi clientul."
|
||||
pageLoadErrorDescription: "De obicei asta este cauzat de o eroare de rețea sau cache-ul
|
||||
browser-ului. Încearcă să cureți cache-ul și apoi să încerci din nou puțin mai târziu."
|
||||
serverIsDead: "Serverul nu răspunde. Te rugăm să aștepți o perioadă și să încerci
|
||||
din nou."
|
||||
youShouldUpgradeClient: "Pentru a vedea această pagină, te rugăm să îți actualizezi
|
||||
clientul."
|
||||
enterListName: "Introdu un nume pentru listă"
|
||||
privacy: "Confidenţialitate"
|
||||
makeFollowManuallyApprove: "Fă cererile de urmărire să necesite aprobare"
|
||||
|
@ -137,14 +145,21 @@ emojiUrl: "URL-ul emoji-ului"
|
|||
addEmoji: "Adaugă un emoji"
|
||||
settingGuide: "Setări recomandate"
|
||||
cacheRemoteFiles: "Ține fișierele externe in cache"
|
||||
cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului de stocare, dar va crește traficul, deoarece thumbnail-urile nu vor fi generate."
|
||||
cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe
|
||||
sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului
|
||||
de stocare, dar va crește traficul, deoarece thumbnail-urile nu vor fi generate."
|
||||
flagAsBot: "Marchează acest cont ca bot"
|
||||
flagAsBotDescription: "Activează această opțiune dacă acest cont este controlat de un program. Daca e activată, aceasta va juca rolul unui indicator pentru dezvoltatori pentru a preveni interacțiunea în lanțuri infinite cu ceilalți boți și ajustează sistemele interne al Firefish pentru a trata acest cont drept un bot."
|
||||
flagAsBotDescription: "Activează această opțiune dacă acest cont este controlat de
|
||||
un program. Daca e activată, aceasta va juca rolul unui indicator pentru dezvoltatori
|
||||
pentru a preveni interacțiunea în lanțuri infinite cu ceilalți boți și ajustează
|
||||
sistemele interne al Firefish pentru a trata acest cont drept un bot."
|
||||
flagAsCat: "Marchează acest cont ca pisică"
|
||||
flagAsCatDescription: "Activează această opțiune dacă acest cont este o pisică."
|
||||
flagShowTimelineReplies: "Arată răspunsurile în cronologie"
|
||||
flagShowTimelineRepliesDescription: "Dacă e activată vor fi arătate în cronologie răspunsurile utilizatorilor către alte notele altor utilizatori."
|
||||
autoAcceptFollowed: "Aprobă automat cererile de urmărire de la utilizatorii pe care îi urmărești"
|
||||
flagShowTimelineRepliesDescription: "Dacă e activată vor fi arătate în cronologie
|
||||
răspunsurile utilizatorilor către alte notele altor utilizatori."
|
||||
autoAcceptFollowed: "Aprobă automat cererile de urmărire de la utilizatorii pe care
|
||||
îi urmărești"
|
||||
addAccount: "Adaugă un cont"
|
||||
loginFailed: "Autentificare eșuată"
|
||||
showOnRemote: "Vezi mai multe pe instanța externă"
|
||||
|
@ -156,7 +171,11 @@ searchWith: "Caută: {q}"
|
|||
youHaveNoLists: "Nu ai nici o listă"
|
||||
followConfirm: "Ești sigur ca vrei să urmărești pe {name}?"
|
||||
proxyAccount: "Cont proxy"
|
||||
proxyAccountDescription: "Un cont proxy este un cont care se comportă ca un urmăritor extern pentru utilizatorii puși sub anumite condiții. De exemplu, când un cineva adaugă un utilizator extern intr-o listă, activitatea utilizatorului extern nu va fi adusă în instanță daca nici un utilizator local nu urmărește acel utilizator, așa că în schimb contul proxy îl va urmări."
|
||||
proxyAccountDescription: "Un cont proxy este un cont care se comportă ca un urmăritor
|
||||
extern pentru utilizatorii puși sub anumite condiții. De exemplu, când un cineva
|
||||
adaugă un utilizator extern intr-o listă, activitatea utilizatorului extern nu va
|
||||
fi adusă în instanță daca nici un utilizator local nu urmărește acel utilizator,
|
||||
așa că în schimb contul proxy îl va urmări."
|
||||
host: "Gazdă"
|
||||
selectUser: "Selectează un utilizator"
|
||||
recipient: "Destinatar"
|
||||
|
@ -186,11 +205,14 @@ instanceInfo: "Informații despre instanță"
|
|||
statistics: "Statistici"
|
||||
clearQueue: "Șterge coada"
|
||||
clearQueueConfirmTitle: "Ești sigur că vrei să cureți coada?"
|
||||
clearQueueConfirmText: "Orice notă rămasă în coadă nu va fi federată. De obicei această operație nu este necesară."
|
||||
clearQueueConfirmText: "Orice notă rămasă în coadă nu va fi federată. De obicei această
|
||||
operație nu este necesară."
|
||||
clearCachedFiles: "Golește cache-ul"
|
||||
clearCachedFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele externe din cache?"
|
||||
clearCachedFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele externe din
|
||||
cache?"
|
||||
blockedInstances: "Instanțe blocate"
|
||||
blockedInstancesDescription: "Scrie hostname-urile instanțelor pe care dorești să le blochezi. Instanțele listate nu vor mai putea să comunice cu această instanță."
|
||||
blockedInstancesDescription: "Scrie hostname-urile instanțelor pe care dorești să
|
||||
le blochezi. Instanțele listate nu vor mai putea să comunice cu această instanță."
|
||||
muteAndBlock: "Amuțiri și Blocări"
|
||||
mutedUsers: "Utilizatori amuțiți"
|
||||
blockedUsers: "Utilizatori blocați"
|
||||
|
@ -238,7 +260,8 @@ saved: "Salvat"
|
|||
messaging: "Chat"
|
||||
upload: "Încarcă"
|
||||
keepOriginalUploading: "Păstrează imaginea originală"
|
||||
keepOriginalUploadingDescription: "Salvează imaginea originala încărcată fără modificări. Dacă e oprită, o versiune pentru afișarea pe web va fi generată la încărcare."
|
||||
keepOriginalUploadingDescription: "Salvează imaginea originala încărcată fără modificări.
|
||||
Dacă e oprită, o versiune pentru afișarea pe web va fi generată la încărcare."
|
||||
fromDrive: "Din Drive"
|
||||
fromUrl: "Din URL"
|
||||
uploadFromUrl: "Încarcă dintr-un URL"
|
||||
|
@ -254,7 +277,8 @@ agreeTo: "Sunt de acord cu {0}"
|
|||
tos: "Termenii de utilizare"
|
||||
start: "Să începem"
|
||||
home: "Acasă"
|
||||
remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația afișată poate fi incompletă."
|
||||
remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația
|
||||
afișată poate fi incompletă."
|
||||
activity: "Activitate"
|
||||
images: "Imagini"
|
||||
birthday: "Zi de naștere"
|
||||
|
@ -287,7 +311,8 @@ unableToDelete: "Nu se poate șterge"
|
|||
inputNewFileName: "Introdu un nou nume de fișier"
|
||||
inputNewDescription: "Introdu o descriere nouă"
|
||||
inputNewFolderName: "Introdu un nume de folder nou"
|
||||
circularReferenceFolder: "Destinația folderului este un subfolder al folderului pe care dorești să îl muți."
|
||||
circularReferenceFolder: "Destinația folderului este un subfolder al folderului pe
|
||||
care dorești să îl muți."
|
||||
hasChildFilesOrFolders: "Acest folder nu este gol, așa că nu poate fi șters."
|
||||
copyUrl: "Copiază URL"
|
||||
rename: "Redenumește"
|
||||
|
@ -318,7 +343,8 @@ yearX: "{year}"
|
|||
pages: "Pagini"
|
||||
enableLocalTimeline: "Activează cronologia locală"
|
||||
enableGlobalTimeline: "Activeaza cronologia globală"
|
||||
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate."
|
||||
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate
|
||||
cronologiile, chiar dacă nu sunt activate."
|
||||
registration: "Inregistrare"
|
||||
enableRegistration: "Activează înregistrările pentru utilizatori noi"
|
||||
invite: "Invită"
|
||||
|
@ -330,9 +356,11 @@ bannerUrl: "URL-ul imaginii de banner"
|
|||
backgroundImageUrl: "URL-ul imaginii de fundal"
|
||||
basicInfo: "Informații de bază"
|
||||
pinnedUsers: "Utilizatori fixați"
|
||||
pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor fi fixați pe pagina \"Explorează\"."
|
||||
pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor
|
||||
fi fixați pe pagina \"Explorează\"."
|
||||
pinnedPages: "Pagini fixate"
|
||||
pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vâruful paginii acestei instanțe, separate de pauze de rând."
|
||||
pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vâruful
|
||||
paginii acestei instanțe, separate de pauze de rând."
|
||||
pinnedClipId: "ID-ul clip-ului pe care să îl fixezi"
|
||||
pinnedNotes: "Notă fixată"
|
||||
hcaptcha: "hCaptcha"
|
||||
|
@ -343,14 +371,17 @@ recaptcha: "reCAPTCHA"
|
|||
enableRecaptcha: "Activează reCAPTCHA"
|
||||
recaptchaSiteKey: "Site key"
|
||||
recaptchaSecretKey: "Secret key"
|
||||
avoidMultiCaptchaConfirm: "Folosirea mai multor sisteme Captcha poate cauza interferență între acestea. Ai dori să dezactivezi alte sisteme Captcha acum active? Dacă preferi să rămână activate, apasă Anulare."
|
||||
avoidMultiCaptchaConfirm: "Folosirea mai multor sisteme Captcha poate cauza interferență
|
||||
între acestea. Ai dori să dezactivezi alte sisteme Captcha acum active? Dacă preferi
|
||||
să rămână activate, apasă Anulare."
|
||||
antennas: "Antene"
|
||||
manageAntennas: "Gestionează Antenele"
|
||||
name: "Nume"
|
||||
antennaSource: "Sursa antenei"
|
||||
antennaKeywords: "Cuvinte cheie ascultate"
|
||||
antennaExcludeKeywords: "Cuvinte cheie excluse"
|
||||
antennaKeywordsDescription: "Separă cu spații pentru o condiție ȘI sau cu o întrerupere de rând pentru o condiție SAU."
|
||||
antennaKeywordsDescription: "Separă cu spații pentru o condiție ȘI sau cu o întrerupere
|
||||
de rând pentru o condiție SAU."
|
||||
notifyAntenna: "Notifică-mă pentru note noi"
|
||||
withFileAntenna: "Doar note cu fișiere"
|
||||
enableServiceworker: "Activează ServiceWorker"
|
||||
|
@ -437,7 +468,8 @@ strongPassword: "Parolă puternică"
|
|||
passwordMatched: "Se potrivește!"
|
||||
passwordNotMatched: "Nu se potrivește"
|
||||
signinWith: "Autentifică-te cu {x}"
|
||||
signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse sunt incorecte."
|
||||
signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse
|
||||
sunt incorecte."
|
||||
tapSecurityKey: "Apasă pe cheia ta de securitate."
|
||||
or: "Sau"
|
||||
language: "Limbă"
|
||||
|
@ -478,19 +510,26 @@ showFeaturedNotesInTimeline: "Arată notele recomandate în cronologii"
|
|||
objectStorage: "Object Storage"
|
||||
useObjectStorage: "Folosește Object Storage"
|
||||
objectStorageBaseUrl: "URL de bază"
|
||||
objectStorageBaseUrlDesc: "URL-ul este folosit pentru referință. Specifică URL-ul CDN-ului sau Proxy-ului tău dacă folosești unul. Pentru S3 folosește 'https://<bucket>.s3.amazonaws.com' și pentru GCS sau servicii echivalente folosește 'https://storage.googleapis.com/<bucket>', etc."
|
||||
objectStorageBaseUrlDesc: "URL-ul este folosit pentru referință. Specifică URL-ul
|
||||
CDN-ului sau Proxy-ului tău dacă folosești unul. Pentru S3 folosește 'https://<bucket>.s3.amazonaws.com'
|
||||
și pentru GCS sau servicii echivalente folosește 'https://storage.googleapis.com/<bucket>',
|
||||
etc."
|
||||
objectStorageBucket: "Bucket"
|
||||
objectStorageBucketDesc: "Te rog specifică numele bucket-ului furnizorului tău."
|
||||
objectStoragePrefix: "Prefix"
|
||||
objectStoragePrefixDesc: "Fișierele vor fi stocate sub directoare cu acest prefix."
|
||||
objectStorageEndpoint: "Endpoint"
|
||||
objectStorageEndpointDesc: "Lasă acest câmp gol dacă folosești AWS S3, dacă nu specifică endpoint-ul ca '<host>' sau '<host>:<port>', depinzând de ce serviciu folosești."
|
||||
objectStorageEndpointDesc: "Lasă acest câmp gol dacă folosești AWS S3, dacă nu specifică
|
||||
endpoint-ul ca '<host>' sau '<host>:<port>', depinzând de ce serviciu folosești."
|
||||
objectStorageRegion: "Regiune"
|
||||
objectStorageRegionDesc: "Specifică o regiune precum 'xx-east-1'. Dacă serviciul tău nu face distincția între regiuni lasă acest câmp gol sau introdu 'us-east-1'."
|
||||
objectStorageRegionDesc: "Specifică o regiune precum 'xx-east-1'. Dacă serviciul tău
|
||||
nu face distincția între regiuni lasă acest câmp gol sau introdu 'us-east-1'."
|
||||
objectStorageUseSSL: "Folosește SSl"
|
||||
objectStorageUseSSLDesc: "Oprește această opțiune dacă nu vei folosi HTTPS pentru conexiunile API-ului"
|
||||
objectStorageUseSSLDesc: "Oprește această opțiune dacă nu vei folosi HTTPS pentru
|
||||
conexiunile API-ului"
|
||||
objectStorageUseProxy: "Conectează-te prin Proxy"
|
||||
objectStorageUseProxyDesc: "Oprește această opțiune dacă vei nu folosi un Proxy pentru conexiunile API-ului"
|
||||
objectStorageUseProxyDesc: "Oprește această opțiune dacă vei nu folosi un Proxy pentru
|
||||
conexiunile API-ului"
|
||||
objectStorageSetPublicRead: "Setează \"public-read\" pentru încărcare"
|
||||
serverLogs: "Loguri server"
|
||||
deleteAll: "Șterge tot"
|
||||
|
@ -518,7 +557,9 @@ sort: "Sortează"
|
|||
ascendingOrder: "Crescător"
|
||||
descendingOrder: "Descrescător"
|
||||
scratchpad: "Scratchpad"
|
||||
scratchpadDescription: "Scratchpad-ul oferă un mediu de experimentare în AiScript. Poți scrie, executa și verifica rezultatele acestuia interacționând cu Firefish în el."
|
||||
scratchpadDescription: "Scratchpad-ul oferă un mediu de experimentare în AiScript.
|
||||
Poți scrie, executa și verifica rezultatele acestuia interacționând cu Firefish
|
||||
în el."
|
||||
output: "Ieșire"
|
||||
script: "Script"
|
||||
disablePagesScript: "Dezactivează AiScript în Pagini"
|
||||
|
@ -526,11 +567,14 @@ updateRemoteUser: "Actualizează informațiile utilizatorului extern"
|
|||
deleteAllFiles: "Șterge toate fișierele"
|
||||
deleteAllFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele?"
|
||||
removeAllFollowing: "Dezurmărește toți utilizatorii urmăriți"
|
||||
removeAllFollowingDescription: "Asta va dez-urmări toate conturile din {host}. Te rog execută asta numai dacă instanța, de ex., nu mai există."
|
||||
removeAllFollowingDescription: "Asta va dez-urmări toate conturile din {host}. Te
|
||||
rog execută asta numai dacă instanța, de ex., nu mai există."
|
||||
userSuspended: "Acest utilizator a fost suspendat."
|
||||
userSilenced: "Acest utilizator a fost setat silențios."
|
||||
yourAccountSuspendedTitle: "Acest cont a fost suspendat"
|
||||
yourAccountSuspendedDescription: "Acest cont a fost suspendat din cauza încălcării termenilor de serviciu al serverului sau ceva similar. Contactează administratorul dacă ai dori să afli un motiv mai detaliat. Te rog nu crea un cont nou."
|
||||
yourAccountSuspendedDescription: "Acest cont a fost suspendat din cauza încălcării
|
||||
termenilor de serviciu al serverului sau ceva similar. Contactează administratorul
|
||||
dacă ai dori să afli un motiv mai detaliat. Te rog nu crea un cont nou."
|
||||
menu: "Meniu"
|
||||
divider: "Separator"
|
||||
addItem: "Adaugă element"
|
||||
|
@ -569,12 +613,14 @@ permission: "Permisiuni"
|
|||
enableAll: "Actevează tot"
|
||||
disableAll: "Dezactivează tot"
|
||||
tokenRequested: "Acordă acces la cont"
|
||||
pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile setate aici."
|
||||
pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile
|
||||
setate aici."
|
||||
notificationType: "Tipul notificării"
|
||||
edit: "Editează"
|
||||
emailServer: "Server email"
|
||||
enableEmail: "Activează distribuția de emailuri"
|
||||
emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți uiți parola"
|
||||
emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți
|
||||
uiți parola"
|
||||
email: "Email"
|
||||
emailAddress: "Adresă de email"
|
||||
smtpConfig: "Configurare Server SMTP"
|
||||
|
@ -582,13 +628,15 @@ smtpHost: "Gazdă"
|
|||
smtpPort: "Port"
|
||||
smtpUser: "Nume de utilizator"
|
||||
smtpPass: "Parolă"
|
||||
emptyToDisableSmtpAuth: "Lasă username-ul și parola necompletate pentru a dezactiva verificarea SMTP"
|
||||
emptyToDisableSmtpAuth: "Lasă username-ul și parola necompletate pentru a dezactiva
|
||||
verificarea SMTP"
|
||||
smtpSecure: "Folosește SSL/TLS implicit pentru conecțiunile SMTP"
|
||||
smtpSecureInfo: "Oprește opțiunea asta dacă STARTTLS este folosit"
|
||||
testEmail: "Testează livrarea emailurilor"
|
||||
wordMute: "Cuvinte pe mut"
|
||||
regexpError: "Eroare de Expresie Regulată"
|
||||
regexpErrorDescription: "A apărut o eroare în expresia regulată pe linia {line} al cuvintelor {tab} setate pe mut:"
|
||||
regexpErrorDescription: "A apărut o eroare în expresia regulată pe linia {line} al
|
||||
cuvintelor {tab} setate pe mut:"
|
||||
instanceMute: "Instanțe pe mut"
|
||||
userSaysSomething: "{name} a spus ceva"
|
||||
makeActive: "Activează"
|
||||
|
@ -604,10 +652,13 @@ create: "Crează"
|
|||
notificationSetting: "Setări notificări"
|
||||
notificationSettingDesc: "Selectează tipurile de notificări care să fie arătate"
|
||||
useGlobalSetting: "Folosește setările globale"
|
||||
useGlobalSettingDesc: "Dacă opțiunea e pornită, notificările contului tău vor fi folosite. Dacă e oprită, configurația va fi individuală."
|
||||
useGlobalSettingDesc: "Dacă opțiunea e pornită, notificările contului tău vor fi folosite.
|
||||
Dacă e oprită, configurația va fi individuală."
|
||||
other: "Altele"
|
||||
regenerateLoginToken: "Regenerează token de login"
|
||||
regenerateLoginTokenDescription: "Regenerează token-ul folosit intern în timpul logări. În mod normal asta nu este necesar. Odată regenerat, toate dispozitivele vor fi delogate."
|
||||
regenerateLoginTokenDescription: "Regenerează token-ul folosit intern în timpul logări.
|
||||
În mod normal asta nu este necesar. Odată regenerat, toate dispozitivele vor fi
|
||||
delogate."
|
||||
setMultipleBySeparatingWithSpace: "Separă mai multe intrări cu spații."
|
||||
fileIdOrUrl: "Introdu ID sau URL"
|
||||
behavior: "Comportament"
|
||||
|
@ -615,13 +666,15 @@ sample: "exemplu"
|
|||
abuseReports: "Rapoarte"
|
||||
reportAbuse: "Raportează"
|
||||
reportAbuseOf: "Raportează {name}"
|
||||
fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacă este despre o notă specifică, te rog introdu URL-ul ei."
|
||||
fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacă este
|
||||
despre o notă specifică, te rog introdu URL-ul ei."
|
||||
abuseReported: "Raportul tău a fost trimis. Mulțumim."
|
||||
reporter: "Raportorul"
|
||||
reporteeOrigin: "Originea raportatului"
|
||||
reporterOrigin: "Originea raportorului"
|
||||
forwardReport: "Redirecționează raportul către instanța externă"
|
||||
forwardReportIsAnonymous: "În locul contului tău, va fi afișat un cont anonim, de sistem, ca raportor către instanța externă."
|
||||
forwardReportIsAnonymous: "În locul contului tău, va fi afișat un cont anonim, de
|
||||
sistem, ca raportor către instanța externă."
|
||||
send: "Trimite"
|
||||
abuseMarkAsResolved: "Marchează raportul ca rezolvat"
|
||||
openInNewTab: "Deschide în tab nou"
|
||||
|
|
|
@ -873,7 +873,7 @@ recommended: "推荐"
|
|||
check: "检查"
|
||||
driveCapOverrideLabel: "修改此用户的网盘容量"
|
||||
driveCapOverrideCaption: "输入 0 或以下的值将容量重置为默认值。"
|
||||
requireAdminForView: "需要使用管理员账号登录才能查看。"
|
||||
requireAdminForView: "您需要使用管理员账号登录才能查看。"
|
||||
isSystemAccount: "该账号由系统自动创建。请不要修改、编辑、删除或以其它方式篡改这个账号,否则可能会破坏您的服务器。"
|
||||
typeToConfirm: "输入 {x} 以确认操作"
|
||||
deleteAccount: "删除账号"
|
||||
|
@ -1263,7 +1263,7 @@ _2fa:
|
|||
step2: "然后,扫描屏幕上显示的二维码。"
|
||||
step2Url: "如果您使用的是桌面程序,您也可以输入这个URL:"
|
||||
step3: "输入您的应用提供的令牌以完成设置。"
|
||||
step4: "从现在开始,任何登录操作都将要求您提供这样一个登录令牌。"
|
||||
step4: "从现在开始,任何登录操作都将要求您提供这样一个令牌。"
|
||||
securityKeyInfo: "除了指纹或 PIN 身份验证外,您还可以通过支持 FIDO2 的硬件安全密钥设置身份验证,以进一步保护您的账号。"
|
||||
token: 2FA 令牌
|
||||
step3Title: 输入验证码
|
||||
|
@ -1934,7 +1934,10 @@ migrationConfirm: "您确实确定要将账号迁移到 {account} 吗?此操
|
|||
noteId: 帖子 ID
|
||||
moveFrom: 从旧账号迁移至此账号
|
||||
defaultReaction: 发出和收到帖子的默认表情符号反应
|
||||
sendModMail: 发送审核通知
|
||||
sendModMail: 发送管理通知
|
||||
moderationNote: "管理笔记"
|
||||
ipFirstAcknowledged: "首次获取此 IP 地址的日期"
|
||||
driveCapacityOverride: "网盘容量变更"
|
||||
isLocked: 该账号设置了关注请求
|
||||
_filters:
|
||||
notesBefore: 帖子早于
|
||||
|
@ -2044,8 +2047,12 @@ publishTimelines: 为访客发布时间线
|
|||
publishTimelinesDescription: 如果启用,在用户登出时本地和全局时间线也会显示在 {url} 上。
|
||||
searchWordsDescription: "要搜索帖子,请输入关键词。交集搜索关键词之间使用空格进行区分,并集搜索关键词之间使用 OR 进行区分。\n例如 '早上
|
||||
晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您还可以组合交集/并集条件,例如
|
||||
'(早上 OR 晚上) 困了' 。\n\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索”
|
||||
将搜索字面包含用户 ID/URL 的帖子。"
|
||||
'(早上 OR 晚上) 困了' 。\n如果您想搜索单词序列(例如一个英语句子),您必须将其放在双引号中,例如 \"Today I learned\" 以区分于交集搜索。\n
|
||||
\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索” 将搜索字面包含用户 ID/URL
|
||||
的帖子。"
|
||||
searchRangeDescription: "如果您要过滤时间段,请按以下格式输入:20220615-20231031\n\n如果您省略年份(例如 0105-0106
|
||||
或 20231105-0110),它将被解释为当前年份。\n\n您还可以省略开始日期或结束日期。 例如 -0102 将过滤搜索结果以仅显示今年 1 月 2 日之前发布的帖子,而
|
||||
20231026- 将过滤结果以仅显示 2023 年 10 月 26 日之后发布的帖子。"
|
||||
messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。"
|
||||
noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
|
||||
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
|
||||
|
|
|
@ -2048,3 +2048,7 @@ searchUsersDescription: "如欲搜尋特定使用者的貼文,請以「@user@e
|
|||
\n輸入「me」以搜尋自己的所有貼文(包含不在主頁顯示、追隨者、指定使用者、祕密貼文)。\n\n輸入「local」以搜尋本地伺服器的貼文。"
|
||||
searchRangeDescription: "如欲搜尋特定期間的貼文,請以「20220615-20231031」的格式輸入日期範圍。\n\n今年的日期可省略年份(例如0105-0106、20231105-0110)。\n\
|
||||
\n開始日期和結果日期可擇一省略。舉例來說,「-0102」表示僅搜尋今年1月2日為止的貼文,「20231026-」表示僅搜尋2023年10月26日以後的貼文。"
|
||||
noAltTextWarning: 有些附件沒有說明,您是否忘記寫了?
|
||||
moderationNote: 管理員備註
|
||||
ipFirstAcknowledged: 首次取得此 IP 位址的日期
|
||||
driveCapacityOverride: 雲端硬碟容量變更
|
||||
|
|
14
package.json
14
package.json
|
@ -43,14 +43,14 @@
|
|||
"gulp-terser": "2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.3",
|
||||
"@biomejs/cli-darwin-arm64": "^1.5.3",
|
||||
"@biomejs/cli-darwin-x64": "^1.5.3",
|
||||
"@biomejs/cli-linux-arm64": "^1.5.3",
|
||||
"@biomejs/cli-linux-x64": "^1.5.3",
|
||||
"@types/node": "20.11.24",
|
||||
"@biomejs/biome": "1.6.1",
|
||||
"@biomejs/cli-darwin-arm64": "^1.6.1",
|
||||
"@biomejs/cli-darwin-x64": "^1.6.1",
|
||||
"@biomejs/cli-linux-arm64": "^1.6.1",
|
||||
"@biomejs/cli-linux-x64": "^1.6.1",
|
||||
"@types/node": "20.11.28",
|
||||
"execa": "8.0.1",
|
||||
"pnpm": "8.15.4",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.4.2"
|
||||
}
|
||||
}
|
||||
|
|
125
packages/backend-rs/Cargo.lock
generated
125
packages/backend-rs/Cargo.lock
generated
|
@ -128,9 +128,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
@ -180,16 +180,6 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-write-file"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -342,9 +332,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.3"
|
||||
version = "3.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
|
@ -388,9 +378,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.89"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -406,9 +396,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
|||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.34"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
|
@ -421,9 +411,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.1"
|
||||
version = "4.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
||||
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -431,9 +421,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -443,11 +433,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.0"
|
||||
version = "4.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
|
||||
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
|
@ -868,9 +858,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -922,6 +912,12 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
@ -1321,18 +1317,6 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
@ -1499,7 +1483,7 @@ version = "0.17.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1672,9 +1656,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -1799,9 +1783,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.24"
|
||||
version = "0.11.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
|
@ -2015,7 +1999,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2056,7 +2040,7 @@ version = "0.12.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec13bfb4c4aef208f68dbea970dd40d13830c868aa8dcb4e106b956e6bb4f2fa"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sea-bae",
|
||||
|
@ -2283,9 +2267,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
|
||||
checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
|
@ -2296,9 +2280,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
|
||||
checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"atoi",
|
||||
|
@ -2308,7 +2292,6 @@ dependencies = [
|
|||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"event-listener",
|
||||
"futures-channel",
|
||||
|
@ -2344,9 +2327,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
|
||||
checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2357,14 +2340,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
|
||||
checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
|
||||
dependencies = [
|
||||
"atomic-write-file",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
|
@ -2384,9 +2366,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
|
||||
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
|
@ -2431,9 +2413,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
|
||||
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
|
@ -2462,7 +2444,6 @@ dependencies = [
|
|||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
|
@ -2476,9 +2457,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
|
||||
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
|
@ -2639,18 +2620,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2735,9 +2716,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
|
@ -3013,9 +2994,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
|||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e"
|
||||
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
"wasite",
|
||||
|
|
|
@ -12,31 +12,31 @@ napi = ["dep:napi", "dep:napi-derive"]
|
|||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.75"
|
||||
async-trait = "0.1.77"
|
||||
cfg-if = "1.0.0"
|
||||
chrono = "0.4.31"
|
||||
chrono = "0.4.35"
|
||||
cuid2 = "0.1.2"
|
||||
jsonschema = "0.17.1"
|
||||
once_cell = "1.19.0"
|
||||
parse-display = "0.8.2"
|
||||
rand = "0.8.5"
|
||||
schemars = { version = "0.8.16", features = ["chrono"] }
|
||||
sea-orm = { version = "0.12.10", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
thiserror = "1.0.52"
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
sea-orm = { version = "0.12.14", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
thiserror = "1.0.58"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
|
||||
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||
napi = { version = "2.14.1", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
|
||||
napi-derive = { version = "2.14.5", optional = true }
|
||||
napi = { version = "2.16.0", default-features = false, features = ["napi9", "tokio_rt"], optional = true }
|
||||
napi-derive = { version = "2.16.0", optional = true }
|
||||
basen = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.1.0"
|
||||
napi-build = "2.1.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
@ -64,6 +64,8 @@ pub enum Relation {
|
|||
DriveFolder,
|
||||
#[sea_orm(has_many = "super::messaging_message::Entity")]
|
||||
MessagingMessage,
|
||||
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||
NoteFile,
|
||||
#[sea_orm(has_many = "super::page::Entity")]
|
||||
Page,
|
||||
#[sea_orm(
|
||||
|
@ -94,6 +96,12 @@ impl Related<super::messaging_message::Entity> for Entity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Related<super::note_file::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::NoteFile.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::page::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Page.def()
|
||||
|
|
|
@ -35,6 +35,7 @@ pub mod muting;
|
|||
pub mod note;
|
||||
pub mod note_edit;
|
||||
pub mod note_favorite;
|
||||
pub mod note_file;
|
||||
pub mod note_reaction;
|
||||
pub mod note_thread_muting;
|
||||
pub mod note_unread;
|
||||
|
|
|
@ -38,8 +38,6 @@ pub struct Model {
|
|||
#[sea_orm(column_name = "visibleUserIds")]
|
||||
pub visible_user_ids: Vec<String>,
|
||||
pub mentions: Vec<String>,
|
||||
#[sea_orm(column_name = "mentionedRemoteUsers", column_type = "Text")]
|
||||
pub mentioned_remote_users: String,
|
||||
pub emojis: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
#[sea_orm(column_name = "hasPoll")]
|
||||
|
@ -100,6 +98,8 @@ pub enum Relation {
|
|||
NoteEdit,
|
||||
#[sea_orm(has_many = "super::note_favorite::Entity")]
|
||||
NoteFavorite,
|
||||
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||
NoteFile,
|
||||
#[sea_orm(has_many = "super::note_reaction::Entity")]
|
||||
NoteReaction,
|
||||
#[sea_orm(has_many = "super::note_unread::Entity")]
|
||||
|
@ -164,6 +164,12 @@ impl Related<super::note_favorite::Entity> for Entity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Related<super::note_file::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::NoteFile.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::note_reaction::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::NoteReaction.def()
|
||||
|
|
48
packages/backend-rs/src/model/entity/note_file.rs
Normal file
48
packages/backend-rs/src/model/entity/note_file.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.12
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "note_file")]
|
||||
pub struct Model {
|
||||
#[sea_orm(column_name = "serialNo", primary_key)]
|
||||
pub serial_no: i64,
|
||||
#[sea_orm(column_name = "noteId")]
|
||||
pub note_id: String,
|
||||
#[sea_orm(column_name = "fileId")]
|
||||
pub file_id: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::drive_file::Entity",
|
||||
from = "Column::FileId",
|
||||
to = "super::drive_file::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
DriveFile,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::note::Entity",
|
||||
from = "Column::NoteId",
|
||||
to = "super::note::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Note,
|
||||
}
|
||||
|
||||
impl Related<super::drive_file::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::DriveFile.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::note::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Note.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -33,6 +33,7 @@ pub use super::muting::Entity as Muting;
|
|||
pub use super::note::Entity as Note;
|
||||
pub use super::note_edit::Entity as NoteEdit;
|
||||
pub use super::note_favorite::Entity as NoteFavorite;
|
||||
pub use super::note_file::Entity as NoteFile;
|
||||
pub use super::note_reaction::Entity as NoteReaction;
|
||||
pub use super::note_thread_muting::Entity as NoteThreadMuting;
|
||||
pub use super::note_unread::Entity as NoteUnread;
|
||||
|
|
|
@ -22,23 +22,23 @@
|
|||
"@swc/core-android-arm64": "1.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bull-board/api": "5.14.2",
|
||||
"@bull-board/koa": "5.14.2",
|
||||
"@bull-board/ui": "5.14.2",
|
||||
"@bull-board/api": "5.15.1",
|
||||
"@bull-board/koa": "5.15.1",
|
||||
"@bull-board/ui": "5.15.1",
|
||||
"@discordapp/twemoji": "^15.0.2",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "12.0.1",
|
||||
"@ladjs/koa-views": "9.0.0",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@redocly/openapi-core": "1.10.3",
|
||||
"@redocly/openapi-core": "1.10.4",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@twemoji/parser": "^15.0.0",
|
||||
"adm-zip": "^0.5.10",
|
||||
"adm-zip": "^0.5.12",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "7.0.0",
|
||||
"archiver": "7.0.1",
|
||||
"argon2": "^0.40.1",
|
||||
"aws-sdk": "2.1571.0",
|
||||
"aws-sdk": "2.1578.0",
|
||||
"axios": "^1.6.7",
|
||||
"backend-rs": "workspace:*",
|
||||
"bcryptjs": "2.4.3",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"cli-highlight": "2.1.11",
|
||||
"color-convert": "2.0.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"date-fns": "3.3.1",
|
||||
"date-fns": "3.5.0",
|
||||
"decompress": "^4.2.1",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"deepl-node": "1.12.0",
|
||||
|
@ -60,9 +60,9 @@
|
|||
"file-type": "19.0.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "14.2.0",
|
||||
"got": "14.2.1",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"happy-dom": "^13.6.2",
|
||||
"happy-dom": "^13.8.6",
|
||||
"hpagent": "1.2.0",
|
||||
"ioredis": "5.3.2",
|
||||
"ip-cidr": "4.0.0",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"json5": "2.2.3",
|
||||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"koa": "2.15.0",
|
||||
"koa": "2.15.1",
|
||||
"koa-body": "^6.0.1",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-favicon": "2.1.0",
|
||||
|
@ -88,7 +88,7 @@
|
|||
"multer": "1.4.5-lts.1",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.11",
|
||||
"nodemailer": "6.9.12",
|
||||
"opencc-js": "^1.0.5",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "^9.2.2",
|
||||
|
@ -100,7 +100,7 @@
|
|||
"punycode": "2.3.1",
|
||||
"pureimage": "0.4.13",
|
||||
"qrcode": "1.5.3",
|
||||
"qs": "6.11.2",
|
||||
"qs": "6.12.0",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.20.10",
|
||||
|
@ -115,7 +115,7 @@
|
|||
"stringz": "2.1.0",
|
||||
"summaly": "2.7.0",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "5.22.0",
|
||||
"systeminformation": "5.22.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"tesseract.js": "^5.0.5",
|
||||
"tinycolor2": "1.6.0",
|
||||
|
@ -129,7 +129,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "0.3.10",
|
||||
"@swc/core": "1.4.4",
|
||||
"@swc/core": "1.4.8",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/bcryptjs": "2.4.6",
|
||||
"@types/color-convert": "^2.0.3",
|
||||
|
@ -138,7 +138,7 @@
|
|||
"@types/fluent-ffmpeg": "2.1.24",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsonld": "1.5.13",
|
||||
"@types/jsrsasign": "10.5.12",
|
||||
"@types/jsrsasign": "10.5.13",
|
||||
"@types/koa": "2.15.0",
|
||||
"@types/koa-bodyparser": "4.3.12",
|
||||
"@types/koa-cors": "0.0.6",
|
||||
|
@ -150,7 +150,7 @@
|
|||
"@types/koa__multer": "2.0.7",
|
||||
"@types/koa__router": "12.0.4",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/node": "20.11.24",
|
||||
"@types/node": "20.11.28",
|
||||
"@types/node-fetch": "2.6.11",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/oauth": "0.9.4",
|
||||
|
@ -183,7 +183,7 @@
|
|||
"ts-loader": "9.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.3.3",
|
||||
"typescript": "5.4.2",
|
||||
"webpack": "^5.90.3",
|
||||
"ws": "8.16.0"
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ function greet() {
|
|||
136,
|
||||
0,
|
||||
)(
|
||||
" If you like Firefish, please consider starring or contributing to the repo. https://firefish.dev/firefish/firefish",
|
||||
" If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish",
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -149,7 +149,7 @@ function showNodejsVersion(): void {
|
|||
|
||||
nodejsLogger.info(`Version ${process.version} detected.`);
|
||||
|
||||
const minVersion = "v18.16.0";
|
||||
const minVersion = "v18.17.0";
|
||||
if (semver.lt(process.version, minVersion)) {
|
||||
nodejsLogger.error(`At least Node.js ${minVersion} required!`);
|
||||
process.exit(1);
|
||||
|
|
|
@ -73,6 +73,7 @@ import { UserPending } from "@/models/entities/user-pending.js";
|
|||
import { Webhook } from "@/models/entities/webhook.js";
|
||||
import { UserIp } from "@/models/entities/user-ip.js";
|
||||
import { NoteEdit } from "@/models/entities/note-edit.js";
|
||||
import { NoteFile } from "@/models/entities/note-file.js";
|
||||
|
||||
import { entities as charts } from "@/services/chart/entities.js";
|
||||
import { dbLogger } from "./logger.js";
|
||||
|
@ -143,6 +144,7 @@ export const entities = [
|
|||
Note,
|
||||
NoteEdit,
|
||||
NoteFavorite,
|
||||
NoteFile,
|
||||
NoteReaction,
|
||||
NoteWatching,
|
||||
NoteThreadMuting,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { EventEmitter } from "node:events";
|
|||
import { inspect } from "node:util";
|
||||
import boot from "./boot/index.js";
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
Error.stackTraceLimit = Number.POSITIVE_INFINITY;
|
||||
EventEmitter.defaultMaxListeners = 128;
|
||||
|
||||
boot().catch((err) => {
|
||||
|
|
|
@ -7,7 +7,7 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
await queryRunner.query(`DROP TABLE IF EXISTS "reversi_game"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "reversi_matching"`);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX IF NOT EXISTS "IDX_note_url" ON "note" ("text")`,
|
||||
`CREATE INDEX IF NOT EXISTS "IDX_note_url" ON "note" ("url")`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "antenna_note"`);
|
||||
await queryRunner.query(
|
||||
|
@ -105,10 +105,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT IF NOT EXISTS "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT IF NOT EXISTS "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_note_url"`);
|
||||
await queryRunner.query(
|
||||
|
@ -124,10 +124,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
`CREATE INDEX IF NOT EXISTS "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT IF NOT EXISTS "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT IF NOT EXISTS "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`,
|
||||
|
@ -139,10 +139,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
`CREATE INDEX IF NOT EXISTS "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT IF NOT EXISTS "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT IF NOT EXISTS "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`,
|
||||
|
|
41
packages/backend/src/migration/1710304584214-note-file.ts
Normal file
41
packages/backend/src/migration/1710304584214-note-file.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class NoteFile1710304584214 implements MigrationInterface {
|
||||
name = "NoteFile1710304584214";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "note_file" (
|
||||
"serialNo" bigserial PRIMARY KEY,
|
||||
"noteId" varchar(32) NOT NULL,
|
||||
"fileId" varchar(32) NOT NULL
|
||||
)`,
|
||||
);
|
||||
await queryRunner.query(`
|
||||
INSERT INTO "note_file" ("noteId", "fileId")
|
||||
SELECT "t"."id", "t"."fid" FROM (
|
||||
SELECT ROW_NUMBER() OVER () AS "rn", * FROM (
|
||||
SELECT "id", UNNEST("fileIds") AS "fid" FROM "note"
|
||||
) AS "s"
|
||||
) AS "t"
|
||||
INNER JOIN "drive_file" ON "drive_file"."id" = "t"."fid"
|
||||
ORDER BY "rn"
|
||||
`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "note_file" ADD FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "note_file" ADD FOREIGN KEY ("fileId") REFERENCES "drive_file" ("id") ON DELETE CASCADE`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_note_file_noteId" ON "note_file" ("noteId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_note_file_fileId" ON "note_file" ("fileId")`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "note_file"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class RemoveMentionedUsersColumn1710688552234
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "note" DROP COLUMN "mentionedRemoteUsers"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "note" ADD "mentionedRemoteUsers" TEXT NOT NULL DEFAULT '[]'::text`,
|
||||
);
|
||||
await queryRunner.query(`CREATE TEMP TABLE IF NOT EXISTS "temp_mentions" AS
|
||||
SELECT "id", "url", "uri", "username", "host"
|
||||
FROM "user"
|
||||
JOIN "user_profile" ON "user"."id" = "user_profile". "userId" WHERE "user"."host" IS NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "temp_mentions_id" ON "temp_mentions"("id")`,
|
||||
);
|
||||
await queryRunner.query(`UPDATE "note" SET "mentionedRemoteUsers" = (
|
||||
SELECT COALESCE(json_agg(row_to_json("data")::jsonb - 'id')::text, '[]') FROM "temp_mentions" AS "data"
|
||||
WHERE "data"."id" = ANY("note"."mentions")
|
||||
)`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class FixMutingIndices1710690239308 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_createdAt"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muteeId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muterId"`);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_createdAt" ON "renote_muting" ("createdAt")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_muteeId" ON "renote_muting" ("muteeId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_muterId" ON "renote_muting" ("muterId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_createdAt" ON "reply_muting" ("createdAt")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_muteeId" ON "reply_muting" ("muteeId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_muterId" ON "reply_muting" ("muterId")`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_createdAt"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muteeId"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muterId"`);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_createdAt" ON "muting" ("createdAt")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_muteeId" ON "muting" ("muteeId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_reply_muting_muterId" ON "muting" ("muterId")`,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
import { URL } from "node:url";
|
||||
import config from "@/config/index.js";
|
||||
import { toASCII } from "punycode";
|
||||
import Logger from "@/services/logger.js";
|
||||
import { inspect } from "node:util";
|
||||
|
||||
const logger = new Logger("convert-host");
|
||||
|
||||
export function getFullApAccount(username: string, host: string | null) {
|
||||
return host
|
||||
|
@ -13,6 +17,20 @@ export function isSelfHost(host: string) {
|
|||
return toPuny(config.host) === toPuny(host);
|
||||
}
|
||||
|
||||
export function isSameOrigin(src: unknown): boolean | null {
|
||||
if (typeof src !== "string") {
|
||||
logger.debug(`unknown origin: ${inspect(src)}`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const u = new URL(src);
|
||||
return u.origin === config.url;
|
||||
} catch (e) {
|
||||
logger.debug(inspect(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractDbHost(uri: string) {
|
||||
const url = new URL(uri);
|
||||
return toPuny(url.hostname);
|
||||
|
|
|
@ -32,6 +32,7 @@ import { packedQueueCountSchema } from "@/models/schema/queue.js";
|
|||
import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
|
||||
import { packedEmojiSchema } from "@/models/schema/emoji.js";
|
||||
import { packedNoteEdit } from "@/models/schema/note-edit.js";
|
||||
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
@ -47,6 +48,7 @@ export const refs = {
|
|||
App: packedAppSchema,
|
||||
MessagingMessage: packedMessagingMessageSchema,
|
||||
Note: packedNoteSchema,
|
||||
NoteFile: packedNoteFileSchema,
|
||||
NoteEdit: packedNoteEdit,
|
||||
NoteReaction: packedNoteReactionSchema,
|
||||
NoteFavorite: packedNoteFavoriteSchema,
|
||||
|
|
|
@ -4,12 +4,17 @@ import {
|
|||
Index,
|
||||
JoinColumn,
|
||||
Column,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
type Relation,
|
||||
} from "typeorm";
|
||||
import { id } from "../id.js";
|
||||
import { Note } from "./note.js";
|
||||
import { User } from "./user.js";
|
||||
import { DriveFolder } from "./drive-folder.js";
|
||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
|
||||
import { NoteFile } from "./note-file.js";
|
||||
|
||||
@Entity()
|
||||
@Index(["userId", "folderId", "id"])
|
||||
|
@ -31,12 +36,6 @@ export class DriveFile {
|
|||
})
|
||||
public userId: User["id"] | null;
|
||||
|
||||
@ManyToOne((type) => User, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
length: 512,
|
||||
|
@ -171,12 +170,6 @@ export class DriveFile {
|
|||
})
|
||||
public folderId: DriveFolder["id"] | null;
|
||||
|
||||
@ManyToOne((type) => DriveFolder, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public folder: DriveFolder | null;
|
||||
|
||||
@Index()
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
|
@ -205,4 +198,30 @@ export class DriveFile {
|
|||
nullable: true,
|
||||
})
|
||||
public requestIp: string | null;
|
||||
|
||||
//#region Relations
|
||||
@OneToMany(
|
||||
() => NoteFile,
|
||||
(noteFile: NoteFile) => noteFile.file,
|
||||
)
|
||||
public noteFiles: Relation<NoteFile[]>;
|
||||
|
||||
@ManyToMany(
|
||||
() => Note,
|
||||
(note: Note) => note.files,
|
||||
)
|
||||
public notes: Relation<Note[]>;
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@ManyToOne(() => DriveFolder, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public folder: DriveFolder | null;
|
||||
//#endregion Relations
|
||||
}
|
||||
|
|
45
packages/backend/src/models/entities/note-file.ts
Normal file
45
packages/backend/src/models/entities/note-file.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
Entity,
|
||||
Index,
|
||||
Column,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
type Relation,
|
||||
} from "typeorm";
|
||||
import { Note } from "./note.js";
|
||||
import { DriveFile } from "./drive-file.js";
|
||||
import { id } from "../id.js";
|
||||
|
||||
@Entity()
|
||||
export class NoteFile {
|
||||
@PrimaryGeneratedColumn("increment")
|
||||
public serialNo: number;
|
||||
|
||||
@Index("IDX_note_file_noteId", { unique: false })
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: false,
|
||||
})
|
||||
public noteId: Note["id"];
|
||||
|
||||
@Index("IDX_note_file_fileId", { unique: false })
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: false,
|
||||
})
|
||||
public fileId: DriveFile["id"];
|
||||
|
||||
//#region Relations
|
||||
@ManyToOne(
|
||||
() => Note,
|
||||
(note: Note) => note.noteFiles,
|
||||
)
|
||||
public note: Relation<Note>;
|
||||
|
||||
@ManyToOne(
|
||||
() => DriveFile,
|
||||
(file: DriveFile) => file.noteFiles,
|
||||
)
|
||||
public file: Relation<DriveFile>;
|
||||
//#endregion Relations
|
||||
}
|
|
@ -2,15 +2,20 @@ import {
|
|||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
type Relation,
|
||||
} from "typeorm";
|
||||
import { User } from "./user.js";
|
||||
import type { DriveFile } from "./drive-file.js";
|
||||
import { DriveFile } from "./drive-file.js";
|
||||
import { id } from "../id.js";
|
||||
import { noteVisibilities } from "../../types.js";
|
||||
import { Channel } from "./channel.js";
|
||||
import { NoteFile } from "./note-file.js";
|
||||
|
||||
@Entity()
|
||||
@Index("IDX_NOTE_TAGS", { synchronize: false })
|
||||
|
@ -34,12 +39,6 @@ export class Note {
|
|||
})
|
||||
public replyId: Note["id"] | null;
|
||||
|
||||
@ManyToOne((type) => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public reply: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@ -48,12 +47,6 @@ export class Note {
|
|||
})
|
||||
public renoteId: Note["id"] | null;
|
||||
|
||||
@ManyToOne((type) => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public renote: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
length: 256,
|
||||
|
@ -93,12 +86,6 @@ export class Note {
|
|||
})
|
||||
public userId: User["id"];
|
||||
|
||||
@ManyToOne((type) => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
|
@ -151,6 +138,8 @@ export class Note {
|
|||
})
|
||||
public score: number;
|
||||
|
||||
// FIXME: file id is not removed from this array even if the file is deleted
|
||||
// TODO: drop this column and use note_files
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@ -183,11 +172,6 @@ export class Note {
|
|||
})
|
||||
public mentions: User["id"][];
|
||||
|
||||
@Column("text", {
|
||||
default: "[]",
|
||||
})
|
||||
public mentionedRemoteUsers: string;
|
||||
|
||||
@Column("varchar", {
|
||||
length: 128,
|
||||
array: true,
|
||||
|
@ -216,12 +200,55 @@ export class Note {
|
|||
})
|
||||
public channelId: Channel["id"] | null;
|
||||
|
||||
@ManyToOne((type) => Channel, {
|
||||
//#region Relations
|
||||
@OneToMany(
|
||||
() => NoteFile,
|
||||
(noteFile: NoteFile) => noteFile.note,
|
||||
)
|
||||
public noteFiles: Relation<NoteFile[]>;
|
||||
|
||||
@ManyToMany(
|
||||
() => DriveFile,
|
||||
(file: DriveFile) => file.notes,
|
||||
)
|
||||
@JoinTable({
|
||||
name: "note_file",
|
||||
joinColumn: {
|
||||
name: "noteId",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "fileId",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
public files: Relation<DriveFile[]>;
|
||||
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public reply: Note | null;
|
||||
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public renote: Note | null;
|
||||
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public channel: Channel | null;
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
//#endregion Relations
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
|
@ -274,10 +301,3 @@ export class Note {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IMentionedRemoteUsers = {
|
||||
uri: string;
|
||||
url?: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}[];
|
||||
|
|
|
@ -66,12 +66,14 @@ import { InstanceRepository } from "./repositories/instance.js";
|
|||
import { Webhook } from "./entities/webhook.js";
|
||||
import { UserIp } from "./entities/user-ip.js";
|
||||
import { NoteEdit } from "./entities/note-edit.js";
|
||||
import { NoteFileRepository } from "./repositories/note-file.js";
|
||||
|
||||
export const Announcements = db.getRepository(Announcement);
|
||||
export const AnnouncementReads = db.getRepository(AnnouncementRead);
|
||||
export const Apps = AppRepository;
|
||||
export const Notes = NoteRepository;
|
||||
export const NoteEdits = db.getRepository(NoteEdit);
|
||||
export const NoteFiles = NoteFileRepository;
|
||||
export const NoteFavorites = NoteFavoriteRepository;
|
||||
export const NoteWatchings = db.getRepository(NoteWatching);
|
||||
export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
|
||||
|
|
4
packages/backend/src/models/repositories/note-file.ts
Normal file
4
packages/backend/src/models/repositories/note-file.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { db } from "@/db/postgre.js";
|
||||
import { NoteFile } from "@/models/entities/note-file.js";
|
||||
|
||||
export const NoteFileRepository = db.getRepository(NoteFile).extend({});
|
|
@ -513,8 +513,8 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
location: profile!.location,
|
||||
birthday: profile!.birthday,
|
||||
fields: profile!.fields,
|
||||
followersCount: followersCount || 0,
|
||||
followingCount: followingCount || 0,
|
||||
followersCount: followersCount ?? null,
|
||||
followingCount: followingCount ?? null,
|
||||
notesCount: user.notesCount,
|
||||
pinnedNoteIds: pins.map((pin) => pin.noteId),
|
||||
pinnedNotes: Notes.packMany(
|
||||
|
@ -528,8 +528,11 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
pinnedPage: profile!.pinnedPageId
|
||||
? Pages.pack(profile!.pinnedPageId, me)
|
||||
: null,
|
||||
publicReactions: profile!.publicReactions,
|
||||
ffVisibility: profile!.ffVisibility,
|
||||
// TODO: federate publicReactions
|
||||
publicReactions:
|
||||
user.host == null ? profile!.publicReactions : false,
|
||||
// TODO: federate ffVisibility
|
||||
ffVisibility: user.host == null ? profile!.ffVisibility : "private",
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: UserSecurityKeys.countBy({
|
||||
|
|
24
packages/backend/src/models/schema/note-file.ts
Normal file
24
packages/backend/src/models/schema/note-file.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
export const packedNoteFileSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
serialNo: {
|
||||
type: "number",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
noteId: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id",
|
||||
example: "xxxxxxxxxx",
|
||||
},
|
||||
fileId: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id",
|
||||
example: "xxxxxxxxxx",
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -1,6 +1,5 @@
|
|||
import promiseLimit from "promise-limit";
|
||||
import * as mfm from "mfm-js";
|
||||
import config from "@/config/index.js";
|
||||
import Resolver from "../resolver.js";
|
||||
import post from "@/services/note/create.js";
|
||||
import { extractMentionedUsers } from "@/services/note/create.js";
|
||||
|
@ -14,7 +13,7 @@ import { extractPollFromQuestion } from "./question.js";
|
|||
import vote from "@/services/note/polls/vote.js";
|
||||
import { apLogger } from "../logger.js";
|
||||
import { DriveFile } from "@/models/entities/drive-file.js";
|
||||
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
|
||||
import { extractDbHost, isSameOrigin, toPuny } from "@/misc/convert-host.js";
|
||||
import {
|
||||
Emojis,
|
||||
Polls,
|
||||
|
@ -234,7 +233,7 @@ export async function createNote(
|
|||
.catch(async (e) => {
|
||||
// トークだったらinReplyToのエラーは無視
|
||||
const uri = getApId(note.inReplyTo);
|
||||
if (uri.startsWith(`${config.url}/`)) {
|
||||
if (isSameOrigin(uri)) {
|
||||
const id = uri.split("/").pop();
|
||||
const talk = await MessagingMessages.findOneBy({ id });
|
||||
if (talk) {
|
||||
|
@ -439,7 +438,7 @@ export async function resolveNote(
|
|||
}
|
||||
//#endregion
|
||||
|
||||
if (uri.startsWith(config.url)) {
|
||||
if (isSameOrigin(uri)) {
|
||||
throw new StatusError(
|
||||
"cannot resolve local note",
|
||||
400,
|
||||
|
@ -556,7 +555,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
|||
if (!uri) throw new Error("Missing note uri");
|
||||
|
||||
// Skip if URI points to this server
|
||||
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
||||
if (isSameOrigin(uri)) throw new Error("uri points local");
|
||||
|
||||
// A new resolver is created if not specified
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
|
|
@ -19,7 +19,7 @@ import { UserNotePining } from "@/models/entities/user-note-pining.js";
|
|||
import { genId } from "@/misc/gen-id.js";
|
||||
import { UserPublickey } from "@/models/entities/user-publickey.js";
|
||||
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
||||
import { toPuny } from "@/misc/convert-host.js";
|
||||
import { isSameOrigin, toPuny } from "@/misc/convert-host.js";
|
||||
import { UserProfile } from "@/models/entities/user-profile.js";
|
||||
import { toArray } from "@/prelude/array.js";
|
||||
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
|
||||
|
@ -138,7 +138,7 @@ export async function fetchPerson(
|
|||
if (cached) return cached;
|
||||
|
||||
// Fetch from the database if the URI points to this server
|
||||
if (uri.startsWith(`${config.url}/`)) {
|
||||
if (isSameOrigin(uri)) {
|
||||
const id = uri.split("/").pop();
|
||||
const u = await Users.findOneBy({ id });
|
||||
if (u) await uriPersonCache.set(uri, u);
|
||||
|
@ -166,7 +166,7 @@ export async function createPerson(
|
|||
): Promise<User> {
|
||||
if (typeof uri !== "string") throw new Error("uri is not string");
|
||||
|
||||
if (uri.startsWith(config.url)) {
|
||||
if (isSameOrigin(uri)) {
|
||||
throw new StatusError(
|
||||
"cannot resolve local user",
|
||||
400,
|
||||
|
@ -419,7 +419,7 @@ export async function updatePerson(
|
|||
if (typeof uri !== "string") throw new Error("uri is not string");
|
||||
|
||||
// Skip if the URI points to this server
|
||||
if (uri.startsWith(`${config.url}/`)) {
|
||||
if (isSameOrigin(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import config from "@/config/index.js";
|
||||
import Resolver from "../resolver.js";
|
||||
import type { IObject, IQuestion } from "../type.js";
|
||||
import { getApId, isQuestion } from "../type.js";
|
||||
import { apLogger } from "../logger.js";
|
||||
import { Notes, Polls } from "@/models/index.js";
|
||||
import type { IPoll } from "@/models/entities/poll.js";
|
||||
import { isSameOrigin } from "@/misc/convert-host.js";
|
||||
|
||||
export async function extractPollFromQuestion(
|
||||
source: string | IObject,
|
||||
|
@ -57,7 +57,7 @@ export async function updateQuestion(
|
|||
const uri = typeof value === "string" ? value : getApId(value);
|
||||
|
||||
// Skip if URI points to this server
|
||||
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
||||
if (isSameOrigin(uri)) throw new Error("uri points local");
|
||||
|
||||
//#region Already registered with this server?
|
||||
const note = await Notes.findOneBy({ uri });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { In, IsNull } from "typeorm";
|
||||
import config from "@/config/index.js";
|
||||
import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||
import { DriveFiles, Notes, Users, Emojis, Polls } from "@/models/index.js";
|
||||
import type { Emoji } from "@/models/entities/emoji.js";
|
||||
|
@ -61,26 +61,6 @@ export default async function renderNote(
|
|||
|
||||
const attributedTo = `${config.url}/users/${note.userId}`;
|
||||
|
||||
const mentions = (
|
||||
JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers
|
||||
).map((x) => x.uri);
|
||||
|
||||
let to: string[] = [];
|
||||
let cc: string[] = [];
|
||||
|
||||
if (note.visibility === "public") {
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"];
|
||||
cc = [`${attributedTo}/followers`].concat(mentions);
|
||||
} else if (note.visibility === "home") {
|
||||
to = [`${attributedTo}/followers`];
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public"].concat(mentions);
|
||||
} else if (note.visibility === "followers") {
|
||||
to = [`${attributedTo}/followers`];
|
||||
cc = mentions;
|
||||
} else {
|
||||
to = mentions;
|
||||
}
|
||||
|
||||
const mentionedUsers =
|
||||
note.mentions.length > 0
|
||||
? await Users.findBy({
|
||||
|
@ -88,6 +68,27 @@ export default async function renderNote(
|
|||
})
|
||||
: [];
|
||||
|
||||
const mentionUris = mentionedUsers
|
||||
// only remote users
|
||||
.filter((user) => Users.isRemoteUser(user))
|
||||
.map((user) => user.uri);
|
||||
|
||||
let to: string[] = [];
|
||||
let cc: string[] = [];
|
||||
|
||||
if (note.visibility === "public") {
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public"];
|
||||
cc = [`${attributedTo}/followers`].concat(mentionUris);
|
||||
} else if (note.visibility === "home") {
|
||||
to = [`${attributedTo}/followers`];
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public"].concat(mentionUris);
|
||||
} else if (note.visibility === "followers") {
|
||||
to = [`${attributedTo}/followers`];
|
||||
cc = mentionUris;
|
||||
} else {
|
||||
to = mentionUris;
|
||||
}
|
||||
|
||||
const hashtagTags = (note.tags || []).map((tag) => renderHashtag(tag));
|
||||
const mentionTags = mentionedUsers.map((u) => renderMention(u));
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Brackets } from "typeorm";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { Note } from "@/models/entities/note.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
@ -7,6 +6,7 @@ import { generateVisibilityQuery } from "@/server/api/common/generate-visibility
|
|||
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
||||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||
import type { SelectQueryBuilder } from "typeorm";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -69,6 +69,9 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
async function search(
|
||||
modifier?: (query: SelectQueryBuilder<Note>) => void,
|
||||
): Promise<Note[]> {
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
|
@ -76,6 +79,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
ps.sinceDate ?? undefined,
|
||||
ps.untilDate ?? undefined,
|
||||
);
|
||||
modifier?.(query);
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
|
@ -87,31 +91,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
});
|
||||
}
|
||||
|
||||
if (ps.query != null) {
|
||||
const q = sqlLikeEscape(ps.query);
|
||||
|
||||
if (ps.searchCwAndAlt) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.text &@~ :q", { q })
|
||||
.orWhere("note.cw &@~ :q", { q })
|
||||
.orWhere(
|
||||
`EXISTS (
|
||||
SELECT FROM "drive_file"
|
||||
WHERE
|
||||
comment &@~ :q
|
||||
AND
|
||||
drive_file."id" = ANY(note."fileIds")
|
||||
)`,
|
||||
{ q },
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
}
|
||||
}
|
||||
|
||||
query.innerJoinAndSelect("note.user", "user");
|
||||
|
||||
// "from: me": search all (public, home, followers, specified) my posts
|
||||
|
@ -153,7 +132,60 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
if (me) generateMutedUserQuery(query, me);
|
||||
if (me) generateBlockedUserQuery(query, me);
|
||||
|
||||
const notes: Note[] = await query.take(ps.limit).getMany();
|
||||
return await query.take(ps.limit).getMany();
|
||||
}
|
||||
|
||||
let notes: Note[];
|
||||
|
||||
if (ps.query != null) {
|
||||
const q = sqlLikeEscape(ps.query);
|
||||
|
||||
if (ps.searchCwAndAlt) {
|
||||
// Whether we should return latest notes first
|
||||
const isDescendingOrder =
|
||||
(ps.sinceId == null || ps.untilId != null) &&
|
||||
(ps.sinceId != null ||
|
||||
ps.untilId != null ||
|
||||
ps.sinceDate == null ||
|
||||
ps.untilDate != null);
|
||||
|
||||
const compare = isDescendingOrder
|
||||
? (lhs: Note, rhs: Note) =>
|
||||
Math.sign(rhs.createdAt.getTime() - lhs.createdAt.getTime())
|
||||
: (lhs: Note, rhs: Note) =>
|
||||
Math.sign(lhs.createdAt.getTime() - rhs.createdAt.getTime());
|
||||
|
||||
notes = [
|
||||
...new Map(
|
||||
(
|
||||
await Promise.all([
|
||||
search((query) => {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
}),
|
||||
search((query) => {
|
||||
query.andWhere("note.cw &@~ :q", { q });
|
||||
}),
|
||||
search((query) => {
|
||||
query
|
||||
.andWhere("drive_file.comment &@~ :q", { q })
|
||||
.innerJoin("note.files", "drive_file");
|
||||
}),
|
||||
])
|
||||
)
|
||||
.flatMap((e) => e)
|
||||
.map((note) => [note.id, note]),
|
||||
).values(),
|
||||
]
|
||||
.sort(compare)
|
||||
.slice(0, ps.limit);
|
||||
} else {
|
||||
notes = await search((query) => {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notes = await search();
|
||||
}
|
||||
|
||||
return await Notes.packMany(notes, me);
|
||||
});
|
||||
|
|
|
@ -90,6 +90,11 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
// TODO: federate ffVisibility
|
||||
if (profile.userHost != null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
|
||||
if (profile.ffVisibility === "private") {
|
||||
if (me == null || me.id !== user.id) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
|
|
|
@ -89,6 +89,11 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
||||
|
||||
// TODO: federate ffVisibility
|
||||
if (profile.userHost != null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
|
||||
if (profile.ffVisibility === "private") {
|
||||
if (me == null || me.id !== user.id) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
|
|
|
@ -49,6 +49,11 @@ export const paramDef = {
|
|||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
|
||||
|
||||
// TODO: federate publicReactions
|
||||
if (profile.userHost != null) {
|
||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||
}
|
||||
|
||||
if (!profile.publicReactions && (me == null || me.id !== ps.userId)) {
|
||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||
}
|
||||
|
|
|
@ -463,7 +463,7 @@ export default abstract class Chart<T extends Schema> {
|
|||
protected commit(diff: Commit<T>, group: string | null = null): void {
|
||||
for (const [k, v] of Object.entries(diff)) {
|
||||
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
|
||||
// rome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
|
||||
// biome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
|
||||
delete diff[k];
|
||||
}
|
||||
this.buffer.push({
|
||||
|
|
|
@ -134,13 +134,6 @@ export async function createMessage(
|
|||
userId: message.userId,
|
||||
visibility: "specified",
|
||||
mentions: [recipientUser].map((u) => u.id),
|
||||
mentionedRemoteUsers: JSON.stringify(
|
||||
[recipientUser].map((u) => ({
|
||||
uri: u.uri,
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
})),
|
||||
),
|
||||
} as Note;
|
||||
|
||||
let renderedNote: Record<string, unknown> = await renderNote(
|
||||
|
|
|
@ -18,7 +18,6 @@ import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instanc
|
|||
import { extractMentions } from "@/misc/extract-mentions.js";
|
||||
import { extractCustomEmojisFromMfm } from "@/misc/extract-custom-emojis-from-mfm.js";
|
||||
import { extractHashtags } from "@/misc/extract-hashtags.js";
|
||||
import type { IMentionedRemoteUsers } from "@/models/entities/note.js";
|
||||
import { Note } from "@/models/entities/note.js";
|
||||
import {
|
||||
Mutings,
|
||||
|
@ -31,6 +30,7 @@ import {
|
|||
Channels,
|
||||
ChannelFollowings,
|
||||
NoteThreadMutings,
|
||||
NoteFiles,
|
||||
} from "@/models/index.js";
|
||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||
import type { App } from "@/models/entities/app.js";
|
||||
|
@ -343,6 +343,12 @@ export default async (
|
|||
|
||||
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
||||
|
||||
await NoteFiles.insert(
|
||||
note.fileIds.map((fileId) => ({ noteId: note.id, fileId })),
|
||||
).catch((e) => {
|
||||
logger.error(inspect(e));
|
||||
});
|
||||
|
||||
res(note);
|
||||
|
||||
// Register host
|
||||
|
@ -744,21 +750,6 @@ async function insertNote(
|
|||
// Append mentions data
|
||||
if (mentionedUsers.length > 0) {
|
||||
insert.mentions = mentionedUsers.map((u) => u.id);
|
||||
const profiles = await UserProfiles.findBy({ userId: In(insert.mentions) });
|
||||
insert.mentionedRemoteUsers = JSON.stringify(
|
||||
mentionedUsers
|
||||
.filter((u) => Users.isRemoteUser(u))
|
||||
.map((u) => {
|
||||
const profile = profiles.find((p) => p.userId === u.id);
|
||||
const url = profile != null ? profile.url : null;
|
||||
return {
|
||||
uri: u.uri,
|
||||
url: url == null ? undefined : url,
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
} as IMentionedRemoteUsers[0];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 投稿を作成
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Brackets, In } from "typeorm";
|
||||
import { Brackets, In, IsNull, Not } from "typeorm";
|
||||
import { publishNoteStream } from "@/services/stream.js";
|
||||
import renderDelete from "@/remote/activitypub/renderer/delete.js";
|
||||
import renderAnnounce from "@/remote/activitypub/renderer/announce.js";
|
||||
|
@ -6,8 +6,8 @@ import renderUndo from "@/remote/activitypub/renderer/undo.js";
|
|||
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
|
||||
import renderTombstone from "@/remote/activitypub/renderer/tombstone.js";
|
||||
import config from "@/config/index.js";
|
||||
import { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
|
||||
import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js";
|
||||
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
import { Notes, Users, Instances } from "@/models/index.js";
|
||||
import {
|
||||
deliverToFollowers,
|
||||
|
@ -199,11 +199,12 @@ async function getMentionedRemoteUsers(note: Note) {
|
|||
const where = [] as any[];
|
||||
|
||||
// mention / reply / dm
|
||||
const uris = (
|
||||
JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers
|
||||
).map((x) => x.uri);
|
||||
if (uris.length > 0) {
|
||||
where.push({ uri: In(uris) });
|
||||
if (note.mentions.length > 0) {
|
||||
where.push({
|
||||
id: In(note.mentions),
|
||||
// only remote users, local users are on the server and do not need to be notified
|
||||
host: Not(IsNull()),
|
||||
});
|
||||
}
|
||||
|
||||
// renote / quote
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import rndstr from "rndstr";
|
||||
import { initDb } from "../src/db/postgre.js";
|
||||
import { initTestDb } from "./utils.js";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import httpSignature from "@peertube/http-signature";
|
||||
import { genRsaKeyPair } from "../src/misc/gen-key-pair.js";
|
||||
import {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
post,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
post,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
post,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import { inspect } from "node:util";
|
||||
import {
|
||||
signup,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
|
||||
import { parse } from "mfm-js";
|
||||
import { extractMentions } from "../src/misc/extract-mentions.js";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import * as openapi from "@redocly/openapi-core";
|
||||
import {
|
||||
async,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
connectStream,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { getFileInfo } from "../src/misc/get-file-info.js";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import * as mfm from "mfm-js";
|
||||
|
||||
import { fromHtml } from "../src/mfm/from-html.js";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
post,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import { Note } from "../src/models/entities/note.js";
|
||||
import {
|
||||
api,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import { just, nothing } from "../../src/prelude/maybe.js";
|
||||
|
||||
describe("just", () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as assert from "assert";
|
||||
import * as assert from "node:assert";
|
||||
import { query } from "../../src/prelude/url.js";
|
||||
|
||||
describe("url", () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import { Following } from "../src/models/entities/following.js";
|
||||
import {
|
||||
api,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
connectStream,
|
||||
|
|
|
@ -26,16 +26,9 @@
|
|||
"paths": {
|
||||
"@/*": ["../src/*"]
|
||||
},
|
||||
"typeRoots": [
|
||||
"../node_modules/@types",
|
||||
"../src/@types"
|
||||
],
|
||||
"lib": [
|
||||
"esnext"
|
||||
]
|
||||
"typeRoots": ["../node_modules/@types", "../src/@types"],
|
||||
"lib": ["esnext"]
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
process.env.NODE_ENV = "test";
|
||||
|
||||
import * as assert from "assert";
|
||||
import * as childProcess from "child_process";
|
||||
import * as assert from "node:assert";
|
||||
import type * as childProcess from "node:child_process";
|
||||
import {
|
||||
async,
|
||||
post,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as childProcess from "child_process";
|
||||
import { SIGKILL } from "constants";
|
||||
import * as childProcess from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import * as http from "node:http";
|
||||
import * as path from "node:path";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { endpoints, Entities } from "firefish-js";
|
||||
import type { Entities, endpoints } from "firefish-js";
|
||||
import FormData from "form-data";
|
||||
import got from "got";
|
||||
import fetch from "node-fetch";
|
||||
|
|
|
@ -25,24 +25,13 @@
|
|||
"rootDir": "./src",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"outDir": "./built",
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/@types"
|
||||
],
|
||||
"lib": [
|
||||
"esnext"
|
||||
]
|
||||
"types": ["node"],
|
||||
"typeRoots": ["./node_modules/@types", "./src/@types"],
|
||||
"lib": ["esnext"]
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
]
|
||||
"include": ["./src/**/*.ts"]
|
||||
}
|
||||
|
|
2
packages/client/@types/theme.d.ts
vendored
2
packages/client/@types/theme.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
declare module "@/themes/*.json5" {
|
||||
import { Theme } from "@/scripts/theme";
|
||||
import type { Theme } from "@/scripts/theme";
|
||||
|
||||
const theme: Theme;
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"city-timezones": "^1.2.1",
|
||||
"compare-versions": "6.1.0",
|
||||
"cropperjs": "2.0.0-beta.4",
|
||||
"date-fns": "3.3.1",
|
||||
"date-fns": "3.5.0",
|
||||
"emojilib": "^3.0.11",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-file-progress": "^1.3.0",
|
||||
|
@ -69,9 +69,9 @@
|
|||
"prettier": "3.2.5",
|
||||
"prismjs": "1.29.0",
|
||||
"punycode": "2.3.1",
|
||||
"rollup": "4.12.0",
|
||||
"rollup": "4.13.0",
|
||||
"s-age": "1.1.2",
|
||||
"sass": "1.71.1",
|
||||
"sass": "1.72.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"stringz": "2.1.0",
|
||||
"swiper": "11.0.7",
|
||||
|
@ -81,10 +81,10 @@
|
|||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"typescript": "5.3.3",
|
||||
"typescript": "5.4.2",
|
||||
"unicode-emoji-json": "^0.4.0",
|
||||
"uuid": "9.0.1",
|
||||
"vite": "5.1.5",
|
||||
"vite": "5.1.6",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-draggable-plus": "^0.3.5",
|
||||
|
|
|
@ -2,9 +2,8 @@ import type { entities } from "firefish-js";
|
|||
import { defineAsyncComponent } from "vue";
|
||||
import { i18n } from "./i18n";
|
||||
import { apiUrl } from "@/config";
|
||||
import { me } from "@/me";
|
||||
import { alert, api, popup, popupMenu, waiting } from "@/os";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import icon from "@/scripts/icon";
|
||||
import { del, get, set } from "@/scripts/idb-proxy";
|
||||
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
|
||||
|
||||
|
@ -12,11 +11,11 @@ import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
|
|||
|
||||
export type Account = entities.MeDetailed;
|
||||
|
||||
export async function signout() {
|
||||
export async function signOut() {
|
||||
waiting();
|
||||
localStorage.removeItem("account");
|
||||
|
||||
await removeAccount($i.id);
|
||||
await removeAccount(me.id);
|
||||
|
||||
const accounts = await getAccounts();
|
||||
|
||||
|
@ -29,7 +28,7 @@ export async function signout() {
|
|||
await fetch(`${apiUrl}/sw/unregister`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
i: $i.token,
|
||||
i: me.token,
|
||||
endpoint: push.endpoint,
|
||||
}),
|
||||
});
|
||||
|
@ -48,7 +47,7 @@ export async function signout() {
|
|||
|
||||
document.cookie = "igi=; path=/";
|
||||
|
||||
if (accounts.length > 0) login(accounts[0].token);
|
||||
if (accounts.length > 0) signIn(accounts[0].token);
|
||||
else unisonReload("/");
|
||||
}
|
||||
|
||||
|
@ -90,7 +89,7 @@ function fetchAccount(token: string): Promise<Account> {
|
|||
if (res.error) {
|
||||
if (res.error.id === "a8c724b3-6e9c-4b46-b1a8-bc3ed6258370") {
|
||||
showSuspendedDialog();
|
||||
signout();
|
||||
signOut();
|
||||
} else {
|
||||
alert({
|
||||
type: "error",
|
||||
|
@ -117,22 +116,23 @@ function showSuspendedDialog() {
|
|||
|
||||
export function updateAccount(accountData) {
|
||||
for (const [key, value] of Object.entries(accountData)) {
|
||||
$i[key] = value;
|
||||
me[key] = value;
|
||||
}
|
||||
localStorage.setItem("account", JSON.stringify($i));
|
||||
localStorage.setItem("account", JSON.stringify(me));
|
||||
}
|
||||
|
||||
export function refreshAccount() {
|
||||
return fetchAccount($i.token).then(updateAccount);
|
||||
export async function refreshAccount() {
|
||||
const accountData = await fetchAccount(me.token);
|
||||
return updateAccount(accountData);
|
||||
}
|
||||
|
||||
export async function login(token: Account["token"], redirect?: string) {
|
||||
export async function signIn(token: Account["token"], redirect?: string) {
|
||||
waiting();
|
||||
if (_DEV_) console.log("logging as token ", token);
|
||||
const me = await fetchAccount(token);
|
||||
localStorage.setItem("account", JSON.stringify(me));
|
||||
const newAccount = await fetchAccount(token);
|
||||
localStorage.setItem("account", JSON.stringify(newAccount));
|
||||
document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う
|
||||
await addAccount(me.id, token);
|
||||
await addAccount(newAccount.id, token);
|
||||
|
||||
if (redirect) {
|
||||
// 他のタブは再読み込みするだけ
|
||||
|
@ -190,11 +190,11 @@ export async function openAccountMenu(
|
|||
}
|
||||
|
||||
function switchAccountWithToken(token: string) {
|
||||
login(token);
|
||||
signIn(token);
|
||||
}
|
||||
|
||||
const storedAccounts = await getAccounts().then((accounts) =>
|
||||
accounts.filter((x) => x.id !== $i.id),
|
||||
accounts.filter((x) => x.id !== me.id),
|
||||
);
|
||||
const accountsPromise = api("users/show", {
|
||||
userIds: storedAccounts.map((x) => x.id),
|
||||
|
@ -256,12 +256,12 @@ export async function openAccountMenu(
|
|||
{
|
||||
type: "link",
|
||||
text: i18n.ts.profile,
|
||||
to: `/@${$i.username}`,
|
||||
avatar: $i,
|
||||
to: `/@${me.username}`,
|
||||
avatar: me,
|
||||
},
|
||||
null,
|
||||
]),
|
||||
...(opts.includeCurrentAccount ? [createItem($i)] : []),
|
||||
...(opts.includeCurrentAccount ? [createItem(me)] : []),
|
||||
...accountItemPromises,
|
||||
...(isMobile ?? false
|
||||
? [
|
||||
|
@ -269,8 +269,8 @@ export async function openAccountMenu(
|
|||
{
|
||||
type: "link",
|
||||
text: i18n.ts.profile,
|
||||
to: `/@${$i.username}`,
|
||||
avatar: $i,
|
||||
to: `/@${me.username}`,
|
||||
avatar: me,
|
||||
},
|
||||
]
|
||||
: [
|
||||
|
@ -304,7 +304,7 @@ export async function openAccountMenu(
|
|||
} else {
|
||||
popupMenu(
|
||||
[
|
||||
...(opts.includeCurrentAccount ? [createItem($i)] : []),
|
||||
...(opts.includeCurrentAccount ? [createItem(me)] : []),
|
||||
...accountItemPromises,
|
||||
],
|
||||
ev.currentTarget ?? ev.target,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:class="{
|
||||
isMe: isMe(message),
|
||||
isRead: message.groupId
|
||||
? message.reads.includes($i?.id)
|
||||
? message.reads.includes(me?.id)
|
||||
: message.isRead,
|
||||
}"
|
||||
:to="
|
||||
|
@ -67,14 +67,14 @@
|
|||
<script lang="ts" setup>
|
||||
import { acct } from "firefish-js";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
|
||||
defineProps<{
|
||||
message: Record<string, any>;
|
||||
}>();
|
||||
|
||||
function isMe(message): boolean {
|
||||
return message.userId === $i?.id;
|
||||
return message.userId === me?.id;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ import Cropper from "cropperjs";
|
|||
import tinycolor from "tinycolor2";
|
||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||
import * as os from "@/os";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import { defaultStore } from "@/store";
|
||||
import { apiUrl, url } from "@/config";
|
||||
import { query } from "@/scripts/url";
|
||||
|
@ -81,7 +81,7 @@ const ok = async () => {
|
|||
method: "POST",
|
||||
body: formData,
|
||||
headers: {
|
||||
authorization: `Bearer ${$i.token}`,
|
||||
authorization: `Bearer ${me.token}`,
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div :class="$style.text">
|
||||
{{ i18n.ts._aboutFirefish.pleaseDonateToFirefish }}
|
||||
<p v-if="$instance.donationLink">
|
||||
<p v-if="instance.donationLink">
|
||||
{{
|
||||
i18n.t("_aboutFirefish.pleaseDonateToHost", {
|
||||
host: hostname,
|
||||
|
@ -27,9 +27,9 @@
|
|||
>{{ i18n.ts._aboutFirefish.donate }}</MkButton
|
||||
>
|
||||
<MkButton
|
||||
v-if="$instance.donationLink"
|
||||
v-if="instance.donationLink"
|
||||
gradate
|
||||
@click="openExternal($instance.donationLink)"
|
||||
@click="openExternal(instance.donationLink)"
|
||||
>{{
|
||||
i18n.t("_aboutFirefish.donateHost", {
|
||||
host: hostname,
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
@dragstart="onDragstart"
|
||||
@dragend="onDragend"
|
||||
>
|
||||
<div v-if="$i?.avatarId == file.id" class="label">
|
||||
<div v-if="me?.avatarId == file.id" class="label">
|
||||
<img src="/client-assets/label.svg" />
|
||||
<p>{{ i18n.ts.avatar }}</p>
|
||||
</div>
|
||||
<div v-if="$i?.bannerId == file.id" class="label">
|
||||
<div v-if="me?.bannerId == file.id" class="label">
|
||||
<img src="/client-assets/label.svg" />
|
||||
<p>{{ i18n.ts.banner }}</p>
|
||||
</div>
|
||||
|
@ -45,7 +45,7 @@ import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
|||
import bytes from "@/filters/bytes";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import icon from "@/scripts/icon";
|
||||
|
||||
const props = withDefaults(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<i :class="icon('ph-dots-three-outline')"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="!hideFollowButton && isSignedIn && $i.id != user.id"
|
||||
v-if="!hideFollowButton && isSignedIn && me.id != user.id"
|
||||
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
|
||||
class="kpoogebi _button follow-button"
|
||||
:class="{
|
||||
|
@ -66,7 +66,7 @@ import type { entities } from "firefish-js";
|
|||
import * as os from "@/os";
|
||||
import { useStream } from "@/stream";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||
import { useRouter } from "@/router";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
|
|
@ -55,6 +55,7 @@ const commonNames = new Map<string, string>([
|
|||
["gnusocial", "GNU social"],
|
||||
["gotosocial", "GoToSocial"],
|
||||
["kbin", "/kbin"],
|
||||
["kmyblue", "kmyblue"],
|
||||
["microblogpub", "microblog.pub"],
|
||||
["nextcloud social", "Nextcloud Social"],
|
||||
["peertube", "PeerTube"],
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { toUnicode } from "punycode";
|
||||
import { host as localHost } from "@/config";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { defaultStore } from "@/store";
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -53,8 +53,8 @@ const url = `/${canonical}`;
|
|||
|
||||
const isMe =
|
||||
isSignedIn &&
|
||||
`@${props.username}@${toUnicode(props.host)}` ===
|
||||
`@${$i.username}@${toUnicode(localHost)}`.toLowerCase();
|
||||
`@${props.username}@${toUnicode(props.host)}`.toLowerCase() ===
|
||||
`@${me.username}@${toUnicode(localHost)}`.toLowerCase();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
<Mfm
|
||||
:text="translation.text"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:i="me"
|
||||
:lang="targetLang"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
|
@ -296,7 +296,7 @@ import { userPage } from "@/filters/user";
|
|||
import * as os from "@/os";
|
||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { i18n } from "@/i18n";
|
||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||
|
@ -355,13 +355,13 @@ const reactButton = ref<HTMLElement>();
|
|||
const appearNote = computed(() =>
|
||||
isRenote ? (note.value.renote as entities.Note) : note.value,
|
||||
);
|
||||
const isMyRenote = isSignedIn && $i.id === note.value.userId;
|
||||
const isMyRenote = isSignedIn && me.id === note.value.userId;
|
||||
const showContent = ref(false);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i?.id,
|
||||
me?.id,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
|
@ -632,9 +632,7 @@ function setPostExpanded(val: boolean) {
|
|||
const accessibleLabel = computed(() => {
|
||||
let label = `${appearNote.value.user.username}; `;
|
||||
if (appearNote.value.renote) {
|
||||
label += `${i18n.t("renoted")} ${
|
||||
appearNote.value.renote.user.username
|
||||
}; `;
|
||||
label += `${i18n.t("renoted")} ${appearNote.value.renote.user.username}; `;
|
||||
if (appearNote.value.renote.cw) {
|
||||
label += `${i18n.t("cw")}: ${appearNote.value.renote.cw}; `;
|
||||
if (postIsExpanded.value) {
|
||||
|
|
|
@ -180,7 +180,7 @@ import { userPage } from "@/filters/user";
|
|||
import * as os from "@/os";
|
||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import { i18n } from "@/i18n";
|
||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||
|
@ -235,7 +235,7 @@ const isDeleted = ref(false);
|
|||
const muted = ref(
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i?.id,
|
||||
me?.id,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<div v-size="{ min: [350, 500] }" class="fefdfafb">
|
||||
<MkAvatar class="avatar" :user="$i" disable-link />
|
||||
<MkAvatar class="avatar" :user="me" disable-link />
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<MkUserName :user="$i" />
|
||||
<MkUserName :user="me" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="content">
|
||||
<Mfm
|
||||
:text="preprocess(text).trim()"
|
||||
:lang="lang"
|
||||
:author="$i"
|
||||
:i="$i"
|
||||
:author="me"
|
||||
:i="me"
|
||||
advanced-mfm
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<Mfm
|
||||
:text="translation.text"
|
||||
:author="appearNote.user"
|
||||
:i="$i"
|
||||
:i="me"
|
||||
:lang="targetLang"
|
||||
:custom-emojis="appearNote.emojis"
|
||||
/>
|
||||
|
@ -211,7 +211,7 @@ import { useRouter } from "@/router";
|
|||
import { userPage } from "@/filters/user";
|
||||
import * as os from "@/os";
|
||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { i18n } from "@/i18n";
|
||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||
import { defaultStore } from "@/store";
|
||||
|
@ -269,7 +269,7 @@ const isDeleted = ref(false);
|
|||
const muted = ref(
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i?.id,
|
||||
me?.id,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
|
|
|
@ -54,7 +54,7 @@ import XNotification from "@/components/MkNotification.vue";
|
|||
import XList from "@/components/MkDateSeparatedList.vue";
|
||||
import XNote from "@/components/MkNote.vue";
|
||||
import { useStream } from "@/stream";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import { i18n } from "@/i18n";
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -73,7 +73,7 @@ const pagination: Paging = {
|
|||
includeTypes: props.includeTypes ?? undefined,
|
||||
excludeTypes: props.includeTypes
|
||||
? undefined
|
||||
: $i.mutingNotificationTypes,
|
||||
: me.mutingNotificationTypes,
|
||||
unreadOnly: props.unreadOnly,
|
||||
})),
|
||||
};
|
||||
|
@ -81,7 +81,7 @@ const pagination: Paging = {
|
|||
const onNotification = (notification) => {
|
||||
const isMuted = props.includeTypes
|
||||
? !props.includeTypes.includes(notification.type)
|
||||
: $i.mutingNotificationTypes.includes(notification.type);
|
||||
: me.mutingNotificationTypes.includes(notification.type);
|
||||
if (isMuted || document.visibilityState === "visible") {
|
||||
stream.send("readNotification", {
|
||||
id: notification.id,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
class="account _button"
|
||||
@click="openAccountMenu"
|
||||
>
|
||||
<MkAvatar :user="postAccount ?? $i" class="avatar" />
|
||||
<MkAvatar :user="postAccount ?? me" class="avatar" />
|
||||
</button>
|
||||
<div class="right">
|
||||
<span
|
||||
|
@ -322,7 +322,7 @@ import MkInfo from "@/components/MkInfo.vue";
|
|||
import { i18n } from "@/i18n";
|
||||
import { instance } from "@/instance";
|
||||
import { getAccounts, openAccountMenu as openAccountMenu_ } from "@/account";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import { uploadFile } from "@/scripts/upload";
|
||||
import { deepClone } from "@/scripts/clone";
|
||||
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
||||
|
@ -517,7 +517,7 @@ if (props.mention) {
|
|||
|
||||
if (
|
||||
props.reply &&
|
||||
(props.reply.user.username !== $i.username ||
|
||||
(props.reply.user.username !== me.username ||
|
||||
(props.reply.user.host != null && props.reply.user.host !== host))
|
||||
) {
|
||||
text.value = `@${props.reply.user.username}${
|
||||
|
@ -539,7 +539,7 @@ if (props.reply && props.reply.text != null) {
|
|||
: `@${x.username}@${toASCII(otherHost)}`;
|
||||
|
||||
// exclude me
|
||||
if ($i.username === x.username && (x.host == null || x.host === host))
|
||||
if (me.username === x.username && (x.host == null || x.host === host))
|
||||
continue;
|
||||
|
||||
// remove duplicates
|
||||
|
@ -573,7 +573,7 @@ if (
|
|||
if (props.reply.visibleUserIds) {
|
||||
os.api("users/show", {
|
||||
userIds: props.reply.visibleUserIds.filter(
|
||||
(uid) => uid !== $i.id && uid !== props.reply.userId,
|
||||
(uid) => uid !== me.id && uid !== props.reply.userId,
|
||||
),
|
||||
}).then((users) => {
|
||||
users.forEach(pushVisibleUser);
|
||||
|
@ -582,7 +582,7 @@ if (
|
|||
visibility.value = "private";
|
||||
}
|
||||
|
||||
if (props.reply.userId !== $i.id) {
|
||||
if (props.reply.userId !== me.id) {
|
||||
os.api("users/show", { userId: props.reply.userId }).then(
|
||||
(user) => {
|
||||
pushVisibleUser(user);
|
||||
|
@ -611,7 +611,7 @@ const addRe = (s: string) => {
|
|||
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
|
||||
useCw.value = true;
|
||||
cw.value =
|
||||
props.reply.user.username === $i.username
|
||||
props.reply.user.username === me.username
|
||||
? props.reply.cw
|
||||
: addRe(props.reply.cw);
|
||||
}
|
||||
|
@ -1194,9 +1194,9 @@ function openAccountMenu(ev: MouseEvent) {
|
|||
{
|
||||
withExtraOperation: false,
|
||||
includeCurrentAccount: true,
|
||||
active: postAccount.value != null ? postAccount.value.id : $i.id,
|
||||
active: postAccount.value != null ? postAccount.value.id : me.id,
|
||||
onChoose: (account) => {
|
||||
if (account.id === $i.id) {
|
||||
if (account.id === me.id) {
|
||||
postAccount.value = null;
|
||||
} else {
|
||||
postAccount.value = account;
|
||||
|
|
|
@ -45,7 +45,6 @@ const isRefreshing = ref(false);
|
|||
const pullDistance = ref(0);
|
||||
|
||||
let disabled = false;
|
||||
|
||||
let supportPointerDesktop = false;
|
||||
let startScreenY: number | null = null;
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
import { ref } from "vue";
|
||||
|
||||
import { getAccounts } from "@/account";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import MkButton from "@/components/MkButton.vue";
|
||||
import { instance } from "@/instance";
|
||||
import { api, apiWithDialog, promiseDialog } from "@/os";
|
||||
|
@ -149,7 +149,7 @@ async function unsubscribe() {
|
|||
|
||||
if (isSignedIn && accounts.length >= 2) {
|
||||
apiWithDialog("sw/unregister", {
|
||||
i: $i.token,
|
||||
i: me.token,
|
||||
endpoint,
|
||||
});
|
||||
} else {
|
||||
|
@ -197,7 +197,7 @@ if (navigator.serviceWorker == null) {
|
|||
instance.swPublickey &&
|
||||
"PushManager" in window &&
|
||||
isSignedIn &&
|
||||
$i.token
|
||||
me.token
|
||||
) {
|
||||
supported.value = true;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { computed } from "vue";
|
|||
import type { entities } from "firefish-js";
|
||||
import { pleaseLogin } from "@/scripts/please-login";
|
||||
import * as os from "@/os";
|
||||
import { $i } from "@/reactiveAccount";
|
||||
import { me } from "@/me";
|
||||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import icon from "@/scripts/icon";
|
||||
|
@ -26,7 +26,7 @@ const props = defineProps<{
|
|||
const canRenote = computed(
|
||||
() =>
|
||||
["public", "home"].includes(props.note.visibility) ||
|
||||
props.note.userId === $i?.id,
|
||||
props.note.userId === me?.id,
|
||||
);
|
||||
|
||||
function quote(): void {
|
||||
|
|
|
@ -28,7 +28,7 @@ import XDetails from "@/components/MkReactionsViewer.details.vue";
|
|||
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||
import * as os from "@/os";
|
||||
import { useTooltip } from "@/scripts/use-tooltip";
|
||||
import { isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn } from "@/me";
|
||||
|
||||
const props = defineProps<{
|
||||
reaction: string;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import type { entities } from "firefish-js";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import XReaction from "@/components/MkReactionsViewer.reaction.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -30,7 +30,7 @@ const reactionsEl = ref<HTMLElement>();
|
|||
|
||||
const initialReactions = new Set(Object.keys(props.note.reactions));
|
||||
|
||||
const isMe = computed(() => isSignedIn && $i.id === props.note.userId);
|
||||
const isMe = computed(() => isSignedIn && me.id === props.note.userId);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -27,7 +27,7 @@ import Ripple from "@/components/MkRipple.vue";
|
|||
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||
import { pleaseLogin } from "@/scripts/please-login";
|
||||
import * as os from "@/os";
|
||||
import { $i, isSignedIn } from "@/reactiveAccount";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { useTooltip } from "@/scripts/use-tooltip";
|
||||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
|
@ -46,7 +46,7 @@ const buttonRef = ref<HTMLElement>();
|
|||
const canRenote = computed(
|
||||
() =>
|
||||
["public", "home"].includes(props.note.visibility) ||
|
||||
props.note.userId === $i.id,
|
||||
props.note.userId === me.id,
|
||||
);
|
||||
|
||||
useTooltip(buttonRef, async (showing) => {
|
||||
|
@ -77,7 +77,7 @@ const hasRenotedBefore = ref(false);
|
|||
if (isSignedIn) {
|
||||
os.api("notes/renotes", {
|
||||
noteId: props.note.id,
|
||||
userId: $i.id,
|
||||
userId: me.id,
|
||||
limit: 1,
|
||||
}).then((res) => {
|
||||
hasRenotedBefore.value = res.length > 0;
|
||||
|
|
|
@ -52,7 +52,7 @@ export default defineComponent({
|
|||
flag: true,
|
||||
radio: "firefish",
|
||||
mfm: `Hello world! This is an @example mention. BTW, you are @${
|
||||
this.$i ? this.$i.username : "guest"
|
||||
this.me ? this.me.username : "guest"
|
||||
}.\nAlso, here is ${config.url} and [example link](${
|
||||
config.url
|
||||
}). for more details, see <https://firefish.dev/firefish/firefish>.\nAs you know #Firefish is open-source software.`,
|
||||
|
|
|
@ -130,9 +130,7 @@ const searchUsers = ref(
|
|||
);
|
||||
const searchRange = ref(
|
||||
searchParams.has("since") || searchParams.has("until")
|
||||
? `${searchParams.get("since") ?? ""}-${
|
||||
searchParams.get("until") ?? ""
|
||||
}`
|
||||
? `${searchParams.get("since") ?? ""}-${searchParams.get("until") ?? ""}`
|
||||
: "",
|
||||
);
|
||||
const searchPostsWithFiles = ref(searchParams.get("withFiles") === "1");
|
||||
|
|
|
@ -142,7 +142,7 @@ import MkInfo from "@/components/MkInfo.vue";
|
|||
import { host as configHost } from "@/config";
|
||||
import { byteify, hexify } from "@/scripts/2fa";
|
||||
import * as os from "@/os";
|
||||
import { login } from "@/account";
|
||||
import { signIn } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
import icon from "@/scripts/icon";
|
||||
|
||||
|
@ -195,7 +195,7 @@ function onUsernameChange() {
|
|||
|
||||
function onLogin(res) {
|
||||
if (props.autoSet) {
|
||||
return login(res.i);
|
||||
return signIn(res.i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -281,7 +281,7 @@ import MkSwitch from "./form/switch.vue";
|
|||
import MkCaptcha from "@/components/MkCaptcha.vue";
|
||||
import * as config from "@/config";
|
||||
import * as os from "@/os";
|
||||
import { login } from "@/account";
|
||||
import { signIn } from "@/account";
|
||||
import { instance } from "@/instance";
|
||||
import { i18n } from "@/i18n";
|
||||
import icon from "@/scripts/icon";
|
||||
|
@ -470,7 +470,7 @@ function onSubmit(): void {
|
|||
emit("signup", res);
|
||||
|
||||
if (props.autoSet) {
|
||||
login(res.i);
|
||||
signIn(res.i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue