Compare commits
17 Commits
13.0.0-rc.
...
13.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d0157b3bfd | ||
![]() |
7fc8d2e6d5 | ||
![]() |
fb0f9711ba | ||
![]() |
92136272b0 | ||
![]() |
e1159e9ef2 | ||
![]() |
a2e61c6708 | ||
![]() |
726959911c | ||
![]() |
d59914b959 | ||
![]() |
07025caee9 | ||
![]() |
1c0289e490 | ||
![]() |
275fcd8bbc | ||
![]() |
0c0aa93668 | ||
![]() |
bfcd5ea440 | ||
![]() |
3ff43cca02 | ||
![]() |
6bd536c526 | ||
![]() |
7738a36014 | ||
![]() |
daddec8362 |
151
.config/docker_example.yml
Normal file
151
.config/docker_example.yml
Normal file
@@ -0,0 +1,151 @@
|
||||
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
# Misskey configuration
|
||||
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
# ┌─────┐
|
||||
#───┘ URL └─────────────────────────────────────────────────────
|
||||
|
||||
# Final accessible URL seen by a user.
|
||||
url: https://example.tld/
|
||||
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# URL SETTINGS AFTER THAT!
|
||||
|
||||
# ┌───────────────────────┐
|
||||
#───┘ Port and TLS settings └───────────────────────────────────
|
||||
|
||||
#
|
||||
# Misskey requires a reverse proxy to support HTTPS connections.
|
||||
#
|
||||
# +----- https://example.tld/ ------------+
|
||||
# +------+ |+-------------+ +----------------+|
|
||||
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
|
||||
# +------+ |+-------------+ +----------------+|
|
||||
# +---------------------------------------+
|
||||
#
|
||||
# You need to set up a reverse proxy. (e.g. nginx)
|
||||
# An encrypted connection with HTTPS is highly recommended
|
||||
# because tokens may be transferred in GET requests.
|
||||
|
||||
# The port that your Misskey server should listen on.
|
||||
port: 3000
|
||||
|
||||
# ┌──────────────────────────┐
|
||||
#───┘ PostgreSQL configuration └────────────────────────────────
|
||||
|
||||
db:
|
||||
host: db
|
||||
port: 5432
|
||||
|
||||
# Database name
|
||||
db: misskey
|
||||
|
||||
# Auth
|
||||
user: example-misskey-user
|
||||
pass: example-misskey-pass
|
||||
|
||||
# Whether disable Caching queries
|
||||
#disableCache: true
|
||||
|
||||
# Extra Connection options
|
||||
#extra:
|
||||
# ssl: true
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Redis configuration └─────────────────────────────────────
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||
#pass: example-pass
|
||||
#prefix: example-prefix
|
||||
#db: 1
|
||||
|
||||
# ┌─────────────────────────────┐
|
||||
#───┘ Elasticsearch configuration └─────────────────────────────
|
||||
|
||||
#elasticsearch:
|
||||
# host: localhost
|
||||
# port: 9200
|
||||
# ssl: false
|
||||
# user:
|
||||
# pass:
|
||||
|
||||
# ┌───────────────┐
|
||||
#───┘ ID generation └───────────────────────────────────────────
|
||||
|
||||
# You can select the ID generation method.
|
||||
# You don't usually need to change this setting, but you can
|
||||
# change it according to your preferences.
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: 'aid'
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
||||
# Whether disable HSTS
|
||||
#disableHsts: true
|
||||
|
||||
# Number of worker processes
|
||||
#clusterLimit: 1
|
||||
|
||||
# Job concurrency per worker
|
||||
# deliverJobConcurrency: 128
|
||||
# inboxJobConcurrency: 16
|
||||
|
||||
# Job rate limiter
|
||||
# deliverJobPerSec: 128
|
||||
# inboxJobPerSec: 16
|
||||
|
||||
# Job attempts
|
||||
# deliverJobMaxAttempts: 12
|
||||
# inboxJobMaxAttempts: 8
|
||||
|
||||
# IP address family used for outgoing request (ipv4, ipv6 or dual)
|
||||
#outgoingAddressFamily: ipv4
|
||||
|
||||
# Syslog option
|
||||
#syslog:
|
||||
# host: localhost
|
||||
# port: 514
|
||||
|
||||
# Proxy for HTTP/HTTPS
|
||||
#proxy: http://127.0.0.1:3128
|
||||
|
||||
proxyBypassHosts:
|
||||
- api.deepl.com
|
||||
- api-free.deepl.com
|
||||
- www.recaptcha.net
|
||||
- hcaptcha.com
|
||||
- challenges.cloudflare.com
|
||||
|
||||
# Proxy for SMTP/SMTPS
|
||||
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
|
||||
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
|
||||
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
|
||||
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: false)
|
||||
#proxyRemoteFiles: true
|
||||
|
||||
# Sign to ActivityPub GET request (default: true)
|
||||
signToActivityPubGet: true
|
||||
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -5,6 +5,11 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
@@ -20,3 +25,8 @@ updates:
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0
|
||||
- package-ecosystem: npm
|
||||
directory: "/packages/sw"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 0
|
||||
|
4
.github/workflows/docker-develop.yml
vendored
4
.github/workflows/docker-develop.yml
vendored
@@ -10,10 +10,10 @@ jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: github.repository == 'misskey-dev/misskey'
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
|
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
||||
yarn_install:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
@@ -33,11 +33,11 @@ jobs:
|
||||
- frontend
|
||||
- sw
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: actions/setup-node@v3.2.0
|
||||
- uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
|
8
.github/workflows/pr-preview-deploy.yml
vendored
8
.github/workflows/pr-preview-deploy.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
github.event.client_payload.slash_command.sha != '' &&
|
||||
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
|
||||
steps:
|
||||
- uses: actions/github-script@v5
|
||||
- uses: actions/github-script@v6.3.3
|
||||
id: check-id
|
||||
env:
|
||||
number: ${{ github.event.client_payload.pull_request.number }}
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
return check[0].id;
|
||||
|
||||
- uses: actions/github-script@v5
|
||||
- uses: actions/github-script@v6.3.3
|
||||
env:
|
||||
check_id: ${{ steps.check-id.outputs.result }}
|
||||
details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
# Check out merge commit
|
||||
- name: Fork based /deploy checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
timeout: 15m
|
||||
|
||||
# Update check run called "integration-fork"
|
||||
- uses: actions/github-script@v5
|
||||
- uses: actions/github-script@v6.3.3
|
||||
id: update-check-run
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -30,11 +30,11 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3.2.0
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'yarn'
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
submodules: true
|
||||
# https://github.com/cypress-io/cypress-docker-images/issues/150
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
#- uses: browser-actions/setup-firefox@latest
|
||||
# if: ${{ matrix.browser == 'firefox' }}
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3.2.0
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'yarn'
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ coverage
|
||||
# config
|
||||
/.config/*
|
||||
!/.config/example.yml
|
||||
!/.config/docker_example.yml
|
||||
!/.config/docker_example.env
|
||||
|
||||
# misskey
|
||||
|
@@ -74,6 +74,7 @@ You should also include the user name that made the change.
|
||||
- Push notification of Antenna note @tamaina
|
||||
- AVIF support @tamaina
|
||||
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
|
||||
- レートリミットをユーザーごとに調整可能に @syuilo
|
||||
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo
|
||||
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo
|
||||
- クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo
|
||||
@@ -99,6 +100,7 @@ You should also include the user name that made the change.
|
||||
- Client: Add link to user RSS feed in profile menu @ssmucny
|
||||
- Client: Compress non-animated PNG files @saschanaz
|
||||
- Client: YouTube window player @sim1222
|
||||
- Client: show readable error when rate limit exceeded @syuilo
|
||||
- Client: enhance dashboard of control panel @syuilo
|
||||
- Client: Vite is upgraded to v4 @syuilo, @tamaina
|
||||
- Client: HMR is available while yarn dev @tamaina
|
||||
|
2
COPYING
2
COPYING
@@ -1,5 +1,5 @@
|
||||
Unless otherwise stated this repository is
|
||||
Copyright © 2014-2022 syuilo and contributers
|
||||
Copyright © 2014-2023 syuilo and contributers
|
||||
|
||||
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
||||
|
||||
|
@@ -933,6 +933,8 @@ unassign: "アサインを解除"
|
||||
color: "色"
|
||||
manageCustomEmojis: "カスタム絵文字の管理"
|
||||
youCannotCreateAnymore: "これ以上作成することはできません。"
|
||||
cannotPerformTemporary: "一時的に利用できません"
|
||||
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
|
||||
|
||||
_role:
|
||||
new: "ロールの作成"
|
||||
@@ -970,6 +972,8 @@ _role:
|
||||
noteEachClipsMax: "クリップ内のノートの最大数"
|
||||
userListMax: "ユーザーリストの作成可能数"
|
||||
userEachUserListsMax: "ユーザーリスト内のユーザーの最大数"
|
||||
rateLimitFactor: "レートリミット"
|
||||
descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。"
|
||||
_condition:
|
||||
isLocal: "ローカルユーザー"
|
||||
isRemote: "リモートユーザー"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.0.0-rc.7",
|
||||
"version": "13.0.0-rc.8",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -28,6 +28,7 @@ export type RoleOptions = {
|
||||
noteEachClipsLimit: number;
|
||||
userListLimit: number;
|
||||
userEachUserListsLimit: number;
|
||||
rateLimitFactor: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_ROLE: RoleOptions = {
|
||||
@@ -45,6 +46,7 @@ export const DEFAULT_ROLE: RoleOptions = {
|
||||
noteEachClipsLimit: 200,
|
||||
userListLimit: 10,
|
||||
userEachUserListsLimit: 50,
|
||||
rateLimitFactor: 1,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -221,6 +223,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||
noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')),
|
||||
userListLimit: Math.max(...getOptionValues('userListLimit')),
|
||||
userEachUserListsLimit: Math.max(...getOptionValues('userEachUserListsLimit')),
|
||||
rateLimitFactor: Math.max(...getOptionValues('rateLimitFactor')),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -224,8 +224,11 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||
limit.key = ep.name;
|
||||
}
|
||||
|
||||
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
|
||||
const factor = user ? (await this.roleService.getUserRoleOptions(user.id)).rateLimitFactor : 1;
|
||||
|
||||
// Rate limit
|
||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(err => {
|
||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
||||
throw new ApiError({
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
|
@@ -26,7 +26,7 @@ export class RateLimiterService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) {
|
||||
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1) {
|
||||
return new Promise<void>((ok, reject) => {
|
||||
if (this.disabled) ok();
|
||||
|
||||
@@ -34,7 +34,7 @@ export class RateLimiterService {
|
||||
const min = (): void => {
|
||||
const minIntervalLimiter = new Limiter({
|
||||
id: `${actor}:${limitation.key}:min`,
|
||||
duration: limitation.minInterval,
|
||||
duration: limitation.minInterval * factor,
|
||||
max: 1,
|
||||
db: this.redisClient,
|
||||
});
|
||||
@@ -62,8 +62,8 @@ export class RateLimiterService {
|
||||
const max = (): void => {
|
||||
const limiter = new Limiter({
|
||||
id: `${actor}:${limitation.key}`,
|
||||
duration: limitation.duration,
|
||||
max: limitation.max,
|
||||
duration: limitation.duration * factor,
|
||||
max: limitation.max / factor,
|
||||
db: this.redisClient,
|
||||
});
|
||||
|
||||
|
@@ -73,6 +73,7 @@
|
||||
"@types/gulp": "4.0.10",
|
||||
"@types/gulp-rename": "2.0.1",
|
||||
"@types/matter-js": "0.18.2",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/sanitize-html": "^2.8.0",
|
||||
"@types/seedrandom": "3.0.4",
|
||||
|
@@ -78,9 +78,9 @@ const inputEl = shallowRef<HTMLElement>();
|
||||
const prefixEl = shallowRef<HTMLElement>();
|
||||
const suffixEl = shallowRef<HTMLElement>();
|
||||
const height =
|
||||
props.small ? 34 :
|
||||
props.large ? 40 :
|
||||
37;
|
||||
props.small ? 33 :
|
||||
props.large ? 39 :
|
||||
36;
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const onInput = (ev: KeyboardEvent) => {
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="fefdfafb">
|
||||
<MkAvatar class="avatar" :user="$i"/>
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<div :class="$style.root">
|
||||
<MkAvatar :class="$style.avatar" :user="$i"/>
|
||||
<div :class="$style.main">
|
||||
<div :class="$style.header">
|
||||
<MkUserName :user="$i"/>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="content">
|
||||
<div>
|
||||
<div :class="$style.content">
|
||||
<Mfm :text="text.trim()" :author="$i" :i="$i"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,75 +22,48 @@ const props = defineProps<{
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fefdfafb {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: clip;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin: 0 10px 0 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0 !important;
|
||||
display: block !important;
|
||||
margin: 0 10px 0 0 !important;
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
border-radius: 8px !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
> .header {
|
||||
margin-bottom: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .body {
|
||||
|
||||
> .cw {
|
||||
cursor: default;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
> .text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .text {
|
||||
cursor: default;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@container (min-width: 350px) {
|
||||
.fefdfafb {
|
||||
> .avatar {
|
||||
margin: 0 10px 0 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 10px 0 0 !important;
|
||||
width: 44px !important;
|
||||
height: 44px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 500px) {
|
||||
.fefdfafb {
|
||||
> .avatar {
|
||||
margin: 0 12px 0 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.avatar {
|
||||
margin: 0 12px 0 0 !important;
|
||||
width: 48px !important;
|
||||
height: 48px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -173,7 +173,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
|
||||
$thumbWidth: 20px;
|
||||
|
||||
> .body {
|
||||
padding: 8px 12px;
|
||||
padding: 7px 12px;
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--panel);
|
||||
border-radius: 6px;
|
||||
|
@@ -65,9 +65,9 @@ const prefixEl = ref(null);
|
||||
const suffixEl = ref(null);
|
||||
const container = ref(null);
|
||||
const height =
|
||||
props.small ? 34 :
|
||||
props.large ? 40 :
|
||||
37;
|
||||
props.small ? 33 :
|
||||
props.large ? 39 :
|
||||
36;
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const onInput = (ev) => {
|
||||
|
@@ -1,42 +1,42 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
|
||||
<div class="gqyayizv _popup">
|
||||
<button key="public" class="_button item" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')">
|
||||
<div class="icon"><i class="ti ti-world"></i></div>
|
||||
<div class="body">
|
||||
<span>{{ i18n.ts._visibility.public }}</span>
|
||||
<span>{{ i18n.ts._visibility.publicDescription }}</span>
|
||||
<div class="_popup" :class="$style.root">
|
||||
<button key="public" class="_button" :class="[$style.item, { [$style.active]: v === 'public' }]" data-index="1" @click="choose('public')">
|
||||
<div :class="$style.icon"><i class="ti ti-world"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.public }}</span>
|
||||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.publicDescription }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button key="home" class="_button item" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')">
|
||||
<div class="icon"><i class="ti ti-home"></i></div>
|
||||
<div class="body">
|
||||
<span>{{ i18n.ts._visibility.home }}</span>
|
||||
<span>{{ i18n.ts._visibility.homeDescription }}</span>
|
||||
<button key="home" class="_button" :class="[$style.item, { [$style.active]: v === 'home' }]" data-index="2" @click="choose('home')">
|
||||
<div :class="$style.icon"><i class="ti ti-home"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.home }}</span>
|
||||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.homeDescription }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button key="followers" class="_button item" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
|
||||
<div class="icon"><i class="ti ti-lock"></i></div>
|
||||
<div class="body">
|
||||
<span>{{ i18n.ts._visibility.followers }}</span>
|
||||
<span>{{ i18n.ts._visibility.followersDescription }}</span>
|
||||
<button key="followers" class="_button" :class="[$style.item, { [$style.active]: v === 'followers' }]" data-index="3" @click="choose('followers')">
|
||||
<div :class="$style.icon"><i class="ti ti-lock"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.followers }}</span>
|
||||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.followersDescription }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button key="specified" :disabled="localOnly" class="_button item" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')">
|
||||
<div class="icon"><i class="ti ti-mail"></i></div>
|
||||
<div class="body">
|
||||
<span>{{ i18n.ts._visibility.specified }}</span>
|
||||
<span>{{ i18n.ts._visibility.specifiedDescription }}</span>
|
||||
<button key="specified" :disabled="localOnly" class="_button" :class="[$style.item, { [$style.active]: v === 'specified' }]" data-index="4" @click="choose('specified')">
|
||||
<div :class="$style.icon"><i class="ti ti-mail"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.specified }}</span>
|
||||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.specifiedDescription }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
<button key="localOnly" class="_button item localOnly" :class="{ active: localOnly }" data-index="5" @click="localOnly = !localOnly">
|
||||
<div class="icon"><i class="ti ti-world-off"></i></div>
|
||||
<div class="body">
|
||||
<span>{{ i18n.ts._visibility.localOnly }}</span>
|
||||
<span>{{ i18n.ts._visibility.localOnlyDescription }}</span>
|
||||
<div :class="$style.divider"></div>
|
||||
<button key="localOnly" class="_button" :class="[$style.item, $style.localOnly, { [$style.active]: localOnly }]" data-index="5" @click="localOnly = !localOnly">
|
||||
<div :class="$style.icon"><i class="ti ti-world-off"></i></div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{ i18n.ts._visibility.localOnly }}</span>
|
||||
<span :class="$style.itemDescription">{{ i18n.ts._visibility.localOnlyDescription }}</span>
|
||||
</div>
|
||||
<div class="toggle"><i :class="localOnly ? 'ti ti-toggle-right' : 'ti ti-toggle-left'"></i></div>
|
||||
<div :class="$style.toggle"><i :class="localOnly ? 'ti ti-toggle-right' : 'ti ti-toggle-left'"></i></div>
|
||||
</button>
|
||||
</div>
|
||||
</MkModal>
|
||||
@@ -79,81 +79,81 @@ function choose(visibility: typeof misskey.noteVisibilities[number]): void {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.gqyayizv {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
width: 240px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
> .divider {
|
||||
margin: 8px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
.divider {
|
||||
margin: 8px 0;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
padding: 8px 14px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: flex;
|
||||
padding: 8px 14px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
&:active {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
&.active {
|
||||
color: var(--fgOnAccent);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--fgOnAccent);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
&.localOnly.active {
|
||||
color: var(--accent);
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
> .body {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
> span:first-child {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> span:last-child:not(:first-child) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
> .toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
&.localOnly.active {
|
||||
color: var(--accent);
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.itemTitle {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.itemDescription {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')">
|
||||
<div class="iuyakobc" :class="{ iconOnly: (text == null) || success }">
|
||||
<i v-if="success" class="ti ti-check icon success"></i>
|
||||
<MkLoading v-else class="icon waiting" :em="true"/>
|
||||
<div v-if="text && !success" class="text">{{ text }}<MkEllipsis/></div>
|
||||
<div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]">
|
||||
<i v-if="success" :class="[$style.icon, $style.success]" class="ti ti-check"></i>
|
||||
<MkLoading v-else :class="[$style.icon, $style.waiting]" :em="true"/>
|
||||
<div v-if="text && !success" :class="$style.text">{{ text }}<MkEllipsis/></div>
|
||||
</div>
|
||||
</MkModal>
|
||||
</template>
|
||||
@@ -35,8 +35,8 @@ watch(() => props.showing, () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iuyakobc {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
padding: 32px;
|
||||
@@ -54,21 +54,21 @@ watch(() => props.showing, () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: 32px;
|
||||
.icon {
|
||||
font-size: 32px;
|
||||
|
||||
&.success {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
&.waiting {
|
||||
opacity: 0.7;
|
||||
}
|
||||
&.success {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
> .text {
|
||||
margin-top: 16px;
|
||||
&.waiting {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -20,7 +20,10 @@ export const apiWithDialog = ((
|
||||
promiseDialog(promise, null, (err) => {
|
||||
let title = null;
|
||||
let text = err.message + '\n' + (err as any).id;
|
||||
if (err.code.startsWith('TOO_MANY')) {
|
||||
if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
title = i18n.ts.cannotPerformTemporary;
|
||||
text = i18n.ts.cannotPerformTemporaryDescription;
|
||||
} else if (err.code.startsWith('TOO_MANY')) {
|
||||
title = i18n.ts.youCannotCreateAnymore;
|
||||
text = `${i18n.ts.error}: ${err.id}`;
|
||||
}
|
||||
|
@@ -38,6 +38,19 @@
|
||||
<FormSlot>
|
||||
<template #label>{{ i18n.ts._role.options }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template>
|
||||
<template #suffix>{{ options_rateLimitFactor_useDefault ? i18n.ts._role.useBaseValue : `${Math.floor(options_rateLimitFactor_value * 100)}%` }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_rateLimitFactor_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange :model-value="options_rateLimitFactor_value * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => options_rateLimitFactor_value = (v / 100)">
|
||||
<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.gtlAvailable }}</template>
|
||||
<template #suffix>{{ options_gtlAvailable_useDefault ? i18n.ts._role.useBaseValue : (options_gtlAvailable_value ? i18n.ts.yes : i18n.ts.no) }}</template>
|
||||
@@ -241,9 +254,11 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'created', payload: any): void;
|
||||
@@ -266,33 +281,35 @@ let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' });
|
||||
let isPublic = $ref(role?.isPublic ?? false);
|
||||
let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false);
|
||||
let options_gtlAvailable_useDefault = $ref(role?.options?.gtlAvailable?.useDefault ?? true);
|
||||
let options_gtlAvailable_value = $ref(role?.options?.gtlAvailable?.value ?? false);
|
||||
let options_gtlAvailable_value = $ref(role?.options?.gtlAvailable?.value ?? instance.baseRole.gtlAvailable);
|
||||
let options_ltlAvailable_useDefault = $ref(role?.options?.ltlAvailable?.useDefault ?? true);
|
||||
let options_ltlAvailable_value = $ref(role?.options?.ltlAvailable?.value ?? false);
|
||||
let options_ltlAvailable_value = $ref(role?.options?.ltlAvailable?.value ?? instance.baseRole.ltlAvailable);
|
||||
let options_canPublicNote_useDefault = $ref(role?.options?.canPublicNote?.useDefault ?? true);
|
||||
let options_canPublicNote_value = $ref(role?.options?.canPublicNote?.value ?? false);
|
||||
let options_canPublicNote_value = $ref(role?.options?.canPublicNote?.value ?? instance.baseRole.canPublicNote);
|
||||
let options_canInvite_useDefault = $ref(role?.options?.canInvite?.useDefault ?? true);
|
||||
let options_canInvite_value = $ref(role?.options?.canInvite?.value ?? false);
|
||||
let options_canInvite_value = $ref(role?.options?.canInvite?.value ?? instance.baseRole.canInvite);
|
||||
let options_canManageCustomEmojis_useDefault = $ref(role?.options?.canManageCustomEmojis?.useDefault ?? true);
|
||||
let options_canManageCustomEmojis_value = $ref(role?.options?.canManageCustomEmojis?.value ?? false);
|
||||
let options_canManageCustomEmojis_value = $ref(role?.options?.canManageCustomEmojis?.value ?? instance.baseRole.canManageCustomEmojis);
|
||||
let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.useDefault ?? true);
|
||||
let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0);
|
||||
let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? instance.baseRole.driveCapacityMb);
|
||||
let options_pinLimit_useDefault = $ref(role?.options?.pinLimit?.useDefault ?? true);
|
||||
let options_pinLimit_value = $ref(role?.options?.pinLimit?.value ?? 0);
|
||||
let options_pinLimit_value = $ref(role?.options?.pinLimit?.value ?? instance.baseRole.pinLimit);
|
||||
let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true);
|
||||
let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0);
|
||||
let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? instance.baseRole.antennaLimit);
|
||||
let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDefault ?? true);
|
||||
let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0);
|
||||
let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? instance.baseRole.wordMuteLimit);
|
||||
let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true);
|
||||
let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0);
|
||||
let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? instance.baseRole.webhookLimit);
|
||||
let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? true);
|
||||
let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? 0);
|
||||
let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? instance.baseRole.clipLimit);
|
||||
let options_noteEachClipsLimit_useDefault = $ref(role?.options?.noteEachClipsLimit?.useDefault ?? true);
|
||||
let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? 0);
|
||||
let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? instance.baseRole.noteEachClipsLimit);
|
||||
let options_userListLimit_useDefault = $ref(role?.options?.userListLimit?.useDefault ?? true);
|
||||
let options_userListLimit_value = $ref(role?.options?.userListLimit?.value ?? 0);
|
||||
let options_userListLimit_value = $ref(role?.options?.userListLimit?.value ?? instance.baseRole.userListLimit);
|
||||
let options_userEachUserListsLimit_useDefault = $ref(role?.options?.userEachUserListsLimit?.useDefault ?? true);
|
||||
let options_userEachUserListsLimit_value = $ref(role?.options?.userEachUserListsLimit?.value ?? 0);
|
||||
let options_userEachUserListsLimit_value = $ref(role?.options?.userEachUserListsLimit?.value ?? instance.baseRole.userEachUserListsLimit);
|
||||
let options_rateLimitFactor_useDefault = $ref(role?.options?.rateLimitFactor?.useDefault ?? true);
|
||||
let options_rateLimitFactor_value = $ref(role?.options?.rateLimitFactor?.value ?? instance.baseRole.rateLimitFactor);
|
||||
|
||||
if (_DEV_) {
|
||||
watch($$(condFormula), () => {
|
||||
@@ -316,6 +333,7 @@ function getOptions() {
|
||||
noteEachClipsLimit: { useDefault: options_noteEachClipsLimit_useDefault, value: options_noteEachClipsLimit_value },
|
||||
userListLimit: { useDefault: options_userListLimit_useDefault, value: options_userListLimit_value },
|
||||
userEachUserListsLimit: { useDefault: options_userEachUserListsLimit_useDefault, value: options_userEachUserListsLimit_value },
|
||||
rateLimitFactor: { useDefault: options_rateLimitFactor_useDefault, value: options_rateLimitFactor_value },
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,14 @@
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role.baseRole }}</template>
|
||||
<div class="_gaps">
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template>
|
||||
<template #suffix>{{ Math.floor(options_rateLimitFactor * 100) }}%</template>
|
||||
<MkRange :model-value="options_rateLimitFactor * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => options_rateLimitFactor = (v / 100)">
|
||||
<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template>
|
||||
</MkRange>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.gtlAvailable }}</template>
|
||||
<template #suffix>{{ options_gtlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
@@ -134,6 +142,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -159,6 +168,7 @@ let options_clipLimit = $ref(instance.baseRole.clipLimit);
|
||||
let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit);
|
||||
let options_userListLimit = $ref(instance.baseRole.userListLimit);
|
||||
let options_userEachUserListsLimit = $ref(instance.baseRole.userEachUserListsLimit);
|
||||
let options_rateLimitFactor = $ref(instance.baseRole.rateLimitFactor);
|
||||
|
||||
async function updateBaseRole() {
|
||||
await os.apiWithDialog('admin/roles/update-default-role-override', {
|
||||
@@ -177,6 +187,7 @@ async function updateBaseRole() {
|
||||
noteEachClipsLimit: options_noteEachClipsLimit,
|
||||
userListLimit: options_userListLimit,
|
||||
userEachUserListsLimit: options_userEachUserListsLimit,
|
||||
rateLimitFactor: options_rateLimitFactor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: #fff;
|
||||
color: var(--fgOnAccent);
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
@@ -150,10 +150,8 @@ hr {
|
||||
}
|
||||
|
||||
._ghost {
|
||||
&, * {
|
||||
@extend ._noSelect;
|
||||
pointer-events: none;
|
||||
}
|
||||
@extend ._noSelect;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
._modalBg {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div v-if="hasDisconnected && $store.state.serverDisconnectedBehavior === 'quiet'" class="nsbbhtug" @click="resetDisconnected">
|
||||
<div>{{ i18n.ts.disconnectedFromServer }}</div>
|
||||
<div class="command">
|
||||
<button class="_textButton" @click="reload">{{ i18n.ts.reload }}</button>
|
||||
<button class="_textButton">{{ i18n.ts.doNothing }}</button>
|
||||
<div v-if="hasDisconnected && $store.state.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected">
|
||||
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div>
|
||||
<div :class="$style.command" class="_buttons">
|
||||
<MkButton :class="$style.commandButton" small primary @click="reload">{{ i18n.ts.reload }}</MkButton>
|
||||
<MkButton :class="$style.commandButton" small>{{ i18n.ts.doNothing }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -12,6 +12,10 @@
|
||||
import { onUnmounted } from 'vue';
|
||||
import { stream } from '@/stream';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
const zIndex = os.claimZIndex('high');
|
||||
|
||||
let hasDisconnected = $ref(false);
|
||||
|
||||
@@ -34,28 +38,22 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nsbbhtug {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: fixed;
|
||||
z-index: 16385;
|
||||
z-index: v-bind(zIndex);
|
||||
bottom: calc(var(--minBottomSpacing) + var(--margin));
|
||||
right: var(--margin);
|
||||
margin: 0;
|
||||
padding: 6px 12px;
|
||||
padding: 12px;
|
||||
font-size: 0.9em;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
opacity: 0.8;
|
||||
border-radius: 4px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
> .command {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.command {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
> button {
|
||||
padding: 0.7em;
|
||||
}
|
||||
}
|
||||
.commandButton {
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }">
|
||||
<div class="calendar" :class="{ isHoliday }">
|
||||
<p class="month-and-year">
|
||||
<span class="year">{{ $t('yearX', { year }) }}</span>
|
||||
<span class="month">{{ $t('monthX', { month }) }}</span>
|
||||
<div :class="[$style.root, { _panel: !widgetProps.transparent }]">
|
||||
<div :class="[$style.calendar, { [$style.isHoliday]: isHoliday }]">
|
||||
<p :class="$style.monthAndYear">
|
||||
<span :class="$style.year">{{ $t('yearX', { year }) }}</span>
|
||||
<span :class="$style.month">{{ $t('monthX', { month }) }}</span>
|
||||
</p>
|
||||
<p v-if="month === 1 && day === 1" class="day">🎉{{ $t('dayX', { day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
|
||||
<p v-else class="day">{{ $t('dayX', { day }) }}</p>
|
||||
<p class="week-day">{{ weekDay }}</p>
|
||||
<p v-else :class="$style.day">{{ $t('dayX', { day }) }}</p>
|
||||
<p :class="$style.weekDay">{{ weekDay }}</p>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div>
|
||||
<p>{{ i18n.ts.today }}<b>{{ dayP.toFixed(1) }}%</b></p>
|
||||
<div class="meter">
|
||||
<div class="val" :style="{ width: `${dayP}%` }"></div>
|
||||
<div :class="$style.info">
|
||||
<div :class="$style.infoSection">
|
||||
<p :class="$style.infoText">{{ i18n.ts.today }}<b :class="$style.percentage">{{ dayP.toFixed(1) }}%</b></p>
|
||||
<div :class="$style.meter">
|
||||
<div :class="$style.meterVal" :style="{ width: `${dayP}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ i18n.ts.thisMonth }}<b>{{ monthP.toFixed(1) }}%</b></p>
|
||||
<div class="meter">
|
||||
<div class="val" :style="{ width: `${monthP}%` }"></div>
|
||||
<div :class="$style.infoSection">
|
||||
<p :class="$style.infoText">{{ i18n.ts.thisMonth }}<b :class="$style.percentage">{{ monthP.toFixed(1) }}%</b></p>
|
||||
<div :class="$style.meter">
|
||||
<div :class="$style.meterVal" :style="{ width: `${monthP}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ i18n.ts.thisYear }}<b>{{ yearP.toFixed(1) }}%</b></p>
|
||||
<div class="meter">
|
||||
<div class="val" :style="{ width: `${yearP}%` }"></div>
|
||||
<div :class="$style.infoSection">
|
||||
<p :class="$style.infoText">{{ i18n.ts.thisYear }}<b :class="$style.percentage">{{ yearP.toFixed(1) }}%</b></p>
|
||||
<div :class="$style.meter">
|
||||
<div :class="$style.meterVal" :style="{ width: `${yearP}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,8 +115,8 @@ defineExpose<WidgetComponentExpose>({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mkw-calendar {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding: 16px 0;
|
||||
|
||||
&:after {
|
||||
@@ -124,91 +124,93 @@ defineExpose<WidgetComponentExpose>({
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
> .calendar {
|
||||
float: left;
|
||||
width: 60%;
|
||||
text-align: center;
|
||||
|
||||
&.isHoliday {
|
||||
> .day {
|
||||
color: #ef95a0;
|
||||
}
|
||||
}
|
||||
|
||||
> .month-and-year, > .week-day {
|
||||
margin: 0;
|
||||
line-height: 18px;
|
||||
font-size: 0.9em;
|
||||
|
||||
> .year, > .month {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
.calendar {
|
||||
float: left;
|
||||
width: 60%;
|
||||
text-align: center;
|
||||
|
||||
&.isHoliday {
|
||||
> .day {
|
||||
margin: 10px 0;
|
||||
line-height: 32px;
|
||||
font-size: 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
> .info {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 40%;
|
||||
padding: 0 16px 0 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
> div {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
> p {
|
||||
display: flex;
|
||||
margin: 0 0 2px 0;
|
||||
font-size: 0.75em;
|
||||
line-height: 18px;
|
||||
opacity: 0.8;
|
||||
|
||||
> b {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .meter {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--X11);
|
||||
border-radius: 8px;
|
||||
|
||||
> .val {
|
||||
height: 4px;
|
||||
transition: width .3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
> .meter > .val {
|
||||
background: #f7796c;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
> .meter > .val {
|
||||
background: #a1de41;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
> .meter > .val {
|
||||
background: #41ddde;
|
||||
}
|
||||
}
|
||||
color: #ef95a0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.monthAndYear,
|
||||
.weekDay {
|
||||
margin: 0;
|
||||
line-height: 18px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.year,
|
||||
.month {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.day {
|
||||
margin: 10px 0;
|
||||
line-height: 32px;
|
||||
font-size: 1.75em;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 40%;
|
||||
padding: 0 16px 0 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.infoSection {
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
> .meter > .meterVal {
|
||||
background: #f7796c;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
> .meter > .meterVal {
|
||||
background: #a1de41;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
> .meter > .meterVal {
|
||||
background: #41ddde;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.infoText {
|
||||
display: flex;
|
||||
margin: 0 0 2px 0;
|
||||
font-size: 0.75em;
|
||||
line-height: 18px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.percentage {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.meter {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--X11);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.meterVal {
|
||||
height: 4px;
|
||||
transition: width .3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
|
||||
<div v-if="widgetProps.showLabel" class="label">{{ tzAbbrev }}</div>
|
||||
<div class="time">
|
||||
<div class="_monospace" :class="[$style.root, { _panel: !widgetProps.transparent }]" :style="{ fontSize: `${widgetProps.fontSize}em` }">
|
||||
<div v-if="widgetProps.showLabel" :class="$style.label">{{ tzAbbrev }}</div>
|
||||
<div>
|
||||
<MkDigitalClock :show-ms="widgetProps.showMs" :offset="tzOffset"/>
|
||||
</div>
|
||||
<div v-if="widgetProps.showLabel" class="label">{{ tzOffsetLabel }}</div>
|
||||
<div v-if="widgetProps.showLabel" :class="$style.label">{{ tzOffsetLabel }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -79,14 +79,14 @@ defineExpose<WidgetComponentExpose>({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mkw-digitalClock {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .label {
|
||||
font-size: 65%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.label {
|
||||
font-size: 65%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,9 +3,9 @@
|
||||
<template #icon><i class="ti ti-note"></i></template>
|
||||
<template #header>{{ i18n.ts._widgets.memo }}</template>
|
||||
|
||||
<div class="otgbylcu">
|
||||
<textarea v-model="text" :placeholder="i18n.ts.placeholder" @input="onChange"></textarea>
|
||||
<button :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
|
||||
<div :class="$style.root">
|
||||
<textarea v-model="text" :class="$style.textarea" :placeholder="i18n.ts.placeholder" @input="onChange"></textarea>
|
||||
<button :class="$style.save" :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
@@ -68,45 +68,45 @@ defineExpose<WidgetComponentExpose>({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.otgbylcu {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding-bottom: 28px + 16px;
|
||||
}
|
||||
|
||||
> textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 16px;
|
||||
color: var(--fg);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
font-size: 0.9em;
|
||||
.textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 16px;
|
||||
color: var(--fg);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
font-size: 0.9em;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
height: 28px;
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: default;
|
||||
}
|
||||
.save {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
height: 28px;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -2555,7 +2555,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:18.11.18":
|
||||
"@types/node@npm:18.11.18, @types/node@npm:^18.11.18":
|
||||
version: 18.11.18
|
||||
resolution: "@types/node@npm:18.11.18"
|
||||
checksum: 03f17f9480f8d775c8a72da5ea7e9383db5f6d85aa5fefde90dd953a1449bd5e4ffde376f139da4f3744b4c83942166d2a7603969a6f8ea826edfb16e6e3b49d
|
||||
@@ -8132,6 +8132,7 @@ __metadata:
|
||||
"@types/gulp": 4.0.10
|
||||
"@types/gulp-rename": 2.0.1
|
||||
"@types/matter-js": 0.18.2
|
||||
"@types/node": ^18.11.18
|
||||
"@types/punycode": 2.1.0
|
||||
"@types/sanitize-html": ^2.8.0
|
||||
"@types/seedrandom": 3.0.4
|
||||
|
Reference in New Issue
Block a user