Merge branch 'develop' of https://codeberg.org/calckey/calckey into feat/module-player

This commit is contained in:
Essem 2023-07-12 21:01:32 -05:00
commit c788adb3e5
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
67 changed files with 2101 additions and 1231 deletions

13
.config/LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1
.gitignore vendored
View file

@ -25,6 +25,7 @@ coverage
!/.config/devenv.yml !/.config/devenv.yml
!/.config/docker_example.env !/.config/docker_example.env
!/.config/helm_values_example.yml !/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config #docker dev config
/dev/docker-compose.yml /dev/docker-compose.yml

View file

@ -137,7 +137,7 @@
- 👍 also triggers generic like/favorite - 👍 also triggers generic like/favorite
- [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671) - [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671)
- [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549) - [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549)
- Many changes from [Foundkey](https://akkoma.dev/FoundKeyGang/Foundkey) - Many changes from [FoundKey](https://akkoma.dev/FoundKeyGang/FoundKey)
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API - https://akkoma.dev/FoundKeyGang/FoundKey/commit/0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins - https://akkoma.dev/FoundKeyGang/FoundKey/commit/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes - https://akkoma.dev/FoundKeyGang/FoundKey/commit/9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes

View file

@ -2704,7 +2704,7 @@ Co-committed-by: naskya <naskya@noreply.codeberg.org>
Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net> Passwords will be automatically re-hashed on sign-in. All new password hashes will be argon2 by default. This uses argon2id and is not configurable. In the very unlikely case someone has more specific needs, a fork is recommended. ChangeLog: Added Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net>
Breaks Calckey -> Misskey migration, but fixes Foundkey -> Calckey migration Breaks Calckey -> Misskey migration, but fixes FoundKey -> Calckey migration
- Add argon - Add argon

29
COPYING
View file

@ -1,15 +1,24 @@
Unless otherwise stated this repository is Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2022 syuilo and contributers Copyright © 2014-2023 syuilo and contributors
Copyright © 2022 thatonecalculator and contributers Copyright © 2022-2023 Kainoa Kanter and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
---
Calckey includes several third-party Open-Source softwares. These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou - .config/
License: MIT - custom/assets/
https://github.com/muan/emojilib/blob/master/LICENSE
and their contents are
Copyright © 2022-2023 Kainoa Kanter and contributors
And are distributed under The Apache License, Version 2.0, you should have received a copy of the license file as LICENSE in each specified directory.
---
Calckey includes several third-party open-source softwares and software libraries.
RsaSignature2017 implementation by Transmute Industries Inc RsaSignature2017 implementation by Transmute Industries Inc
License: MIT License: MIT
@ -23,6 +32,6 @@ Chiptune2.js by Simon Gündling
License: MIT License: MIT
https://github.com/deskjet/chiptune2.js#license https://github.com/deskjet/chiptune2.js#license
libopenmpt (as part of openmpt) by OpenMPT Licenses for all softwares and software libraries installed via the Node Package Manager ("npm") can be found by running the following shell command in the root directory of this repository:
License: BSD 3-Clause
https://github.com/OpenMPT/openmpt/blob/master/LICENSE pnpm licenses list

View file

@ -72,6 +72,14 @@
# 🌠 Getting started # 🌠 Getting started
Want to just join a Calckey server? View the list here, pick one, and join:
### https://calckey.org/join
---
Want to make your own? Keep reading!
This guide will work for both **starting from scratch** and **migrating from Misskey**. This guide will work for both **starting from scratch** and **migrating from Misskey**.
## 🔰 Easy installers ## 🔰 Easy installers
@ -208,9 +216,9 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
- Edit `.config/default.yml`, making sure to fill out required fields. - Edit `.config/default.yml`, making sure to fill out required fields.
- Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker. - Also copy and edit `.config/docker_example.env` to `.config/docker.env` if you're using Docker.
## 🚚 Migrating from Misskey to Calckey ## 🚚 Migrating from Misskey/FoundKey to Calckey
For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md). For migrating from Misskey v13, Misskey v12, and FoundKey, read [this document](https://codeberg.org/calckey/calckey/src/branch/develop/docs/migrate.md).
## 🌐 Web proxy ## 🌐 Web proxy

13
custom/assets/LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,10 +1,11 @@
# 🚚 Migrating from Misskey to Calckey # 🚚 Migrating from Misskey/FoundKey to Calckey
The following procedure may not work depending on your environment and version of Misskey. All the guides below assume you're starting in the root of the repo directory.
**Make sure you** ### Before proceeding
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.** - **Ensure you have stopped all master and worker processes of Misskey.**
- **Ensure you have backups of the database before performing any commands.**
## Misskey v13 and above ## Misskey v13 and above
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method # build using prefered method
``` ```
## Foundkey ## FoundKey
```sh ```sh
cd packages/backend cd packages/backend
sed -i '12s/^/\/\//' ./migration/1663399074403-resize-comments-drive-file.js
LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)" LINE_NUM="$(npx typeorm migration:show -d ormconfig.js | grep -n uniformThemecolor1652859567549 | cut -d ':' -f 1)"
NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | nl)" NUM_MIGRATIONS="$(npx typeorm migration:show -d ormconfig.js | tail -n+"$LINE_NUM" | grep '\[X\]' | wc -l)"
for i in $(seq 1 $NUM_MIGRAIONS); do for i in $(seq 1 $NUM_MIGRATIONS); do
npx typeorm migration:revert -d ormconfig.js npx typeorm migration:revert -d ormconfig.js
done done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## Reverse ## Reverse
You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to Foundkey, though. You ***cannot*** migrate back to Misskey from Calckey due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life.

View file

@ -2144,3 +2144,6 @@ _skinTones:
swipeOnMobile: Permet lliscar entre pàgines swipeOnMobile: Permet lliscar entre pàgines
enableIdenticonGeneration: Habilitar la generació d'Identicon enableIdenticonGeneration: Habilitar la generació d'Identicon
enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor enableServerMachineStats: Habilitar les estadístiques del maquinari del servidor
showPopup: Notificar els usuaris amb una finestra emergent
showWithSparkles: Mostra amb espurnes
youHaveUnreadAnnouncements: Tens anuncis sense llegir

View file

@ -645,6 +645,7 @@ useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker" useFullReactionPicker: "Use full-size reaction picker"
width: "Width" width: "Width"
height: "Height" height: "Height"
xl: "XL"
large: "Big" large: "Big"
medium: "Medium" medium: "Medium"
small: "Small" small: "Small"
@ -1118,6 +1119,9 @@ enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup" showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles" showWithSparkles: "Show with sparkles"
youHaveUnreadAnnouncements: "You have unread announcements" youHaveUnreadAnnouncements: "You have unread announcements"
donationLink: "Link to donation page"
neverShow: "Don't show again"
remindMeLater: "Maybe later"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing
@ -1216,6 +1220,10 @@ _aboutMisskey:
source: "Source code" source: "Source code"
translation: "Translate Calckey" translation: "Translate Calckey"
donate: "Donate to Calckey" donate: "Donate to Calckey"
donateTitle: "Enjoying Calckey?"
pleaseDonateToCalckey: "Please consider donating to Calckey to support its development."
pleaseDonateToHost: "Please also consider donating to your home server, {host}, to help support its operation costs."
donateHost: "Donate to {host}"
morePatrons: "We also appreciate the support of many other helpers not listed here. morePatrons: "We also appreciate the support of many other helpers not listed here.
Thank you! 🥰" Thank you! 🥰"
patrons: "Calckey patrons" patrons: "Calckey patrons"

View file

@ -983,6 +983,8 @@ enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にす
showPopup: "ポップアップを表示してユーザーに知らせる" showPopup: "ポップアップを表示してユーザーに知らせる"
showWithSparkles: "タイトルをキラキラさせる" showWithSparkles: "タイトルをキラキラさせる"
youHaveUnreadAnnouncements: "未読のお知らせがあります" youHaveUnreadAnnouncements: "未読のお知らせがあります"
neverShow: "今後表示しない"
remindMeLater: "また後で"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"

View file

@ -1,16 +1,16 @@
{ {
"name": "calckey", "name": "calckey",
"version": "14.0.0-rc3", "version": "14.0.0-dev71",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://codeberg.org/calckey/calckey.git" "url": "https://codeberg.org/calckey/calckey.git"
}, },
"packageManager": "pnpm@8.6.6", "packageManager": "pnpm@8.6.7",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"build": "pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp", "build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start", "start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test", "start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate", "init": "pnpm run migrate",
@ -21,13 +21,13 @@
"watch": "pnpm run dev", "watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js", "dev": "pnpm node ./scripts/dev.js",
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start", "dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
"lint": "pnpm -r run lint", "lint": "pnpm -r --parallel run lint",
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "cypress run", "cy:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha", "mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha", "test": "pnpm run mocha",
"format": "pnpm -r run format", "format": "pnpm -r --parallel run format",
"clean": "pnpm node ./scripts/clean.js", "clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js", "clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all" "cleanall": "pnpm run clean-all"
@ -36,17 +36,17 @@
"chokidar": "^3.3.1" "chokidar": "^3.3.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1", "@napi-rs/cli": "^2.16.1",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.11.18", "@types/gulp": "4.0.13",
"@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.2",
"@types/gulp-rename": "2.0.1", "@types/node": "20.4.1",
"chalk": "4.1.2", "chalk": "4.1.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
@ -59,6 +59,6 @@
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"rome": "^12.1.3", "rome": "^12.1.3",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "4.9.4" "typescript": "5.1.6"
} }
} }

View file

@ -0,0 +1,15 @@
export class DonationLink1689136347561 {
name = "DonationLink1689136347561";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ADD "donationLink" character varying(256)`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" DROP COLUMN "DonationLink1689136347561"`,
);
}
}

View file

@ -25,16 +25,16 @@
"@tensorflow/tfjs-node": "3.21.1" "@tensorflow/tfjs-node": "3.21.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.2.0", "@bull-board/api": "5.6.0",
"@bull-board/koa": "5.2.0", "@bull-board/koa": "5.6.0",
"@bull-board/ui": "5.2.0", "@bull-board/ui": "5.6.0",
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0", "@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3", "@koa/cors": "3.4.3",
"@koa/multer": "3.0.2", "@koa/multer": "3.0.2",
"@koa/router": "9.0.1", "@koa/router": "9.0.1",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.0.0-beta.120", "@redocly/openapi-core": "1.0.0-beta.131",
"@sinonjs/fake-timers": "9.1.2", "@sinonjs/fake-timers": "9.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0", "@tensorflow/tfjs": "^4.2.0",
@ -45,17 +45,17 @@
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1277.0", "aws-sdk": "2.1413.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.5", "blurhash": "2.0.5",
"bull": "4.10.4", "bull": "4.10.4",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "5.2.0", "chalk": "5.3.0",
"chalk-template": "0.4.0", "chalk-template": "0.4.0",
"chokidar": "3.5.3", "chokidar": "^3.5.3",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"color-convert": "2.0.1", "color-convert": "2.0.1",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
@ -68,15 +68,15 @@
"got": "12.5.3", "got": "12.5.3",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.0.11", "ip-cidr": "3.1.0",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "20.0.3", "jsdom": "20.0.3",
"jsonld": "8.2.0", "jsonld": "8.2.0",
"jsrsasign": "10.6.1", "jsrsasign": "10.8.6",
"koa": "2.13.4", "koa": "2.14.2",
"koa-body": "^6.0.1", "koa-body": "^6.0.1",
"koa-bodyparser": "4.3.0", "koa-bodyparser": "4.4.1",
"koa-favicon": "2.1.0", "koa-favicon": "2.1.0",
"koa-json-body": "5.3.0", "koa-json-body": "5.3.0",
"koa-logger": "3.2.1", "koa-logger": "3.2.1",
@ -98,9 +98,9 @@
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "^0.10.0", "oauth": "^0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.1.2", "otpauth": "^9.1.3",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.0", "pg": "8.11.1",
"private-ip": "2.3.4", "private-ip": "2.3.4",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@ -110,7 +110,7 @@
"qs": "6.11.2", "qs": "6.11.2",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.19.0", "re2": "1.19.1",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"redis-semaphore": "5.3.1", "redis-semaphore": "5.3.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@ -119,7 +119,7 @@
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"semver": "7.5.1", "semver": "7.5.4",
"sharp": "0.32.1", "sharp": "0.32.1",
"sonic-channel": "^1.3.1", "sonic-channel": "^1.3.1",
"stringz": "2.1.0", "stringz": "2.1.0",
@ -130,27 +130,26 @@
"tinycolor2": "1.5.2", "tinycolor2": "1.5.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.11", "typeorm": "0.3.17",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"web-push": "3.6.1", "web-push": "3.6.3",
"websocket": "1.0.34", "websocket": "1.0.34",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62", "@swc/core": "^1.3.68",
"@types/adm-zip": "^0.5.0", "@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.20", "@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1", "@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.4", "@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.5", "@types/koa": "2.13.6",
"@types/koa-bodyparser": "4.3.10", "@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
@ -169,7 +168,7 @@
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/pug": "2.0.6", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.1",
"@types/qs": "6.9.7", "@types/qs": "6.9.7",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4", "@types/ratelimiter": "3.4.4",
@ -177,17 +176,16 @@
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/uuid": "8.3.4", "@types/uuid": "9.0.2",
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.5",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "^8.42.0", "eslint": "^8.44.0",
"execa": "6.1.0", "execa": "6.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
@ -195,11 +193,11 @@
"pug": "3.0.2", "pug": "3.0.2",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.3", "swc-loader": "^0.2.3",
"ts-loader": "9.4.3", "ts-loader": "9.4.4",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.1.3", "typescript": "5.1.6",
"webpack": "^5.85.1", "webpack": "^5.88.1",
"ws": "8.13.0" "ws": "8.13.0"
} }
} }

View file

@ -20,8 +20,6 @@ export function nyaize(text: string): string {
) )
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥") .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥")
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥") .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥")
// zh-CN, zh-TW
.replace(/(妙|庙|描|渺|瞄|秒|苗|藐|廟)/g, "喵")
// el-GR // el-GR
.replaceAll("να", "νια") .replaceAll("να", "νια")
.replaceAll("ΝΑ", "ΝΙΑ") .replaceAll("ΝΑ", "ΝΙΑ")

View file

@ -556,4 +556,10 @@ export class Meta {
default: true, default: true,
}) })
public enableIdenticonGeneration: boolean; public enableIdenticonGeneration: boolean;
@Column("varchar", {
length: 256,
nullable: true,
})
public donationLink: string | null;
} }

View file

@ -1,4 +1,5 @@
import type Bull from "bull"; import type Bull from "bull";
import type { DoneCallback } from "bull";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "../../logger.js";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
@ -11,7 +12,7 @@ const logger = queueLogger.createSubLogger("index-all-notes");
export default async function indexAllNotes( export default async function indexAllNotes(
job: Bull.Job<Record<string, unknown>>, job: Bull.Job<Record<string, unknown>>,
done: () => void, done: DoneCallback,
): Promise<void> { ): Promise<void> {
logger.info("Indexing all notes..."); logger.info("Indexing all notes...");
@ -20,7 +21,7 @@ export default async function indexAllNotes(
let total: number = (job.data.total as number) ?? 0; let total: number = (job.data.total as number) ?? 0;
let running = true; let running = true;
const take = 100000; const take = 10000;
const batch = 100; const batch = 100;
while (running) { while (running) {
logger.info( logger.info(
@ -41,13 +42,14 @@ export default async function indexAllNotes(
}, },
relations: ["user"], relations: ["user"],
}); });
} catch (e) { } catch (e: any) {
logger.error(`Failed to query notes ${e}`); logger.error(`Failed to query notes ${e}`);
continue; done(e);
break;
} }
if (notes.length === 0) { if (notes.length === 0) {
job.progress(100); await job.progress(100);
running = false; running = false;
break; break;
} }
@ -55,7 +57,7 @@ export default async function indexAllNotes(
try { try {
const count = await Notes.count(); const count = await Notes.count();
total = count; total = count;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
} catch (e) {} } catch (e) {}
for (let i = 0; i < notes.length; i += batch) { for (let i = 0; i < notes.length; i += batch) {
@ -69,12 +71,12 @@ export default async function indexAllNotes(
indexedCount += chunk.length; indexedCount += chunk.length;
const pct = (indexedCount / total) * 100; const pct = (indexedCount / total) * 100;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
job.progress(+pct.toFixed(1)); await job.progress(+pct.toFixed(1));
logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`); logger.info(`Indexed notes ${indexedCount}/${total ? total : "?"}`);
} }
cursor = notes[notes.length - 1].id; cursor = notes[notes.length - 1].id;
job.update({ indexedCount, cursor, total }); await job.update({ indexedCount, cursor, total });
if (notes.length < take) { if (notes.length < take) {
running = false; running = false;

View file

@ -50,7 +50,7 @@ export async function importMastoPost(
text: text || undefined, text: text || undefined,
reply, reply,
renote: null, renote: null,
cw: post.sensitive, cw: post.object.sensitive ? post.object.summary : undefined,
localOnly: false, localOnly: false,
visibility: "hidden", visibility: "hidden",
visibleUsers: [], visibleUsers: [],

View file

@ -491,6 +491,11 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
donationLink: {
type: "string",
optional: true,
nullable: true,
}
}, },
}, },
} as const; } as const;
@ -604,5 +609,6 @@ export default define(meta, paramDef, async (ps, me) => {
experimentalFeatures: instance.experimentalFeatures, experimentalFeatures: instance.experimentalFeatures,
enableServerMachineStats: instance.enableServerMachineStats, enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration, enableIdenticonGeneration: instance.enableIdenticonGeneration,
donationLink: instance.donationLink,
}; };
}); });

View file

@ -177,6 +177,9 @@ export const paramDef = {
postImports: { type: "boolean" }, postImports: { type: "boolean" },
}, },
}, },
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
}, },
required: [], required: [],
} as const; } as const;
@ -568,6 +571,21 @@ export default define(meta, paramDef, async (ps, me) => {
set.experimentalFeatures = ps.experimentalFeatures || undefined; set.experimentalFeatures = ps.experimentalFeatures || undefined;
} }
if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats;
}
if (ps.enableIdenticonGeneration !== undefined) {
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
}
if (ps.donationLink !== undefined) {
set.donationLink = ps.donationLink;
if (set.donationLink && !/^https?:\/\//i.test(set.donationLink)) {
set.donationLink = `https://${set.donationLink}`;
}
}
await db.transaction(async (transactionalEntityManager) => { await db.transaction(async (transactionalEntityManager) => {
const metas = await transactionalEntityManager.find(Meta, { const metas = await transactionalEntityManager.find(Meta, {
order: { order: {

View file

@ -389,6 +389,11 @@ export const meta = {
nullable: false, nullable: false,
default: "⭐", default: "⭐",
}, },
donationLink: {
type: "string",
optional: "true",
nullable: true,
}
}, },
}, },
} as const; } as const;
@ -491,6 +496,7 @@ export default define(meta, paramDef, async (ps, me) => {
translatorAvailable: translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
defaultReaction: instance.defaultReaction, defaultReaction: instance.defaultReaction,
donationLink: instance.donationLink,
...(ps.detail ...(ps.detail
? { ? {

View file

@ -247,7 +247,7 @@ export default class Connection {
for (const obj of objs) { for (const obj of objs) {
const { type, body } = obj; const { type, body } = obj;
console.log(type, body); // console.log(type, body);
switch (type) { switch (type) {
case "readNotification": case "readNotification":
this.onReadNotification(body); this.onReadNotification(body);

View file

@ -16,7 +16,7 @@
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/gulp": "4.0.11", "@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2", "@types/gulp-rename": "2.0.2",
"@types/katex": "0.16.0", "@types/katex": "0.16.0",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.2",
@ -29,8 +29,8 @@
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.3.4",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "5.0.2", "autosize": "5.0.2",
"blurhash": "1.1.5", "blurhash": "2.0.5",
"broadcast-channel": "4.19.1", "broadcast-channel": "5.1.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer", "browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"chart.js": "4.3.0", "chart.js": "4.3.0",
@ -39,57 +39,58 @@
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"city-timezones": "^1.2.1", "city-timezones": "^1.2.1",
"compare-versions": "5.0.3", "compare-versions": "6.0.0",
"cropperjs": "2.0.0-beta.2", "cropperjs": "2.0.0-beta.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib", "emojilib": "github:thatonecalculator/emojilib",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eventemitter3": "4.0.7", "eventemitter3": "5.0.1",
"focus-trap": "^7.4.3", "fast-blurhash": "^1.1.2",
"focus-trap": "^7.5.2",
"focus-trap-vue": "^4.0.2", "focus-trap-vue": "^4.0.2",
"gsap": "^3.11.5", "gsap": "^3.12.2",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"json5": "2.2.3", "json5": "2.2.3",
"katex": "0.16.7", "katex": "0.16.8",
"libopenmpt-wasm": "github:TheEssem/libopenmpt-packaging#build", "libopenmpt-wasm": "github:TheEssem/libopenmpt-packaging#build",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"photoswipe": "5.3.7", "photoswipe": "5.3.8",
"prettier": "3.0.0", "prettier": "3.0.0",
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0", "prismjs": "1.29.0",
"punycode": "2.1.1", "punycode": "2.3.0",
"querystring": "0.2.1", "querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.23.1", "rollup": "3.26.2",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.62.1", "sass": "1.63.6",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"swiper": "9.3.2", "swiper": "10.0.4",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.146.0", "three": "0.146.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.5.2", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.6", "tsc-alias": "1.8.7",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "5.1.3", "typescript": "5.1.6",
"unicode-emoji-json": "^0.4.0", "unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.3.9", "vite": "4.4.2",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vue": "3.3.4", "vue": "3.3.4",
"vue-draggable-plus": "^0.2.2",
"vue-isyourpasswordsafe": "^2.0.0", "vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0", "vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2"
"vuedraggable": "4.1.0"
} }
} }

View file

@ -5,6 +5,13 @@
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle> <MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
<p v-else>{{ title }}</p> <p v-else>{{ title }}</p>
</div> </div>
<div :class="$style.time">
<MkTime :time="announcement.createdAt" />
<div v-if="announcement.updatedAt">
{{ i18n.ts.updatedAt }}:
<MkTime :time="announcement.createdAt" />
</div>
</div>
<Mfm :text="text" /> <Mfm :text="text" />
<img <img
v-if="imageUrl != null" v-if="imageUrl != null"
@ -68,6 +75,10 @@ const gotIt = () => {
} }
} }
.time {
font-size: 0.8rem;
}
.gotIt { .gotIt {
margin: 8px 0 0 0; margin: 8px 0 0 0;
} }

View file

@ -13,7 +13,9 @@
<template #default="{ width, height }"> <template #default="{ width, height }">
<div <div
class="mk-cropper-dialog" class="mk-cropper-dialog"
:style="`--vw: ${width}px; --vh: ${height}px;`" :style="`--vw: ${width ? `${width}px` : '100%'}; --vh: ${
height ? `${height}px` : '100%'
};`"
> >
<Transition name="fade"> <Transition name="fade">
<div v-if="loading" class="loading"> <div v-if="loading" class="loading">

View file

@ -0,0 +1,180 @@
<template>
<transition name="slide-fade">
<div v-if="show" class="_panel _shadow _acrylic" :class="$style.root">
<div :class="$style.icon">
<i class="ph-hand-heart ph-bold ph-5x" />
</div>
<div :class="$style.main">
<div :class="$style.title">
{{ i18n.ts._aboutMisskey.donateTitle }}
</div>
<div :class="$style.text">
{{ i18n.ts._aboutMisskey.pleaseDonateToCalckey }}
<p v-if="$instance.donationLink">
{{
i18n.t("_aboutMisskey.pleaseDonateToHost", {
host: hostname,
})
}}
</p>
</div>
<div class="_flexList" style="gap: 0.6rem;">
<MkButton
primary
@click="
openExternal('https://opencollective.com/calckey')
"
>{{ i18n.ts._aboutMisskey.donate }}</MkButton
>
<MkButton
v-if="$instance.donationLink"
gradate
@click="openExternal($instance.donationLink)"
>{{
i18n.t("_aboutMisskey.donateHost", {
host: hostname,
})
}}</MkButton
>
</div>
<div class="_flexList" style="margin-top: 0.6rem">
<MkButton @click="close">{{
i18n.ts.remindMeLater
}}</MkButton>
<MkButton @click="neverShow">{{
i18n.ts.neverShow
}}</MkButton>
</div>
</div>
<button class="_button" :class="$style.close" @click="close">
<i class="ph-x ph-bold ph-lg"></i>
</button>
</div>
</transition>
</template>
<script lang="ts" setup>
import { ref, nextTick } from "vue";
import MkButton from "@/components/MkButton.vue";
import { host } from "@/config";
import { i18n } from "@/i18n";
import * as os from "@/os";
import { instance } from "@/instance";
let show = ref(false);
const emit = defineEmits<{
(ev: "closed"): void;
}>();
const hostname = instance.name?.length < 38 ? instance.name : host;
const zIndex = os.claimZIndex("low");
function slideIn() {
show.value = false;
nextTick(() => {
show.value = true;
});
}
slideIn();
function close() {
localStorage.setItem("latestDonationInfoShownAt", Date.now().toString());
emit("closed");
show.value = false;
}
function neverShow() {
localStorage.setItem("neverShowDonationInfo", "true");
close();
}
function openExternal(link) {
window.open(link, "_blank");
}
</script>
<style lang="scss" scoped>
.slide-fade-enter-from {
opacity: 0;
transform: translateY(100%);
}
.slide-fade-enter-active {
transition: opacity 0.5s, transform 0.5s ease-out;
}
.slide-fade-enter-to {
opacity: 1;
transform: translateY(0);
}
.slide-fade-leave-from {
opacity: 1;
transform: translateY(0);
}
.slide-fade-leave-active {
transition: opacity 0.5s, transform 0.5s ease-out;
}
.slide-fade-leave-to {
opacity: 0;
transform: translateY(100%);
}
</style>
<style lang="scss" module>
.root {
position: fixed;
z-index: v-bind(zIndex);
bottom: var(--margin);
left: 2%;
bottom: 2%;
margin: auto;
box-sizing: border-box;
width: calc(100% - (var(--margin) * 2));
max-width: 500px;
display: flex;
}
.icon {
text-align: center;
padding-top: 25px;
width: 100px;
color: var(--accent);
}
@media (max-width: 500px) {
.icon {
width: 80px;
}
}
@media (max-width: 450px) {
.icon {
width: 70px;
}
}
.main {
padding: 25px 25px 25px 0;
flex: 1;
}
.close {
position: absolute;
top: 8px;
right: 8px;
padding: 8px;
}
.title {
font-weight: bold;
}
.text {
margin: 0.7em 0 1em 0;
}
</style>

View file

@ -12,7 +12,7 @@
:transparent-bg="true" :transparent-bg="true"
:manual-showing="manualShowing" :manual-showing="manualShowing"
:src="src" :src="src"
@click="modal?.close()" @click="checkForShift"
@opening="opening" @opening="opening"
@close="emit('close')" @close="emit('close')"
@closed="emit('closed')" @closed="emit('closed')"
@ -31,7 +31,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkEmojiPicker from "@/components/MkEmojiPicker.vue"; import MkEmojiPicker from "@/components/MkEmojiPicker.vue";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -58,20 +58,49 @@ const emit = defineEmits<{
const modal = ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
const picker = ref<InstanceType<typeof MkEmojiPicker>>(); const picker = ref<InstanceType<typeof MkEmojiPicker>>();
const isShiftKeyPressed = ref(false);
const keydownHandler = (e) => {
if (e.key === "Shift") {
isShiftKeyPressed.value = true;
}
};
const keyupHandler = (e) => {
if (e.key === "Shift") {
isShiftKeyPressed.value = false;
}
};
function checkForShift(ev?: MouseEvent) {
if (!isShiftKeyPressed.value) {
modal.value?.close(ev);
}
}
function chosen(emoji: any) { function chosen(emoji: any) {
emit("done", emoji); emit("done", emoji);
modal.value?.close(); checkForShift();
} }
function opening() { function opening() {
try { try {
picker.value?.reset(); picker.value?.reset();
} catch (e) { } catch (e) {
console.error(`Something's wrong with restting the emoji picker: ${e}`); console.error("Something's wrong with resetting the emoji picker", e);
} }
picker.value?.focus(); picker.value?.focus();
} }
onMounted(() => {
window.addEventListener("keydown", keydownHandler);
window.addEventListener("keyup", keyupHandler);
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", keydownHandler);
window.removeEventListener("keyup", keyupHandler);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -21,7 +21,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted } from "vue";
import { decode } from "blurhash"; import { decodeBlurHash } from "fast-blurhash";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -47,8 +47,8 @@ const canvas = $ref<HTMLCanvasElement>();
let loaded = $ref(false); let loaded = $ref(false);
function draw() { function draw() {
if (props.hash == null) return; if (props.hash == null || canvas == null) return;
const pixels = decode(props.hash, props.size, props.size); const pixels = decodeBlurHash(props.hash, props.size, props.size);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const imageData = ctx!.createImageData(props.size, props.size); const imageData = ctx!.createImageData(props.size, props.size);
imageData.data.set(pixels); imageData.data.set(pixels);

View file

@ -226,11 +226,13 @@ watch(
display: flex; display: flex;
min-width: max-content; min-width: max-content;
width: 110px; width: 110px;
transition: width 0.2s cubic-bezier(0,0,0,1); transition: width 0.2s cubic-bezier(0, 0, 0, 1);
[data-plyr="volume"] { [data-plyr="volume"] {
width: 0; width: 0;
flex-grow: 1; flex-grow: 1;
transition: margin 0.3s, opacity .2s 0.2s; transition:
margin 0.3s,
opacity 0.2s 0.2s;
} }
&:not(:hover):not(:focus-within) { &:not(:hover):not(:focus-within) {
width: 0px; width: 0px;
@ -238,7 +240,9 @@ watch(
[data-plyr="volume"] { [data-plyr="volume"] {
margin-inline: 0px; margin-inline: 0px;
opacity: 0; opacity: 0;
transition: margin 0.3s, opacity 0.1s; transition:
margin 0.3s,
opacity 0.1s;
} }
} }
} }

View file

@ -869,6 +869,9 @@ defineExpose({
margin: 0; margin: 0;
padding: 8px; padding: 8px;
opacity: 0.7; opacity: 0.7;
&:disabled {
opacity: 0.5 !important;
}
flex-grow: 1; flex-grow: 1;
max-width: 3.5em; max-width: 3.5em;
width: max-content; width: max-content;

View file

@ -477,6 +477,9 @@ function noteClick(e) {
margin: 0; margin: 0;
padding: 8px; padding: 8px;
opacity: 0.7; opacity: 0.7;
&:disabled {
opacity: 0.5 !important;
}
flex-grow: 1; flex-grow: 1;
max-width: 3.5em; max-width: 3.5em;
width: max-content; width: max-content;

View file

@ -1,16 +1,16 @@
<template> <template>
<div v-show="files.length != 0" class="skeikyzd"> <div v-show="files.length != 0" class="skeikyzd">
<XDraggable <VueDraggable
v-model="_files" v-model="_files"
class="files" class="files"
item-key="id"
animation="150" animation="150"
delay="100" delay="100"
delay-on-touch-only="true" delay-on-touch-only="true"
> >
<template #item="{ element }">
<div <div
class="file" class="file"
v-for="element in _files"
:key="element.id"
@click="showFileMenu(element, $event)" @click="showFileMenu(element, $event)"
@contextmenu.prevent="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"
> >
@ -24,27 +24,19 @@
<i class="ph-warning ph-bold ph-lg icon"></i> <i class="ph-warning ph-bold ph-lg icon"></i>
</div> </div>
</div> </div>
</template> </VueDraggable>
</XDraggable>
<p class="remain">{{ 16 - files.length }}/16</p> <p class="remain">{{ 16 - files.length }}/16</p>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, defineAsyncComponent } from "vue"; import { defineAsyncComponent, ref, computed } from "vue";
import { VueDraggable } from "vue-draggable-plus";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue"; import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
export default defineComponent({ const props = defineProps({
components: {
XDraggable: defineAsyncComponent(() =>
import("vuedraggable").then((x) => x.default),
),
MkDriveFileThumbnail,
},
props: {
files: { files: {
type: Array, type: Array,
required: true, required: true,
@ -53,65 +45,55 @@ export default defineComponent({
type: Function, type: Function,
required: false, required: false,
}, },
}, });
emits: ["updated", "detach", "changeSensitive", "changeName"], const emits = defineEmits([
"updated",
"detach",
"changeSensitive",
"changeName",
]);
data() { const _files = computed({
return { get: () => props.files,
menu: null as Promise<null> | null, set: (value) => emits("updated", value),
i18n, });
};
},
computed: { const detachMedia = (id) => {
_files: { if (props.detachMediaFn) {
get() { props.detachMediaFn(id);
return this.files;
},
set(value) {
this.$emit("updated", value);
},
},
},
methods: {
detachMedia(id) {
if (this.detachMediaFn) {
this.detachMediaFn(id);
} else { } else {
this.$emit("detach", id); emits("detach", id);
} }
}, };
toggleSensitive(file) {
function toggleSensitive(file) {
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: file.id, fileId: file.id,
isSensitive: !file.isSensitive, isSensitive: !file.isSensitive,
}).then(() => { }).then(() => {
this.$emit("changeSensitive", file, !file.isSensitive); emits("changeSensitive", file, !file.isSensitive);
}); });
}, }
async rename(file) {
async function rename(file) {
const { canceled, result } = await os.inputText({ const { canceled, result } = await os.inputText({
title: i18n.ts.enterFileName, title: i18n.ts.enterFileName,
default: file.name, default: file.name,
allowEmpty: false,
}); });
if (canceled) return; if (canceled) return;
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: file.id, fileId: file.id,
name: result, name: result,
}).then(() => { }).then(() => {
this.$emit("changeName", file, result); emits("changeName", file, result);
file.name = result; file.name = result;
}); });
}, }
async describe(file) { async function describe(file) {
os.popup( os.popup(
defineAsyncComponent( defineAsyncComponent(() => import("@/components/MkMediaCaption.vue")),
() => import("@/components/MkMediaCaption.vue"),
),
{ {
title: i18n.ts.describeFile, title: i18n.ts.describeFile,
input: { input: {
@ -123,8 +105,7 @@ export default defineComponent({
{ {
done: (result) => { done: (result) => {
if (!result || result.canceled) return; if (!result || result.canceled) return;
let comment = let comment = result.result.length === 0 ? null : result.result;
result.result.length === 0 ? null : result.result;
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: file.id, fileId: file.id,
comment: comment, comment: comment,
@ -135,18 +116,16 @@ export default defineComponent({
}, },
"closed", "closed",
); );
}, }
showFileMenu(file, ev: MouseEvent) { function showFileMenu(file, ev: MouseEvent) {
if (this.menu) return; os.popupMenu(
this.menu = os
.popupMenu(
[ [
{ {
text: i18n.ts.renameFile, text: i18n.ts.renameFile,
icon: "ph-cursor-text ph-bold ph-lg", icon: "ph-cursor-text ph-bold ph-lg",
action: () => { action: () => {
this.rename(file); rename(file);
}, },
}, },
{ {
@ -157,30 +136,27 @@ export default defineComponent({
? "ph-eye ph-bold ph-lg" ? "ph-eye ph-bold ph-lg"
: "ph-eye-slash ph-bold ph-lg", : "ph-eye-slash ph-bold ph-lg",
action: () => { action: () => {
this.toggleSensitive(file); toggleSensitive(file);
}, },
}, },
{ {
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: "ph-subtitles ph-bold ph-lg", icon: "ph-subtitles ph-bold ph-lg",
action: () => { action: () => {
this.describe(file); describe(file);
}, },
}, },
{ {
text: i18n.ts.attachCancel, text: i18n.ts.attachCancel,
icon: "ph-x ph-bold ph-lg", icon: "ph-x ph-bold ph-lg",
action: () => { action: () => {
this.detachMedia(file.id); detachMedia(file.id);
}, },
}, },
], ],
ev.currentTarget ?? ev.target, (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined,
) );
.then(() => (this.menu = null)); }
},
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -219,8 +195,8 @@ export default defineComponent({
top: 0; top: 0;
left: 0; left: 0;
z-index: 2; z-index: 2;
background: rgba(17, 17, 17, 0.7); background: var(--header);
color: #fff; color: var(--fg);
> .icon { > .icon {
margin: auto; margin: auto;

View file

@ -10,8 +10,13 @@
<i class="ph-repeat ph-bold ph-lg"></i> <i class="ph-repeat ph-bold ph-lg"></i>
<p v-if="count > 0 && !detailedView" class="count">{{ count }}</p> <p v-if="count > 0 && !detailedView" class="count">{{ count }}</p>
</button> </button>
<button v-else class="eddddedb _button"> <button
<i class="ph-prohibit ph-bold ph-lg"></i> v-else
class="eddddedb _button"
disabled="true"
v-tooltip.noDelay.bottom="i18n.ts.disabled"
>
<i class="ph-repeat ph-bold ph-lg"></i>
</button> </button>
</template> </template>

View file

@ -253,6 +253,9 @@ onUnmounted(() => {
font-size: 1em; font-size: 1em;
white-space: nowrap; white-space: nowrap;
margin-bottom: 0.2em; margin-bottom: 0.2em;
text-decoration: underline;
text-decoration-color: transparent;
transition: text-decoration-color 0.2s;
} }
p { p {
margin-bottom: -0.5em; margin-bottom: -0.5em;
@ -279,7 +282,7 @@ onUnmounted(() => {
&:focus-within { &:focus-within {
background: var(--panelHighlight); background: var(--panelHighlight);
h3 { h3 {
text-decoration: underline; text-decoration-color: currentColor;
} }
} }
} }

View file

@ -28,13 +28,8 @@
i18n.ts.close i18n.ts.close
}}</MkButton> }}</MkButton>
</header> </header>
<XDraggable <VueDraggable v-model="widgets_" handle=".handle" animation="150">
v-model="widgets_" <div v-for="element in widgets_" :key="element.id">
item-key="id"
handle=".handle"
animation="150"
>
<template #item="{ element }">
<div class="customize-container"> <div class="customize-container">
<button <button
class="config _button" class="config _button"
@ -58,8 +53,8 @@
/> />
</div> </div>
</div> </div>
</template> </div>
</XDraggable> </VueDraggable>
</template> </template>
<component <component
:is="`mkw-${widget.name}`" :is="`mkw-${widget.name}`"
@ -78,14 +73,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, reactive, ref, computed } from "vue"; import { defineAsyncComponent, reactive, ref, computed } from "vue";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import { VueDraggable } from "vue-draggable-plus";
import MkSelect from "@/components/form/select.vue"; import MkSelect from "@/components/form/select.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { widgets as widgetDefs } from "@/widgets"; import { widgets as widgetDefs } from "@/widgets";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
const XDraggable = defineAsyncComponent(() => import("vuedraggable"));
type Widget = { type Widget = {
name: string; name: string;
id: string; id: string;

View file

@ -69,7 +69,7 @@ const alt = computed(() =>
vertical-align: -0.25em; vertical-align: -0.25em;
&.custom { &.custom {
height: 2.5em; height: 2em;
vertical-align: middle; vertical-align: middle;
transition: transform 0.2s ease; transition: transform 0.2s ease;

View file

@ -75,13 +75,12 @@ const target = self ? null : "_blank";
<style lang="scss" scoped> <style lang="scss" scoped>
.url { .url {
white-space: nowrap;
max-width: 80%;
display: inline-block;
overflow: clip;
text-overflow: ellipsis;
text-decoration: none !important; text-decoration: none !important;
line-height: 1.05; > span {
text-decoration: underline var(--fgTransparent);
text-decoration-thickness: 1px;
transition: text-decoration-color 0.2s;
}
> .icon { > .icon {
padding-left: 2px; padding-left: 2px;
@ -111,5 +110,9 @@ const target = self ? null : "_blank";
> .hash { > .hash {
font-style: italic; font-style: italic;
} }
&:hover span {
text-decoration-color: var(--link);
}
} }
</style> </style>

View file

@ -409,7 +409,6 @@ export default defineComponent({
key: Math.random(), key: Math.random(),
to: `/tags/${encodeURIComponent(token.props.hashtag)}`, to: `/tags/${encodeURIComponent(token.props.hashtag)}`,
style: "color:var(--hashtag);", style: "color:var(--hashtag);",
class: "_link",
}, },
`#${token.props.hashtag}`, `#${token.props.hashtag}`,
), ),

View file

@ -450,6 +450,29 @@ function checkForSplash() {
} }
localStorage.setItem("lastUsed", Date.now().toString()); localStorage.setItem("lastUsed", Date.now().toString());
const latestDonationInfoShownAt = localStorage.getItem(
"latestDonationInfoShownAt",
);
const neverShowDonationInfo = localStorage.getItem("neverShowDonationInfo");
if (
neverShowDonationInfo !== "true" &&
new Date($i.createdAt).getTime() < Date.now() - 1000 * 60 * 60 * 24 * 3 &&
!location.pathname.startsWith("/miauth")
) {
if (
latestDonationInfoShownAt == null ||
new Date(latestDonationInfoShownAt).getTime() <
Date.now() - 1000 * 60 * 60 * 24 * 30
) {
popup(
defineAsyncComponent(() => import("@/components/MkDonation.vue")),
{},
{},
"closed",
);
}
}
if ("Notification" in window) { if ("Notification" in window) {
// 許可を得ていなかったらリクエスト // 許可を得ていなかったらリクエスト
if (Notification.permission === "default") { if (Notification.permission === "default") {

View file

@ -101,8 +101,9 @@
><Mfm ><Mfm
:text="'@freeplay@calckey.social (UI/UX)'" :text="'@freeplay@calckey.social (UI/UX)'"
/></FormLink> /></FormLink>
<FormLink to="/@nmkj@calckey.jp" <FormLink to="/@namekuji@calckey.social"
><Mfm :text="'@nmkj@calckey.jp (Backend)'" ><Mfm
:text="'@namekuji@calckey.social (Backend)'"
/></FormLink> /></FormLink>
<FormLink to="/@dev@post.naskya.net" <FormLink to="/@dev@post.naskya.net"
><Mfm :text="'@dev@post.naskya.net (Backend)'" ><Mfm :text="'@dev@post.naskya.net (Backend)'"

View file

@ -93,6 +93,21 @@
external external
>{{ i18n.ts.tos }}</FormLink >{{ i18n.ts.tos }}</FormLink
> >
<FormLink
v-if="$instance.donationLink"
:to="$instance.donationLink"
external
>
<template #icon
><i class="ph-money ph-bold ph-lg"></i
></template>
{{
i18n.t("_aboutMisskey.donateHost", {
host: $instance.name || host,
})
}}
<template #suffix>Donate</template>
</FormLink>
</FormSection> </FormSection>
<FormSuspense :p="initStats"> <FormSuspense :p="initStats">
@ -163,7 +178,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, watch } from "vue"; import { ref, computed, onMounted, watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import XEmojis from "./about.emojis.vue"; import XEmojis from "./about.emojis.vue";
import XFederation from "./about.federation.vue"; import XFederation from "./about.federation.vue";

View file

@ -157,7 +157,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch } from "vue"; import { computed, watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkSwitch from "@/components/form/switch.vue"; import MkSwitch from "@/components/form/switch.vue";

View file

@ -53,6 +53,20 @@
i18n.ts.maintainerEmail i18n.ts.maintainerEmail
}}</template> }}</template>
</FormInput> </FormInput>
<FormInput
v-model="donationLink"
class="_formBlock"
>
<template #prefix
><i
class="ph-hand-heart ph-bold ph-lg"
></i
></template>
<template #label>{{
i18n.ts.donationLink
}}</template>
</FormInput>
</FormSplit> </FormSplit>
<FormTextarea v-model="pinnedUsers" class="_formBlock"> <FormTextarea v-model="pinnedUsers" class="_formBlock">
@ -435,6 +449,7 @@ let description: string | null = $ref(null);
let tosUrl: string | null = $ref(null); let tosUrl: string | null = $ref(null);
let maintainerName: string | null = $ref(null); let maintainerName: string | null = $ref(null);
let maintainerEmail: string | null = $ref(null); let maintainerEmail: string | null = $ref(null);
let donationLink: string | null = $ref(null);
let iconUrl: string | null = $ref(null); let iconUrl: string | null = $ref(null);
let bannerUrl: string | null = $ref(null); let bannerUrl: string | null = $ref(null);
let logoImageUrl: string | null = $ref(null); let logoImageUrl: string | null = $ref(null);
@ -481,6 +496,7 @@ async function init() {
defaultDarkTheme = meta.defaultDarkTheme; defaultDarkTheme = meta.defaultDarkTheme;
maintainerName = meta.maintainerName; maintainerName = meta.maintainerName;
maintainerEmail = meta.maintainerEmail; maintainerEmail = meta.maintainerEmail;
donationLink = meta.donationLink;
enableLocalTimeline = !meta.disableLocalTimeline; enableLocalTimeline = !meta.disableLocalTimeline;
enableGlobalTimeline = !meta.disableGlobalTimeline; enableGlobalTimeline = !meta.disableGlobalTimeline;
enableRecommendedTimeline = !meta.disableRecommendedTimeline; enableRecommendedTimeline = !meta.disableRecommendedTimeline;
@ -527,6 +543,7 @@ function save() {
defaultDarkTheme: defaultDarkTheme === "" ? null : defaultDarkTheme, defaultDarkTheme: defaultDarkTheme === "" ? null : defaultDarkTheme,
maintainerName, maintainerName,
maintainerEmail, maintainerEmail,
donationLink,
disableLocalTimeline: !enableLocalTimeline, disableLocalTimeline: !enableLocalTimeline,
disableGlobalTimeline: !enableGlobalTimeline, disableGlobalTimeline: !enableGlobalTimeline,
disableRecommendedTimeline: !enableRecommendedTimeline, disableRecommendedTimeline: !enableRecommendedTimeline,

View file

@ -15,8 +15,13 @@
class="_card announcement" class="_card announcement"
> >
<div class="_title"> <div class="_title">
<span v-if="$i && !announcement.isRead">🆕 </span <span v-if="$i && !announcement.isRead">🆕 </span>
>{{ announcement.title }} <h3>{{ announcement.title }}</h3>
<MkTime :time="announcement.createdAt" />
<div v-if="announcement.updatedAt">
{{ i18n.ts.updatedAt }}:
<MkTime :time="announcement.createdAt" />
</div>
</div> </div>
<div class="_content"> <div class="_content">
<Mfm :text="announcement.text" /> <Mfm :text="announcement.text" />
@ -76,6 +81,10 @@ definePageMetadata({
margin-bottom: var(--margin); margin-bottom: var(--margin);
} }
> ._title {
padding: 14px 32px !important;
}
> ._content { > ._content {
> img { > img {
display: block; display: block;

View file

@ -112,7 +112,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, defineComponent, inject, watch } from "vue"; import { computed, onMounted, defineComponent, inject, watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import MkChannelPreview from "@/components/MkChannelPreview.vue"; import MkChannelPreview from "@/components/MkChannelPreview.vue";
import MkChannelList from "@/components/MkChannelList.vue"; import MkChannelList from "@/components/MkChannelList.vue";

View file

@ -38,7 +38,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, onMounted } from "vue"; import { computed, watch, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import XFeatured from "./explore.featured.vue"; import XFeatured from "./explore.featured.vue";
import XUsers from "./explore.users.vue"; import XUsers from "./explore.users.vue";

View file

@ -107,7 +107,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineComponent, watch, onMounted } from "vue"; import { computed, defineComponent, watch, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import MkFolder from "@/components/MkFolder.vue"; import MkFolder from "@/components/MkFolder.vue";
import MkPagination from "@/components/MkPagination.vue"; import MkPagination from "@/components/MkPagination.vue";

View file

@ -338,7 +338,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from "vue"; import { watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import type * as calckey from "calckey-js"; import type * as calckey from "calckey-js";
import MkChart from "@/components/MkChart.vue"; import MkChart from "@/components/MkChart.vue";

View file

@ -90,7 +90,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, markRaw, onMounted, onUnmounted, watch } from "vue"; import { ref, markRaw, onMounted, onUnmounted, watch } from "vue";
import * as Acct from "calckey-js/built/acct"; import * as Acct from "calckey-js/built/acct";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkChatPreview from "@/components/MkChatPreview.vue"; import MkChatPreview from "@/components/MkChatPreview.vue";

View file

@ -52,7 +52,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import { notificationTypes } from "calckey-js"; import { notificationTypes } from "calckey-js";
import XNotifications from "@/components/MkNotifications.vue"; import XNotifications from "@/components/MkNotifications.vue";

View file

@ -1,27 +1,27 @@
<template> <template>
<XDraggable <VueDraggable
v-model="blocks" v-model="blocks"
tag="div" tag="div"
item-key="id"
handle=".drag-handle" handle=".drag-handle"
:group="{ name: 'blocks' }" :group="{ name: 'blocks' }"
animation="150" animation="150"
swap-threshold="0.5" swap-threshold="0.5"
> >
<template #item="{ element }">
<component <component
v-for="element in blocks"
:key="element"
:is="'x-' + element.type" :is="'x-' + element.type"
:value="element" :value="element"
:hpml="hpml" :hpml="hpml"
@update:value="updateItem" @update:value="updateItem"
@remove="() => removeItem(element)" @remove="() => removeItem(element)"
/> />
</template> </VueDraggable>
</XDraggable>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue"; import { defineComponent, defineAsyncComponent } from "vue";
import { VueDraggable } from "vue-draggable-plus";
import XSection from "./els/page-editor.el.section.vue"; import XSection from "./els/page-editor.el.section.vue";
import XText from "./els/page-editor.el.text.vue"; import XText from "./els/page-editor.el.text.vue";
import XTextarea from "./els/page-editor.el.textarea.vue"; import XTextarea from "./els/page-editor.el.textarea.vue";
@ -41,9 +41,7 @@ import * as os from "@/os";
export default defineComponent({ export default defineComponent({
components: { components: {
XDraggable: defineAsyncComponent(() => VueDraggable,
import("vuedraggable").then((x) => x.default),
),
XSection, XSection,
XText, XText,
XImage, XImage,

View file

@ -118,19 +118,19 @@
<div v-else-if="tab === 'variables'"> <div v-else-if="tab === 'variables'">
<div class="qmuvgica"> <div class="qmuvgica">
<XDraggable <VueDraggable
v-show="variables.length > 0" v-show="variables.length > 0"
v-model="variables" v-model="variables"
tag="div" tag="div"
class="variables" class="variables"
item-key="name"
handle=".drag-handle" handle=".drag-handle"
:group="{ name: 'variables' }" :group="{ name: 'variables' }"
animation="150" animation="150"
swap-threshold="0.5" swap-threshold="0.5"
> >
<template #item="{ element }">
<XVariable <XVariable
v-for="element in variables"
:key="element.name"
:model-value="element" :model-value="element"
:removable="true" :removable="true"
:hpml="hpml" :hpml="hpml"
@ -139,8 +139,7 @@
:draggable="true" :draggable="true"
@remove="() => removeVariable(element)" @remove="() => removeVariable(element)"
/> />
</template> </VueDraggable>
</XDraggable>
<MkButton <MkButton
v-if="!readonly" v-if="!readonly"
@ -174,6 +173,7 @@ import { blockDefs } from "@/scripts/hpml/index";
import { HpmlTypeChecker } from "@/scripts/hpml/type-checker"; import { HpmlTypeChecker } from "@/scripts/hpml/type-checker";
import { url } from "@/config"; import { url } from "@/config";
import { collectPageVars } from "@/scripts/collect-page-vars"; import { collectPageVars } from "@/scripts/collect-page-vars";
import { VueDraggable } from "vue-draggable-plus";
import * as os from "@/os"; import * as os from "@/os";
import { selectFile } from "@/scripts/select-file"; import { selectFile } from "@/scripts/select-file";
import { mainRouter } from "@/router"; import { mainRouter } from "@/router";
@ -181,10 +181,6 @@ import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata"; import { definePageMetadata } from "@/scripts/page-metadata";
import { $i } from "@/account"; import { $i } from "@/account";
const XDraggable = defineAsyncComponent(() =>
import("vuedraggable").then((x) => x.default),
);
const props = defineProps<{ const props = defineProps<{
initPageId?: string; initPageId?: string;
initPageName?: string; initPageName?: string;

View file

@ -81,7 +81,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, onMounted } from "vue"; import { computed, watch, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import MkPagePreview from "@/components/MkPagePreview.vue"; import MkPagePreview from "@/components/MkPagePreview.vue";
import MkPagination from "@/components/MkPagination.vue"; import MkPagination from "@/components/MkPagination.vue";

View file

@ -41,7 +41,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, onMounted } from "vue"; import { computed, watch, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import XNotes from "@/components/MkNotes.vue"; import XNotes from "@/components/MkNotes.vue";
import XUserList from "@/components/MkUserList.vue"; import XUserList from "@/components/MkUserList.vue";

View file

@ -82,7 +82,7 @@
<p> <p>
{{ `${i18n.ts.lastUsedDate}: ${key.lastUsed}` }} {{ `${i18n.ts.lastUsedDate}: ${key.lastUsed}` }}
</p> </p>
<div class="_buttons _flexList"> <div class="_flexList">
<MkButton @click="renameKey(key)" <MkButton @click="renameKey(key)"
><i ><i
class="ph-pencil-line ph-bold ph-lg" class="ph-pencil-line ph-bold ph-lg"

View file

@ -10,32 +10,30 @@
i18n.ts.reactionSettingDescription i18n.ts.reactionSettingDescription
}}</template> }}</template>
<div v-panel style="border-radius: 6px"> <div v-panel style="border-radius: 6px">
<XDraggable <VueDraggable
v-model="reactions" v-model="reactions"
class="zoaiodol" :class="$style.root"
:item-key="(item) => item"
animation="150" animation="150"
delay="100" delay="100"
@end="save"
delay-on-touch-only="true" delay-on-touch-only="true"
> >
<template #item="{ element }"> <div
<button v-for="item in reactions"
:key="item"
class="_button item" class="_button item"
@click="remove(element, $event)" @click="remove(item, $event)"
> >
<MkEmoji <MkEmoji
:emoji="element" :emoji="item"
style="height: 1.7em" style="height: 1.7em"
class="emoji" class="emoji"
/> />
</button> </div>
</template> </VueDraggable>
<template #footer>
<button class="_button add" @click="chooseEmoji"> <button class="_button add" @click="chooseEmoji">
<i class="ph-plus ph-bold ph-lg"></i> <i class="ph-plus ph-bold ph-lg"></i>
</button> </button>
</template>
</XDraggable>
</div> </div>
<template #caption <template #caption
>{{ i18n.ts.reactionSettingDescription2 }} >{{ i18n.ts.reactionSettingDescription2 }}
@ -85,7 +83,7 @@
<option :value="1">{{ i18n.ts.small }}</option> <option :value="1">{{ i18n.ts.small }}</option>
<option :value="2">{{ i18n.ts.medium }}</option> <option :value="2">{{ i18n.ts.medium }}</option>
<option :value="3">{{ i18n.ts.large }}</option> <option :value="3">{{ i18n.ts.large }}</option>
<option :value="4">{{ i18n.ts.large }}+</option> <option :value="4">{{ i18n.ts.xl }}</option>
</FormRadios> </FormRadios>
<FormSwitch <FormSwitch
@ -124,8 +122,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, watch } from "vue"; import { defineAsyncComponent, watch } from "vue";
import XDraggable from "vuedraggable"; import { VueDraggable } from "vue-draggable-plus";
import FormInput from "@/components/form/input.vue";
import FormRadios from "@/components/form/radios.vue"; import FormRadios from "@/components/form/radios.vue";
import FromSlot from "@/components/form/slot.vue"; import FromSlot from "@/components/form/slot.vue";
import FormButton from "@/components/MkButton.vue"; import FormButton from "@/components/MkButton.vue";
@ -184,6 +181,7 @@ function remove(reaction, ev: MouseEvent) {
text: i18n.ts.remove, text: i18n.ts.remove,
action: () => { action: () => {
reactions = reactions.filter((x) => x !== reaction); reactions = reactions.filter((x) => x !== reaction);
save();
}, },
}, },
], ],
@ -221,20 +219,11 @@ function chooseEmoji(ev: MouseEvent) {
}).then((emoji) => { }).then((emoji) => {
if (!reactions.includes(emoji)) { if (!reactions.includes(emoji)) {
reactions.push(emoji); reactions.push(emoji);
save();
} }
}); });
} }
watch(
$$(reactions),
() => {
save();
},
{
deep: true,
},
);
watch(enableEmojiReactions, async () => { watch(enableEmojiReactions, async () => {
await reloadAsk(); await reloadAsk();
}); });
@ -253,15 +242,11 @@ const headerTabs = $computed(() => []);
definePageMetadata({ definePageMetadata({
title: i18n.ts.reaction, title: i18n.ts.reaction,
icon: "ph-smiley ph-bold ph-lg", icon: "ph-smiley ph-bold ph-lg",
action: {
icon: "ph-eye ph-bold ph-lg",
handler: preview,
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.zoaiodol { .root {
padding: 12px; padding: 12px;
font-size: 1.1em; font-size: 1.1em;
@ -270,10 +255,12 @@ definePageMetadata({
padding: 8px; padding: 8px;
cursor: move; cursor: move;
} }
}
> .add { .add {
display: inline-block; display: inline-block;
padding: 8px; padding: 8px;
} margin-left: 12px;
margin-bottom: 12px;
} }
</style> </style>

View file

@ -41,7 +41,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, onMounted } from "vue"; import { computed, watch, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import XNotes from "@/components/MkNotes.vue"; import XNotes from "@/components/MkNotes.vue";
import XUserList from "@/components/MkUserList.vue"; import XUserList from "@/components/MkUserList.vue";

View file

@ -65,7 +65,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, onMounted } from "vue"; import { computed, ref, onMounted } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import XTutorial from "@/components/MkTutorialDialog.vue"; import XTutorial from "@/components/MkTutorialDialog.vue";
import XTimeline from "@/components/MkTimeline.vue"; import XTimeline from "@/components/MkTimeline.vue";

View file

@ -26,8 +26,9 @@
class="banner" class="banner"
:style="{ :style="{
backgroundImage: `url('${user.bannerUrl}')`, backgroundImage: `url('${user.bannerUrl}')`,
'--backgroundImageStatic': defaultStore '--backgroundImageStatic':
.state.useBlurEffect && user.bannerUrl defaultStore.state.useBlurEffect &&
user.bannerUrl
? `url('${getStaticImageUrl( ? `url('${getStaticImageUrl(
user.bannerUrl, user.bannerUrl,
)}')` )}')`

View file

@ -1,13 +1,16 @@
import { getBlurHashAverageColor } from "fast-blurhash";
function rgbToHex(rgb: number[]): string {
return `#${rgb
.map((x) => {
const hex = x.toString(16);
return hex.length === 1 ? `0${hex}` : hex;
})
.join("")}`;
}
export function extractAvgColorFromBlurhash(hash: string) { export function extractAvgColorFromBlurhash(hash: string) {
return typeof hash === "string" return typeof hash === "string"
? `#${[...hash.slice(2, 6)] ? rgbToHex(getBlurHashAverageColor(hash))
.map((x) =>
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(
x,
),
)
.reduce((a, c) => a * 83 + c, 0)
.toString(16)
.padStart(6, "0")}`
: undefined; : undefined;
} }

View file

@ -145,8 +145,11 @@ a {
cursor: pointer; cursor: pointer;
color: inherit; color: inherit;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
&:hover {
text-decoration: underline; text-decoration: underline;
text-decoration-color: transparent;
transition: text-decoration-color 0.2s;
&:hover {
text-decoration-color: currentColor;
} }
} }
@ -632,24 +635,26 @@ hr {
._link { ._link {
position: relative; position: relative;
color: var(--link); color: var(--link);
text-decoration: none !important; text-underline-offset: 0.2em;
&::before, &::after { // &::before,
content: ""; // &::after {
position: absolute; // content: "";
bottom: 0; // position: absolute;
left: 0; // bottom: 0;
width: 0%; // left: 0;
border-bottom: 1px solid var(--link); // width: 0%;
transition: 0.3s ease-in-out; // border-bottom: 1px solid currentColor;
} // transition: 0.3s ease-in-out;
&::before { // }
width: 100%; // &::before {
opacity: .4; // width: 100%;
} // opacity: 0.4;
&:hover:after, &:focus:after { // }
width: 100%; // &:hover:after,
} // &:focus:after {
// width: 100%;
// }
} }
._caption { ._caption {

View file

@ -26,7 +26,7 @@
<MkA <MkA
v-click-anime v-click-anime
v-tooltip.noDelay.right="i18n.ts.timeline" v-tooltip.noDelay.right="i18n.ts.timeline"
class="nav-item index" class="item index"
active-class="active" active-class="active"
to="/" to="/"
exact exact
@ -46,7 +46,7 @@
v-tooltip.noDelay.right=" v-tooltip.noDelay.right="
i18n.ts[navbarItemDef[item].title] i18n.ts[navbarItemDef[item].title]
" "
class="nav-item _button" class="item _button"
:class="[item, { active: navbarItemDef[item].active }]" :class="[item, { active: navbarItemDef[item].active }]"
active-class="active" active-class="active"
:to="navbarItemDef[item].to" :to="navbarItemDef[item].to"
@ -66,9 +66,6 @@
<span <span
v-if="navbarItemDef[item].indicated" v-if="navbarItemDef[item].indicated"
class="indicator" class="indicator"
:class="{
animateIndicator: $store.state.animation,
}"
><i class="icon ph-circle ph-fill"></i ><i class="icon ph-circle ph-fill"></i
></span> ></span>
</component> </component>
@ -78,7 +75,7 @@
v-if="$i.isAdmin || $i.isModerator" v-if="$i.isAdmin || $i.isModerator"
v-click-anime v-click-anime
v-tooltip.noDelay.right="i18n.ts.controlPanel" v-tooltip.noDelay.right="i18n.ts.controlPanel"
class="nav-item _button" class="item _button"
active-class="active" active-class="active"
to="/admin" to="/admin"
> >
@ -91,7 +88,6 @@
updateAvailable updateAvailable
" "
class="indicator" class="indicator"
:class="{ animateIndicator: $store.state.animation }"
></span ></span
><i class="icon ph-door ph-bold ph-fw ph-lg"></i ><i class="icon ph-door ph-bold ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.controlPanel }}</span> ><span class="text">{{ i18n.ts.controlPanel }}</span>
@ -99,24 +95,21 @@
<button <button
v-click-anime v-click-anime
v-tooltip.noDelay.right="i18n.ts.more" v-tooltip.noDelay.right="i18n.ts.more"
class="nav-item _button" class="item _button"
@click="more" @click="more"
> >
<i <i
class="icon ph-dots-three-outline ph-bold ph-fw ph-lg" class="icon ph-dots-three-outline ph-bold ph-fw ph-lg"
></i ></i
><span class="text">{{ i18n.ts.more }}</span> ><span class="text">{{ i18n.ts.more }}</span>
<span <span v-if="otherMenuItemIndicated" class="indicator"
v-if="otherMenuItemIndicated"
class="indicator"
:class="{ animateIndicator: $store.state.animation }"
><i class="icon ph-circle ph-fill"></i ><i class="icon ph-circle ph-fill"></i
></span> ></span>
</button> </button>
<MkA <MkA
v-click-anime v-click-anime
v-tooltip.noDelay.right="i18n.ts.settings" v-tooltip.noDelay.right="i18n.ts.settings"
class="nav-item _button" class="item _button"
active-class="active" active-class="active"
to="/settings" to="/settings"
> >
@ -127,7 +120,7 @@
<div class="bottom"> <div class="bottom">
<button <button
v-tooltip.noDelay.right="i18n.ts.note" v-tooltip.noDelay.right="i18n.ts.note"
class="nav-item _button post" class="item _button post"
data-cy-open-post-form data-cy-open-post-form
@click="os.post" @click="os.post"
> >
@ -272,125 +265,6 @@ function more(ev: MouseEvent) {
flex-direction: column; flex-direction: column;
} }
.nav-item {
position: relative;
display: flex;
align-items: center;
padding-inline: 30px;
line-height: 2.85rem;
margin-bottom: 0.5rem;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
&:before, &.post::after {
content: "";
display: block;
width: calc(100% - 34px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
opacity: 0;
z-index: -2;
}
> .icon {
position: relative;
width: 32px;
margin-right: 8px;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
}
> .animateIndicator {
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
}
&:hover,
&:focus-within {
text-decoration: none;
color: var(--navHoverFg);
transition: color 0.4s ease;
}
&.active {
color: var(--navActive);
}
&:hover,
&:focus-within,
&.active {
color: var(--accent);
opacity: 1;
transition: color 0.4s, opacity 0.4s;
&::before {
opacity: 1;
}
}
}
.post {
padding-inline: 0;
color: var(--fgOnAccent);
font-weight: bold;
&::before {
opacity: 1;
background: linear-gradient(
90deg,
var(--buttonGradateA),
var(--buttonGradateB)
);
}
&::after {
background: var(--accentLighten) !important;
opacity: 0;
z-index: -1;
transition: opacity 0.2s;
}
&:hover,
&:focus-within,
&.active {
&::after {
opacity: 1;
}
}
> .icon,
> .text {
position: relative;
left: 3rem;
margin: 0;
width: auto;
color: var(--fgOnAccent);
transform: translateY(0em);
}
> .text {
margin-left: 1rem;
}
}
&:not(.iconOnly) { &:not(.iconOnly) {
> .body { > .body {
margin-left: -200px; margin-left: -200px;
@ -431,6 +305,57 @@ function more(ev: MouseEvent) {
> .bottom { > .bottom {
padding: 20px 0; padding: 20px 0;
> .post {
position: relative;
width: 100%;
height: 40px;
color: var(--fgOnAccent);
font-weight: bold;
text-align: left;
display: flex;
align-items: center;
&:before {
content: "";
display: block;
width: calc(100% - 38px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(
90deg,
var(--buttonGradateA),
var(--buttonGradateB)
);
}
&:hover,
&:focus-within,
&.active {
&:before {
background: var(--accentLighten);
transition: all 0.4s ease;
}
}
> .icon,
> .text {
position: relative;
left: 3rem;
color: var(--fgOnAccent);
transform: translateY(0em);
}
> .text {
margin-left: 1rem;
}
}
> .instance { > .instance {
position: relative; position: relative;
display: block; display: block;
@ -468,6 +393,75 @@ function more(ev: MouseEvent) {
margin: 16px 16px; margin: 16px 16px;
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
} }
> .item {
position: relative;
display: flex;
align-items: center;
padding-left: 30px;
line-height: 2.85rem;
margin-bottom: 0.5rem;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> .icon {
position: relative;
width: 32px;
margin-right: 8px;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
}
&:hover,
&:focus-within {
text-decoration: none;
color: var(--navHoverFg);
transition: all 0.4s ease;
}
&.active {
color: var(--navActive);
}
&:hover,
&:focus-within,
&.active {
color: var(--accent);
transition: all 0.4s ease;
&:before {
content: "";
display: block;
width: calc(100% - 34px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
}
}
} }
} }
} }
@ -499,6 +493,52 @@ function more(ev: MouseEvent) {
> .bottom { > .bottom {
padding: 20px 0; padding: 20px 0;
> .post {
display: block;
position: relative;
width: 100%;
height: 52px;
margin-bottom: 16px;
text-align: center;
&:before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 52px;
aspect-ratio: 1/1;
border-radius: 100%;
background: linear-gradient(
90deg,
var(--buttonGradateA),
var(--buttonGradateB)
);
}
&:hover,
&:focus-within,
&.active {
&:before {
background: var(--accentLighten);
transition: all 0.4s ease;
}
}
> .icon {
position: relative;
color: var(--fgOnAccent);
}
> .text {
display: none;
}
}
> .help { > .help {
position: relative; position: relative;
display: block; display: block;
@ -537,11 +577,12 @@ function more(ev: MouseEvent) {
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
} }
} > .item {
} display: block;
.nav-item { position: relative;
padding: 1.1rem 0; padding: 1.1rem 0;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
width: 100%;
text-align: center; text-align: center;
> .icon { > .icon {
@ -561,32 +602,42 @@ function more(ev: MouseEvent) {
left: 24px; left: 24px;
color: var(--navIndicator); color: var(--navIndicator);
font-size: 8px; font-size: 8px;
}
> .animateIndicator {
animation: blink 1s infinite; animation: blink 1s infinite;
} }
}
.post { &:hover,
width: 100%; &:focus-within,
height: 52px; &.active {
margin-bottom: 16px; text-decoration: none;
&:before, &::after { color: var(--accent);
inset: 0; transition: all 0.4s ease;
&:before {
content: "";
display: block;
height: 100%;
aspect-ratio: 1;
margin: auto; margin: auto;
width: 52px; position: absolute;
aspect-ratio: 1/1; top: 0;
} left: 0;
> .icon { right: 0;
left: unset; bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
} }
> .icon,
> .text { > .text {
display: none; opacity: 1;
}
}
}
} }
} }
} }
.nav-item { .item {
outline: none; outline: none;
&:focus-visible:before { &:focus-visible:before {
outline: auto; outline: auto;

View file

@ -23,9 +23,9 @@ const extensions = [
]; ];
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
fs.mkdirSync(__dirname + "/../../built", { recursive: true }); fs.mkdirSync(`${__dirname}/../../built`, { recursive: true });
fs.writeFileSync( fs.writeFileSync(
__dirname + "/../../built/meta.json", `${__dirname}/../../built/meta.json`,
JSON.stringify({ version: meta.version }), JSON.stringify({ version: meta.version }),
"utf-8", "utf-8",
); );
@ -40,15 +40,16 @@ export default defineConfig(({ command, mode }) => {
pluginJson5(), pluginJson5(),
viteCompression({ viteCompression({
algorithm: "brotliCompress", algorithm: "brotliCompress",
verbose: false,
}), }),
], ],
resolve: { resolve: {
extensions, extensions,
alias: { alias: {
"@/": __dirname + "/src/", "@/": `${__dirname}/src/`,
"/client-assets/": __dirname + "/assets/", "/client-assets/": `${__dirname}/assets/`,
"/static-assets/": __dirname + "/../backend/assets/", "/static-assets/": `${__dirname}/../backend/assets/`,
}, },
}, },
@ -82,7 +83,7 @@ export default defineConfig(({ command, mode }) => {
}, },
cssCodeSplit: true, cssCodeSplit: true,
assetsInlineLimit: 0, assetsInlineLimit: 0,
outDir: __dirname + "/../../built/_client_dist_", outDir: `${__dirname}/../../built/_client_dist_`,
assetsDir: ".", assetsDir: ".",
emptyOutDir: false, emptyOutDir: false,
sourcemap: process.env.NODE_ENV === "development", sourcemap: process.env.NODE_ENV === "development",
@ -94,5 +95,7 @@ export default defineConfig(({ command, mode }) => {
optimizeDeps: { optimizeDeps: {
auto: true, auto: true,
}, },
logLevel: "warn",
}; };
}); });

View file

@ -1541,7 +1541,7 @@ export default class Misskey implements MegalodonInterface {
if (!res.data || (res.data != 'public' && res.data != 'home' && res.data != 'followers' && res.data != 'specified')) if (!res.data || (res.data != 'public' && res.data != 'home' && res.data != 'followers' && res.data != 'specified'))
return 'public'; return 'public';
return this.converter.visibility(res.data); return this.converter.visibility(res.data);
}); }).catch(_ => 'public')
} }
public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> { public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> {

View file

@ -156,7 +156,7 @@ namespace MisskeyAPI {
id: u.id, id: u.id,
username: u.username, username: u.username,
acct: acct, acct: acct,
display_name: u.name, display_name: u.name || u.username,
locked: u.isLocked, locked: u.isLocked,
created_at: u.createdAt, created_at: u.createdAt,
followers_count: u.followersCount, followers_count: u.followersCount,

View file

@ -86,6 +86,13 @@
"@irfan@calckey.social", "@irfan@calckey.social",
"@dvd@dvd.chat", "@dvd@dvd.chat",
"@charlie2alpha@electricrequiem.com", "@charlie2alpha@electricrequiem.com",
"@arndot@layer8.space",
"@ryan@c.ryanccn.dev",
"@lapastora_deprova@calckey.social",
"@rameez@calckey.social",
"@dracoling@firetribe.org",
"@Space6host@calckey.social",
"@zakalwe@plasmatrap.com",
"\nInterkosmos Link" "\nInterkosmos Link"
] ]
} }

File diff suppressed because it is too large Load diff