Merge branch 'develop' into feature/default-post-target-detect-from-path
This commit is contained in:
commit
f680469877
|
@ -54,7 +54,7 @@ jobs:
|
|||
- name: Copy API.json
|
||||
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: api-artifact
|
||||
path: ${{ matrix.api-json-name }}
|
||||
|
@ -67,7 +67,7 @@ jobs:
|
|||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: |
|
||||
echo "$PR_NUMBER" > ./pr_number
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: api-artifact
|
||||
path: pr_number
|
||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
- name: Echo full diff
|
||||
run: cat ./api-full.json.diff
|
||||
- name: Upload full diff to Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: api-artifact
|
||||
path: |
|
||||
|
|
|
@ -108,12 +108,12 @@ jobs:
|
|||
wait-on: 'http://localhost:61812'
|
||||
headed: true
|
||||
browser: ${{ matrix.browser }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: ${{ matrix.browser }}-cypress-screenshots
|
||||
path: cypress/screenshots
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ matrix.browser }}-cypress-videos
|
||||
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -16,6 +16,7 @@
|
|||
## 2023.x.x (unreleased)
|
||||
|
||||
### Note
|
||||
- Node.js 20.10.0が最小要件になりました
|
||||
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
|
||||
|
||||
**影響:**
|
||||
|
@ -36,8 +37,10 @@
|
|||
|
||||
### Client
|
||||
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
|
||||
- Feat: データセーバーでコードハイライトの読み込みを削減できるように
|
||||
- Feat: MFMのアニメーション要素(`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加
|
||||
- Feat: 画面に雪を降らせられるように
|
||||
- Enhance: MFMのアニメーション要素(`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)に `delay` オプションを追加
|
||||
- Enhance: センシティブと判断されたウェブサイトのサムネイルを非表示に
|
||||
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
|
||||
- Enhance: 投稿フォームの絵文字ピッカーをリアクション時に使用するものと同じのを使用するように #12336 #12560
|
||||
- Enhance: リアクション用ピン留め絵文字と投稿時の絵文字入力用ピン留め絵文字を分けて設定できるように #12560
|
||||
- Enhance: 絵文字のオートコンプリート機能強化 #12364
|
||||
|
@ -48,16 +51,19 @@
|
|||
- Enhance: Shareページで投稿を完了すると、親ウィンドウ(親フレーム)にpostMessageするように
|
||||
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
|
||||
- Enhance: ノートプレビューに「内容を隠す」が反映されるように
|
||||
- Enhance: データセーバーでコードハイライトの読み込みを削減できるように
|
||||
- Enhance: データセーバーの適用範囲を個別で設定できるように
|
||||
- 従来のデータセーバーの設定はリセットされます
|
||||
- Enhance: タイムライン上のタブからリスト、アンテナ、チャンネルの管理ページにジャンプできるように
|
||||
- Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように
|
||||
- Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように
|
||||
- Feat: センシティブと判断されたウェブサイトのサムネイルをぼかすように
|
||||
- ウェブサイトをセンシティブと判断する仕組みが動いていないため、summalyProxyを使用しないと機能しません。
|
||||
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
||||
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
||||
- Enhance: 絵文字の詳細ページに記載される情報を追加
|
||||
- Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように
|
||||
- MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました
|
||||
- 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります
|
||||
(例: ` ```js ` → Javascript, ` ```ais ` → AiScript)
|
||||
- Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
|
||||
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
|
||||
- Fix: コードエディタが正しく表示されない問題を修正
|
||||
- Fix: プロフィールの「ファイル」にセンシティブな画像がある際のデザインを修正
|
||||
- Fix: 一度に大量の通知が入った際に通知音が音割れする問題を修正
|
||||
|
@ -67,6 +73,7 @@
|
|||
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
|
||||
- Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正
|
||||
- Fix: ノート中のリアクションの表示を微調整 #12650
|
||||
- Fix: AiScriptの`readline`が不正な値を返すことがある問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
||||
|
@ -85,6 +92,7 @@
|
|||
- Fix: 「みつける」が年越し時に壊れる問題を修正
|
||||
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
|
||||
- Fix: モデレーションログがモデレーターは閲覧できないように修正
|
||||
- Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない
|
||||
|
||||
## 2023.11.1
|
||||
|
||||
|
|
|
@ -128,8 +128,8 @@ export interface Locale {
|
|||
"pinnedEmojisForReactionSettingDescription": string;
|
||||
"pinnedEmojisSettingDescription": string;
|
||||
"emojiPickerDisplay": string;
|
||||
"copyFromPinnedEmojisForReaction": string;
|
||||
"copyFromPinnedEmojis": string;
|
||||
"overwriteFromPinnedEmojisForReaction": string;
|
||||
"overwriteFromPinnedEmojis": string;
|
||||
"reactionSettingDescription2": string;
|
||||
"rememberNoteVisibility": string;
|
||||
"attachCancel": string;
|
||||
|
@ -1182,6 +1182,8 @@ export interface Locale {
|
|||
"reloadRequiredToApplySettings": string;
|
||||
"remainingN": string;
|
||||
"overwriteContentConfirm": string;
|
||||
"seasonalScreenEffect": string;
|
||||
"decorate": string;
|
||||
"_announcement": {
|
||||
"forExistingUsers": string;
|
||||
"forExistingUsersDescription": string;
|
||||
|
|
|
@ -125,8 +125,8 @@ emojiPicker: "絵文字ピッカー"
|
|||
pinnedEmojisForReactionSettingDescription: "リアクション時にピン留め表示する絵文字を設定できます"
|
||||
pinnedEmojisSettingDescription: "絵文字入力時にピン留め表示する絵文字を設定できます"
|
||||
emojiPickerDisplay: "ピッカーの表示"
|
||||
copyFromPinnedEmojisForReaction: "リアクション設定からコピーする"
|
||||
copyFromPinnedEmojis: "絵文字設定からコピーする"
|
||||
overwriteFromPinnedEmojisForReaction: "リアクション設定から上書きする"
|
||||
overwriteFromPinnedEmojis: "全般設定から上書きする"
|
||||
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
|
||||
rememberNoteVisibility: "公開範囲を記憶する"
|
||||
attachCancel: "添付取り消し"
|
||||
|
@ -1179,6 +1179,8 @@ code: "コード"
|
|||
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"
|
||||
remainingN: "残り: {n}"
|
||||
overwriteContentConfirm: "現在の内容に上書きされますがよろしいですか?"
|
||||
seasonalScreenEffect: "季節に応じた画面の演出"
|
||||
decorate: "デコる"
|
||||
|
||||
_announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
|
|
10
package.json
10
package.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2023.12.0-beta.4",
|
||||
"version": "2023.12.0-beta.5",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"packageManager": "pnpm@8.12.1",
|
||||
"workspaces": [
|
||||
"packages/frontend",
|
||||
"packages/backend",
|
||||
|
@ -46,10 +46,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"execa": "8.0.1",
|
||||
"cssnano": "6.0.1",
|
||||
"cssnano": "6.0.2",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.32",
|
||||
"terser": "5.24.0",
|
||||
"terser": "5.26.0",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -57,7 +57,7 @@
|
|||
"@typescript-eslint/parser": "6.14.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.6.1",
|
||||
"eslint": "8.55.0",
|
||||
"eslint": "8.56.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"ncp": "2.0.0"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.16.0"
|
||||
"node": ">=20.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/boot/entry.js",
|
||||
|
@ -90,7 +90,7 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "4.15.3",
|
||||
"bullmq": "4.15.4",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.1",
|
||||
"chalk": "5.3.0",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"file-type": "18.7.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "13.0.0",
|
||||
"got": "14.0.0",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.1",
|
||||
|
@ -222,8 +222,8 @@
|
|||
"@typescript-eslint/parser": "6.14.0",
|
||||
"aws-sdk-client-mock": "3.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"execa": "8.0.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
|
|
|
@ -74,6 +74,14 @@ export const packedUserLiteSchema = {
|
|||
format: 'url',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
offsetX: {
|
||||
type: 'number',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
offsetY: {
|
||||
type: 'number',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -138,7 +138,7 @@ export class ActivityPubServerService {
|
|||
return;
|
||||
}
|
||||
|
||||
const algo = match[1];
|
||||
const algo = match[1].toUpperCase();
|
||||
const digestValue = match[2];
|
||||
|
||||
if (algo !== 'SHA-256') {
|
||||
|
|
|
@ -61,6 +61,9 @@ export class FileServerService {
|
|||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
reply.header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
"@tabler/icons-webfont": "2.44.0",
|
||||
"@vitejs/plugin-vue": "4.5.2",
|
||||
"@vue/compiler-sfc": "3.3.11",
|
||||
"astring": "1.8.6",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
|
||||
"astring": "1.8.6",
|
||||
"broadcast-channel": "6.0.0",
|
||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
|
||||
"buraha": "0.0.1",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"escape-regexp": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
"eventemitter3": "5.0.1",
|
||||
"gsap": "3.12.3",
|
||||
"gsap": "3.12.4",
|
||||
"idb-keyval": "6.2.1",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"is-file-animated": "1.0.2",
|
||||
|
@ -56,8 +56,8 @@
|
|||
"punycode": "2.3.1",
|
||||
"rollup": "4.9.0",
|
||||
"sanitize-html": "2.11.0",
|
||||
"shiki": "0.14.6",
|
||||
"sass": "1.69.5",
|
||||
"shiki": "0.14.7",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.159.0",
|
||||
|
@ -69,29 +69,29 @@
|
|||
"typescript": "5.3.3",
|
||||
"uuid": "9.0.1",
|
||||
"v-code-diff": "1.7.2",
|
||||
"vite": "5.0.8",
|
||||
"vite": "5.0.10",
|
||||
"vue": "3.3.11",
|
||||
"vuedraggable": "next"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "7.6.4",
|
||||
"@storybook/addon-essentials": "7.6.4",
|
||||
"@storybook/addon-interactions": "7.6.4",
|
||||
"@storybook/addon-links": "7.6.4",
|
||||
"@storybook/addon-storysource": "7.6.4",
|
||||
"@storybook/addons": "7.6.4",
|
||||
"@storybook/blocks": "7.6.4",
|
||||
"@storybook/core-events": "7.6.4",
|
||||
"@storybook/addon-actions": "7.6.5",
|
||||
"@storybook/addon-essentials": "7.6.5",
|
||||
"@storybook/addon-interactions": "7.6.5",
|
||||
"@storybook/addon-links": "7.6.5",
|
||||
"@storybook/addon-storysource": "7.6.5",
|
||||
"@storybook/addons": "7.6.5",
|
||||
"@storybook/blocks": "7.6.5",
|
||||
"@storybook/core-events": "7.6.5",
|
||||
"@storybook/jest": "0.2.3",
|
||||
"@storybook/manager-api": "7.6.4",
|
||||
"@storybook/preview-api": "7.6.4",
|
||||
"@storybook/react": "7.6.4",
|
||||
"@storybook/react-vite": "7.6.4",
|
||||
"@storybook/manager-api": "7.6.5",
|
||||
"@storybook/preview-api": "7.6.5",
|
||||
"@storybook/react": "7.6.5",
|
||||
"@storybook/react-vite": "7.6.5",
|
||||
"@storybook/testing-library": "0.2.2",
|
||||
"@storybook/theming": "7.6.4",
|
||||
"@storybook/types": "7.6.4",
|
||||
"@storybook/vue3": "7.6.4",
|
||||
"@storybook/vue3-vite": "7.6.4",
|
||||
"@storybook/theming": "7.6.5",
|
||||
"@storybook/types": "7.6.5",
|
||||
"@storybook/vue3": "7.6.5",
|
||||
"@storybook/vue3-vite": "7.6.5",
|
||||
"@testing-library/vue": "8.0.1",
|
||||
"@types/escape-regexp": "0.0.3",
|
||||
"@types/estree": "1.0.5",
|
||||
|
@ -111,11 +111,12 @@
|
|||
"acorn": "8.11.2",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "13.6.1",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-vue": "9.19.2",
|
||||
"fast-glob": "3.3.2",
|
||||
"happy-dom": "10.0.3",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.5",
|
||||
"msw": "1.3.2",
|
||||
"msw-storybook-addon": "1.10.0",
|
||||
|
@ -124,7 +125,7 @@
|
|||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"storybook": "7.6.4",
|
||||
"storybook": "7.6.5",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
|
|
|
@ -12,19 +12,16 @@ import { version, ui, lang, updateLocale, locale } from '@/config.js';
|
|||
import { applyTheme } from '@/scripts/theme.js';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||
import { i18n, updateI18n } from '@/i18n.js';
|
||||
import { confirm, alert, post, popup, toast } from '@/os.js';
|
||||
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
|
||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
||||
import { fetchInstance, instance } from '@/instance.js';
|
||||
import { deviceKind } from '@/scripts/device-kind.js';
|
||||
import { reloadChannel } from '@/scripts/unison-reload.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
||||
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||
import { mainRouter } from '@/router.js';
|
||||
|
||||
export async function common(createVue: () => App<Element>) {
|
||||
console.info(`Misskey v${version}`);
|
||||
|
|
|
@ -20,6 +20,7 @@ import { mainRouter } from '@/router.js';
|
|||
import { initializeSw } from '@/scripts/initialize-sw.js';
|
||||
import { deckStore } from '@/ui/deck/deck-store.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
import { SnowfallEffect } from '@/scripts/snowfall-effect.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated } = await common(() => createApp(
|
||||
|
@ -75,6 +76,13 @@ export async function mainBoot() {
|
|||
},
|
||||
};
|
||||
|
||||
if (defaultStore.state.enableSeasonalScreenEffect) {
|
||||
const month = new Date().getMonth() + 1;
|
||||
if (month === 12 || month === 1) {
|
||||
new SnowfallEffect().render();
|
||||
}
|
||||
}
|
||||
|
||||
if ($i) {
|
||||
// only add post shortcuts if logged in
|
||||
hotkeys['p|n'] = post;
|
||||
|
|
|
@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="!inline ?? true"/>
|
||||
</template>
|
||||
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
|
||||
<XCode v-else-if="show" :code="code" :lang="lang"/>
|
||||
<XCode v-else-if="show && lang" :code="code" :lang="lang"/>
|
||||
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
|
||||
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
|
||||
<div :class="$style.codePlaceholderContainer">
|
||||
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
|
||||
|
@ -47,6 +48,21 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
|
|||
border-radius: .3em;
|
||||
}
|
||||
|
||||
.codeBlockFallbackRoot {
|
||||
display: block;
|
||||
overflow-wrap: anywhere;
|
||||
color: #D4D4D4;
|
||||
background: #1E1E1E;
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.codeBlockFallbackCode {
|
||||
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
|
||||
}
|
||||
|
||||
.codePlaceholderRoot {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
|
@ -4,30 +4,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
|
||||
<div :class="$style.codeEditorScroller">
|
||||
<textarea
|
||||
ref="inputEl"
|
||||
v-model="vModel"
|
||||
:class="[$style.textarea]"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
autocomplete="off"
|
||||
wrap="off"
|
||||
spellcheck="false"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
@input="onInput"
|
||||
></textarea>
|
||||
<XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/>
|
||||
<div>
|
||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
|
||||
<div :class="$style.codeEditorScroller">
|
||||
<textarea
|
||||
ref="inputEl"
|
||||
v-model="vModel"
|
||||
:class="[$style.textarea]"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
autocomplete="off"
|
||||
wrap="off"
|
||||
spellcheck="false"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
@input="onInput"
|
||||
></textarea>
|
||||
<XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.caption"><slot name="caption"></slot></div>
|
||||
<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import XCode from '@/components/MkCode.core.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@ -36,6 +44,8 @@ const props = withDefaults(defineProps<{
|
|||
required?: boolean;
|
||||
readonly?: boolean;
|
||||
disabled?: boolean;
|
||||
debounce?: boolean;
|
||||
manualSave?: boolean;
|
||||
}>(), {
|
||||
lang: 'js',
|
||||
});
|
||||
|
@ -54,6 +64,8 @@ const focused = ref(false);
|
|||
const changed = ref(false);
|
||||
const inputEl = shallowRef<HTMLTextAreaElement>();
|
||||
|
||||
const focus = () => inputEl.value?.focus();
|
||||
|
||||
const onInput = (ev) => {
|
||||
v.value = ev.target?.value ?? v.value;
|
||||
changed.value = true;
|
||||
|
@ -100,16 +112,48 @@ const updated = () => {
|
|||
emit('update:modelValue', v.value);
|
||||
};
|
||||
|
||||
const debouncedUpdated = debounce(1000, updated);
|
||||
|
||||
watch(modelValue, newValue => {
|
||||
v.value = newValue ?? '';
|
||||
});
|
||||
|
||||
watch(v, () => {
|
||||
updated();
|
||||
watch(v, newValue => {
|
||||
if (!props.manualSave) {
|
||||
if (props.debounce) {
|
||||
debouncedUpdated();
|
||||
} else {
|
||||
updated();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.label {
|
||||
font-size: 0.85em;
|
||||
padding: 0 0 8px 0;
|
||||
user-select: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-size: 0.85em;
|
||||
padding: 8px 0 0 0;
|
||||
color: var(--fgTransparentWeak);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.save {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.codeEditorRoot {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
|
@ -117,6 +161,7 @@ watch(v, () => {
|
|||
overflow-y: hidden;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border-radius: 6px;
|
||||
padding: 0;
|
||||
color: var(--fg);
|
||||
border: solid 1px var(--panel);
|
||||
|
@ -157,9 +202,10 @@ watch(v, () => {
|
|||
caret-color: rgb(225, 228, 232);
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
outline: 0;
|
||||
min-width: calc(100% - 24px);
|
||||
height: calc(100% - 24px);
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
line-height: 1.5em;
|
||||
font-size: 1em;
|
||||
|
|
|
@ -67,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
|
||||
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
|
||||
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
|
||||
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
|
||||
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
|
||||
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
||||
</div>
|
||||
|
@ -1038,6 +1039,16 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
|
||||
.colorBar {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 12px;
|
||||
width: 5px;
|
||||
height: 100% ;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.submitInner {
|
||||
padding: 0 12px;
|
||||
line-height: 34px;
|
||||
|
@ -1296,5 +1307,6 @@ defineExpose({
|
|||
.headerRight {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,16 +12,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>misskey</title>
|
||||
<title>[DEV] Loading...</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self';
|
||||
worker-src 'self';
|
||||
script-src 'self';
|
||||
script-src 'self' 'unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
|
||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
||||
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
|
||||
/>
|
||||
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
|
|
@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<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" code tall spellcheck="false">
|
||||
<MkCodeEditor v-model="script" lang="is">
|
||||
<template #label>{{ i18n.ts._play.script }}</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
<div class="_buttons">
|
||||
<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
|
||||
|
@ -40,6 +40,7 @@ import * as os from '@/os.js';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { useRouter } from '@/router.js';
|
||||
|
|
|
@ -165,12 +165,8 @@ async function run() {
|
|||
return new Promise(ok => {
|
||||
os.inputText({
|
||||
title: q,
|
||||
}).then(({ canceled, result: a }) => {
|
||||
if (canceled) {
|
||||
ok('');
|
||||
} else {
|
||||
ok(a);
|
||||
}
|
||||
}).then(({ result: a }) => {
|
||||
ok(a ?? '');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -26,9 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
|
||||
<MkTextarea v-model="valueForEditor" tall code>
|
||||
<MkCodeEditor v-model="valueForEditor" lang="json5">
|
||||
<template #label>{{ i18n.ts.value }} (JSON)</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
|
||||
<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
|
||||
|
@ -52,7 +52,7 @@ import { i18n } from '@/i18n.js';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
|
||||
|
|
|
@ -4,51 +4,56 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="_gaps">
|
||||
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||
<div>
|
||||
<div v-if="!loading" class="_gaps">
|
||||
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||
|
||||
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
||||
<div>{{ i18n.ts.inUse }}</div>
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
||||
|
||||
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
||||
<div>{{ i18n.ts.inUse }}</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||
:angle="avatarDecoration.angle"
|
||||
:flipH="avatarDecoration.flipH"
|
||||
:offsetX="avatarDecoration.offsetX"
|
||||
:offsetY="avatarDecoration.offsetY"
|
||||
:active="true"
|
||||
@click="openDecoration(avatarDecoration, i)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||
:angle="avatarDecoration.angle"
|
||||
:flipH="avatarDecoration.flipH"
|
||||
:offsetX="avatarDecoration.offsetX"
|
||||
:offsetY="avatarDecoration.offsetY"
|
||||
:active="true"
|
||||
@click="openDecoration(avatarDecoration, i)"
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
:decoration="avatarDecoration"
|
||||
@click="openDecoration(avatarDecoration)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="avatarDecoration in avatarDecorations"
|
||||
:key="avatarDecoration.id"
|
||||
:decoration="avatarDecoration"
|
||||
@click="openDecoration(avatarDecoration)"
|
||||
/>
|
||||
<div v-else>
|
||||
<MkLoading/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MkLoading/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, defineAsyncComponent } from 'vue';
|
||||
import { ref, defineAsyncComponent, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XDecoration from './profile.avatar-decoration.decoration.vue';
|
||||
import XDecoration from './avatar-decoration.decoration.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const loading = ref(true);
|
||||
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
|
||||
|
@ -59,7 +64,7 @@ os.api('get-avatar-decorations').then(_avatarDecorations => {
|
|||
});
|
||||
|
||||
function openDecoration(avatarDecoration, index?: number) {
|
||||
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration.dialog.vue')), {
|
||||
os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {
|
||||
decoration: avatarDecoration,
|
||||
usingIndex: index,
|
||||
}, {
|
||||
|
@ -115,9 +120,25 @@ function detachAllDecorations() {
|
|||
$i.avatarDecorations = [];
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.avatarDecorations,
|
||||
icon: 'ti ti-sparkles',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
||||
.current {
|
||||
padding: 16px;
|
||||
border-radius: var(--radius);
|
|
@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
|
||||
|
||||
<MkTextarea v-model="localCustomCss" manualSave tall code style="tab-size: 2;">
|
||||
<MkCodeEditor v-model="localCustomCss" manualSave lang="css">
|
||||
<template #label>CSS</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||
|
|
|
@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_buttons">
|
||||
<MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||
<MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||
<MkButton inline danger @click="copyFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojis }}</MkButton>
|
||||
<MkButton inline danger @click="overwriteFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojis }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_buttons">
|
||||
<MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||
<MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
|
||||
<MkButton inline danger @click="copyFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.copyFromPinnedEmojisForReaction }}</MkButton>
|
||||
<MkButton inline danger @click="overwriteFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojisForReaction }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
@ -164,7 +164,7 @@ function previewEmoji(ev: MouseEvent) {
|
|||
emojiPicker.show(getHTMLElement(ev));
|
||||
}
|
||||
|
||||
async function copyFromPinnedEmojis() {
|
||||
async function overwriteFromPinnedEmojis() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.overwriteContentConfirm,
|
||||
|
@ -177,7 +177,7 @@ async function copyFromPinnedEmojis() {
|
|||
pinnedEmojisForReaction.value = [...pinnedEmojis.value];
|
||||
}
|
||||
|
||||
async function copyFromPinnedEmojisForReaction() {
|
||||
async function overwriteFromPinnedEmojisForReaction() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.overwriteContentConfirm,
|
||||
|
|
|
@ -122,6 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
|
||||
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
|
||||
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
|
||||
<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
|
||||
</div>
|
||||
<div>
|
||||
<MkRadios v-model="emojiStyle">
|
||||
|
@ -289,6 +290,7 @@ const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificati
|
|||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
|
@ -328,6 +330,7 @@ watch([
|
|||
highlightSensitiveMedia,
|
||||
keepScreenOn,
|
||||
disableStreamingTimeline,
|
||||
enableSeasonalScreenEffect,
|
||||
], async () => {
|
||||
await reloadAsk();
|
||||
});
|
||||
|
|
|
@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps_m">
|
||||
<FormInfo warn>{{ i18n.ts._plugin.installWarn }}</FormInfo>
|
||||
|
||||
<MkTextarea v-model="code" tall code>
|
||||
<MkCodeEditor v-model="code" lang="is">
|
||||
<template #label>{{ i18n.ts.code }}</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
|
||||
<div>
|
||||
<MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
||||
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, computed } from 'vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
|
|
@ -5,12 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div class="_panel">
|
||||
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
</div>
|
||||
<div :class="$style.avatarContainer">
|
||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
|
||||
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
|
||||
<MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
|
||||
|
@ -83,13 +88,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||
</FormSlot>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-sparkles"></i></template>
|
||||
<template #label>{{ i18n.ts.avatarDecorations }}</template>
|
||||
|
||||
<XAvatarDecoration/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||
|
||||
|
@ -264,19 +262,19 @@ definePageMetadata({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.avatarAndBanner {
|
||||
.banner {
|
||||
position: relative;
|
||||
height: 130px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 10px;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.avatarContainer {
|
||||
display: inline-block;
|
||||
margin-top: -50px;
|
||||
padding-bottom: 16px;
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
|
|
|
@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkTextarea v-model="installThemeCode" code>
|
||||
<MkCodeEditor v-model="installThemeCode" lang="json5">
|
||||
<template #label>{{ i18n.ts._theme.code }}</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
|
||||
import * as os from '@/os.js';
|
||||
|
|
|
@ -51,9 +51,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.editCode }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkTextarea v-model="themeCode" tall code>
|
||||
<MkCodeEditor v-model="themeCode" lang="json5">
|
||||
<template #label>{{ i18n.ts._theme.code }}</template>
|
||||
</MkTextarea>
|
||||
</MkCodeEditor>
|
||||
<MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
@ -80,6 +80,7 @@ import { v4 as uuid } from 'uuid';
|
|||
import JSON5 from 'json5';
|
||||
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
|
|
|
@ -136,9 +136,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkLazy>
|
||||
</template>
|
||||
<div v-if="!disableNotes">
|
||||
<div style="margin-bottom: 8px;">{{ i18n.ts.featured }}</div>
|
||||
<MkLazy>
|
||||
<MkNotes :class="$style.tl" :noGap="true" :pagination="pagination"/>
|
||||
<XTimeline :user="user"/>
|
||||
</MkLazy>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -193,6 +192,7 @@ function calcAge(birthdate: string): number {
|
|||
|
||||
const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
|
||||
const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
|
||||
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
|
@ -219,14 +219,6 @@ watch(moderationNote, async () => {
|
|||
await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
|
||||
});
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'users/featured-notes' as const,
|
||||
limit: 10,
|
||||
params: computed(() => ({
|
||||
userId: props.user.id,
|
||||
})),
|
||||
};
|
||||
|
||||
const style = computed(() => {
|
||||
if (props.user.bannerUrl == null) return {};
|
||||
return {
|
||||
|
|
|
@ -4,18 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkSpacer :contentMax="800" style="padding-top: 0">
|
||||
<MkStickyContainer>
|
||||
<template #header>
|
||||
<MkTab v-model="include" :class="$style.tab">
|
||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||
</MkTab>
|
||||
</template>
|
||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||
</MkStickyContainer>
|
||||
</MkSpacer>
|
||||
<MkStickyContainer>
|
||||
<template #header>
|
||||
<MkTab v-model="tab" :class="$style.tab">
|
||||
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||
</MkTab>
|
||||
</template>
|
||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -29,24 +28,29 @@ const props = defineProps<{
|
|||
user: Misskey.entities.UserDetailed;
|
||||
}>();
|
||||
|
||||
const include = ref<string | null>('all');
|
||||
const tab = ref<string | null>('all');
|
||||
|
||||
const pagination = {
|
||||
const pagination = computed(() => tab.value === 'featured' ? {
|
||||
endpoint: 'users/featured-notes' as const,
|
||||
limit: 10,
|
||||
params: {
|
||||
userId: props.user.id,
|
||||
},
|
||||
} : {
|
||||
endpoint: 'users/notes' as const,
|
||||
limit: 10,
|
||||
params: computed(() => ({
|
||||
params: {
|
||||
userId: props.user.id,
|
||||
withRenotes: include.value === 'all',
|
||||
withReplies: include.value === 'all',
|
||||
withChannelNotes: include.value === 'all',
|
||||
withFiles: include.value === 'files',
|
||||
})),
|
||||
};
|
||||
withRenotes: tab.value === 'all',
|
||||
withReplies: tab.value === 'all',
|
||||
withChannelNotes: tab.value === 'all',
|
||||
withFiles: tab.value === 'files',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.tab {
|
||||
margin: calc(var(--margin) / 2) 0;
|
||||
padding: calc(var(--margin) / 2) 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div>
|
||||
<div v-if="user">
|
||||
<XHome v-if="tab === 'home'" :user="user"/>
|
||||
<XTimeline v-else-if="tab === 'notes'" :user="user"/>
|
||||
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
|
||||
<XTimeline :user="user"/>
|
||||
</MkSpacer>
|
||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
||||
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
||||
|
|
|
@ -54,6 +54,10 @@ export const routes = [{
|
|||
path: '/profile',
|
||||
name: 'profile',
|
||||
component: page(() => import('./pages/settings/profile.vue')),
|
||||
}, {
|
||||
path: '/avatar-decoration',
|
||||
name: 'avatarDecoration',
|
||||
component: page(() => import('./pages/settings/avatar-decoration.vue')),
|
||||
}, {
|
||||
path: '/roles',
|
||||
name: 'roles',
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -412,6 +412,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
code: false,
|
||||
} as Record<string, boolean>,
|
||||
},
|
||||
enableSeasonalScreenEffect: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
|
||||
sound_masterVolume: {
|
||||
where: 'device',
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, test, assert, afterEach } from 'vitest';
|
||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||
import { afterEach, assert, describe, test } from 'vitest';
|
||||
import { cleanup, render, type RenderResult } from '@testing-library/vue';
|
||||
import './init';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
import { directives } from '@/directives/index.js';
|
||||
import { components } from '@/components/index.js';
|
||||
import XHome from '@/pages/user/home.vue';
|
||||
import 'intersection-observer';
|
||||
|
||||
describe('XHome', () => {
|
||||
const renderHome = (user: Partial<Misskey.entities.UserDetailed>): RenderResult => {
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"@types/node": "20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "6.14.0",
|
||||
"@typescript-eslint/parser": "6.14.0",
|
||||
"eslint": "8.55.0",
|
||||
"eslint": "8.56.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-fetch-mock": "3.0.3",
|
||||
"jest-websocket-mock": "2.5.0",
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "6.14.0",
|
||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"nodemon": "3.0.2",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
|
|
1219
pnpm-lock.yaml
1219
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue