Compare commits

...

16 Commits

Author SHA1 Message Date
syuilo b83d07da28 New translations ja-JP.yml (Chinese Traditional) 2023-09-07 20:56:23 +09:00
syuilo 90b058e226 2023.9.0-beta.4 2023-09-07 17:01:05 +09:00
syuilo c2383fac16 Update CHANGELOG.md 2023-09-07 17:00:54 +09:00
syuilo b449f3e7a8
New Crowdin updates (#11790)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)
2023-09-07 16:58:41 +09:00
woxtu 32f5949935
chore: Goodbye gulp (#11447)
* Add a script for building assets

* Replace with script executions

* Remove gulp dependencies

* Fix dependencies

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-09-07 16:56:17 +09:00
anatawa12 4c3935bf80
chore(frontend): renote of note in sensitive channel is now home renote by default. (#11476)
* chore(frontend): renote of note in sensitive channel is now home renote by default.

* docs: センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-09-07 16:48:47 +09:00
syuilo dc1a91a5b2 fix(dev): use 20.5.1 instead of 20.x
Fix #11793
2023-09-07 16:38:47 +09:00
syuilo fd70a700f8 Revert "Revert "pnpm@8.7.4""
This reverts commit b1efc298f7.
2023-09-07 16:20:35 +09:00
syuilo d8dc10829c enhance(backend): 古いアンテナを自動停止するか切り替え可能に
Resolve #11785
2023-09-07 16:20:28 +09:00
syuilo b1efc298f7 Revert "pnpm@8.7.4"
This reverts commit 54c6fb762a.
2023-09-07 16:10:32 +09:00
syuilo 98462ccbaf Update pnpm-lock.yaml 2023-09-06 18:35:40 +09:00
syuilo ca00a08e6e feat: introduce aidx and make it default
Co-Authored-By: MeiMei <30769358+mei23@users.noreply.github.com>
2023-09-06 18:33:51 +09:00
syuilo 54c6fb762a pnpm@8.7.4 2023-09-06 18:21:51 +09:00
かっこかり 22d966e92d
enhance(frontend): データセーバーモードで隠れる画像を増やす等 (#11779)
* enhance datasaver mode

* サムネイルがないとき変な角丸にならんようにする

* Avoid using wildcard selector

* Avoid wildcard

* Update MkMediaImage.vue

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-09-06 17:36:14 +09:00
Sayamame-beans bf303238f0
feat(backend): Webhook設定でsecretを空に出来るように (#11784)
* feat(backend): allow webhook secret to be empty

* Update CHANGELOG.md
2023-09-06 13:02:33 +09:00
anatawa12 b0eae49eaa
feat(frontend): remove renote with by admin privilege (#11789) 2023-09-05 19:29:52 +09:00
40 changed files with 766 additions and 2421 deletions

View File

@ -114,6 +114,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@ -121,7 +122,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@ -125,6 +125,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@ -132,7 +133,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@ -114,6 +114,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@ -121,7 +122,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
id: 'aidx'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@ -12,4 +12,4 @@ db:
redis:
host: 127.0.0.1
port: 56312
id: aid
id: aidx

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [20.x]
node-version: [20.5.1]
services:
postgres:

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [20.x]
node-version: [20.5.1]
steps:
- uses: actions/checkout@v4.0.0
@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20.x]
node-version: [20.5.1]
browser: [chrome]
services:

View File

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20.x]
node-version: [20.5.1]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

View File

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [20.x]
node-version: [20.5.1]
steps:
- uses: actions/checkout@v4.0.0

View File

@ -21,6 +21,7 @@
- お知らせのバナー表示やダイアログ表示が可能に
- お知らせのアイコンを設定可能に
- チャンネルをセンシティブ指定できるようになりました
- センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました
- 二要素認証のバックアップコードが生成されるようになりました ref. https://github.com/MisskeyIO/misskey/pull/121
### Client
@ -35,6 +36,8 @@
- Enhance: ノート検索にローカルのみ検索可能なオプションの追加
- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように
- Enhance: Renote自体を通報できるように
- Enhance: データセーバーモードの強化
- Enhance: Renoteを管理者権限で削除可能に
- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
- Playの操作を行うAPI TokenをAPIコンソールから発行できるように
- Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正
@ -50,6 +53,8 @@
- cacheRemoteFilesの初期値はfalseになりました
- ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善
- Webhookのペイロードにサーバーのurlが含まれるようになりました
- Webhook設定でsecretを空に出来るように
- 使われていないアンテナの自動停止を設定可能に
- Fix: 一部のfeatured noteを照会できない問題を修正
- Fix: muteがapiからのuser list timeline取得で機能しない問題を修正
- Fix: ジョブキュー管理画面の認証を回避できる問題を修正

View File

@ -135,6 +135,7 @@ redis:
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@ -142,7 +143,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: "aid"
id: "aidx"
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@ -1,65 +0,0 @@
/**
* Gulp tasks
*/
import * as fs from 'node:fs';
import gulp from 'gulp';
import replace from 'gulp-replace';
import terser from 'gulp-terser';
import cssnano from 'gulp-cssnano';
import locales from './locales/index.js';
import meta from './package.json' assert { type: "json" };
gulp.task('copy:backend:views', () =>
gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views'))
);
gulp.task('copy:frontend:fonts', () =>
gulp.src('./packages/frontend/node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/_frontend_dist_/fonts/'))
);
gulp.task('copy:frontend:tabler-icons', () =>
gulp.src('./packages/frontend/node_modules/@tabler/icons-webfont/**/*').pipe(gulp.dest('./built/_frontend_dist_/tabler-icons/'))
);
gulp.task('copy:frontend:locales', cb => {
fs.mkdirSync('./built/_frontend_dist_/locales', { recursive: true });
const v = { '_version_': meta.version };
for (const [lang, locale] of Object.entries(locales)) {
fs.writeFileSync(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
}
cb();
});
gulp.task('build:backend:script', () => {
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(terser({
toplevel: true
}))
.pipe(gulp.dest('./packages/backend/built/server/web/'));
});
gulp.task('build:backend:style', () => {
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css'])
.pipe(cssnano({
zindex: false
}))
.pipe(gulp.dest('./packages/backend/built/server/web/'));
});
gulp.task('build', gulp.parallel(
'copy:frontend:locales', 'copy:backend:views', 'build:backend:script', 'build:backend:style', 'copy:frontend:fonts', 'copy:frontend:tabler-icons'
));
gulp.task('default', gulp.task('build'));
gulp.task('watch', () => {
gulp.watch([
'./packages/*/src/**/*',
], { ignoreInitial: false }, gulp.task('build'));
});

View File

@ -45,6 +45,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
@ -655,6 +656,7 @@ behavior: "Verhalten"
sample: "Beispiel"
abuseReports: "Meldungen"
reportAbuse: "Melden"
reportAbuseRenote: "Renote melden"
reportAbuseOf: "{name} melden"
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
abuseReported: "Deine Meldung wurde versendet. Vielen Dank."

View File

@ -45,6 +45,7 @@ pin: "Pin to profile"
unpin: "Unpin from profile"
copyContent: "Copy contents"
copyLink: "Copy link"
copyLinkRenote: "Copy renote link"
delete: "Delete"
deleteAndEdit: "Delete and edit"
deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it."
@ -655,6 +656,7 @@ behavior: "Behavior"
sample: "Sample"
abuseReports: "Reports"
reportAbuse: "Report"
reportAbuseRenote: "Report renote"
reportAbuseOf: "Report {name}"
fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL."
abuseReported: "Your report has been sent. Thank you very much."

View File

@ -45,6 +45,7 @@ pin: "Fijar al perfil"
unpin: "Desfijar"
copyContent: "Copiar contenido"
copyLink: "Copiar enlace"
copyLinkRenote: "Copiar enlace de renota"
delete: "Borrar"
deleteAndEdit: "Borrar y editar"
deleteAndEditConfirm: "¿Estás seguro de que quieres borrar esta nota y editarla? Perderás todas las reacciones, renotas y respuestas."
@ -655,6 +656,7 @@ behavior: "Comportamiento"
sample: "Muestra"
abuseReports: "Reportes"
reportAbuse: "Reportar"
reportAbuseRenote: "Reportar renota"
reportAbuseOf: "Reportar a {name}"
fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
abuseReported: "Se ha enviado el reporte. Muchas gracias."
@ -1754,6 +1756,10 @@ _permissions:
"write:gallery": "Editar galería"
"read:gallery-likes": "Ver favoritos de la galería"
"write:gallery-likes": "Editar favoritos de la galería"
"read:flash": "Ver Play"
"write:flash": "Editar Plays"
"read:flash-likes": "Ver los Play que me gustan"
"write:flash-likes": "Editar lista de Play que me gustan"
_auth:
shareAccessTitle: "Permisos de la aplicación"
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
@ -2024,6 +2030,8 @@ _deck:
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas columnas donde quieras."
widgetsIntroduction: "Por favor selecciona \"Editar Widgets\" en el menú columna y agrega un widget."
useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple"
usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada"
flexible: "Autoajustar ancho"
_columns:
main: "Principal"
widgets: "Widgets"

View File

@ -45,6 +45,7 @@ pin: "Fissa sul profilo"
unpin: "Non fissare sul profilo"
copyContent: "Copia il contenuto"
copyLink: "Copia il link"
copyLinkRenote: "Copia collegamento alla Rinota"
delete: "Elimina"
deleteAndEdit: "Elimina e modifica"
deleteAndEditConfirm: "Vuoi davvero cancellare questa nota e scriverla di nuovo? Verranno eliminate anche tutte le reazioni, rinote e risposte collegate."
@ -655,6 +656,7 @@ behavior: "Comportamento"
sample: "Esempio"
abuseReports: "Segnalazioni"
reportAbuse: "Segnala"
reportAbuseRenote: "Segnala la Rinota"
reportAbuseOf: "Segnala {name}"
fillAbuseReportDescription: "Per favore, spiegaci il motivo della segnalazione. Se riguarda una Nota precisa, indica anche l'indirizzo URL."
abuseReported: "La segnalazione è stata inviata. Grazie."
@ -2028,8 +2030,8 @@ _deck:
introduction2: "È possibile aggiungere colonne in qualsiasi momento premendo + sulla destra dello schermo."
widgetsIntroduction: "Dal menu della colonna, selezionare \"Modifica i riquadri\" per aggiungere un un riquadro con funzionalità"
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
usedAsMinWidthWhenFlexible: "Lunghezza minima sarò usata quando l'opzione \"Lunghezza automacia\" è attivata"
flexible: "Lunghezza automatica"
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
flexible: "Larghezza flessibile"
_columns:
main: "Principale"
widgets: "Riquadri"

View File

@ -45,6 +45,7 @@ pin: "Fixar no perfil"
unpin: "Desafixar do perfil"
copyContent: "Copiar conteúdos"
copyLink: "Copiar link"
copyLinkRenote: "Copiar o link da repostagem"
delete: "Excluir"
deleteAndEdit: "Excluir e editar"
deleteAndEditConfirm: "Deseja excluir esta nota e editá-la novamente? Todas as reações, compartilhamentos e respostas a esta nota também serão excluídas."
@ -654,6 +655,7 @@ behavior: "Comportamento"
sample: "Exemplo"
abuseReports: "Denúncias"
reportAbuse: "Denúncias"
reportAbuseRenote: "Reportar repostagem"
reportAbuseOf: "Denunciar {name}"
fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela."
abuseReported: "Denúncia enviada. Obrigado por sua ajuda."
@ -917,23 +919,42 @@ pleaseSelect: "Por favor, selecione."
reverse: "Inversão"
colored: "Colorido"
refreshInterval: "Intervalo de atualização"
type: "Tipo"
speed: "Velocidade"
slow: "Lento"
fast: "Rápido"
sensitiveMediaDetection: "Detecção de conteúdo sensível"
localOnly: "Apenas local"
remoteOnly: "Apenas remoto"
cannotUploadBecauseExceedsFileSizeLimit: "Não é possível realizar o upload deste arquivo porque ele excede o tamanho máximo permitido."
beta: "Beta"
enableAutoSensitive: "Marcar automaticamente como conteúdo sensível"
enableAutoSensitiveDescription: "Quando disponível, a marcação de mídia sensível será automaticamente atribuído ao conteúdo de mídia usando aprendizado de máquina. Mesmo que você desative essa função, em alguns servidores, isso pode ser configurado automaticamente."
activeEmailValidationDescription: "A validação do endereço de e-mail do usuário será realizada de forma mais rigorosa, considerando se é um endereço descartável ou se é possível realizar comunicação efetiva. Se desativado, apenas a validade do formato do endereço será verificada como uma sequência de caracteres."
shuffle: "Aleatório"
account: "Contas"
move: "Mover"
pushNotification: "Notificações Push"
subscribePushNotification: "Ativar notificações push"
unsubscribePushNotification: "Desativar notificações push"
windowMinimize: "Minimizar"
windowRestore: "Restaurar"
caption: "legenda"
tools: "Ferramentas"
like: "Curtir"
unlike: "Remover curtida"
numberOfLikes: "Número de curtidas"
show: "Visualizar"
neverShow: "Não exibir novamente"
remindMeLater: "Lembrar mais tarde"
didYouLikeMisskey: "Você gostou do Misskey?"
pleaseDonate: "O Misskey é um software gratuito utilizado por {host}. Para que possamos continuar o desenvolvimento, pedimos que considerem fazer doações. A sua contribuição é muito importante!"
roles: "Cargos"
role: "Cargo"
noRole: "Nenhum cargo"
normalUser: "Usuários padrão"
undefined: "Indefinido"
assign: "Atribuir"
unassign: "Remover"
color: "Cor"
manageCustomEmojis: "Gerenciar Emojis customizados"
@ -953,7 +974,7 @@ thisPostMayBeAnnoying: "Esta nota pode incomodar outras pessoas."
thisPostMayBeAnnoyingHome: "Postar na linha do tempo inicial"
thisPostMayBeAnnoyingCancel: "Cancelar"
thisPostMayBeAnnoyingIgnore: "Postar mesmo assim"
collapseRenotes: "Ocultar Renotes já visualizadas"
collapseRenotes: "Ocultar repostagens já visualizadas"
internalServerError: "Erro interno de servidor"
emailNotSupported: "O envio de e-mails não é suportado nesta instância"
likeOnly: "Apenas curtidas"
@ -963,8 +984,19 @@ rolesAssignedToMe: "Cargos atribuídos a mim"
unfavoriteConfirm: "Deseja realmente remover dos favoritos?"
drivecleaner: "Limpeza do drive"
retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?"
reactionsList: "Reações"
renotesList: "Repostagens"
leftTop: "Superior esquerdo"
rightTop: "Superior direito"
leftBottom: "Inferior esquerdo"
rightBottom: "Inferior direito"
vertical: "Vertical"
horizontal: "Exibir painel lateral inteiro"
position: "Posição"
serverRules: "Regras do servidor"
continue: "Continuar"
preservedUsernamesDescription: "Liste os nomes de usuário que deseja reservar, separando-os por quebras de linha. Os nomes de usuário especificados aqui não poderão ser utilizados durante a criação de contas. No entanto, esta restrição não se aplica quando a conta é criada por um administrador. Além disso, as contas que já existem não serão afetadas."
archive: "Arquivo"
channelArchiveConfirmTitle: "Deseja realmente arquivar {name}?"
youFollowing: "Seguindo"
preventAiLearningDescription: "Solicita-se que o conteúdo de notas e imagens enviadas não seja usado como objeto de aprendizado por sistemas externos de geração de texto ou imagens. Isso é alcançado incluindo a flag 'noai' na resposta HTML. No entanto, o cumprimento dessa solicitação depende do próprio sistema de IA, portanto, não é garantia total de prevenção de aprendizado."
@ -1268,6 +1300,8 @@ _menuDisplay:
sideFull: "Exibir painel lateral inteiro"
top: "Exibir barra superior"
hide: "Ocultar"
_instanceMute:
instanceMuteDescription: "Todas as notas e repostagens do servidor configurado serão silenciados, incluindo respostas aos usuários do servidor mutado."
_theme:
description: "Descrição"
alpha: "Opacidade"
@ -1402,6 +1436,7 @@ _notification:
youGotMention: "{name} te mencionou"
youGotReply: "{name} te respondeu"
youGotQuote: "{name} te citou"
youRenoted: "Repostagens de {name}"
youWereFollowed: "Você tem um novo seguidor"
youReceivedFollowRequest: "Você recebeu um pedido de seguidor"
yourFollowRequestAccepted: "Seu pedido de seguidor foi aceito"
@ -1454,3 +1489,4 @@ _webhookSettings:
_events:
follow: "Quando seguindo um usuário"
followed: "Quando sendo seguido"
renote: "Quando repostado"

View File

@ -45,6 +45,7 @@ pin: "置顶"
unpin: "取消置顶"
copyContent: "复制内容"
copyLink: "复制链接"
copyLinkRenote: "复制转帖链接"
delete: "删除"
deleteAndEdit: "删除并编辑"
deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。"
@ -655,6 +656,7 @@ behavior: "行为"
sample: "示例"
abuseReports: "举报"
reportAbuse: "举报"
reportAbuseRenote: "举报转帖"
reportAbuseOf: "举报 {name}"
fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写 URL 地址。"
abuseReported: "内容已发送。感谢您提交信息。"

View File

@ -45,6 +45,7 @@ pin: "置頂"
unpin: "取消置頂"
copyContent: "複製內容"
copyLink: "複製連結"
copyLinkRenote: "複製轉貼連結"
delete: "刪除"
deleteAndEdit: "刪除並編輯"
deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應、轉發和回覆也將會消失。"
@ -655,6 +656,7 @@ behavior: "行為"
sample: "範例"
abuseReports: "檢舉"
reportAbuse: "檢舉"
reportAbuseRenote: "檢舉轉貼"
reportAbuseOf: "檢舉{name}"
fillAbuseReportDescription: "請填寫檢舉的詳細理由。如有需要,請附上相關 URL。"
abuseReported: "檢舉完成。感謝您的報告。"

View File

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "2023.9.0-beta.3",
"version": "2023.9.0-beta.4",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@8.7.1",
"packageManager": "pnpm@8.7.4",
"workspaces": [
"packages/frontend",
"packages/backend",
@ -15,7 +15,8 @@
"private": true,
"scripts": {
"build-pre": "node ./scripts/build-pre.js",
"build": "pnpm build-pre && pnpm -r build && pnpm gulp",
"build-assets": "node ./scripts/build-assets.mjs",
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
@ -23,7 +24,6 @@
"migrate": "cd packages/backend && pnpm migrate",
"check:connect": "cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"gulp": "pnpm exec gulp build",
"watch": "pnpm dev",
"dev": "node ./scripts/dev.mjs",
"lint": "pnpm -r lint",
@ -34,7 +34,6 @@
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm -r test",
"test-and-coverage": "pnpm -r test-and-coverage",
"format": "pnpm exec gulp format",
"clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js",
"cleanall": "pnpm clean-all"
@ -45,17 +44,13 @@
},
"dependencies": {
"execa": "8.0.1",
"gulp": "4.0.2",
"gulp-cssnano": "2.1.3",
"gulp-rename": "2.0.0",
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"cssnano": "6.0.1",
"js-yaml": "4.1.0",
"postcss": "8.4.27",
"terser": "5.19.2",
"typescript": "5.2.2"
},
"devDependencies": {
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@typescript-eslint/eslint-plugin": "6.6.0",
"@typescript-eslint/parser": "6.6.0",
"cross-env": "7.0.3",

View File

@ -120,6 +120,7 @@
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"ms": "3.0.0-canary.1",
"nanoid": "4.0.2",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
"nodemailer": "6.9.4",

View File

@ -88,6 +88,7 @@ type Source = {
perChannelMaxNoteCacheCount?: number;
perUserNotificationsMaxCount?: number;
deactivateAntennaThreshold?: number;
};
export type Config = {
@ -161,6 +162,7 @@ export type Config = {
redisForJobQueue: RedisOptions & RedisOptionsSource;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
};
const _filename = fileURLToPath(import.meta.url);
@ -252,6 +254,7 @@ export function loadConfig(): Config {
clientManifestExists: clientManifestExists,
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300,
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
};
}

View File

@ -8,6 +8,7 @@ import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { genAid, parseAid } from '@/misc/id/aid.js';
import { genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
@ -31,6 +32,7 @@ export class IdService {
switch (this.method) {
case 'aid': return genAid(date);
case 'aidx': return genAidx(date);
case 'meid': return genMeid(date);
case 'meidg': return genMeidg(date);
case 'ulid': return ulid(date.getTime());
@ -43,6 +45,7 @@ export class IdService {
public parse(id: string): { date: Date; } {
switch (this.method) {
case 'aid': return parseAid(id);
case 'aidx': return parseAidx(id);
case 'objectid': return parseObjectId(id);
case 'meid': return parseMeid(id);
case 'meidg': return parseMeidg(id);

View File

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
// AIDX
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ4の[個体ID] + 長さ4の[カウンタ]
// (c) mei23
// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
import { customAlphabet } from 'nanoid';
export const aidxRegExp = /^[0-9a-z]{16}$/;
const TIME2000 = 946684800000;
const TIME_LENGTH = 8;
const NODE_LENGTH = 4;
const NOISE_LENGTH = 4;
const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)();
let counter = 0;
function getTime(time: number): string {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36).padStart(TIME_LENGTH, '0').slice(-TIME_LENGTH);
}
function getNoise(): string {
return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH);
}
export function genAidx(date: Date): string {
const t = date.getTime();
if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date');
counter++;
return getTime(t) + nodeId + getNoise();
}
export function parseAidx(id: string): { date: Date; } {
const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000;
return { date: new Date(time) };
}

View File

@ -10,6 +10,7 @@ import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepositor
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import type { Config } from '@/config.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@ -18,6 +19,9 @@ export class CleanProcessorService {
private logger: Logger;
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.userIpsRepository)
private userIpsRepository: UserIpsRepository,
@ -54,12 +58,14 @@ export class CleanProcessorService {
reason: 'word',
});
// 7日以上使われてないアンテナを停止
this.antennasRepository.update({
lastUsedAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 7))),
}, {
isActive: false,
});
// 使われてないアンテナを停止
if (this.config.deactivateAntennaThreshold > 0) {
this.antennasRepository.update({
lastUsedAt: LessThan(new Date(Date.now() - this.config.deactivateAntennaThreshold)),
}, {
isActive: false,
});
}
const expiredRoleAssignments = await this.roleAssignmentsRepository.createQueryBuilder('assign')
.where('assign.expiresAt IS NOT NULL')

View File

@ -34,12 +34,12 @@ export const paramDef = {
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
url: { type: 'string', minLength: 1, maxLength: 1024 },
secret: { type: 'string', minLength: 1, maxLength: 1024 },
secret: { type: 'string', maxLength: 1024, default: '' },
on: { type: 'array', items: {
type: 'string', enum: webhookEventTypes,
} },
},
required: ['name', 'url', 'secret', 'on'],
required: ['name', 'url', 'on'],
} as const;
// TODO: ロジックをサービスに切り出す

View File

@ -34,13 +34,13 @@ export const paramDef = {
webhookId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 },
url: { type: 'string', minLength: 1, maxLength: 1024 },
secret: { type: 'string', minLength: 1, maxLength: 1024 },
secret: { type: 'string', maxLength: 1024, default: '' },
on: { type: 'array', items: {
type: 'string', enum: webhookEventTypes,
} },
active: { type: 'boolean' },
},
required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'],
required: ['webhookId', 'name', 'url', 'on', 'active'],
} as const;
// TODO: ロジックをサービスに切り出す

View File

@ -12,7 +12,7 @@ import { GlobalModule } from '@/GlobalModule.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { genAid } from '@/misc/id/aid.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -33,7 +33,7 @@ describe('AnnouncementService', () => {
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
return usersRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
username: un,
usernameLower: un,
@ -44,7 +44,7 @@ describe('AnnouncementService', () => {
function createAnnouncement(data: Partial<MiAnnouncement> = {}) {
return announcementsRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
updatedAt: null,
title: 'Title',

View File

@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { genAid } from '@/misc/id/aid.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -37,7 +37,7 @@ describe('RoleService', () => {
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
return usersRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
username: un,
usernameLower: un,
@ -48,7 +48,7 @@ describe('RoleService', () => {
function createRole(data: Partial<MiRole> = {}) {
return rolesRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
updatedAt: new Date(),
lastUsedAt: new Date(),

View File

@ -6,6 +6,7 @@
import { ulid } from 'ulid';
import { describe, test, expect } from '@jest/globals';
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
@ -19,6 +20,13 @@ describe('misc:id', () => {
expect(parseAid(gotAid).date.getTime()).toBe(date.getTime());
});
test('aidx', () => {
const date = new Date();
const gotAidx = genAidx(date);
expect(gotAidx).toMatch(aidxRegExp);
expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime());
});
test('meid', () => {
const date = new Date();
const gotMeid = genMeid(date);

View File

@ -98,8 +98,6 @@
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
"@types/estree": "1.0.1",
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/matter-js": "0.19.0",
"@types/micromatch": "4.0.2",
"@types/node": "20.5.9",

View File

@ -4,34 +4,41 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick">
<a
:class="$style.imageContainer"
:href="image.url"
:title="image.name"
<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click.stop="onclick">
<component
:is="disableImageLink ? 'div' : 'a'"
v-bind="disableImageLink ? {
title: image.name,
class: $style.imageContainer,
} : {
title: image.name,
class: $style.imageContainer,
href: image.url,
style: 'cursor: zoom-in;'
}"
>
<ImgWithBlurhash
:hash="image.blurhash"
:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url"
:forceBlurhash="hide"
:cover="hide"
:cover="hide || cover"
:alt="image.comment || image.name"
:title="image.comment || image.name"
:width="image.properties.width"
:height="image.properties.height"
:style="hide ? 'filter: brightness(0.5);' : null"
/>
</a>
</component>
<template v-if="hide">
<div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper">
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
<span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
</div>
</template>
<template v-else>
<template v-else-if="controls">
<div :class="$style.indicators">
<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
<div v-if="image.comment" :class="$style.indicator">ALT</div>
@ -54,10 +61,17 @@ import { i18n } from '@/i18n';
import * as os from '@/os';
import { iAmModerator } from '@/account';
const props = defineProps<{
const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile;
raw?: boolean;
}>();
cover?: boolean;
disableImageLink?: boolean;
controls?: boolean;
}>(), {
cover: false,
disableImageLink: false,
controls: true,
});
let hide = $ref(true);
let darkMode: boolean = $ref(defaultStore.state.darkMode);
@ -70,6 +84,9 @@ const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
);
function onclick() {
if (!props.controls) {
return;
}
if (hide) {
hide = false;
}
@ -167,7 +184,6 @@ function showMenu(ev: MouseEvent) {
.imageContainer {
display: block;
cursor: zoom-in;
overflow: hidden;
width: 100%;
height: 100%;

View File

@ -319,9 +319,15 @@ function renote(viaKeyboard = false) {
const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
let visibility = appearNote.visibility;
visibility = smallerVisibility(visibility, configuredVisibility);
if (appearNote.channel?.isSensitive) {
visibility = smallerVisibility(visibility, 'home');
}
os.api('notes/create', {
localOnly,
visibility: smallerVisibility(appearNote.visibility, configuredVisibility),
visibility,
renoteId: appearNote.id,
}).then(() => {
os.toast(i18n.ts.renoted);
@ -425,22 +431,26 @@ async function clip() {
}
function showRenoteMenu(viaKeyboard = false): void {
function getUnrenote(): MenuItem {
return {
text: i18n.ts.unrenote,
icon: 'ti ti-trash',
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
});
isDeleted.value = true;
},
};
}
if (isMyRenote) {
pleaseLogin();
os.popupMenu([
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
null,
{
text: i18n.ts.unrenote,
icon: 'ti ti-trash',
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
});
isDeleted.value = true;
},
},
getUnrenote(),
], renoteTime.value, {
viaKeyboard: viaKeyboard,
});
@ -449,6 +459,7 @@ function showRenoteMenu(viaKeyboard = false): void {
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
null,
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
], renoteTime.value, {
viaKeyboard: viaKeyboard,
});

View File

@ -5,7 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<div v-if="page.eyeCatchingImage" class="thumbnail" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<div v-if="page.eyeCatchingImage" class="thumbnail">
<MediaImage
:image="page.eyeCatchingImage"
:disableImageLink="true"
:controls="false"
:cover="true"
:class="$style.eyeCatchingImageRoot"
/>
</div>
<article>
<header>
<h1 :title="page.title">{{ page.title }}</h1>
@ -23,12 +31,22 @@ SPDX-License-Identifier: AGPL-3.0-only
import { } from 'vue';
import * as Misskey from 'misskey-js';
import { userName } from '@/filters/user';
import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{
page: Misskey.entities.Page;
}>();
</script>
<style module>
.eyeCatchingImageRoot {
width: 100%;
height: 200px;
border-radius: var(--radius) var(--radius) 0 0;
overflow: hidden;
}
</style>
<style lang="scss" scoped>
.vhpxefrj {
display: block;
@ -39,32 +57,15 @@ const props = defineProps<{
}
> .thumbnail {
width: 100%;
height: 200px;
background-position: center;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
> button {
font-size: 3.5em;
opacity: 0.7;
&:hover {
font-size: 4em;
opacity: 0.9;
}
}
& + article {
left: 100px;
width: calc(100% - 100px);
border-radius: 0 0 var(--radius) var(--radius);
}
}
> article {
background-color: var(--panel);
padding: 16px;
border-radius: var(--radius);
> header {
margin-bottom: 8px;

View File

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div v-else>
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`">
<div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.enableDataSaverMode ? '' : `background-image: url('${thumbnail}')`">
</div>
<article :class="$style.body">
<header :class="$style.header">
@ -260,6 +260,7 @@ onUnmounted(() => {
height: 100%;
background-position: center;
background-size: cover;
background-color: var(--bg);
display: flex;
justify-content: center;
align-items: center;

View File

@ -5,20 +5,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<ImgWithBlurhash v-if="image" style="max-width: 100%;" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :width="image.properties.width" :height="image.properties.height" :cover="false"/>
<MediaImage
v-if="image"
:image="image"
:disableImageLink="true"
/>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { ImageBlock } from './block.type';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{
block: ImageBlock,
page: Misskey.entities.Page,
}>();
const image = props.page.attachedFiles.find(x => x.id === props.block.fileId);
const image = ref<Misskey.entities.DriveFile>(props.page.attachedFiles.find(x => x.id === props.block.fileId));
</script>

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }">
<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }" class="_gaps_s">
<XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/>
</div>
</template>

View File

@ -16,7 +16,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
-->
<div class="banner">
<img v-if="page.eyeCatchingImageId" :src="page.eyeCatchingImage.url"/>
<MkMediaImage
v-if="page.eyeCatchingImageId"
:image="page.eyeCatchingImage"
:cover="true"
:disableImageLink="true"
class="thumbnail"
/>
</div>
<div class="content">
<XPage :page="page"/>
@ -74,6 +80,7 @@ import XPage from '@/components/page/page.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { url } from '@/config';
import MkMediaImage from '@/components/MkMediaImage.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
import MkContainer from '@/components/MkContainer.vue';
import MkPagination from '@/components/MkPagination.vue';
@ -204,11 +211,14 @@ definePageMetadata(computed(() => page ? {
}
> .banner {
> img {
> .thumbnail {
// TODO:
display: block;
width: 100%;
height: 150px;
height: auto;
aspect-ratio: 3/1;
border-radius: var(--radius);
overflow: hidden;
object-fit: cover;
}
}

File diff suppressed because it is too large Load Diff

87
scripts/build-assets.mjs Normal file
View File

@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import cssnano from 'cssnano';
import postcss from 'postcss';
import * as terser from 'terser';
import locales from '../locales/index.js';
import meta from '../package.json' assert { type: "json" };
async function copyFrontendFonts() {
await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
}
async function copyFrontendTablerIcons() {
await fs.cp('./packages/frontend/node_modules/@tabler/icons-webfont', './built/_frontend_dist_/tabler-icons', { dereference: true, recursive: true });
}
async function copyFrontendLocales() {
await fs.mkdir('./built/_frontend_dist_/locales', { recursive: true });
const v = { '_version_': meta.version };
for (const [lang, locale] of Object.entries(locales)) {
await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
}
}
async function copyBackendViews() {
await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true });
}
async function buildBackendScript() {
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
for (const file of [
'./packages/backend/src/server/web/boot.js',
'./packages/backend/src/server/web/bios.js',
'./packages/backend/src/server/web/cli.js'
]) {
let source = await fs.readFile(file, { encoding: 'utf-8' });
source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
const { code } = await terser.minify(source, { toplevel: true });
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code);
}
}
async function buildBackendStyle() {
await fs.mkdir('./packages/backend/built/server/web', { recursive: true });
for (const file of [
'./packages/backend/src/server/web/style.css',
'./packages/backend/src/server/web/bios.css',
'./packages/backend/src/server/web/cli.css',
'./packages/backend/src/server/web/error.css'
]) {
const source = await fs.readFile(file, { encoding: 'utf-8' });
const { css } = await postcss([cssnano({ zindex: false })]).process(source, { from: undefined });
await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, css);
}
}
async function build() {
await Promise.all([
copyFrontendFonts(),
copyFrontendTablerIcons(),
copyFrontendLocales(),
copyBackendViews(),
buildBackendScript(),
buildBackendStyle(),
]);
}
await build();
if (process.argv.includes("--watch")) {
const watcher = fs.watch('./packages', { recursive: true });
for await (const event of watcher) {
if (/^[a-z]+\/src/.test(event.filename)) {
await build();
}
}
}

View File

@ -23,7 +23,13 @@ await execa('pnpm', ['build-pre'], {
stderr: process.stderr,
});
execa('pnpm', ['exec', 'gulp', 'watch'], {
await execa('pnpm', ['build-assets'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
});
execa('pnpm', ['build-assets', '--watch'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,