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/docker_example.env
!/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config
/dev/docker-compose.yml

View file

@ -137,7 +137,7 @@
- 👍 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 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/4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins
- 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>
Breaks Calckey -> Misskey migration, but fixes Foundkey -> Calckey migration
Breaks Calckey -> Misskey migration, but fixes FoundKey -> Calckey migration
- Add argon

29
COPYING
View file

@ -1,15 +1,24 @@
Unless otherwise stated this repository is
Copyright © 2014-2022 syuilo and contributers
Copyright © 2022 thatonecalculator and contributers
Unless specified otherwise, the entirety of this repository is subject to the following:
Copyright © 2014-2023 syuilo and contributors
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.
---
Calckey includes several third-party Open-Source softwares.
These specific configuration directories:
Emoji keywords for Unicode 11 and below by Mu-An Chiou
License: MIT
https://github.com/muan/emojilib/blob/master/LICENSE
- .config/
- custom/assets/
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
License: MIT
@ -23,6 +32,6 @@ Chiptune2.js by Simon Gündling
License: MIT
https://github.com/deskjet/chiptune2.js#license
libopenmpt (as part of openmpt) by OpenMPT
License: BSD 3-Clause
https://github.com/OpenMPT/openmpt/blob/master/LICENSE
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:
pnpm licenses list

View file

@ -72,6 +72,14 @@
# 🌠 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**.
## 🔰 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.
- 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

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**
- **stopped all master and worker processes of Misskey.**
- **have backups of the database before performing any commands.**
### Before proceeding
- **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
@ -77,15 +78,16 @@ NODE_ENV=production pnpm run migrate
# build using prefered method
```
## Foundkey
## FoundKey
```sh
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)"
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
done
@ -100,4 +102,4 @@ NODE_ENV=production pnpm run migrate
## 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
enableIdenticonGeneration: Habilitar la generació d'Identicon
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"
width: "Width"
height: "Height"
xl: "XL"
large: "Big"
medium: "Medium"
small: "Small"
@ -1118,6 +1119,9 @@ enableIdenticonGeneration: "Enable Identicon generation"
showPopup: "Notify users with popup"
showWithSparkles: "Show with sparkles"
youHaveUnreadAnnouncements: "You have unread announcements"
donationLink: "Link to donation page"
neverShow: "Don't show again"
remindMeLater: "Maybe later"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1216,6 +1220,10 @@ _aboutMisskey:
source: "Source code"
translation: "Translate 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.
Thank you! 🥰"
patrons: "Calckey patrons"

View file

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

View file

@ -1,16 +1,16 @@
{
"name": "calckey",
"version": "14.0.0-rc3",
"version": "14.0.0-dev71",
"codename": "aqua",
"repository": {
"type": "git",
"url": "https://codeberg.org/calckey/calckey.git"
},
"packageManager": "pnpm@8.6.6",
"packageManager": "pnpm@8.6.7",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r run build && pnpm run gulp",
"build": "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 --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate",
@ -21,13 +21,13 @@
"watch": "pnpm run dev",
"dev": "pnpm node ./scripts/dev.js",
"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:run": "cypress run",
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
"mocha": "pnpm --filter backend run mocha",
"test": "pnpm run mocha",
"format": "pnpm -r run format",
"format": "pnpm -r --parallel run format",
"clean": "pnpm node ./scripts/clean.js",
"clean-all": "pnpm node ./scripts/clean-all.js",
"cleanall": "pnpm run clean-all"
@ -36,17 +36,17 @@
"chokidar": "^3.3.1"
},
"dependencies": {
"@bull-board/api": "5.2.0",
"@bull-board/ui": "5.2.0",
"@bull-board/api": "5.6.0",
"@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1",
"@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0",
"seedrandom": "^3.0.5"
},
"devDependencies": {
"@types/node": "18.11.18",
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/node": "20.4.1",
"chalk": "4.1.2",
"cross-env": "7.0.3",
"cypress": "10.11.0",
@ -59,6 +59,6 @@
"install-peers": "^1.0.4",
"rome": "^12.1.3",
"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"
},
"dependencies": {
"@bull-board/api": "5.2.0",
"@bull-board/koa": "5.2.0",
"@bull-board/ui": "5.2.0",
"@bull-board/api": "5.6.0",
"@bull-board/koa": "5.6.0",
"@bull-board/ui": "5.6.0",
"@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3",
"@koa/multer": "3.0.2",
"@koa/router": "9.0.1",
"@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",
"@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0",
@ -45,17 +45,17 @@
"autobind-decorator": "2.4.0",
"autolinker": "4.0.0",
"autwh": "0.1.0",
"aws-sdk": "2.1277.0",
"aws-sdk": "2.1413.0",
"axios": "^1.4.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
"blurhash": "2.0.5",
"bull": "4.10.4",
"cacheable-lookup": "7.0.0",
"calckey-js": "workspace:*",
"cbor": "8.1.0",
"chalk": "5.2.0",
"chalk": "5.3.0",
"chalk-template": "0.4.0",
"chokidar": "3.5.3",
"chokidar": "^3.5.3",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
@ -68,15 +68,15 @@
"got": "12.5.3",
"hpagent": "0.1.2",
"ioredis": "5.3.2",
"ip-cidr": "3.0.11",
"ip-cidr": "3.1.0",
"is-svg": "4.3.2",
"js-yaml": "4.1.0",
"jsdom": "20.0.3",
"jsonld": "8.2.0",
"jsrsasign": "10.6.1",
"koa": "2.13.4",
"jsrsasign": "10.8.6",
"koa": "2.14.2",
"koa-body": "^6.0.1",
"koa-bodyparser": "4.3.0",
"koa-bodyparser": "4.4.1",
"koa-favicon": "2.1.0",
"koa-json-body": "5.3.0",
"koa-logger": "3.2.1",
@ -98,9 +98,9 @@
"nsfwjs": "2.4.2",
"oauth": "^0.10.0",
"os-utils": "0.0.14",
"otpauth": "^9.1.2",
"otpauth": "^9.1.3",
"parse5": "7.1.2",
"pg": "8.11.0",
"pg": "8.11.1",
"private-ip": "2.3.4",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@ -110,7 +110,7 @@
"qs": "6.11.2",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.19.0",
"re2": "1.19.1",
"redis-lock": "0.1.4",
"redis-semaphore": "5.3.1",
"reflect-metadata": "0.1.13",
@ -119,7 +119,7 @@
"rss-parser": "3.13.0",
"sanitize-html": "2.10.0",
"seedrandom": "^3.0.5",
"semver": "7.5.1",
"semver": "7.5.4",
"sharp": "0.32.1",
"sonic-channel": "^1.3.1",
"stringz": "2.1.0",
@ -130,27 +130,26 @@
"tinycolor2": "1.5.2",
"tmp": "0.2.1",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.11",
"typeorm": "0.3.17",
"ulid": "2.3.0",
"uuid": "9.0.0",
"web-push": "3.6.1",
"web-push": "3.6.3",
"websocket": "1.0.34",
"xev": "3.0.2"
},
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62",
"@swc/core": "^1.3.68",
"@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.9",
"@types/cbor": "6.0.0",
"@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/jsdom": "20.0.1",
"@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.4",
"@types/koa": "2.13.5",
"@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.6",
"@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21",
@ -169,7 +168,7 @@
"@types/probe-image-size": "^7.2.0",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0",
"@types/qrcode": "1.5.1",
"@types/qs": "6.9.7",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4",
@ -177,17 +176,16 @@
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0",
"@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3",
"@types/uuid": "8.3.4",
"@types/uuid": "9.0.2",
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.4",
"@types/ws": "8.5.5",
"autobind-decorator": "2.4.0",
"cross-env": "7.0.3",
"eslint": "^8.42.0",
"eslint": "^8.44.0",
"execa": "6.1.0",
"json5": "2.2.3",
"json5-loader": "4.0.1",
@ -195,11 +193,11 @@
"pug": "3.0.2",
"strict-event-emitter-types": "2.0.0",
"swc-loader": "^0.2.3",
"ts-loader": "9.4.3",
"ts-loader": "9.4.4",
"ts-node": "10.9.1",
"tsconfig-paths": "4.2.0",
"typescript": "5.1.3",
"webpack": "^5.85.1",
"typescript": "5.1.6",
"webpack": "^5.88.1",
"ws": "8.13.0"
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -177,6 +177,9 @@ export const paramDef = {
postImports: { type: "boolean" },
},
},
enableServerMachineStats: { type: "boolean" },
enableIdenticonGeneration: { type: "boolean" },
donationLink: { type: "string", nullable: true },
},
required: [],
} as const;
@ -568,6 +571,21 @@ export default define(meta, paramDef, async (ps, me) => {
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) => {
const metas = await transactionalEntityManager.find(Meta, {
order: {

View file

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

View file

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

View file

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

View file

@ -5,6 +5,13 @@
<MkSparkle v-if="isGoodNews">{{ title }}</MkSparkle>
<p v-else>{{ title }}</p>
</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" />
<img
v-if="imageUrl != null"
@ -68,6 +75,10 @@ const gotIt = () => {
}
}
.time {
font-size: 0.8rem;
}
.gotIt {
margin: 8px 0 0 0;
}

View file

@ -13,7 +13,9 @@
<template #default="{ width, height }">
<div
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">
<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"
:manual-showing="manualShowing"
:src="src"
@click="modal?.close()"
@click="checkForShift"
@opening="opening"
@close="emit('close')"
@closed="emit('closed')"
@ -31,7 +31,7 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { ref, onMounted, onBeforeUnmount } from "vue";
import MkModal from "@/components/MkModal.vue";
import MkEmojiPicker from "@/components/MkEmojiPicker.vue";
import { defaultStore } from "@/store";
@ -58,20 +58,49 @@ const emit = defineEmits<{
const modal = ref<InstanceType<typeof MkModal>>();
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) {
emit("done", emoji);
modal.value?.close();
checkForShift();
}
function opening() {
try {
picker.value?.reset();
} 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();
}
onMounted(() => {
window.addEventListener("keydown", keydownHandler);
window.addEventListener("keyup", keyupHandler);
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", keydownHandler);
window.removeEventListener("keyup", keyupHandler);
});
</script>
<style lang="scss" scoped>

View file

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

View file

@ -226,11 +226,13 @@ watch(
display: flex;
min-width: max-content;
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"] {
width: 0;
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) {
width: 0px;
@ -238,7 +240,9 @@ watch(
[data-plyr="volume"] {
margin-inline: 0px;
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;
padding: 8px;
opacity: 0.7;
&:disabled {
opacity: 0.5 !important;
}
flex-grow: 1;
max-width: 3.5em;
width: max-content;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -75,13 +75,12 @@ const target = self ? null : "_blank";
<style lang="scss" scoped>
.url {
white-space: nowrap;
max-width: 80%;
display: inline-block;
overflow: clip;
text-overflow: ellipsis;
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 {
padding-left: 2px;
@ -111,5 +110,9 @@ const target = self ? null : "_blank";
> .hash {
font-style: italic;
}
&:hover span {
text-decoration-color: var(--link);
}
}
</style>

View file

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

View file

@ -450,6 +450,29 @@ function checkForSplash() {
}
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.permission === "default") {

View file

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

View file

@ -93,6 +93,21 @@
external
>{{ 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>
<FormSuspense :p="initStats">
@ -163,7 +178,7 @@
<script lang="ts" setup>
import { ref, computed, onMounted, watch } from "vue";
import { Virtual } from "swiper";
import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import XEmojis from "./about.emojis.vue";
import XFederation from "./about.federation.vue";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,12 +26,13 @@
class="banner"
:style="{
backgroundImage: `url('${user.bannerUrl}')`,
'--backgroundImageStatic': defaultStore
.state.useBlurEffect && user.bannerUrl
? `url('${getStaticImageUrl(
user.bannerUrl,
)}')`
: null,
'--backgroundImageStatic':
defaultStore.state.useBlurEffect &&
user.bannerUrl
? `url('${getStaticImageUrl(
user.bannerUrl,
)}')`
: null,
}"
></div>
<div class="fade"></div>

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) {
return typeof hash === "string"
? `#${[...hash.slice(2, 6)]
.map((x) =>
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(
x,
),
)
.reduce((a, c) => a * 83 + c, 0)
.toString(16)
.padStart(6, "0")}`
? rgbToHex(getBlurHashAverageColor(hash))
: undefined;
}

View file

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

View file

@ -26,7 +26,7 @@
<MkA
v-click-anime
v-tooltip.noDelay.right="i18n.ts.timeline"
class="nav-item index"
class="item index"
active-class="active"
to="/"
exact
@ -46,7 +46,7 @@
v-tooltip.noDelay.right="
i18n.ts[navbarItemDef[item].title]
"
class="nav-item _button"
class="item _button"
:class="[item, { active: navbarItemDef[item].active }]"
active-class="active"
:to="navbarItemDef[item].to"
@ -66,9 +66,6 @@
<span
v-if="navbarItemDef[item].indicated"
class="indicator"
:class="{
animateIndicator: $store.state.animation,
}"
><i class="icon ph-circle ph-fill"></i
></span>
</component>
@ -78,7 +75,7 @@
v-if="$i.isAdmin || $i.isModerator"
v-click-anime
v-tooltip.noDelay.right="i18n.ts.controlPanel"
class="nav-item _button"
class="item _button"
active-class="active"
to="/admin"
>
@ -91,7 +88,6 @@
updateAvailable
"
class="indicator"
:class="{ animateIndicator: $store.state.animation }"
></span
><i class="icon ph-door ph-bold ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.controlPanel }}</span>
@ -99,24 +95,21 @@
<button
v-click-anime
v-tooltip.noDelay.right="i18n.ts.more"
class="nav-item _button"
class="item _button"
@click="more"
>
<i
class="icon ph-dots-three-outline ph-bold ph-fw ph-lg"
></i
><span class="text">{{ i18n.ts.more }}</span>
<span
v-if="otherMenuItemIndicated"
class="indicator"
:class="{ animateIndicator: $store.state.animation }"
<span v-if="otherMenuItemIndicated" class="indicator"
><i class="icon ph-circle ph-fill"></i
></span>
</button>
<MkA
v-click-anime
v-tooltip.noDelay.right="i18n.ts.settings"
class="nav-item _button"
class="item _button"
active-class="active"
to="/settings"
>
@ -127,7 +120,7 @@
<div class="bottom">
<button
v-tooltip.noDelay.right="i18n.ts.note"
class="nav-item _button post"
class="item _button post"
data-cy-open-post-form
@click="os.post"
>
@ -272,125 +265,6 @@ function more(ev: MouseEvent) {
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) {
> .body {
margin-left: -200px;
@ -431,6 +305,57 @@ function more(ev: MouseEvent) {
> .bottom {
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 {
position: relative;
display: block;
@ -468,6 +393,75 @@ function more(ev: MouseEvent) {
margin: 16px 16px;
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 {
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 {
position: relative;
display: block;
@ -537,56 +577,67 @@ function more(ev: MouseEvent) {
border-top: solid 0.5px var(--divider);
}
}
}
.nav-item {
padding: 1.1rem 0;
margin-bottom: 0.2rem;
text-align: center;
> .item {
display: block;
position: relative;
padding: 1.1rem 0;
margin-bottom: 0.2rem;
width: 100%;
text-align: center;
> .icon {
display: block;
margin: 0 auto;
opacity: 0.7;
transform: translateY(0em);
}
> .icon {
display: block;
margin: 0 auto;
opacity: 0.7;
transform: translateY(0em);
}
> .text {
display: none;
}
> .text {
display: none;
}
> .indicator {
position: absolute;
top: 6px;
left: 24px;
color: var(--navIndicator);
font-size: 8px;
}
> .indicator {
position: absolute;
top: 6px;
left: 24px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .animateIndicator {
animation: blink 1s infinite;
}
}
.post {
width: 100%;
height: 52px;
margin-bottom: 16px;
&:before, &::after {
inset: 0;
margin: auto;
width: 52px;
aspect-ratio: 1/1;
}
> .icon {
left: unset;
}
> .text {
display: none;
&:hover,
&:focus-within,
&.active {
text-decoration: none;
color: var(--accent);
transition: all 0.4s ease;
&:before {
content: "";
display: block;
height: 100%;
aspect-ratio: 1;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
> .icon,
> .text {
opacity: 1;
}
}
}
}
}
}
.nav-item {
.item {
outline: none;
&:focus-visible:before {
outline: auto;

View file

@ -23,9 +23,9 @@ const extensions = [
];
export default defineConfig(({ command, mode }) => {
fs.mkdirSync(__dirname + "/../../built", { recursive: true });
fs.mkdirSync(`${__dirname}/../../built`, { recursive: true });
fs.writeFileSync(
__dirname + "/../../built/meta.json",
`${__dirname}/../../built/meta.json`,
JSON.stringify({ version: meta.version }),
"utf-8",
);
@ -40,15 +40,16 @@ export default defineConfig(({ command, mode }) => {
pluginJson5(),
viteCompression({
algorithm: "brotliCompress",
verbose: false,
}),
],
resolve: {
extensions,
alias: {
"@/": __dirname + "/src/",
"/client-assets/": __dirname + "/assets/",
"/static-assets/": __dirname + "/../backend/assets/",
"@/": `${__dirname}/src/`,
"/client-assets/": `${__dirname}/assets/`,
"/static-assets/": `${__dirname}/../backend/assets/`,
},
},
@ -82,7 +83,7 @@ export default defineConfig(({ command, mode }) => {
},
cssCodeSplit: true,
assetsInlineLimit: 0,
outDir: __dirname + "/../../built/_client_dist_",
outDir: `${__dirname}/../../built/_client_dist_`,
assetsDir: ".",
emptyOutDir: false,
sourcemap: process.env.NODE_ENV === "development",
@ -94,5 +95,7 @@ export default defineConfig(({ command, mode }) => {
optimizeDeps: {
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'))
return 'public';
return this.converter.visibility(res.data);
});
}).catch(_ => 'public')
}
public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> {

View file

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

View file

@ -86,6 +86,13 @@
"@irfan@calckey.social",
"@dvd@dvd.chat",
"@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"
]
}

File diff suppressed because it is too large Load diff