Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • TransFem-org/Sharkey
  • fEmber/Sharkey
  • kopper/Sharkey
  • dakkar/Sharkey-thenautilus
  • esm/Sharkey
  • blueb/Sharkey
  • aura/Sharkey
  • alicem/Sharkey
  • dimkr/Sharkey
  • vvinrg/Sharkey
  • tess/Sharkey
  • Latte_macchiato/Sharkey
  • skyevg/Sharkey
  • TransFem-org/sharkey-trans-fem
  • limepotato/sharkey
  • elrant/villkey
  • kio/kitsukey
  • 4censord/Sharkey
  • kanade/Sharkey
  • kakkokari-gtyih/Sharkey
  • piuvas/Sharkey
  • kio/gaykey
  • owlbear/Sharkey
  • esurio/Sharkey
  • cuteBoiButt/Sharkey
  • magi/Sharkey
  • chikorita157/Sharkey
  • CenTdemeern1/Sharkey
  • GeopJr/Sharkey
  • lhcfl/sharkey
  • cody/Sharkey
  • puppygirlhornypost/Sharkey
  • Sneexy/Sharkey
  • zotan/Sharkey
  • Marie/Sharkey
  • maciejla/Sharkey
  • transitory/Sharkey
  • Caramel/Sharkey
  • fly_mc/Sharkey
  • 87/Sharkey
  • echo/Sharkey
  • transfemsoc/Sharkey
  • ch0ccyra1n/Sharkey
  • interru/Sharkey
  • legiayayana/Sharkey
  • elizabeth-dev/Sharkey
  • JeDaYoshi/Sharkey
  • bunnybeam/Sharkey
  • and-then-there-were-two/Sharkey
  • easrng/Sharkey
  • HellhoundSoftware/Sharkey
  • realkinetix/Sharkey
  • sandervankasteel/Sharkey
  • jacobwhall/Sharkey
  • Daniel/Sharkey
  • rhythmkey/rhythmkey
56 results
Show changes
Commits on Source (728)
Showing with 784 additions and 28 deletions
......@@ -167,8 +167,18 @@ id: 'aidx'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
maxNoteLength: 3000
# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
#maxNoteLength: 3000
# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
#maxRemoteNoteLength: 100000
# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
#maxCwLength: 500
# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
#maxRemoteCwLength: 5000
# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
#maxAltTextLength: 20000
# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
#maxRemoteAltTextLength: 100000
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
......
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: 'http://misskey.local'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 61812
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: db
port: 5432
# Database name
db: misskey
# Auth
user: postgres
pass: postgres
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 32
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
#maxNoteLength: 3000
# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
#maxRemoteNoteLength: 100000
# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
#maxCwLength: 500
# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
#maxRemoteCwLength: 5000
# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
#maxAltTextLength: 20000
# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
#maxRemoteAltTextLength: 100000
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
# Proxy remote files (default: true)
proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: true)
signToActivityPubGet: true
allowedPrivateNetworks: [
'127.0.0.1/32'
]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
......@@ -163,6 +163,14 @@ redis:
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
......@@ -242,8 +250,18 @@ id: 'aidx'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
maxNoteLength: 3000
# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
#maxNoteLength: 3000
# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
#maxRemoteNoteLength: 100000
# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
#maxCwLength: 500
# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
#maxRemoteCwLength: 5000
# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
#maxAltTextLength: 20000
# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
#maxRemoteAltTextLength: 100000
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
......
......@@ -172,6 +172,16 @@ redis:
# # You can specify more ioredis options...
# #username: example-username
#redisForReactions:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
......@@ -251,8 +261,18 @@ id: 'aidx'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
maxNoteLength: 3000
# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
#maxNoteLength: 3000
# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
#maxRemoteNoteLength: 100000
# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
#maxCwLength: 500
# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
#maxRemoteCwLength: 5000
# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
#maxAltTextLength: 20000
# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
#maxRemoteAltTextLength: 100000
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
......
......@@ -103,6 +103,14 @@ redis:
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
......
......@@ -3,6 +3,8 @@
set -xe
sudo chown node node_modules
sudo apt-get update
sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
git config --global --add safe.directory /workspace
git submodule update --init
corepack install
......@@ -12,3 +14,4 @@ pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build
pnpm migrate
pnpm exec cypress install
......@@ -38,6 +38,7 @@ coverage
!/.config/example.yml
!/.config/docker_example.yml
!/.config/docker_example.env
!/.config/cypress-devcontainer.yml
docker-compose.yml
compose.yml
.devcontainer/compose.yml
......@@ -47,6 +48,7 @@ compose.yml
/build
built
built-test
js-built
/data
/.cache-loader
/db
......@@ -66,8 +68,9 @@ temp
tsdoc-metadata.json
misskey-assets
# Sharkey
/packages/megalodon/lib
# Vite temporary files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# blender backups
*.blend1
......@@ -78,3 +81,6 @@ misskey-assets
# VSCode addon
.favorites.json
# Sharkey
/packages/megalodon/lib
......@@ -20,9 +20,9 @@ testCommit:
- pnpm install --frozen-lockfile
- pnpm run build
- pnpm run migrate
- pnpm run --filter='!megalodon' --workspace-concurrency=1 test
- pnpm run --filter=backend lint
- pnpm run --filter=frontend eslint
- pnpm run --filter='!megalodon' test
- pnpm run --filter=backend --filter=misskey-js lint
- pnpm run --filter=frontend --filter=frontend-embed eslint
cache:
key: test
policy: pull-push
......
## 2024.9.0
### General
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
- 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
- Feat: パスキーでログインボタンを実装 (#14574)
- Feat: フォローされた際のメッセージを設定できるように
- Feat: 連合をホワイトリスト制にできるように
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
- Feat: データエクスポートが完了した際に通知を発行するように
- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
- Enhance: 依存関係の更新
- Enhance: l10nの更新
### Client
- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
- Enhance: アイコンデコレーション管理画面にプレビューを追加
- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
- Enhance: ScratchpadにUIインスペクターを追加
- Enhance: Play編集画面の項目の並びを少しリデザイン
- Enhance: 各種メニューをドロワー表示するかどうか設定可能に
- Enhance: AiScriptのMk:C:containerのオプションに`borderStyle``borderRadius`を追加
- Enhance: CWでも絵文字をクリックしてメニューを表示できるように
- Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/265)
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
- Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 )
### Server
- Feat: Misskey® Reactions Boost Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
- この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
- Fix: Continue importing from file if single emoji import fails
- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634)
- Fix: `<link rel="alternate">`を追って照会するのはOKレスポンスが返却された場合のみに
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/633)
- Fix: メールにスタイルが適用されていなかった問題を修正
## 2024.8.0
### General
......
......@@ -529,7 +529,8 @@ enumの列挙の内容の削除は、その値をもつレコードを全て削
### Migration作成方法
packages/backendで:
```sh
pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
pnpm run build
pnpm dlx typeorm migration:generate -d ormconfig.js -o migration/<migration name>
```
- 生成後、ファイルをmigration下に移してください
......@@ -573,6 +574,26 @@ marginはそのコンポーネントを使う側が設定する
### indexというファイル名を使うな
ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
## CSS Recipe
### Lighten CSS vars
``` css
color: hsl(from var(--accent) h s calc(l + 10));
```
### Darken CSS vars
``` css
color: hsl(from var(--accent) h s calc(l - 10));
```
### Add alpha to CSS vars
``` css
color: color(from var(--accent) srgb r g b / 0.5);
```
## Merging from Misskey into Sharkey
Make sure you have both remotes in the same clone (`git remote add misskey
......@@ -590,15 +611,11 @@ seems to do a decent job)
*after that commit*, do all the extra work, on the same branch:
* copy all changes (commit after each step):
* in `packages/backend/src/core/NoteCreateService.ts`, from `create` to
`import` (and vice versa if `git` got confused!)
* in
`packages/backend/src/core/activitypub/models/ApNoteService.ts`,
from `createNote` to `updateNote`
* from `packages/backend/src/core/NoteCreateService.ts` to
`packages/backend/src/core/NoteEditService.vue`
* in `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
from `createNote` to `updateNote`
* from `packages/backend/src/server/api/endpoints/notes/create.ts`
to `packages/backend/src/server/api/endpoints/notes/edit.ts`
* from `packages/frontend/src/components/MkNote*.vue` to
......@@ -614,9 +631,9 @@ seems to do a decent job)
* check the changes against our `develop` (`git diff develop`) and
against Misskey (`git diff misskey/develop`)
* re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit
* build the frontend: `rm -rf built/; NODE_ENV=development pnpm --filter=frontend
build` (the `development` tells it to keep some of the original
filenames in the built files)
* build the frontend: `rm -rf built/; NODE_ENV=development pnpm
--filter=frontend --filter=frontend-embed build` (the `development`
tells it to keep some of the original filenames in the built files)
* make sure there aren't any new `ti-*` classes (Tabler Icons), and
replace them with appropriate `ph-*` ones (Phosphor Icons):
`grep -rP '["'\'']ti[ -](?!fw)' -- built/` should show you what to change.
......
......@@ -23,9 +23,10 @@ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
RUN pnpm build
RUN node scripts/trim-deps.mjs
RUN mv packages/frontend/assets sharkey-assets
RUN mv packages/frontend-embed/assets sharkey-embed-assets
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm prune
RUN rm -r node_modules packages/frontend packages/sw
RUN rm -r node_modules packages/frontend packages/frontend-shared packages/frontend-embed packages/sw
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --prod --frozen-lockfile --aggregate-output
RUN rm -rf .git
......@@ -64,6 +65,7 @@ COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/lib ./pack
COPY --chown=sharkey:sharkey --from=build /sharkey/fluent-emojis ./fluent-emojis
COPY --chown=sharkey:sharkey --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist
COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-assets ./packages/frontend/assets
COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-embed-assets ./packages/frontend-embed/assets
COPY --chown=sharkey:sharkey pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --chown=sharkey:sharkey packages/backend/package.json ./packages/backend/package.json
......
# Upgrade Notes
## 2024.9.0
### Following Feed
When upgrading an existing instance to version 2024.9.0, the Following Feed will initially be empty.
The feed will gradually fill as new posts federate, but it may be desirable to back-fill the feed with existing data.
This database script will populate the feed with the latest post of each type for all users, ensuring that data is fully populated after the update.
Run this after migrations but before starting the instance.
Warning: the script may take a long time to execute!
```postgresql
INSERT INTO latest_note (user_id, note_id, is_public, is_reply, is_quote)
SELECT
"userId" as user_id,
id as note_id,
visibility = 'public' AS is_public,
"replyId" IS NOT NULL AS is_reply,
(
"renoteId" IS NOT NULL
AND (
text IS NOT NULL
OR cw IS NOT NULL
OR "replyId" IS NOT NULL
OR "hasPoll"
OR "fileIds" != '{}'
)
) AS is_quote
FROM note
WHERE ( -- Exclude pure renotes (boosts)
"renoteId" IS NULL
OR text IS NOT NULL
OR cw IS NOT NULL
OR "replyId" IS NOT NULL
OR "hasPoll"
OR "fileIds" != '{}'
)
ORDER BY id DESC -- This part is very important: it ensures that we only load the *latest* notes of each type. Do not remove it!
ON CONFLICT DO NOTHING; -- Any conflicts are guaranteed to be older notes that we can ignore.
```
......@@ -124,6 +124,14 @@ redis:
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
......
......@@ -53,7 +53,7 @@ services:
# restart: always
# image: mcaptcha/mcaptcha:latest
# networks:
# shonks:
# shonk:
# aliases:
# - localhost
# ports:
......@@ -63,6 +63,8 @@ services:
# environment:
# PORT: 7493
# MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
# MCAPTCHA_allow_registration: true
# MCAPTCHA_server_DOMAIN: "example.tld"
# depends_on:
# db:
# condition: service_healthy
......@@ -72,7 +74,7 @@ services:
# mcaptcha_redis:
# image: mcaptcha/cache:latest
# networks:
# - shonks
# - shonk
# healthcheck:
# test: "redis-cli ping"
# interval: 5s
......
files:
- source: /locales/ja-JP.yml
translation: /locales/%locale%.yml
- source: /sharkey-locales/en-US.yml
translation: /sharkey-locales/%locale%.yml
update_option: update_as_unapproved
/*
* SPDX-FileCopyrightText: dakkar and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* This is a ESLint rule to report use of the `i18n.ts` and `i18n.tsx`
* objects that reference translation items that don't actually exist
* in the lexicon (the `locale/` files)
*/
/* given a MemberExpression node, collects all the member names
*
* e.g. for a bit of code like `foo=one.two.three`, `collectMembers`
* called on the node for `three` would return `['one', 'two',
* 'three']`
*/
function collectMembers(node) {
if (!node) return [];
if (node.type !== 'MemberExpression') return [];
// this is something like `foo[bar]`
if (node.computed) return [];
return [ node.property.name, ...collectMembers(node.parent) ];
}
/* given an object and an array of names, recursively descends the
* object via those names
*
* e.g. `walkDown({one:{two:{three:15}}},['one','two','three'])` would
* return 15
*/
function walkDown(locale, path) {
if (!locale) return null;
if (!path || path.length === 0 || !path[0]) return locale;
return walkDown(locale[path[0]], path.slice(1));
}
/* given a MemberExpression node, returns its attached CallExpression
* node if present
*
* e.g. for a bit of code like `foo=one.two.three()`,
* `findCallExpression` called on the node for `three` would return
* the node for function call (which is the parent of the `one` and
* `two` nodes, and holds the nodes for the argument list)
*
* if the code had been `foo=one.two.three`, `findCallExpression`
* would have returned null, because there's no function call attached
* to the MemberExpressions
*/
function findCallExpression(node) {
if (!node.parent) return null;
// the second half of this guard protects from cases like
// `foo(one.two.three)` where the CallExpression is parent of the
// MemberExpressions, but via `arguments`, not `callee`
if (node.parent.type === 'CallExpression' && node.parent.callee === node) return node.parent;
if (node.parent.type === 'MemberExpression') return findCallExpression(node.parent);
return null;
}
// same, but for Vue expressions (`<I18n :src="i18n.ts.foo">`)
function findVueExpression(node) {
if (!node.parent) return null;
if (node.parent.type.match(/^VExpr/) && node.parent.expression === node) return node.parent;
if (node.parent.type === 'MemberExpression') return findVueExpression(node.parent);
return null;
}
function areArgumentsOneObject(node) {
return node.arguments.length === 1 &&
node.arguments[0].type === 'ObjectExpression';
}
// only call if `areArgumentsOneObject(node)` is true
function getArgumentObjectProperties(node) {
return new Set(node.arguments[0].properties.map(
p => {
if (p.key && p.key.type === 'Identifier') return p.key.name;
return null;
},
));
}
function getTranslationParameters(translation) {
return new Set(Array.from(translation.matchAll(/\{(\w+)\}/g)).map( m => m[1] ));
}
function setDifference(a,b) {
const result = [];
for (const element of a.values()) {
if (!b.has(element)) {
result.push(element);
}
}
return result;
}
/* the actual rule body
*/
function theRuleBody(context,node) {
// we get the locale/translations via the options; it's the data
// that goes into a specific language's JSON file, see
// `scripts/build-assets.mjs`
const locale = context.options[0];
// sometimes we get MemberExpression nodes that have a
// *descendent* with the right identifier: skip them, we'll get
// the right ones as well
if (node.object?.name !== 'i18n') {
return;
}
// `method` is going to be `'ts'` or `'tsx'`, `path` is going to
// be the various translation steps/names
const [ method, ...path ] = collectMembers(node);
const pathStr = `i18n.${method}.${path.join('.')}`;
// does that path point to a real translation?
const translation = walkDown(locale, path);
if (!translation) {
context.report({
node,
message: `translation missing for ${pathStr}`,
});
return;
}
// we hit something weird, assume the programmers know what
// they're doing (this is usually some complicated slicing of
// the translation structure)
if (typeof(translation) !== 'string') return;
const callExpression = findCallExpression(node);
const vueExpression = findVueExpression(node);
// some more checks on how the translation is called
if (method === 'ts') {
// the `<I18n> component gets parametric translations via
// `i18n.ts.*`, but we error out elsewhere
if (translation.match(/\{/) && !vueExpression) {
context.report({
node,
message: `translation for ${pathStr} is parametric, but called via 'ts'`,
});
return;
}
if (callExpression) {
context.report({
node,
message: `translation for ${pathStr} is not parametric, but is called as a function`,
});
}
}
if (method === 'tsx') {
if (!translation.match(/\{/)) {
context.report({
node,
message: `translation for ${pathStr} is not parametric, but called via 'tsx'`,
});
return;
}
if (!callExpression && !vueExpression) {
context.report({
node,
message: `translation for ${pathStr} is parametric, but not called as a function`,
});
return;
}
// we're not currently checking arguments when used via the
// `<I18n>` component, because it's too complicated (also, it
// would have to be done inside the `if (method === 'ts')`)
if (!callExpression) return;
if (!areArgumentsOneObject(callExpression)) {
context.report({
node,
message: `translation for ${pathStr} should be called with a single object as argument`,
});
return;
}
const translationParameters = getTranslationParameters(translation);
const parameterCount = translationParameters.size;
const callArguments = getArgumentObjectProperties(callExpression);
const argumentCount = callArguments.size;
if (parameterCount !== argumentCount) {
context.report({
node,
message: `translation for ${pathStr} has ${parameterCount} parameters, but is called with ${argumentCount} arguments`,
});
}
// node 20 doesn't have `Set.difference`...
const extraArguments = setDifference(callArguments, translationParameters);
const missingArguments = setDifference(translationParameters, callArguments);
if (extraArguments.length > 0) {
context.report({
node,
message: `translation for ${pathStr} passes unused arguments ${extraArguments.join(' ')}`,
});
}
if (missingArguments.length > 0) {
context.report({
node,
message: `translation for ${pathStr} does not pass arguments ${missingArguments.join(' ')}`,
});
}
}
}
function theRule(context) {
// we get the locale/translations via the options; it's the data
// that goes into a specific language's JSON file, see
// `scripts/build-assets.mjs`
const locale = context.options[0];
// for all object member access that have an identifier 'i18n'...
return context.getSourceCode().parserServices.defineTemplateBodyVisitor(
{
// this is for <template> bits, needs work
'MemberExpression:has(Identifier[name=i18n])': (node) => theRuleBody(context, node),
},
{
// this is for normal code
'MemberExpression:has(Identifier[name=i18n])': (node) => theRuleBody(context, node),
},
);
}
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'assert that all translations used are present in the locale files',
},
schema: [
// here we declare that we need the locale/translation as a
// generic object
{ type: 'object', additionalProperties: true },
],
},
create: theRule,
};
/*
* SPDX-FileCopyrightText: dakkar and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
const {RuleTester} = require("eslint");
const localeRule = require("./locale");
const locale = { foo: { bar: 'ok', baz: 'good {x}' }, top: '123' };
const ruleTester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
ecmaVersion: 2015,
},
});
function testCase(code,errors) {
return { code, errors, options: [ locale ], filename: 'test.ts' };
}
function testCaseVue(code,errors) {
return { code, errors, options: [ locale ], filename: 'test.vue' };
}
ruleTester.run(
'sharkey-locale',
localeRule,
{
valid: [
testCase('i18n.ts.foo.bar'),
testCase('i18n.ts.top'),
testCase('i18n.tsx.foo.baz({x:1})'),
testCase('whatever.i18n.ts.blah.blah'),
testCase('whatever.i18n.tsx.does.not.matter'),
testCase('whatever(i18n.ts.foo.bar)'),
testCaseVue('<template><p>{{ i18n.ts.foo.bar }}</p></template>'),
testCaseVue('<template><I18n :src="i18n.ts.foo.baz"/></template>'),
// we don't detect the problem here, but should still accept it
testCase('i18n.ts.foo["something"]'),
testCase('i18n.ts.foo[something]'),
],
invalid: [
testCase('i18n.ts.not', 1),
testCase('i18n.tsx.deep.not', 1),
testCase('i18n.tsx.deep.not({x:12})', 1),
testCase('i18n.tsx.top({x:1})', 1),
testCase('i18n.ts.foo.baz', 1),
testCase('i18n.tsx.foo.baz', 1),
testCase('i18n.tsx.foo.baz({y:2})', 2),
testCaseVue('<template><p>{{ i18n.ts.not }}</p></template>', 1),
testCaseVue('<template><I18n :src="i18n.ts.not"/></template>', 1),
],
},
);
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root]">
<div :inert="disabled" :class="[{ [$style.disabled]: disabled }]">
<slot></slot>
</div>
<div v-if="disabled" :class="[$style.cover]"></div>
</div>
</template>
<script lang="ts" setup>
defineProps<{
disabled?: boolean;
}>();
</script>
<style lang="scss" module>
.root {
position: relative;
}
.disabled {
opacity: 0.7;
}
.cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: not-allowed;
--color: color(from var(--error) srgb r g b / 0.25);
background-size: auto auto;
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
}
</style>
使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ
......@@ -626,10 +626,7 @@ abuseReported: "أُرسل البلاغ، شكرًا لك"
reporter: "المُبلّغ"
reporteeOrigin: "أصل البلاغ"
reporterOrigin: "أصل المُبلّغ"
forwardReport: "وجّه البلاغ إلى المثيل البعيد"
forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول."
send: "أرسل"
abuseMarkAsResolved: "علّم البلاغ كمحلول"
openInNewTab: "افتح في لسان جديد"
defaultNavigationBehaviour: "سلوك الملاحة الافتراضي"
editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك"
......@@ -1255,7 +1252,6 @@ _theme:
buttonBg: "خلفية الأزرار"
buttonHoverBg: "خلفية الأزرار (عند التمرير فوقها)"
inputBorder: "حواف حقل الإدخال"
listItemHoverBg: "خلفية عناصر القائمة (عند التمرير فوقها)"
driveFolderBg: "خلفية مجلد قرص التخزين"
messageBg: "خلفية المحادثة"
_sfx:
......@@ -1533,6 +1529,7 @@ _notification:
reaction: "التفاعل"
receiveFollowRequest: "طلبات المتابعة"
followRequestAccepted: "طلبات المتابعة المقبولة"
login: "لِج"
app: "إشعارات التطبيقات المرتبطة"
_actions:
followBack: "تابعك بالمثل"
......