Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
2023-01-16 17:14:02 +09:00
103 changed files with 14535 additions and 19731 deletions

View File

@ -0,0 +1,18 @@
name: Check copyright year
on:
push:
branches:
- master
- develop
jobs:
check_copyright_year:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.2.0
- run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!"
exit 1
fi

View File

@ -8,22 +8,26 @@ on:
pull_request:
jobs:
yarn_install:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- uses: actions/setup-node@v3.6.0
with:
node-version: 18.x
cache: 'yarn'
cache: 'pnpm'
- run: corepack enable
- run: yarn install --immutable
- run: pnpm i --frozen-lockfile
lint:
needs: [yarn_install]
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
@ -37,10 +41,14 @@ jobs:
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- uses: actions/setup-node@v3.6.0
with:
node-version: 18.x
cache: 'yarn'
cache: 'pnpm'
- run: corepack enable
- run: yarn install --immutable
- run: yarn workspace ${{ matrix.workspace }} run lint
- run: pnpm i --frozen-lockfile
- run: pnpm --filter ${{ matrix.workspace }} run lint

View File

@ -23,7 +23,6 @@ jobs:
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
YARN_CHECKSUM_BEHAVIOR: update
redis:
image: redis:6
ports:
@ -33,21 +32,26 @@ jobs:
- uses: actions/checkout@v3.3.0
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache: 'pnpm'
- run: corepack enable
- run: yarn install --immutable
- name: Check yarn.lock
run: git diff --exit-code yarn.lock
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: yarn build
run: pnpm build
- name: Test
run: yarn jest-and-coverage
run: pnpm jest-and-coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
@ -86,19 +90,22 @@ jobs:
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache: 'pnpm'
- run: corepack enable
- run: yarn install --immutable
env:
YARN_CHECKSUM_BEHAVIOR: update
- run: pnpm i --frozen-lockfile
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: yarn build
run: pnpm build
# https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091
- name: ALSA Env
run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
@ -106,7 +113,7 @@ jobs:
uses: cypress-io/github-action@v4
with:
install: false
start: yarn start:test
start: pnpm start:test
wait-on: 'http://localhost:61812'
headless: false
browser: ${{ matrix.browser }}

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"search.exclude": {
"**/node_modules": true
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
network-timeout 600000

View File

@ -1,42 +0,0 @@
httpTimeout: 600000
nmHoistingLimits: none
nodeLinker: pnpm
packageExtensions:
"@bull-board/api@*":
peerDependencies:
"@bull-board/ui": "*"
"@tensorflow/tfjs@*":
dependencies:
long: "*"
chartjs-adapter-date-fns@*:
peerDependencies:
date-fns: "*"
consolidate@*:
dependencies:
ejs: "*"
# these are needed to extend fastify types
"@fastify/accepts@*":
peerDependencies:
fastify: "*"
"@fastify/cookie@*":
peerDependencies:
fastify: "*"
"@fastify/static@*":
peerDependencies:
fastify: "*"
"@fastify/view@*":
peerDependencies:
fastify: "*"
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
progressBarStyle: patrick
yarnPath: .yarn/releases/yarn-3.3.0.cjs

View File

@ -9,7 +9,7 @@
You should also include the user name that made the change.
-->
## 13.0.0 (unreleased)
## 13.0.0 (2023/01/16)
### TL;DR
- New features (Role system, Misskey Play, New widgets, New charts, 🍪👈, etc)
@ -20,7 +20,7 @@ You should also include the user name that made the change.
### Notable features
- ロール機能
- 従来より柔軟にユーザーの権限を管理できます。例えば、「インスタンスのパトロンはアンテナを30個まで作れる」「基本的にLTLは見れないが、許可した人だけ見れる」「招待制インスタンスだけどユーザーなら誰でも他者を招待できる」のような運用はもちろん、「ローカルユーザーかつアカウント作成から1日未満のユーザーはパブリックな投稿を行えない」のように複数条件を組み合わせて、自動でロールを付与する設定も可能です。
- 従来より柔軟にユーザーのポリシーを管理できます。例えば、「インスタンスのパトロンはアンテナを30個まで作れる」「基本的にLTLは見れないが、許可した人だけ見れる」「招待制インスタンスだけどユーザーなら誰でも他者を招待できる」のような運用はもちろん、「ローカルユーザーかつアカウント作成から1日未満のユーザーはパブリックな投稿を行えない」のように複数条件を組み合わせて、自動でロールを付与する設定も可能です。
- Misskey Play
- 従来の動的なPagesに代わる、新しいプラットフォームです。動的なコンテンツ(アプリケーション)に特化していて、Pagesに比べてはるかに柔軟なアプリケーションを作成可能です。
@ -31,8 +31,7 @@ You should also include the user name that made the change.
- Misskey not using 15 specific features at 13.0.0, but may do so in the future.
- Elasticsearchのサポートが削除されました
- 代わりに今後任意の検索プロバイダを設定できる仕組みを構想しています。その仕組みを使えば今まで通りElasticsearchも利用できます
- Migrate to Yarn Berry (v3.2.1) @ThatOneCalculator
- You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic
- Yarnからpnpmに移行されました
- インスタンスブロックはサブドメインにも適用されるようになります
- ロールの導入に伴い、いくつかの機能がロールと統合されました
- モデレーターはロールに統合されました。今までのモデレーター情報は失われるため、予めモデレーター一覧を記録しておき、アップデート後にモデレーターロールを作りアサインし直してください。
@ -64,6 +63,7 @@ You should also include the user name that made the change.
- API: `user`および`note`エンティティに`emojis`プロパティが含まれなくなりました
- API: `user`エンティティに`avatarColor`および`bannerColor`プロパティが含まれなくなりました
- API: `instance`エンティティに`latestStatus``lastCommunicatedAt``latestRequestSentAt`プロパティが含まれなくなりました
- API: `instance`エンティティの`caughtAt``firstRetrievedAt`に名前が変わりました
### Improvements
- Role system @syuilo
@ -88,6 +88,7 @@ You should also include the user name that made the change.
- Server: Judge instance block by endsWith @tamaina
- Server: improve note scoring for featured notes @CyberRex0
- Server: アンケート選択肢の文字数制限を緩和 @syuilo
- Server: プロフィールの文字数制限を緩和 @syuilo
- Server: add rate limits for some endpoints @syuilo
- Server: improve stats api performance @syuilo
- Server: improve nodeinfo performance @syuilo
@ -122,6 +123,7 @@ You should also include the user name that made the change.
- Client: add new mfm function (position, fg, bg) @syuilo
- Client: show fireworks when visit user who today is birthday @syuilo
- Client: show bot warning on screen when logged in as bot account @syuilo
- Client: AiScriptからカスタム絵文字一覧を参照できるように @syuilo
- Client: improve overall performance of client @syuilo
- Client: ui tweaks @syuilo
- Client: clicker game @syuilo

View File

@ -2,27 +2,27 @@ ARG NODE_VERSION=18.13.0-bullseye
FROM node:${NODE_VERSION} AS builder
ARG NODE_ENV=production
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential
WORKDIR /misskey
COPY [".yarnrc.yml", "package.json", "yarn.lock", "./"]
COPY [".yarn", "./.yarn"]
COPY ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY ["scripts", "./scripts"]
COPY ["packages/backend/package.json", "./packages/backend/"]
COPY ["packages/frontend/package.json", "./packages/frontend/"]
COPY ["packages/sw/package.json", "./packages/sw/"]
RUN yarn install --immutable
RUN npm i -g pnpm
RUN pnpm i --frozen-lockfile
COPY . ./
ARG NODE_ENV=production
RUN git submodule update --init
RUN yarn build
RUN pnpm build
FROM node:${NODE_VERSION}-slim AS runner
@ -37,17 +37,18 @@ RUN apt-get update \
&& groupadd -g "${GID}" misskey \
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey
RUN npm i -g pnpm
USER misskey
WORKDIR /misskey
COPY --chown=misskey:misskey --from=builder /misskey/.yarn/install-state.gz ./.yarn/install-state.gz
COPY --chown=misskey:misskey --from=builder /misskey/node_modules ./node_modules
COPY --chown=misskey:misskey --from=builder /misskey/built ./built
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/built ./packages/backend/built
COPY --chown=misskey:misskey --from=builder /misskey/packages/frontend/node_modules ./packages/frontend/node_modules
COPY --chown=misskey:misskey --from=builder /misskey/fluent-emojis /misskey/fluent-emojis
COPY --chown=misskey:misskey . ./
ENV NODE_ENV=production
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["yarn", "run", "migrateandstart"]
CMD ["pnpm", "run", "migrateandstart"]

View File

@ -818,6 +818,12 @@ cannotLoad: "تعذر التحميل"
like: "أعجبني"
show: "المظهر"
color: "اللون"
_role:
priority: "الأولوية"
_priority:
low: "منخفضة"
middle: "متوسط"
high: "عالية"
_emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة"
@ -841,6 +847,7 @@ _accountDelete:
_ad:
back: "رجوع"
reduceFrequencyOfThisAd: "قلل عرض هذا الإعلان"
hide: "لا تظهره بتاتًا"
_forgotPassword:
enterEmail: "أدخل البريد الإلكتروني المرتبط بحسابك لكي يرسل إليك رابط لإعادة تعيين كلمة المرور."
ifNoEmail: "إذا لم تربط حسابك ببريد إلكتروني سيتوجب عليك التواصل مع مدير الموقع."

View File

@ -854,6 +854,12 @@ account: "অ্যাকাউন্টগুলি"
like: "পছন্দ করা"
show: "প্রদর্শন"
color: "রং"
_role:
priority: "অগ্রাধিকার"
_priority:
low: "নিম্ন"
middle: "মাঝারি"
high: "উচ্চ"
_emailUnavailable:
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
@ -878,6 +884,7 @@ _accountDelete:
_ad:
back: "পিছনে"
reduceFrequencyOfThisAd: "এই বিজ্ঞাপনটি কম দেখান"
hide: "দেখাবেন না"
_forgotPassword:
enterEmail: "আপনি আপনার অ্যাকাউন্টের জন্য নিবন্ধিত ইমেল ঠিকানা লিখুন. সেই ঠিকানায় একটি পাসওয়ার্ড রিসেট লিঙ্ক পাঠানো হবে।"
ifNoEmail: "আপনি যদি নিবন্ধনের সময় ই-মেইল ঠিকানা না দিয়ে থাকেন, তাহলে অনুগ্রহ করে প্রশাসকের সাথে যোগাযোগ করুন।"

View File

@ -612,6 +612,12 @@ fast: "Rychlá"
account: "Účty"
show: "Zobrazit"
color: "Barva"
_role:
priority: "Priorita"
_priority:
low: "Nízká"
middle: "Střední"
high: "Vysoká"
_ad:
back: "Zpět"
_gallery:

View File

@ -933,6 +933,8 @@ unassign: "Entfernen"
color: "Farbe"
manageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
youCannotCreateAnymore: "Du hast das Erstellungslimit erreicht."
cannotPerformTemporary: "Vorübergehend nicht verfügbar"
cannotPerformTemporaryDescription: "Diese Aktion ist wegen des Überschreitenes des Ausführungslimits temporär nicht verfügbar. Bitte versuche es nach einiger Zeit erneut."
_role:
new: "Rolle erstellen"
edit: "Rolle bearbeiten"
@ -949,11 +951,17 @@ _role:
isPublic: "Öffentliche Rolle"
descriptionOfIsPublic: "Ist dies aktiviert, so kann jeder die Liste der Benutzer, die dieser Rolle zugewiesen sind, einsehen. Zusätzlich wird diese Rolle im Profil zugewiesener Benutzer angezeigt."
options: "Optionen"
policies: "Richtlinien"
baseRole: "Rollenvorlage"
useBaseValue: "Wert der Rollenvorlage verwenden"
chooseRoleToAssign: "Zuzuweisende Rolle auswählen"
canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen"
descriptionOfCanEditMembersByModerator: "Wenn aktiviert, so können Moderatoren und Adminstratoren anderen Benutzern diese Rolle zuweisen bzw. diese Zuweisung aufheben. Wenn deaktiviert, so ist es nur Administratoren möglich, Zuweisungen dieser Rolle zu verwalten."
priority: "Priorität"
_priority:
low: "Niedrig"
middle: "Mittel"
high: "Hoch"
_options:
gtlAvailable: "Kann auf die globale Chronik zugreifen"
ltlAvailable: "Kann auf die lokale Chronik zugreifen"
@ -969,6 +977,8 @@ _role:
noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips"
userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
userEachUserListsMax: "Maximale Anzahl an Benutzerlisten"
rateLimitFactor: "Versuchsanzahl"
descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
_condition:
isLocal: "Lokaler Benutzer"
isRemote: "Benutzer fremder Instanz"
@ -1013,6 +1023,7 @@ _accountDelete:
_ad:
back: "Zurück"
reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
hide: "Nie anzeigen"
_forgotPassword:
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator."

View File

@ -933,6 +933,10 @@ unassign: "Unassign"
color: "Color"
manageCustomEmojis: "Manage Custom Emojis"
youCannotCreateAnymore: "You've hit the creation limit."
cannotPerformTemporary: "Temporarily unavailable"
cannotPerformTemporaryDescription: "This action cannot be performed temporarily due to exceeding the execution limit. Please wait for a while and then try again."
preset: "Presets"
selectFromPresets: "Choose from presets"
_role:
new: "New role"
edit: "Edit role"
@ -949,11 +953,17 @@ _role:
isPublic: "Public role"
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
options: "Role options"
policies: "Policies"
baseRole: "Base role"
useBaseValue: "Use base role value"
chooseRoleToAssign: "Select the role to assign"
canEditMembersByModerator: "Allow moderators to edit the list members of this role"
descriptionOfCanEditMembersByModerator: "When turned on, moderators as well as administrators will be able to assign and unassign users to this role. When turned off, only administrators will be able to assign users."
priority: "Priority"
_priority:
low: "Low"
middle: "Medium"
high: "High"
_options:
gtlAvailable: "Viewing the global timeline"
ltlAvailable: "Viewing the local timeline"
@ -969,6 +979,9 @@ _role:
noteEachClipsMax: "Maximum number of notes within a clip"
userListMax: "Maximum number of user lists"
userEachUserListsMax: "Maximum number of users within a user list"
rateLimitFactor: "Rate limit"
descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. "
canHideAds: "Remove ads"
_condition:
isLocal: "Local user"
isRemote: "Remote user"
@ -1013,6 +1026,7 @@ _accountDelete:
_ad:
back: "Back"
reduceFrequencyOfThisAd: "Show this ad less"
hide: "Never show"
_forgotPassword:
enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it."
ifNoEmail: "If you did not use an email during registration, please contact the instance administrator instead."

View File

@ -919,6 +919,12 @@ numberOfProfileView: "Número de vistas de perfil"
like: "¡Muy bien!"
show: "Apariencia"
color: "Color"
_role:
priority: "Prioridad"
_priority:
low: "Baja"
middle: "Mediano"
high: "Alta"
_sensitiveMediaDetection:
description: "Reduce el esfuerzo de la moderación el el servidor a través del reconocimiento automático de contenido NSFW usando 'Machine Learning'. Esto puede incrementar ligeramente la carga en el servidor."
sensitivity: "Sensibilidad de detección"
@ -951,6 +957,7 @@ _accountDelete:
_ad:
back: "Deseleccionar"
reduceFrequencyOfThisAd: "Mostrar menos este anuncio."
hide: "No mostrar"
_forgotPassword:
enterEmail: "Ingrese el correo usado para registrar la cuenta. Se enviará un link para resetear la contraseña."
ifNoEmail: "Si no utilizó un correo para crear la cuenta, contáctese con el administrador."

View File

@ -916,6 +916,12 @@ show: "Affichage"
neverShow: "Ne plus afficher"
remindMeLater: "Peut-être plus tard"
color: "Couleur"
_role:
priority: "Priorité"
_priority:
low: "Basse"
middle: "Moyen"
high: "Haute"
_sensitiveMediaDetection:
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
sensitivity: "Sensibilité de la détection"
@ -948,6 +954,7 @@ _accountDelete:
_ad:
back: "Retour"
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
hide: "Cacher "
_forgotPassword:
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte. Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette adresse."
ifNoEmail: "Si vous n'avez pas enregistré d'adresse e-mail, merci de contacter l'administrateur·rice de votre instance."

View File

@ -860,6 +860,12 @@ unlike: "Tidak Suka"
numberOfLikes: "Jumlah yang disukai"
show: "Tampilkan"
color: "Warna"
_role:
priority: "Prioritas"
_priority:
low: "Rendah"
middle: "Sedang"
high: "Tinggi"
_emailUnavailable:
used: "Alamat surel ini telah digunakan"
format: "Format tidak valid."
@ -884,6 +890,7 @@ _accountDelete:
_ad:
back: "Kembali"
reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit"
hide: "Jangan tampilkan"
_forgotPassword:
enterEmail: "Masukkan alamat surel yang kamu gunakan pada saat mendaftar. Sebuah tautan untuk mengatur ulang kata sandi kamu akan dikirimkan ke alamat surel tersebut."
ifNoEmail: "Apabila kamu tidak menggunakan surel pada saat pendaftaran, mohon hubungi admin segera."

View File

@ -8,7 +8,7 @@ search: "Cerca"
notifications: "Notifiche"
username: "Nome utente"
password: "Password"
forgotPassword: "Hai dimenticato la tua password?"
forgotPassword: "Hai dimenticato la password?"
fetchingAsApObject: "Recuperando dal Fediverso..."
ok: "OK"
gotIt: "Ho capito"
@ -325,7 +325,7 @@ connectService: "Connessione"
disconnectService: "Disconnessione "
enableLocalTimeline: "Abilita Timeline locale"
enableGlobalTimeline: "Abilita Timeline federata"
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e i moderatori potranno sempre accederci."
disablingTimelinesInfo: "Anche disabilitandole, gli Amministratori e i Moderatori potranno comunque accedervi."
registration: "Iscriviti"
enableRegistration: "Permettere nuove registrazioni"
invite: "Invita"
@ -340,7 +340,7 @@ pinnedUsers: "Utenti in evidenza"
pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina \"Esplora\", un@ per riga."
pinnedPages: "Pagine in evidenza"
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
pinnedClipId: "ID della nota in evidenza"
pinnedClipId: "ID della Clip in evidenza"
pinnedNotes: "Nota fissata"
hcaptcha: "hCaptcha"
enableHcaptcha: "Abilita hCaptcha"
@ -651,11 +651,11 @@ random: "Casuale"
system: "Sistema"
switchUi: "Cambiare interfaccia"
desktop: "Desktop"
clip: "Nota"
clip: "Clip"
createNew: "Crea"
optional: "Opzionale"
createNewClip: "Nuova Nota"
unclip: "Rimuovi la nota"
optional: "facoltativo"
createNewClip: "Crea una Clip"
unclip: "Togli Nota dalla Clip"
confirmToUnclipAlreadyClippedNote: "Questa nota è già inclusa in \"{name}\". Si desidera escludere la nota?"
public: "Pubblica"
i18nInfo: "Misskey è tradotto in diverse lingue da volontari. Anche tu puoi contribuire su {link}."
@ -932,27 +932,36 @@ assign: "Assegna"
unassign: "Disassegna"
color: "Colore"
manageCustomEmojis: "Gestisci le emoji personalizzate"
youCannotCreateAnymore: "Non puoi creare, hai raggiunto il limite."
cannotPerformTemporary: "Indisponibilità temporanea"
cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché si è raggiunto il limite di esecuzioni possibili. Per favore, riprova più tardi."
_role:
new: "Nuovo ruolo"
edit: "Modifica ruolo"
name: "Nome del ruolo"
description: "Descrizione del ruolo"
permission: "Permessi del ruolo"
permission: "Permessi globali del ruolo"
descriptionOfPermission: "<b>Moderatori</b> possono svolgere le attività di moderazione basilari.\n<b>Amministratori</b> possono modificare la configurazione dell'istanza."
assignTarget: "Assegna il target"
descriptionOfAssignTarget: "<b>Manuale</b> per assegnare manualmente questo ruolo ai profili.\n<b>Condizionale</b> per assegnare o rimuovere automaticamente questo ruolo ai profili, secondo determinate condizioni."
assignTarget: "Modalità di assegnazione del ruolo"
descriptionOfAssignTarget: "<b>Manuale</b>: per assegnare manualmente questo ruolo ai profili.\n<b>Condizionale</b>: per assegnare o rimuovere automaticamente questo ruolo ai profili, a precise condizioni."
manual: "Manuale"
conditional: "Condizionale"
condition: "Condizioni"
isConditionalRole: "Questo è un ruolo condizionato"
isPublic: "Ruolo pubblico"
descriptionOfIsPublic: "La lista di profili assegnati a questo ruolo è visibile a chiunque. Inoltre, il ruolo verrà mostrato nei relativi profili."
descriptionOfIsPublic: "La lista di profili assegnati a questo ruolo è visibile a chiunque. Inoltre, il nome del ruolo verrà mostrato pubblicamente nei relativi profili."
options: "Opzioni del ruolo"
policies: "Policy"
baseRole: "Ruolo di base"
useBaseValue: "Eredita dal ruolo base"
chooseRoleToAssign: "Seleziona il ruolo da assegnare"
canEditMembersByModerator: "Consenti ai Moderatori di modificare i membri di questo ruolo"
descriptionOfCanEditMembersByModerator: "Se attivo, anche i Moderatori potranno assegnare o togliere questo ruolo. Altrimenti, se disattivo, potranno solo gli Amministratori."
canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo"
descriptionOfCanEditMembersByModerator: "Se disattivo, potranno farlo solamente gli Amministratori."
priority: "Priorità"
_priority:
low: "Bassa"
middle: "Medio"
high: "Alta"
_options:
gtlAvailable: "Disponibilità della Timeline Federata"
ltlAvailable: "Disponibilità della Timeline Locale"
@ -960,12 +969,25 @@ _role:
canInvite: "Genera codici di invito all'istanza"
canManageCustomEmojis: "Gestire le emoji personalizzate"
driveCapacity: "Capienza del Drive"
antennaMax: "Numero massimo di Antenne"
pinMax: "Quantità massima di Note in primo piano"
antennaMax: "Quantità massima di Antenne"
wordMuteMax: "Lunghezza massima del filtro parole"
webhookMax: "Quantità massima di Webhook"
clipMax: "Quantità massima di Clip"
noteEachClipsMax: "Quantità massima di Note nella Clip"
userListMax: "Quantità massima di liste"
userEachUserListsMax: "Quantità massima di profili per lista"
rateLimitFactor: "Limite del rapporto"
descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più."
_condition:
isLocal: "Profilo locale"
isRemote: "Profilo remoto"
createdLessThan: "Creato meno di"
createdMoreThan: "Creato più di"
followersLessThanOrEq: "Ha meno di N follower"
followersMoreThanOrEq: "Ha più di N follower"
followingLessThanOrEq: "Segue N profili o meno"
followingMoreThanOrEq: "Segue N profili o più"
and: "E"
or: "O"
not: "NON"
@ -1001,6 +1023,7 @@ _accountDelete:
_ad:
back: "Indietro"
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
hide: "Nascondi"
_forgotPassword:
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
ifNoEmail: "Se nessun indirizzo e-mail è stato registrato, si prega di contattare l'amministratore·trice dell'istanza."
@ -1384,7 +1407,7 @@ _cw:
_poll:
noOnlyOneChoice: "Sono necessarie almeno 2 risposte"
choiceN: "Opzione {n}"
noMore: "Hai aggiunto il numero massimo di opzioni."
noMore: "Hai raggiunto il limite di opzioni."
canMultipleVote: "Possibilità di risposte multiple"
expiration: "Scadenza"
infinite: "Non scade"

View File

@ -937,6 +937,8 @@ manageCustomEmojis: "カスタム絵文字の管理"
youCannotCreateAnymore: "これ以上作成することはできません。"
cannotPerformTemporary: "一時的に利用できません"
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
preset: "プリセット"
selectFromPresets: "プリセットから選択"
_role:
new: "ロールの作成"
@ -982,6 +984,7 @@ _role:
userEachUserListsMax: "ユーザーリスト内のユーザーの最大数"
rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。"
canHideAds: "広告の非表示"
_condition:
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
@ -1036,6 +1039,7 @@ _accountDelete:
_ad:
back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
hide: "表示しない"
_forgotPassword:
enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"

View File

@ -926,29 +926,74 @@ didYouLikeMisskey: "Misskeyを気に入っとっただけましたん"
pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
roles: "ロール"
role: "ロール"
normalUser: "一般ユーザー"
undefined: "未定義"
assign: "アサイン"
unassign: "アサインを解除"
color: "色"
manageCustomEmojis: "カスタム絵文字の管理"
youCannotCreateAnymore: "これ以上作れなさそうや"
cannotPerformTemporary: "一時的に利用できへんで"
cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。"
preset: "プリセット"
selectFromPresets: "プリセットから選ぶ"
_role:
new: "ロールの作成"
edit: "ロールの編集"
name: "ロール名"
description: "ロールの説明"
permission: "ロールの権限"
descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関わる操作を行えるで。\n<b>管理者</b>はインスタンスの全ての設定を変更できるで。"
assignTarget: "アサインターゲット"
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれてるかを手動で管理するで。\n<b>コンディショナル</b>は条件を設定して、それに合うユーザーが自動で含まれるようになるで。"
manual: "マニュアル"
conditional: "コンディショナル"
condition: "条件"
isConditionalRole: "これはコンディショナルロールやで"
isPublic: "ロールを公開"
descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができるで。そんで、ユーザーのプロフィールでこのロールが表示されるで。"
options: "オプション"
policies: "ポリシー"
baseRole: "ベースロール"
useBaseValue: "ベースロールの値を使用"
chooseRoleToAssign: "アサインするロールを選択"
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになるで。オフにすると管理者のみが行えるで。"
priority: "優先度"
_priority:
low: "低い"
middle: "中"
high: "高い"
_options:
gtlAvailable: "グローバルタイムラインの閲覧"
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canInvite: "インスタンス招待コードの発行"
canManageCustomEmojis: "カスタム絵文字の管理"
driveCapacity: "ドライブ容量"
pinMax: "ノートのピン留めの最大数"
antennaMax: "アンテナの作成可能数"
wordMuteMax: "ワードミュートの最大文字数"
webhookMax: "Webhockの作成可能数"
clipMax: "クリップの作成可能数"
noteEachClipsMax: "クリップ内のノートの最大数"
userListMax: "ユーザーリストの作成可能数"
userEachUserListsMax: "ユーザーリスト内のユーザーの最大数"
rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "ちっちゃいほど制限が緩くなって、大きいほど制限されるで。"
canHideAds: "広告を表示させへん"
_condition:
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
createdLessThan: "アカウント作成から~以内"
createdMoreThan: "アカウント作成から~経過"
followersLessThanOrEq: "フォロワー数が~以下"
followersMoreThanOrEq: "フォロワー数が~以上"
followingLessThanOrEq: "フォロー数が~以下"
followingMoreThanOrEq: "フォロー数が~以上"
and: "~かつ~"
or: "~または~"
not: "~ではない"
_sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。"
sensitivity: "検出感度やで"
@ -981,6 +1026,7 @@ _accountDelete:
_ad:
back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げるで"
hide: "表示せん"
_forgotPassword:
enterEmail: "アカウントに登録したメールアドレスをここに入力してや。そのアドレス宛に、パスワードリセット用のリンクが送られるから待っててな~。"
ifNoEmail: "メールアドレスを登録してへんのやったら、管理者まで教えてな~。"

View File

@ -933,6 +933,8 @@ unassign: "할당 취소"
color: "색"
manageCustomEmojis: "커스텀 이모지 관리"
youCannotCreateAnymore: "더 이상 생성할 수 없습니다."
cannotPerformTemporary: "일시적으로 사용할 수 없음"
cannotPerformTemporaryDescription: "조작 횟수 제한을 초과하여 일시적으로 사용이 불가합니다. 잠시 후 다시 시도해 주세요."
_role:
new: "새 역할 생성"
edit: "역할 수정"
@ -949,11 +951,17 @@ _role:
isPublic: "공개 역할"
descriptionOfIsPublic: "역할에 할당된 사용자를 누구나 볼 수 있습니다. 또한 사용자 프로필에 이 역할이 표시됩니다."
options: "옵션"
policies: "정책"
baseRole: "기본 역할"
useBaseValue: "기본값 사용"
chooseRoleToAssign: "할당할 역할 선택"
canEditMembersByModerator: "모더레이터의 역할 수정 허용"
descriptionOfCanEditMembersByModerator: "이 옵션을 켜면 모더레이터도 이 역할에 사용자를 추가하거나 삭제할 수 있습니다. 꺼져 있으면 관리자만 가능합니다."
priority: "우선순위"
_priority:
low: "낮음"
middle: "보통"
high: "높음"
_options:
gtlAvailable: "글로벌 타임라인 보이기"
ltlAvailable: "로컬 타임라인 보이기"
@ -969,6 +977,8 @@ _role:
noteEachClipsMax: "각 클립에 추가할 수 있는 노트 수"
userListMax: "생성할 수 있는 리스트 수"
userEachUserListsMax: "리스트당 최대 사용자 수"
rateLimitFactor: "속도 제한"
descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다."
_condition:
isLocal: "로컬 사용자"
isRemote: "리모트 사용자"
@ -1013,6 +1023,7 @@ _accountDelete:
_ad:
back: "뒤로"
reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기"
hide: "보이지 않음"
_forgotPassword:
enterEmail: "여기에 계정에 등록한 메일 주소를 입력해 주세요. 입력한 메일 주소로 비밀번호 재설정 링크를 발송합니다."
ifNoEmail: "메일 주소를 등록하지 않은 경우, 관리자에 문의해 주십시오."

View File

@ -869,6 +869,12 @@ loggedInAsBot: "Jesteś obecnie zalogowany/a jako bot"
like: "Polub"
show: "Wyświetlanie"
color: "Kolor"
_role:
priority: "Priorytet"
_priority:
low: "Niski"
middle: "Średnie"
high: "Wysoki"
_sensitiveMediaDetection:
description: "Zmniejsza wysiłek związany z moderacją serwera dzięki automatycznemu rozpoznawaniu zawartości NSFW za pomocą uczenia maszynowego. To nieznacznie zwiększy obciążenie serwera."
setSensitiveFlagAutomatically: "Oznacz jako NSFW"
@ -896,6 +902,7 @@ _accountDelete:
_ad:
back: "Wróć"
reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej"
hide: "Nigdy nie pokazuj"
_forgotPassword:
enterEmail: "Wpisz adres e-mail użyty do rejestracji. Zostanie do niego wysłany link, za pomocą którego możesz zresetować hasło."
ifNoEmail: "Jeżeli nie podano adresu e-mail podczas rejestracji, skontaktuj się z administratorem zamiast tego."

View File

@ -648,6 +648,9 @@ sent: "Trimite"
searchByGoogle: "Caută"
file: "Fișiere"
show: "Arată"
_role:
_priority:
middle: "Mediu"
_email:
_follow:
title: "te-a urmărit"

View File

@ -867,6 +867,12 @@ windowRestore: "Восстановить"
like: "Нравится!"
show: "Отображение"
color: "Цвет"
_role:
priority: "Приоритет"
_priority:
low: "Низкий"
middle: "Средне"
high: "Высокий"
_sensitiveMediaDetection:
description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
setSensitiveFlagAutomatically: "Установить флаг NSFW"
@ -894,6 +900,7 @@ _accountDelete:
_ad:
back: "Выход"
reduceFrequencyOfThisAd: "Реже показывать эту рекламу"
hide: "Не показывать"
_forgotPassword:
enterEmail: "Введите адрес электронной почты, который ввели при регистрации. На неё будет выслана ссылка для смены пароля."
ifNoEmail: "Если вы не ввели свой адрес электронной почты, свяжитесь с администратором ресурса, чтобы сменить пароль."

View File

@ -918,6 +918,12 @@ remindMeLater: "Pripomenúť neskôr"
didYouLikeMisskey: "Páči sa vám Misskey?"
pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, prispejte, aby sme ho mohli ďalej rozvíjať!"
color: "Farba"
_role:
priority: "Priorita"
_priority:
low: "Málo"
middle: "Stredné"
high: "Vysoká"
_sensitiveMediaDetection:
description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
sensitivity: "Citlivosť detekcie"
@ -950,6 +956,7 @@ _accountDelete:
_ad:
back: "Späť"
reduceFrequencyOfThisAd: "Túto reklamu zobrazovať menej"
hide: "Nikdy nezobrazovať"
_forgotPassword:
enterEmail: "Zadajte emailovú adresu, ktorú ste použili pri registrácii. Pošleme vám na ňu odkaz, cez ktorý si môžete obnoviť heslo."
ifNoEmail: "Ak ste pri registrácii nepoužili email, prosím kontaktujte administrátora."

View File

@ -953,6 +953,11 @@ _role:
chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด"
canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ"
priority: "ลำดับความสำคัญ"
_priority:
low: "ต่ำ"
middle: "ปานกลาง"
high: "สูง"
_options:
gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
@ -1005,6 +1010,7 @@ _accountDelete:
_ad:
back: "ย้อนกลับ"
reduceFrequencyOfThisAd: "แสดงโฆษณานี้ให้น้อยลง"
hide: "ไม่ต้องแสดง"
_forgotPassword:
enterEmail: "ป้อนที่อยู่อีเมลที่คุณเคยใช้ในการลงทะเบียนไว้ ลิงก์ที่คุณสามารถรีเซ็ตรหัสผ่านได้นั้นจะถูกส่งไปนะ"
ifNoEmail: "ถ้าหากคุณไม่ได้ใช้อีเมลระหว่างการลงทะเบียน กรุณาติดต่อผู้ดูแลระบบอินสแตนซ์แทนนะ"

View File

@ -895,6 +895,12 @@ caption: "Підпис"
like: "Вподобати"
show: "Відображення"
color: "Колір"
_role:
priority: "Пріоритет"
_priority:
low: "Низький"
middle: "Середній"
high: "Високий"
_sensitiveMediaDetection:
sensitivity: "Чутливість детектування"
setSensitiveFlagAutomatically: "Позначити як NSFW"
@ -920,6 +926,7 @@ _accountDelete:
_ad:
back: "Назад"
reduceFrequencyOfThisAd: "Показувати цю рекламу менше"
hide: "Не відображати"
_gallery:
my: "Моя галерея"
liked: "Вподобане"

View File

@ -897,6 +897,12 @@ move: "Di chuyển"
like: "Thích"
show: "Hiển thị"
color: "Màu sắc"
_role:
priority: "Ưu tiên"
_priority:
low: "Thấp"
middle: "Vừa"
high: "Cao"
_sensitiveMediaDetection:
description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ."
sensitivity: "Phát hiện nhạy cảm"
@ -929,6 +935,7 @@ _accountDelete:
_ad:
back: "Quay lại"
reduceFrequencyOfThisAd: "Hiện ít lại"
hide: "Không hiển thị"
_forgotPassword:
enterEmail: "Nhập địa chỉ email bạn đã sử dụng để đăng ký. Một liên kết mà bạn có thể đặt lại mật khẩu của mình sau đó sẽ được gửi đến nó."
ifNoEmail: "Nếu bạn không sử dụng email lúc đăng ký, vui lòng liên hệ với quản trị viên."

View File

@ -933,6 +933,10 @@ unassign: "取消分配"
color: "颜色"
manageCustomEmojis: "管理自定义表情符号"
youCannotCreateAnymore: "抱歉,您无法再创建更多了。"
cannotPerformTemporary: "暂时不可用"
cannotPerformTemporaryDescription: "因操作过于频繁,暂时不可用,请稍后再试。"
preset: "預設值"
selectFromPresets: "從預設值中選擇"
_role:
new: "创建角色"
edit: "编辑角色"
@ -949,11 +953,17 @@ _role:
isPublic: "角色公开"
descriptionOfIsPublic: "任何人都可以看到分配该角色的用户。而用户的个人资料也将显示该角色。"
options: "选项"
policies: "策略"
baseRole: "基本角色"
useBaseValue: "使用基本角色的值"
chooseRoleToAssign: "选择要分配的角色"
canEditMembersByModerator: "允许监察者编辑成员"
descriptionOfCanEditMembersByModerator: "如果选中,监察者和管理员都能够为用户分配/取消分配角色。如果未选中,则只有管理员可以执行此操作。"
priority: "优先级"
_priority:
low: "低"
middle: "中"
high: "高"
_options:
gtlAvailable: "查看全局时间线"
ltlAvailable: "查看本地时间线"
@ -969,6 +979,8 @@ _role:
noteEachClipsMax: "单个便签内的贴文数量限制"
userListMax: "用户列表创建数量限制"
userEachUserListsMax: "单个用户列表内用户数量限制"
rateLimitFactor: "速率限制"
descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
_condition:
isLocal: "是本地用户"
isRemote: "是远程用户"
@ -1013,6 +1025,7 @@ _accountDelete:
_ad:
back: "返回"
reduceFrequencyOfThisAd: "减少此广告的频率"
hide: "不显示"
_forgotPassword:
enterEmail: "请输入您验证账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。"
ifNoEmail: "如果您没有使用电子邮件地址进行验证,请联系管理员。"

View File

@ -932,6 +932,8 @@ assign: "指派"
unassign: "取消指派"
color: "顏色"
manageCustomEmojis: "管理自訂表情符號"
cannotPerformTemporary: "暫時無法進行"
cannotPerformTemporaryDescription: "由於超過操作次數限制,暫時無法進行。請過一段時間之後再嘗試。"
_role:
new: "建立角色"
edit: "編輯角色"
@ -948,11 +950,17 @@ _role:
isPublic: "角色為公開"
descriptionOfIsPublic: "任何人都可以看到被指派了角色的使用者。此外,使用者的個人檔案將顯示這個角色。"
options: "選項"
policies: "政策"
baseRole: "基本角色"
useBaseValue: "使用基本角色的值"
chooseRoleToAssign: "選擇要指派的角色"
canEditMembersByModerator: "允許編輯監察員的成員"
descriptionOfCanEditMembersByModerator: "如果開啟,管理員與監察員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。"
priority: "優先級"
_priority:
low: "低"
middle: "中"
high: "高"
_options:
gtlAvailable: "瀏覽全域時間軸"
ltlAvailable: "瀏覽本地時間軸"
@ -1008,6 +1016,7 @@ _accountDelete:
_ad:
back: "返回"
reduceFrequencyOfThisAd: "降低此廣告的頻率 "
hide: "隱藏"
_forgotPassword:
enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。 "

View File

@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "13.0.0-rc.9-simkey",
"codename": "indigo",
"version": "13.0.0-simkey",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/sim1222/misskey.git"
},
"packageManager": "yarn@3.3.0",
"packageManager": "pnpm@7.24.3",
"workspaces": [
"packages/frontend",
"packages/backend",
@ -15,27 +15,27 @@
"private": true,
"scripts": {
"build-pre": "node ./scripts/build-pre.js",
"build": "yarn build-pre && yarn workspaces foreach run build && yarn run gulp",
"build": "pnpm build-pre && pnpm -r build && pnpm gulp",
"start": "cd packages/backend && node ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
"init": "yarn migrate",
"migrate": "cd packages/backend && yarn run typeorm migration:run -d ormconfig.js",
"migrateandstart": "yarn migrate && yarn start",
"gulp": "gulp build",
"watch": "yarn dev",
"init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm typeorm migration:run -d ormconfig.js",
"migrateandstart": "pnpm migrate && pnpm start",
"gulp": "pnpm exec gulp build",
"watch": "pnpm dev",
"dev": "node ./scripts/dev.js",
"lint": "yarn workspaces foreach 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",
"jest": "cd packages/backend && cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand",
"jest-and-coverage": "cd packages/backend && cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand",
"test": "yarn jest",
"test-and-coverage": "yarn jest-and-coverage",
"format": "gulp format",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cd packages/backend && pnpm cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand",
"jest-and-coverage": "cd packages/backend && pnpm cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand",
"test": "pnpm jest",
"test-and-coverage": "pnpm jest-and-coverage",
"format": "pnpm exec gulp format",
"clean": "node ./scripts/clean.js",
"clean-all": "node ./scripts/clean-all.js",
"cleanall": "yarn clean-all"
"cleanall": "pnpm clean-all"
},
"resolutions": {
"chokidar": "^3.3.1",
@ -48,7 +48,8 @@
"gulp-rename": "2.0.0",
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"js-yaml": "4.1.0"
"js-yaml": "4.1.0",
"typescript": "4.9.4"
},
"devDependencies": {
"@types/gulp": "4.0.10",
@ -58,8 +59,7 @@
"cross-env": "7.0.3",
"cypress": "12.3.0",
"eslint": "^8.31.0",
"start-server-and-test": "1.15.2",
"typescript": "4.9.4"
"start-server-and-test": "1.15.2"
},
"optionalDependencies": {
"@tensorflow/tfjs-core": "^4.2.0"

View File

@ -0,0 +1,11 @@
export class firstRetrievedAt1673812883772 {
name = 'firstRetrievedAt1673812883772'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "caughtAt" TO "firstRetrievedAt"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "firstRetrievedAt" TO "caughtAt"`);
}
}

View File

@ -6,15 +6,15 @@
"scripts": {
"start": "node ./built/index.js",
"start:test": "NODE_ENV=test node ./built/index.js",
"migrate": "typeorm migration:run -d ormconfig.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"lint": "tsc --noEmit && eslint --quiet \"src/**/*.ts\"",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand",
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
"test": "yarn jest",
"test-and-coverage": "yarn jest-and-coverage"
"test": "pnpm jest",
"test-and-coverage": "pnpm jest-and-coverage"
},
"optionalDependencies": {
"@tensorflow/tfjs": "^4.1.0",
@ -116,6 +116,7 @@
"tsconfig-paths": "4.1.2",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.11",
"typescript": "4.9.4",
"ulid": "2.3.0",
"undici": "^5.15.0",
"unzipper": "0.10.11",
@ -180,7 +181,6 @@
"execa": "6.1.0",
"jest": "29.3.1",
"jest-mock": "^29.3.1",
"node-fetch": "3.3.0",
"typescript": "4.9.4"
"node-fetch": "3.3.0"
}
}

View File

@ -34,7 +34,7 @@ export class FederatedInstanceService {
const i = await this.instancesRepository.insert({
id: this.idService.genId(),
host,
caughtAt: new Date(),
firstRetrievedAt: new Date(),
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
this.cache.set(host, i);

View File

@ -19,6 +19,7 @@ export type RolePolicies = {
canPublicNote: boolean;
canInvite: boolean;
canManageCustomEmojis: boolean;
canHideAds: boolean;
driveCapacityMb: number;
pinLimit: number;
antennaLimit: number;
@ -37,6 +38,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canPublicNote: true,
canInvite: false,
canManageCustomEmojis: false,
canHideAds: false,
driveCapacityMb: 100,
pinLimit: 5,
antennaLimit: 5,
@ -212,7 +214,7 @@ export class RoleService implements OnApplicationShutdown {
if (p2.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value));
const p1 = policies.filter(policy => policy.priority === 1);
if (p1.length > 0) return aggregate(p2.map(policy => policy.useDefault ? basePolicies[name] : policy.value));
if (p1.length > 0) return aggregate(p1.map(policy => policy.useDefault ? basePolicies[name] : policy.value));
return aggregate(policies.map(policy => policy.useDefault ? basePolicies[name] : policy.value));
}
@ -223,6 +225,7 @@ export class RoleService implements OnApplicationShutdown {
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
pinLimit: calc('pinLimit', vs => Math.max(...vs)),
antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),

View File

@ -29,7 +29,7 @@ export class InstanceEntityService {
const meta = await this.metaService.fetch();
return {
id: instance.id,
caughtAt: instance.caughtAt.toISOString(),
firstRetrievedAt: instance.firstRetrievedAt.toISOString(),
host: instance.host,
usersCount: instance.usersCount,
notesCount: instance.notesCount,

View File

@ -13,7 +13,7 @@ export class Instance {
@Column('timestamp with time zone', {
comment: 'The caught date of the Instance.',
})
public caughtAt: Date;
public firstRetrievedAt: Date;
/**
* ホスト

View File

@ -232,6 +232,6 @@ export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const;
export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const;
export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;

View File

@ -6,7 +6,7 @@ export const packedFederationInstanceSchema = {
optional: false, nullable: false,
format: 'id',
},
caughtAt: {
firstRetrievedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',

View File

@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
case '-following': query.orderBy('instance.followingCount', 'ASC'); break;
case '+followers': query.orderBy('instance.followersCount', 'DESC'); break;
case '-followers': query.orderBy('instance.followersCount', 'ASC'); break;
case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break;
case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break;
case '+firstRetrievedAt': query.orderBy('instance.firstRetrievedAt', 'DESC'); break;
case '-firstRetrievedAt': query.orderBy('instance.firstRetrievedAt', 'ASC'); break;
case '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC', 'NULLS LAST'); break;
case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC', 'NULLS FIRST'); break;

View File

@ -2,7 +2,7 @@
<div class="bcekxzvu _margin _panel blur">
<div class="target">
<MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`">
<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true" :disable-link="true"/>
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
<div class="names">
<MkUserName class="name" :user="report.targetUser"/>
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>

View File

@ -6,7 +6,7 @@
</template>
</div>
<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, color: c.color ?? null }" :text="c.text"/>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text"/>
<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="c.onClick">{{ c.text }}</MkButton>
<div v-else-if="c.type === 'buttons'" class="_buttons">
<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>
@ -32,7 +32,7 @@
<template v-if="c.caption" #caption>{{ c.caption }}</template>
<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
</MkSelect>
<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="openPostForm">{{ c.text }}</MkButton>
<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" inline @click="openPostForm">{{ c.text }}</MkButton>
<MkFolder v-else-if="c.type === 'folder'" :default-open="c.opened">
<template #label>{{ c.title }}</template>
<template v-for="child in c.children" :key="child">

View File

@ -1,7 +1,7 @@
<template>
<div>
<div v-for="user in users" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
<MkAvatar :user="user" style="width:32px;height:32px;" :show-indicator="true"/>
<MkAvatar :user="user" style="width:32px;height:32px;" indicator link preview/>
</div>
</div>
</template>

View File

@ -23,7 +23,7 @@
@leave="leave"
@after-leave="afterLeave"
>
<div v-show="showBody" ref="content" :class="[$style.content, { omitted }]">
<div v-show="showBody" ref="content" :class="[$style.content, { [$style.omitted]: omitted }]">
<slot></slot>
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
<span :class="$style.fadeLabel">{{ $ts.showMore }}</span>

View File

@ -118,7 +118,6 @@ export default defineComponent({
z-index: 10;
position: sticky;
top: var(--stickyTop, 0px);
padding: var(--x-padding);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(20px));

View File

@ -5,7 +5,7 @@
</div>
<article>
<header>
<MkAvatar :user="post.user" class="avatar"/>
<MkAvatar :user="post.user" class="avatar" link preview/>
</header>
<footer>
<span class="title">{{ post.title }}</span>

View File

@ -12,7 +12,7 @@
<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
<div v-if="isRenote" :class="$style.renote">
<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user"/>
<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user" link preview/>
<i class="ti ti-repeat" style="margin-right: 4px;"></i>
<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
<template #user>
@ -35,7 +35,7 @@
</div>
</div>
<article :class="$style.article" @contextmenu.stop="onContextmenu">
<MkAvatar v-once :class="$style.avatar" :user="appearNote.user"/>
<MkAvatar v-once :class="$style.avatar" :user="appearNote.user" link preview/>
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>

View File

@ -11,7 +11,7 @@
<MkNoteSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/>
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
<div v-if="isRenote" class="renote">
<MkAvatar class="avatar" :user="note.user"/>
<MkAvatar class="avatar" :user="note.user" link preview/>
<i class="ti ti-repeat"></i>
<I18n :src="i18n.ts.renotedBy" tag="span">
<template #user>
@ -35,7 +35,7 @@
</div>
<article class="article" @contextmenu.stop="onContextmenu">
<header class="header">
<MkAvatar class="avatar" :user="appearNote.user" :show-indicator="true"/>
<MkAvatar class="avatar" :user="appearNote.user" indicator link preview/>
<div class="body">
<div class="top">
<MkA v-user-preview="appearNote.user.id" class="name" :to="userPage(appearNote.user)">

View File

@ -1,6 +1,6 @@
<template>
<div :class="$style.root">
<MkAvatar :class="$style.avatar" :user="$i"/>
<MkAvatar :class="$style.avatar" :user="$i" link preview/>
<div :class="$style.main">
<div :class="$style.header">
<MkUserName :user="$i"/>

View File

@ -1,6 +1,6 @@
<template>
<div :class="$style.root">
<MkAvatar :class="$style.avatar" :user="note.user"/>
<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>

View File

@ -1,7 +1,7 @@
<template>
<div :class="[$style.root, { [$style.children]: depth > 1 }]">
<div :class="$style.main">
<MkAvatar :class="$style.avatar" :user="note.user"/>
<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.body">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>

View File

@ -1,8 +1,8 @@
<template>
<div ref="elRef" :class="$style.root">
<div v-once :class="$style.head">
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user"/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user"/>
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
<div :class="[$style.subIcon, $style['t_' + notification.type]]">
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>

View File

@ -0,0 +1,72 @@
<template>
<div ref="content" :class="[$style.content, { [$style.omitted]: omitted }]">
<slot></slot>
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
<span :class="$style.fadeLabel">{{ $ts.showMore }}</span>
</button>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
const props = withDefaults(defineProps<{
maxHeight: number;
}>(), {
maxHeight: 200,
});
let content = $ref<HTMLElement>();
let omitted = $ref(false);
let ignoreOmit = $ref(false);
onMounted(() => {
const calcOmit = () => {
if (omitted || ignoreOmit) return;
omitted = content.offsetHeight > props.maxHeight;
};
calcOmit();
new ResizeObserver((entries, observer) => {
calcOmit();
}).observe(content);
});
</script>
<style lang="scss" module>
.content {
--stickyTop: 0px;
&.omitted {
position: relative;
max-height: v-bind("props.maxHeight + 'px'");
overflow: hidden;
> .fade {
display: block;
position: absolute;
z-index: 10;
bottom: 0;
left: 0;
width: 100%;
height: 64px;
background: linear-gradient(0deg, var(--panel), var(--X15));
> .fadeLabel {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> .fadeLabel {
background: var(--panelHighlight);
}
}
}
}
}
</style>

View File

@ -1,6 +1,11 @@
<template>
<MkA v-adaptive-bg :to="`/admin/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
<div :class="$style.title">
<span :class="$style.icon">
<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i>
<i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i>
<i v-else class="ti ti-user" style="opacity: 0.7;"></i>
</span>
<span :class="$style.name">{{ role.name }}</span>
<span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span>
<span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span>
@ -31,6 +36,10 @@ const props = defineProps<{
display: flex;
}
.icon {
margin-right: 8px;
}
.name {
font-weight: bold;
}
@ -42,5 +51,6 @@ const props = defineProps<{
.description {
opacity: 0.7;
font-size: 85%;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }]">
<MkAvatar class="avatar" :user="user" :disable-link="true" :show-indicator="true"/>
<MkAvatar class="avatar" :user="user" indicator/>
<div class="body">
<span class="name"><MkUserName class="name" :user="user"/></span>
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>

View File

@ -1,7 +1,7 @@
<template>
<div class="_panel blur vjnjpkug">
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<MkAvatar class="avatar" :user="user" indicator/>
<div class="title">
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>

View File

@ -5,7 +5,7 @@
<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''">
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<MkAvatar class="avatar" :user="user" indicator/>
<div class="title">
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
<p class="username"><MkAcct :user="user"/></p>

View File

@ -9,8 +9,8 @@
@closed="$emit('closed')"
>
<template #header>{{ i18n.ts.selectUser }}</template>
<div class="tbhwbxda">
<div class="form">
<div :class="$style.root">
<div :class="$style.form">
<FormSplit :min-width="170">
<MkInput v-model="username" :autofocus="true" @update:model-value="search">
<template #label>{{ i18n.ts.username }}</template>
@ -22,27 +22,27 @@
</MkInput>
</FormSplit>
</div>
<div v-if="username != '' || host != ''" class="result" :class="{ hit: users.length > 0 }">
<div v-if="users.length > 0" class="users">
<div v-for="user in users" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
<div v-if="username != '' || host != ''" :class="[$style.result, { [$style.hit]: users.length > 0 }]">
<div v-if="users.length > 0" :class="$style.users">
<div v-for="user in users" :key="user.id" class="_button" :class="[$style.user, { [$style.selected]: selected && selected.id === user.id }]" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" :class="$style.avatar" indicator/>
<div :class="$style.userBody">
<MkUserName :user="user" :class="$style.userName"/>
<MkAcct :user="user" :class="$style.userAcct"/>
</div>
</div>
</div>
<div v-else class="empty">
<div v-else :class="$style.empty">
<span>{{ i18n.ts.noUsers }}</span>
</div>
</div>
<div v-if="username == '' && host == ''" class="recent">
<div class="users">
<div v-for="user in recentUsers" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
<div v-if="username == '' && host == ''" :class="$style.recent">
<div :class="$style.users">
<div v-for="user in recentUsers" :key="user.id" class="_button" :class="[$style.user, { [$style.selected]: selected && selected.id === user.id }]" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" :class="$style.avatar" indicator/>
<div :class="$style.userBody">
<MkUserName :user="user" :class="$style.userName"/>
<MkAcct :user="user" :class="$style.userAcct"/>
</div>
</div>
</div>
@ -60,6 +60,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { $i } from '@/account';
const emit = defineEmits<{
(ev: 'ok', selected: misskey.entities.UserDetailed): void;
@ -67,6 +68,10 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const props = defineProps<{
includeSelf?: boolean;
}>();
let username = $ref('');
let host = $ref('');
let users: misskey.entities.UserDetailed[] = $ref([]);
@ -110,81 +115,83 @@ onMounted(() => {
os.api('users/show', {
userIds: defaultStore.state.recentlyUsedUsers,
}).then(users => {
recentUsers = users;
if (props.includeSelf) {
recentUsers = [$i, ...users];
} else {
recentUsers = users;
}
});
});
</script>
<style lang="scss" scoped>
.tbhwbxda {
> .form {
padding: 0 var(--root-margin);
<style lang="scss" module>
.root {
}
.form {
padding: 0 var(--root-margin);
}
.result,
.recent {
display: flex;
flex-direction: column;
overflow: auto;
height: 100%;
&.result.hit {
padding: 0;
}
> .result, > .recent {
display: flex;
flex-direction: column;
overflow: auto;
height: 100%;
&.result.hit {
padding: 0;
}
&.recent {
padding: 0;
}
> .users {
flex: 1;
overflow: auto;
padding: 8px 0;
> .user {
display: flex;
align-items: center;
padding: 8px var(--root-margin);
font-size: 14px;
&:hover {
background: var(--X7);
}
&.selected {
background: var(--accent);
color: #fff;
}
> * {
pointer-events: none;
user-select: none;
}
> .avatar {
width: 45px;
height: 45px;
}
> .body {
padding: 0 8px;
min-width: 0;
> .name {
display: block;
font-weight: bold;
}
> .acct {
opacity: 0.5;
}
}
}
}
> .empty {
opacity: 0.7;
text-align: center;
}
&.recent {
padding: 0;
}
}
.users {
flex: 1;
overflow: auto;
padding: 8px 0;
}
.user {
display: flex;
align-items: center;
padding: 8px var(--root-margin);
font-size: 14px;
&:hover {
background: var(--X7);
}
&.selected {
background: var(--accent);
color: #fff;
}
}
.userBody {
padding: 0 8px;
min-width: 0;
}
.avatar {
width: 45px;
height: 45px;
}
.userName {
display: block;
font-weight: bold;
}
.userAcct {
opacity: 0.5;
}
.empty {
opacity: 0.7;
text-align: center;
padding: 16px;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div v-if="chosen" :class="$style.root">
<div v-if="chosen && !shouldHide" :class="$style.root">
<div v-if="!showMenu" :class="[$style.main, $style['form_' + chosen.place]]">
<a :href="chosen.url" target="_blank" :class="$style.link">
<img :src="chosen.imageUrl" :class="$style.img">
@ -11,6 +11,7 @@
<div>Ads by {{ host }}</div>
<!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>-->
<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton>
<MkButton v-if="$i && $i.policies.canHideAds" :class="$style.menuButton" @click="hide">{{ $ts._ad.hide }}</MkButton>
<button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button>
</div>
</div>
@ -25,6 +26,7 @@ import { host } from '@/config';
import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store';
import * as os from '@/os';
import { $i } from '@/account';
type Ad = (typeof instance)['ads'][number];
@ -81,6 +83,7 @@ const choseAd = (): Ad | null => {
};
const chosen = ref(choseAd());
let shouldHide = $ref(chosen.value && $i && $i.policies.canHideAds && defaultStore.state.hiddenAds.includes(chosen.value.id));
function reduceFrequency(): void {
if (chosen.value == null) return;
@ -90,6 +93,13 @@ function reduceFrequency(): void {
chosen.value = choseAd();
showMenu.value = false;
}
function hide() {
if (chosen.value == null) return;
defaultStore.push('hiddenAds', chosen.value.id);
os.success();
shouldHide = true;
}
</script>
<style lang="scss" module>

View File

@ -1,11 +1,11 @@
<template>
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" class="_noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
<span v-if="!link" v-user-preview="preview ? user.id : undefined" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" class="_noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
<img :class="$style.inner" :src="url" decoding="async"/>
<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
</span>
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
<MkA v-else v-user-preview="preview ? user.id : undefined" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
<img :class="$style.inner" :src="url" decoding="async"/>
<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
</MkA>
</template>
@ -21,14 +21,14 @@ import { defaultStore } from '@/store';
const props = withDefaults(defineProps<{
user: misskey.entities.User;
target?: string | null;
disableLink?: boolean;
disablePreview?: boolean;
showIndicator?: boolean;
link?: boolean;
preview?: boolean;
indicator?: boolean;
}>(), {
target: null,
disableLink: false,
disablePreview: false,
showIndicator: false,
link: false,
preview: false,
indicator: false,
});
const emit = defineEmits<{

View File

@ -1,11 +1,11 @@
<template>
<div v-if="show" ref="el" :class="[$style.root, { [$style.slim]: narrow, [$style.thin]: thin_ }]" :style="{ background: bg }" @click="onClick">
<div v-if="narrow" :class="$style.buttonsLeft">
<MkAvatar v-if="props.displayMyAvatar && $i" :class="$style.avatar" :user="$i" :disable-preview="true"/>
<MkAvatar v-if="props.displayMyAvatar && $i" :class="$style.avatar" :user="$i"/>
</div>
<template v-if="metadata">
<div v-if="!hideTitle" :class="$style.titleContainer" @click="showTabsPopup">
<MkAvatar v-if="metadata.avatar" :class="$style.titleAvatar" :user="metadata.avatar" :disable-preview="true" :show-indicator="true"/>
<MkAvatar v-if="metadata.avatar" :class="$style.titleAvatar" :user="metadata.avatar" indicator/>
<i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i>
<div :class="$style.title">

View File

@ -14,7 +14,7 @@
<span v-if="pathname != ''" :class="$style.pathname">{{ self ? pathname.substring(1) : pathname }}</span>
<span :class="$style.query">{{ query }}</span>
<span :class="$style.hash">{{ hash }}</span>
<i v-if="target === '_blank'" class="ti ti-external-link icon"></i>
<i v-if="target === '_blank'" :class="$style.icon" class="ti ti-external-link"></i>
</component>
</template>

View File

@ -322,9 +322,11 @@ export function form(title, form) {
});
}
export async function selectUser() {
export async function selectUser(opts: { includeSelf?: boolean } = {}) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/MkUserSelectDialog.vue')), {}, {
popup(defineAsyncComponent(() => import('@/components/MkUserSelectDialog.vue')), {
includeSelf: opts.includeSelf,
}, {
ok: user => {
resolve(user);
},

View File

@ -1,5 +1,7 @@
<template>
<div class="driuhtrh">
<div class="driuhtrh _gaps">
<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
<div class="query">
<MkInput v-model="q" class="" :placeholder="$ts.search">
<template #prefix><i class="ti ti-search"></i></template>
@ -38,6 +40,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkTab from '@/components/MkTab.vue';
import * as os from '@/os';
import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
import { i18n } from '@/i18n';
const customEmojiCategories = getCustomEmojiCategories();
const customEmojiTags = getCustomEmojiTags();
@ -81,7 +84,6 @@ watch($$(selectedTags), () => {
> .query {
background: var(--bg);
padding: 16px;
> .tags {
> .tag {
@ -101,13 +103,10 @@ watch($$(selectedTags), () => {
}
> .emojis {
--x-padding: 0 16px;
.zuvgdzyt {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
grid-gap: 12px;
margin: 0 var(--margin) var(--margin) var(--margin);
}
}
}

View File

@ -28,8 +28,8 @@
<option value="-following">{{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+followers">{{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
</MkSelect>
</FormSplit>
</div>

View File

@ -32,8 +32,8 @@
<option value="-following">{{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+followers">{{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
<option value="+firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
<option value="-firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
</MkSelect>
</FormSplit>
</div>

View File

@ -4,7 +4,7 @@
<MkLoading v-if="fetching"/>
<div v-else :class="$style.root" class="_panel">
<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/user-info/${user.id}`">
<MkAvatar :user="user" class="avatar" :show-indicator="true" :disable-link="true"/>
<MkAvatar :user="user" class="avatar" indicator/>
</MkA>
</div>
</Transition>

View File

@ -14,7 +14,7 @@
</MkInput>
<MkSelect v-model="rolePermission" :readonly="readonly">
<template #label>{{ i18n.ts._role.permission }}</template>
<template #label><i class="ti ti-shield-lock"></i> {{ i18n.ts._role.permission }}</template>
<template #caption><div v-html="i18n.ts._role.descriptionOfPermission.replaceAll('\n', '<br>')"></div></template>
<option value="normal">{{ i18n.ts.normalUser }}</option>
<option value="moderator">{{ i18n.ts.moderator }}</option>
@ -22,7 +22,7 @@
</MkSelect>
<MkSelect v-model="target" :readonly="readonly">
<template #label>{{ i18n.ts._role.assignTarget }}</template>
<template #label><i class="ti ti-users"></i> {{ i18n.ts._role.assignTarget }}</template>
<template #caption><div v-html="i18n.ts._role.descriptionOfAssignTarget.replaceAll('\n', '<br>')"></div></template>
<option value="manual">{{ i18n.ts._role.manual }}</option>
<option value="conditional">{{ i18n.ts._role.conditional }}</option>
@ -36,11 +36,19 @@
</MkFolder>
<FormSlot>
<template #label>{{ i18n.ts._role.policies }}</template>
<template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template>
<div class="_gaps_s">
<MkFolder>
<MkInput v-model="q" type="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkFolder v-if="matchQuery([i18n.ts._role._options.rateLimitFactor, 'rateLimitFactor'])">
<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template>
<template #suffix>{{ policies.rateLimitFactor.useDefault ? i18n.ts._role.useBaseValue : `${Math.floor(policies.rateLimitFactor.value * 100)}%` }} <i :class="getPriorityIcon(policies.rateLimitFactor)"></i></template>
<template #suffix>
<span v-if="policies.rateLimitFactor.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ `${Math.floor(policies.rateLimitFactor.value * 100)}%` }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.rateLimitFactor)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.rateLimitFactor.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -55,9 +63,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.gtlAvailable, 'gtlAvailable'])">
<template #label>{{ i18n.ts._role._options.gtlAvailable }}</template>
<template #suffix>{{ policies.gtlAvailable.useDefault ? i18n.ts._role.useBaseValue : (policies.gtlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.gtlAvailable)"></i></template>
<template #suffix>
<span v-if="policies.gtlAvailable.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.gtlAvailable.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.gtlAvailable)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.gtlAvailable.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -71,9 +83,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.ltlAvailable, 'ltlAvailable'])">
<template #label>{{ i18n.ts._role._options.ltlAvailable }}</template>
<template #suffix>{{ policies.ltlAvailable.useDefault ? i18n.ts._role.useBaseValue : (policies.ltlAvailable.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.ltlAvailable)"></i></template>
<template #suffix>
<span v-if="policies.ltlAvailable.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.ltlAvailable.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.ltlAvailable)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.ltlAvailable.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -87,9 +103,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPublicNote, 'canPublicNote'])">
<template #label>{{ i18n.ts._role._options.canPublicNote }}</template>
<template #suffix>{{ policies.canPublicNote.useDefault ? i18n.ts._role.useBaseValue : (policies.canPublicNote.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canPublicNote)"></i></template>
<template #suffix>
<span v-if="policies.canPublicNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.canPublicNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.canPublicNote)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.canPublicNote.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -103,9 +123,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
<template #suffix>{{ policies.canInvite.useDefault ? i18n.ts._role.useBaseValue : (policies.canInvite.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canInvite)"></i></template>
<template #suffix>
<span v-if="policies.canInvite.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.canInvite.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.canInvite)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.canInvite.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -119,9 +143,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageCustomEmojis, 'canManageCustomEmojis'])">
<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template>
<template #suffix>{{ policies.canManageCustomEmojis.useDefault ? i18n.ts._role.useBaseValue : (policies.canManageCustomEmojis.value ? i18n.ts.yes : i18n.ts.no) }} <i :class="getPriorityIcon(policies.canManageCustomEmojis)"></i></template>
<template #suffix>
<span v-if="policies.canManageCustomEmojis.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.canManageCustomEmojis.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.canManageCustomEmojis)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.canManageCustomEmojis.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -135,9 +163,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #suffix>{{ policies.driveCapacityMb.useDefault ? i18n.ts._role.useBaseValue : (policies.driveCapacityMb.value + 'MB') }} <i :class="getPriorityIcon(policies.driveCapacityMb)"></i></template>
<template #suffix>
<span v-if="policies.driveCapacityMb.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.driveCapacityMb.value + 'MB' }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.driveCapacityMb)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.driveCapacityMb.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -151,9 +183,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
<template #suffix>{{ policies.pinLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.pinLimit.value) }} <i :class="getPriorityIcon(policies.pinLimit)"></i></template>
<template #suffix>
<span v-if="policies.pinLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.pinLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.pinLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.pinLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -166,9 +202,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.antennaMax, 'antennaLimit'])">
<template #label>{{ i18n.ts._role._options.antennaMax }}</template>
<template #suffix>{{ policies.antennaLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.antennaLimit.value) }} <i :class="getPriorityIcon(policies.antennaLimit)"></i></template>
<template #suffix>
<span v-if="policies.antennaLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.antennaLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.antennaLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.antennaLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -181,9 +221,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.wordMuteMax, 'wordMuteLimit'])">
<template #label>{{ i18n.ts._role._options.wordMuteMax }}</template>
<template #suffix>{{ policies.wordMuteLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.wordMuteLimit.value) }} <i :class="getPriorityIcon(policies.wordMuteLimit)"></i></template>
<template #suffix>
<span v-if="policies.wordMuteLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.wordMuteLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.wordMuteLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.wordMuteLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -197,9 +241,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.webhookMax, 'webhookLimit'])">
<template #label>{{ i18n.ts._role._options.webhookMax }}</template>
<template #suffix>{{ policies.webhookLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.webhookLimit.value) }} <i :class="getPriorityIcon(policies.webhookLimit)"></i></template>
<template #suffix>
<span v-if="policies.webhookLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.webhookLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.webhookLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.webhookLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -212,9 +260,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.clipMax, 'clipLimit'])">
<template #label>{{ i18n.ts._role._options.clipMax }}</template>
<template #suffix>{{ policies.clipLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.clipLimit.value) }} <i :class="getPriorityIcon(policies.clipLimit)"></i></template>
<template #suffix>
<span v-if="policies.clipLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.clipLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.clipLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.clipLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -227,9 +279,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.noteEachClipsMax, 'noteEachClipsLimit'])">
<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template>
<template #suffix>{{ policies.noteEachClipsLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.noteEachClipsLimit.value) }} <i :class="getPriorityIcon(policies.noteEachClipsLimit)"></i></template>
<template #suffix>
<span v-if="policies.noteEachClipsLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.noteEachClipsLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.noteEachClipsLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.noteEachClipsLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -242,9 +298,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.userListMax, 'userListLimit'])">
<template #label>{{ i18n.ts._role._options.userListMax }}</template>
<template #suffix>{{ policies.userListLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.userListLimit.value) }} <i :class="getPriorityIcon(policies.userListLimit)"></i></template>
<template #suffix>
<span v-if="policies.userListLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.userListLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.userListLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.userListLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -257,9 +317,13 @@
</div>
</MkFolder>
<MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.userEachUserListsMax, 'userEachUserListsLimit'])">
<template #label>{{ i18n.ts._role._options.userEachUserListsMax }}</template>
<template #suffix>{{ policies.userEachUserListsLimit.useDefault ? i18n.ts._role.useBaseValue : (policies.userEachUserListsLimit.value) }} <i :class="getPriorityIcon(policies.userEachUserListsLimit)"></i></template>
<template #suffix>
<span v-if="policies.userEachUserListsLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.userEachUserListsLimit.value }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.userEachUserListsLimit)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.userEachUserListsLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
@ -271,6 +335,26 @@
</MkRange>
</div>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
<template #suffix>
<span v-if="policies.canHideAds.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ policies.canHideAds.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(policies.canHideAds)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="policies.canHideAds.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="policies.canHideAds.value" :disabled="policies.canHideAds.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="policies.canHideAds.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
<template #label>{{ i18n.ts._role.priority }}</template>
</MkRange>
</div>
</MkFolder>
</div>
</FormSlot>
@ -312,6 +396,7 @@ const ROLE_POLICIES = [
'canPublicNote',
'canInvite',
'canManageCustomEmojis',
'canHideAds',
'driveCapacityMb',
'pinLimit',
'antennaLimit',
@ -335,6 +420,7 @@ const props = defineProps<{
}>();
const role = props.role;
let q = $ref('');
let name = $ref(role?.name ?? 'New Role');
let description = $ref(role?.description ?? '');
@ -367,6 +453,11 @@ function getPriorityIcon(option) {
return 'ti ti-point';
}
function matchQuery(keywords: string[]): boolean {
if (q.trim().length === 0) return true;
return keywords.some(keyword => keyword.toLowerCase().includes(q.toLowerCase()));
}
async function save() {
if (props.readonly) return;
if (role) {
@ -403,5 +494,11 @@ async function save() {
</script>
<style lang="scss" module>
.useDefaultLabel {
opacity: 0.7;
}
.priorityIndicator {
margin-left: 8px;
}
</style>

View File

@ -77,7 +77,9 @@ async function del() {
}
function assign() {
os.selectUser().then(async (user) => {
os.selectUser({
includeSelf: true,
}).then(async (user) => {
await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id });
role.users.push(user);
});

View File

@ -121,6 +121,14 @@
</MkInput>
</MkFolder>
<MkFolder>
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
<template #suffix>{{ policies.canHideAds ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canHideAds">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
</div>
</MkFolder>
@ -156,6 +164,7 @@ const ROLE_POLICIES = [
'canPublicNote',
'canInvite',
'canManageCustomEmojis',
'canHideAds',
'driveCapacityMb',
'pinLimit',
'antennaLimit',

View File

@ -1,14 +1,14 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions"/></template>
<MkSpacer :content-max="800">
<MkSpacer :content-max="800">
<div v-if="clip">
<div class="okzinsic _panel blur">
<div v-if="clip.description" class="description">
<Mfm :text="clip.description" :is-note="false" :i="$i"/>
</div>
<div class="user">
<MkAvatar :user="clip.user" class="avatar" :show-indicator="true"/> <MkUserName :user="clip.user" :nowrap="false"/>
<MkAvatar :user="clip.user" class="avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/>
</div>
</div>

View File

@ -2,13 +2,14 @@
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
<div class="_gaps_m">
<div class="_gaps">
<MkInput v-model="title">
<template #label>{{ i18n.ts._play.title }}</template>
</MkInput>
<MkTextarea v-model="summary">
<template #label>{{ i18n.ts._play.summary }}</template>
</MkTextarea>
<MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ti ti-chevron-down"></i></MkButton>
<MkTextarea v-model="script" class="_monospace" tall spellcheck="false">
<template #label>{{ i18n.ts._play.script }}</template>
</MkTextarea>
@ -32,6 +33,122 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkInput from '@/components/MkInput.vue';
import { useRouter } from '@/router';
const PRESET_DEFAULT = `/// @ 0.12.2
var name = ""
Ui:render([
Ui:C:textInput({
label: "Your name"
onInput: @(v) { name = v }
})
Ui:C:button({
text: "Hello"
onClick: @() {
Mk:dialog(null \`Hello, {name}!\`)
}
})
])
`;
const PRESET_OMIKUJI = `/// @ 0.12.2
// ユーザーごとに日替わりのおみくじのプリセット
// 選択肢
let choices = [
"ギガ吉"
"大吉"
"吉"
"中吉"
"小吉"
"末吉"
"凶"
"大凶"
]
// シードが「ユーザーID+今日の日付」である乱数生成器を用意
let random = Math:gen_rng(\`{USER_ID}{Date:year()}{Date:month()}{Date:day()}\`)
// ランダムに選択肢を選ぶ
let chosen = choices[random(0 (choices.len - 1))]
// 結果のテキスト
let result = \`今日のあなたの運勢は **{chosen}** です。\`
// UIを表示
Ui:render([
Ui:C:container({
align: 'center'
children: [
Ui:C:mfm({ text: result })
Ui:C:postFormButton({
text: "投稿する"
rounded: true
primary: true
form: {
text: \`{result}{Str:lf}{THIS_URL}\`
}
})
]
})
])
`;
const PRESET_TIMELINE = `/// @ 0.12.2
// APIリクエストを行いローカルタイムラインを表示するプリセット
@fetch() {
Ui:render([
Ui:C:container({
align: 'center'
children: [
Ui:C:text({ text: "読み込み中..." })
]
})
])
// タイムライン取得
let notes = Mk:api("notes/local-timeline" {})
// それぞれのートごとにUI要素作成
let noteEls = []
each (let note, notes) {
let el = Ui:C:container({
bgColor: "#444"
fgColor: "#fff"
padding: 10
rounded: true
children: [
Ui:C:mfm({
text: note.user.name
bold: true
})
Ui:C:mfm({
text: note.text
})
]
})
noteEls.push(el)
}
// UIを表示
Ui:render([
Ui:C:text({ text: "ローカル タイムライン" })
Ui:C:button({
text: "更新"
onClick: @() {
fetch()
}
})
Ui:C:container({
children: noteEls
})
])
}
fetch()
`;
const router = useRouter();
const props = defineProps<{
@ -49,23 +166,21 @@ if (props.id) {
let title = $ref(flash?.title ?? 'New Play');
let summary = $ref(flash?.summary ?? '');
let permissions = $ref(flash?.permissions ?? []);
let script = $ref(flash?.script ?? `/// @ 0.12.2
let script = $ref(flash?.script ?? PRESET_DEFAULT);
var name = ""
Ui:render([
Ui:C:textInput({
label: "Your name"
onInput: @(v) { name = v }
})
Ui:C:button({
text: "Hello"
onClick: @() {
Mk:dialog(null \`Hello, {name}!\`)
}
})
])
`);
function selectPreset(ev: MouseEvent) {
os.popupMenu([{
text: 'Omikuji',
action: () => {
script = PRESET_OMIKUJI;
},
}, {
text: 'Timeline viewer',
action: () => {
script = PRESET_TIMELINE;
},
}], ev.currentTarget ?? ev.target);
}
async function save() {
if (flash) {

View File

@ -130,7 +130,7 @@ const parser = new Parser();
let started = $ref(false);
let aiscript = $shallowRef<Interpreter | null>(null);
const root = ref<AsUiRoot>();
const components: Ref<AsUiComponent>[] = [];
const components: Ref<AsUiComponent>[] = $ref([]);
function start() {
started = true;

View File

@ -12,7 +12,7 @@
<template #default="{items}">
<div class="mk-follow-requests">
<div v-for="req in items" :key="req.id" class="user _panel blur">
<MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/>
<MkAvatar class="avatar" :user="req.follower" indicator link preview/>
<div class="body">
<div class="name">
<MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>

View File

@ -28,7 +28,7 @@
</div>
</div>
<div class="user">
<MkAvatar :user="post.user" class="avatar"/>
<MkAvatar :user="post.user" class="avatar" link preview/>
<div class="name">
<MkUserName :user="post.user" style="display: block;"/>
<MkAcct :user="post.user"/>

View File

@ -38,7 +38,7 @@
<FormSection>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.registeredAt }}</template>
<template #value><MkTime mode="detail" :time="instance.caughtAt"/></template>
<template #value><MkTime mode="detail" :time="instance.firstRetrievedAt"/></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.updatedAt }}</template>

View File

@ -16,7 +16,7 @@
:data-index="i"
>
<div>
<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" :show-indicator="true"/>
<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/>
<header v-if="message.groupId">
<span class="name">{{ message.group.name }}</span>
<MkTime :time="message.createdAt" class="time"/>

View File

@ -1,10 +1,13 @@
<template>
<div
class="pemppnzi blur"
:class="$style['root']"
class="blur"
@dragover.stop="onDragover"
@drop.stop="onDrop"
>
<textarea
:class="$style['textarea']"
class="_acrylic"
ref="textEl"
v-model="text"
:placeholder="i18n.ts.inputMessageHere"
@ -12,17 +15,17 @@
@compositionupdate="onCompositionUpdate"
@paste="onPaste"
></textarea>
<footer>
<div v-if="file" class="file" @click="file = null">{{ file.name }}</div>
<div class="buttons">
<button class="_button" @click="chooseFile"><i class="ti ti-photo-plus"></i></button>
<button class="_button" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
<button class="send _button" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send">
<footer :class="$style['footer']">
<div v-if="file" :class="$style['file']" @click="file = null">{{ file.name }}</div>
<div :class="$style['buttons']">
<button class="_button" :class="$style['button']" @click="chooseFile"><i class="ti ti-photo-plus"></i></button>
<button class="_button" :class="$style['button']" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
<button class="_button" :class="[$style['button'], $style['send']]" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send">
<template v-if="!sending"><i class="ti ti-send"></i></template><template v-if="sending"><MkLoading :em="true"/></template>
</button>
</div>
</footer>
<input ref="fileEl" type="file" @change="onChangeFile"/>
<input :class="$style['file-input']" ref="fileEl" type="file" @change="onChangeFile"/>
</div>
</template>
@ -236,131 +239,129 @@ defineExpose({
});
</script>
<style lang="scss" scoped>
.pemppnzi {
<style lang="scss" module>
.root {
position: relative;
}
> textarea {
cursor: auto;
.textarea {
cursor: auto;
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 80px;
margin: 0;
padding: 16px 16px 0 16px;
resize: none;
font-size: 1em;
font-family: inherit;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
box-sizing: border-box;
color: var(--fg);
}
.footer {
position: sticky;
bottom: 0;
background: var(--panel);
}
.file {
padding: 8px;
color: var(--fg);
background: transparent;
cursor: pointer;
}
/*
.files {
display: block;
margin: 0;
padding: 0 8px;
list-style: none;
&:after {
content: '';
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 80px;
margin: 0;
padding: 16px 16px 0 16px;
resize: none;
font-size: 1em;
font-family: inherit;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
box-sizing: border-box;
color: var(--fg);
background: rgba(12, 18, 16, 0.85);
backdrop-filter: var(--blur, blur(15px));
clear: both;
}
footer {
position: sticky;
bottom: 0;
background: var(--panel);
> .file {
padding: 8px;
color: var(--fg);
background: transparent;
cursor: pointer;
}
}
.files {
> li {
display: block;
margin: 0;
padding: 0 8px;
list-style: none;
&:after {
content: '';
display: block;
clear: both;
}
> li {
display: block;
float: left;
margin: 4px;
padding: 0;
width: 64px;
height: 64px;
background-color: #eee;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
cursor: move;
&:hover {
> .remove {
display: block;
}
}
float: left;
margin: 4px;
padding: 0;
width: 64px;
height: 64px;
background-color: #eee;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
cursor: move;
&:hover {
> .remove {
display: none;
position: absolute;
right: -6px;
top: -6px;
margin: 0;
padding: 0;
background: transparent;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
cursor: pointer;
display: block;
}
}
}
.buttons {
display: flex;
._button {
margin: 0;
padding: 16px;
font-size: 1em;
font-weight: normal;
text-decoration: none;
transition: color 0.1s ease;
&:hover {
color: var(--accent);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
> .send {
margin-left: auto;
color: var(--accent);
&:hover {
color: var(--accentLighten);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
}
input[type=file] {
display: none;
}
}
.file-remove {
display: none;
position: absolute;
right: -6px;
top: -6px;
margin: 0;
padding: 0;
background: transparent;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
cursor: pointer;
}
*/
.buttons {
display: flex;
}
.button {
margin: 0;
padding: 16px;
font-size: 1em;
font-weight: normal;
text-decoration: none;
transition: color 0.1s ease;
&:hover {
color: var(--accent);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
.send {
margin-left: auto;
color: var(--accent);
&:hover {
color: var(--accentLighten);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
.file-input {
display: none;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="thvuemwp" :class="{ isMe }">
<MkAvatar class="avatar" :user="message.user" :show-indicator="true"/>
<MkAvatar class="avatar" :user="message.user" indicator link preview/>
<div class="content">
<div class="balloon" :class="{ noText: message.text == null }">
<button v-if="isMe" class="delete-button" :title="$ts.delete" @click="del">

View File

@ -19,7 +19,7 @@
<div class="">
<div class="users">
<div v-for="user in users" :key="user.id" class="user _panel blur">
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
<MkAvatar :user="user" class="avatar" indicator link preview/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>

View File

@ -21,7 +21,7 @@
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user">
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
<MkAvatar :user="item.user" class="avatar" indicator link preview/> <MkUserName :user="item.user" :nowrap="false"/>
</div>
</MkA>
</div>

View File

@ -27,7 +27,7 @@
</div>
</div>
<div class="user">
<MkAvatar :user="page.user" class="avatar"/>
<MkAvatar :user="page.user" class="avatar" link preview/>
<div class="name">
<MkUserName :user="page.user" style="display: block;"/>
<MkAcct :user="page.user"/>

View File

@ -53,7 +53,7 @@ let aiscript: Interpreter;
const code = ref('');
const logs = ref<any[]>([]);
const root = ref<AsUiRoot>();
let components: Ref<AsUiComponent>[] = [];
let components: Ref<AsUiComponent>[] = $ref([]);
let uiKey = $ref(0);
const saved = miLocalStorage.getItem('scratchpad');

View File

@ -2,7 +2,7 @@
<div class="_gaps_m">
<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<div class="avatar">
<MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/>
<MkAvatar class="avatar" :user="$i" @click="changeAvatar"/>
<MkButton primary rounded class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
</div>
<MkButton primary rounded class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>

View File

@ -5,7 +5,7 @@
<FormSuspense :p="init">
<div v-if="tab === 'overview'" class="_gaps_m">
<div class="aeakzknw">
<MkAvatar class="avatar" :user="user" :show-indicator="true"/>
<MkAvatar class="avatar" :user="user" indicator link preview/>
<div class="body">
<span class="name"><MkUserName class="name" :user="user"/></span>
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>

View File

@ -29,7 +29,7 @@
<MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/>
<MkAvatar class="avatar" :user="user" indicator/>
<div class="title">
<MkUserName :user="user" :nowrap="false" class="name"/>
<div class="bottom">
@ -44,8 +44,10 @@
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">{{ role.name }}</span>
</div>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
<MkOmit>
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
</MkOmit>
</div>
<div class="fields system">
<dl v-if="user.location" class="field">
@ -121,6 +123,7 @@ import MkContainer from '@/components/MkContainer.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
import MkTab from '@/components/MkTab.vue';
import MkOmit from '@/components/MkOmit.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkEmoji from '@/components/global/MkEmoji.vue';
import { getScrollPosition } from '@/scripts/scroll';

View File

@ -2,6 +2,7 @@ import { utils, values } from '@syuilo/aiscript';
import * as os from '@/os';
import { $i } from '@/account';
import { miLocalStorage } from '@/local-storage';
import { customEmojis } from '@/custom-emojis';
export function createAiScriptEnv(opts) {
let apiRequests = 0;
@ -9,6 +10,7 @@ export function createAiScriptEnv(opts) {
USER_ID: $i ? values.STR($i.id) : values.NULL,
USER_NAME: $i ? values.STR($i.name) : values.NULL,
USER_USERNAME: $i ? values.STR($i.username) : values.NULL,
CUSTOM_EMOJIS: utils.jsToVal(customEmojis),
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
await os.alert({
type: type ? type.value : 'info',

View File

@ -39,6 +39,7 @@ export type AsUiMfm = AsUiComponentBase & {
type: 'mfm';
text?: string;
size?: number;
bold?: boolean;
color?: string;
font?: 'serif' | 'sans-serif' | 'monospace';
};
@ -208,6 +209,8 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
if (text) utils.assertString(text);
const size = def.value.get('size');
if (size) utils.assertNumber(size);
const bold = def.value.get('bold');
if (bold) utils.assertBoolean(bold);
const color = def.value.get('color');
if (color) utils.assertString(color);
const font = def.value.get('font');
@ -216,6 +219,7 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
return {
text: text?.value,
size: size?.value,
bold: bold?.value,
color: color?.value,
font: font?.value,
};

View File

@ -86,6 +86,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: [] as string[],
},
hiddenAds: {
where: 'account',
default: [] as string[],
},
menu: {
where: 'deviceAccount',
@ -297,10 +301,10 @@ interface Watcher {
/**
* 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
*/
import { miLocalStorage } from './local-storage';
import lightTheme from '@/themes/l-simkey.json5';
import darkTheme from '@/themes/d-simkey.json5';
import { Note, UserDetailed } from 'misskey-js/built/entities';
import { miLocalStorage } from './local-storage';
export class ColdDeviceStorage {
public static default = {

View File

@ -98,10 +98,15 @@ a {
color: inherit;
tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
&:hover {
text-decoration: underline;
}
&[target="_blank"] {
-webkit-touch-callout: default;
}
}
textarea, input {

View File

@ -149,6 +149,7 @@ if ($i) {
z-index: 4000000;
top: 15px;
right: 15px;
pointer-events: none;
&:before {
content: "";

View File

@ -9,7 +9,7 @@
<div v-if="info" class="page active link">
<div class="title">
<i v-if="info.icon" class="icon" :class="info.icon"></i>
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" indicator/>
<span v-if="info.title" class="text">{{ info.title }}</span>
<MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/>
</div>
@ -28,7 +28,7 @@
</button>
<div v-if="info" class="title">
<i v-if="info.icon" class="icon" :class="info.icon"></i>
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" indicator/>
<span v-if="info.title" class="text">{{ info.title }}</span>
<MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/>
</div>

View File

@ -50,7 +50,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
const parser = new Parser();
const root = ref<AsUiRoot>();
const components: Ref<AsUiComponent>[] = [];
const components: Ref<AsUiComponent>[] = $ref([]);
async function run() {
const aiscript = new Interpreter({

View File

@ -2,7 +2,7 @@
<div class="_panel">
<div :class="$style.container" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<div :class="$style.avatarContainer">
<MkAvatar :class="$style.avatar" :user="$i" :disable-link="true" :disable-preview="true"/>
<MkAvatar :class="$style.avatar" :user="$i"/>
</div>
<div :class="$style.bodyContainer">
<div :class="$style.body">

View File

@ -10,9 +10,9 @@
</div>
<MkLoading v-else-if="fetching"/>
<div v-else class="users">
<MkA v-for="user in users" :key="user.id" class="user">
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
</MkA>
<span v-for="user in users" :key="user.id" class="user">
<MkAvatar :user="user" class="avatar" indicator link preview/>
</span>
</div>
</div>
</MkContainer>

View File

@ -60,11 +60,11 @@ export default defineConfig(({ command, mode }) => {
css: {
modules: {
generateScopedName: (name, filename, css) => {
const id = (path.relative(__dirname, filename.split('?')[0]) + '-' + name).replace(/[\\\/\.\?&=]/g, '-').replace(/(src-|vue-)/g, '');
if (process.env.NODE_ENV === 'production') {
return 'x' + toBase62(hash(`${filename} ${name}`)).substring(0, 4);
return 'x' + toBase62(hash(id)).substring(0, 4);
} else {
//return 'x' + toBase62(hash(`${filename} ${name}`)).substring(0, 4) + '-' + name;
return (path.relative(__dirname, filename.split('?')[0]) + '-' + name).replace(/[\\\/\.\?&=]/g, '-').replace(/(src-|vue-)/g, '');
return id;
}
},
},

13495
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,4 @@
packages:
- 'packages/backend'
- 'packages/frontend'
- 'packages/sw'

Some files were not shown because too many files have changed in this diff Show More