Compare commits

...

18 Commits

Author SHA1 Message Date
syuilo
2e537e618c 12.50.0 2020-10-25 01:30:38 +09:00
syuilo
fe3b7a2ad3 New Crowdin updates (#6756)
* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)
2020-10-25 01:24:55 +09:00
syuilo
90db793fd0 regesit 2020-10-25 01:24:01 +09:00
syuilo
7bd2a6ad61 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-10-25 01:23:41 +09:00
syuilo
745f4d2439 regedit 2020-10-25 01:23:23 +09:00
syuilo
254cfaea28 自前ルーティング (#6759)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
2020-10-25 01:21:41 +09:00
syuilo
d4da5a1eea Update dependencies 🚀 2020-10-24 11:12:29 +09:00
syuilo
c0f8297414 Fix migration bug 2020-10-23 17:46:31 +09:00
syuilo
834cb2ea1a 12.49.1 2020-10-22 23:31:11 +09:00
syuilo
d82769abd4 New Crowdin updates (#6748)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)
2020-10-22 23:30:54 +09:00
syuilo
adf01ed4a4 Fix #6749 (#6754) 2020-10-22 23:10:23 +09:00
syuilo
09c007b3aa Update dependencies 🚀 2020-10-22 23:09:03 +09:00
syuilo
526ff177aa Update dependenceis 🚀 2020-10-21 22:20:03 +09:00
syuilo
0e40d4e796 Clean up 2020-10-21 21:50:24 +09:00
syuilo
172ebab7bd Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-10-21 19:36:50 +09:00
syuilo
aa4493fe5c Fix api permission definition 2020-10-21 19:36:47 +09:00
syuilo
a68a88f79e Update README.md 2020-10-20 17:32:17 +09:00
syuilo
1de7dc94e1 Delete CHANGELOG.md 2020-10-19 20:55:19 +09:00
76 changed files with 2182 additions and 3438 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=github)](http://makeapullrequest.com)
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
**A forever evolving, sophisticated microblogging platform.**
**A forever evolving, professional microblogging platform.**
<p align="justify">
<a href="https://join.misskey.page/">Misskey</a> is a decentralized microblogging platform born on Earth.

View File

@@ -586,6 +586,13 @@ setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehr
fileIdOrUrl: "Datei-ID oder URL"
chatOpenBehavior: "Verhalten des Chatfensters bei Öffnung"
sample: "Beispiel"
abuseReports: "Melden"
reportAbuse: "Melden"
reportAbuseOf: "{name} melden"
fillAbuseReportDescription: "Bitte gib Details für diese Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
abuseReported: "Die Meldung wurde versendet. Vielen Dank."
send: "Senden"
abuseMarkAsResolved: "Meldung als gelöst markieren"
_serverDisconnectedBehavior:
reload: "Automatisch aktualisieren"
dialog: "Warnungsfenster zeigen"

View File

@@ -586,6 +586,13 @@ setMultipleBySeparatingWithSpace: "You can set multiple by separating them with
fileIdOrUrl: "File-ID or URL"
chatOpenBehavior: "Behavior of the chat window when opened"
sample: "Sample"
abuseReports: "Reports"
reportAbuse: "Report"
reportAbuseOf: "Report {name}"
fillAbuseReportDescription: "Please fill in the report details. If it is about a specific note, please include its URL."
abuseReported: "Your report has been sent. Thank you very much."
send: "Send"
abuseMarkAsResolved: "Mark report as resolved"
_serverDisconnectedBehavior:
reload: "Automatically reload"
dialog: "Show warning dialog"

View File

@@ -593,6 +593,10 @@ fillAbuseReportDescription: "通報理由の詳細を記入してください。
abuseReported: "内容が送信されました。ご報告ありがとうございました。"
send: "送信"
abuseMarkAsResolved: "対応済みにする"
openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "これらの設定を編集するとアカウントが破損する可能性があります。"
_serverDisconnectedBehavior:
reload: "自動でリロード"

File diff suppressed because it is too large Load Diff

View File

@@ -586,6 +586,13 @@ setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。"
fileIdOrUrl: "文件ID或者URL"
chatOpenBehavior: "聊天窗口打开时的行为"
sample: "示例"
abuseReports: "举报"
reportAbuse: "举报"
reportAbuseOf: "举报{name}"
fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子请同时填写URL地址。"
abuseReported: "内容已发送。感谢您的报告。"
send: "发送"
abuseMarkAsResolved: "处理完毕"
_serverDisconnectedBehavior:
reload: "自动重载"
dialog: "对话框警告"

View File

@@ -16,6 +16,9 @@ noNotes: "貼文不可用。"
noNotifications: "沒有通知"
instance: "實例"
settings: "設定"
basicSettings: "基本設定"
otherSettings: "其他設定"
openInWindow: "在新視窗開啟"
profile: "個人檔案"
timeline: "時間軸"
noAccountDescription: "此用戶還沒有自我介紹"
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "要刪除並再次編輯嗎?此貼文的所有反應,
addToList: "添加至清單"
sendMessage: "發送訊息"
copyUsername: "複製用戶名"
searchUser: "搜尋用戶"
reply: "回覆"
loadMore: "瀏覽更多"
youGotNewFollower: "您有新的追隨者"
@@ -66,7 +70,10 @@ followers: "追隨者"
followsYou: "追隨你的人"
createList: "建立清單"
manageLists: "管理清單"
error: "錯誤"
somethingHappened: "發生錯誤"
retry: "重試"
pageLoadError: "載入頁面失敗"
enterListName: "輸入清單名稱"
privacy: "隱私"
makeFollowManuallyApprove: "手動審核追隨請求"
@@ -105,6 +112,8 @@ unsuspendConfirm: "確定解凍此帳號?"
selectList: "選擇清單"
selectAntenna: "選擇天線"
selectWidget: "選擇小工具"
editWidgets: "編輯小工具"
editWidgetsExit: "停止編輯"
customEmojis: "自訂表情符號"
emoji: "表情符號"
emojiName: "表情符號名稱"
@@ -428,18 +437,23 @@ category: "類別"
tags: "標籤"
docSource: "文件來源"
createAccount: "建立帳戶"
existingAcount: "現有帳戶"
regenerate: "再生"
fontSize: "字體大小"
openImageInNewTab: "於新分頁中開啟圖片"
local: "本地"
remote: "遠端"
total: "合計"
appearance: "外觀"
accountSettings: "帳戶設置"
objectStoragePrefix: "前綴"
objectStorageUseSSL: "使用SSL"
objectStorageUseProxy: "使用網路代理"
serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄"
sounds: "音效"
none: "無"
showInPage: "在頁面中顯示"
volume: "音量"
details: "詳細資訊"
chooseEmoji: "選擇您的表情符號\n"
@@ -448,6 +462,8 @@ recentUsed: "最近使用"
install: "安裝"
uninstall: "解除安裝"
installedApps: "已授權的應用程式"
nothing: "未發現"
installedDate: "安裝時間"
lastUsedDate: "最後上線日期"
state: "狀態"
sort: "排序"

View File

@@ -7,29 +7,23 @@ export class refineAbuseUserReport1603094348345 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_d049123c413e68ca52abe734203"`);
await queryRunner.query(`DROP INDEX "IDX_d049123c413e68ca52abe73420"`);
await queryRunner.query(`DROP INDEX "IDX_5cd442c3b2e74fdd99dae20243"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "userId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "targetUserId" character varying(32) NOT NULL`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "userId" TO "targetUserId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "assigneeId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolved" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(2048) NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId") `);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(2048) NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`CREATE INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a" ON "abuse_user_report" ("resolved") `);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de" FOREIGN KEY ("assigneeId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_08b883dd5fdd6f9c4c1572b36de"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`);
await queryRunner.query(`DROP INDEX "IDX_2b15aaf4a0dc5be3499af7ab6a"`);
await queryRunner.query(`DROP INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "comment"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(512) NOT NULL`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "comment" character varying(512) NOT NULL DEFAULT '{}'::varchar[]`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolved"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "assigneeId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "targetUserId"`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "userId" character varying(32) NOT NULL`);
await queryRunner.query(`ALTER TABLE "abuse_user_report" RENAME COLUMN "targetUserId" TO "userId"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5cd442c3b2e74fdd99dae20243" ON "abuse_user_report" ("userId", "reporterId") `);
await queryRunner.query(`CREATE INDEX "IDX_d049123c413e68ca52abe73420" ON "abuse_user_report" ("userId") `);
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_d049123c413e68ca52abe734203" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.49.0",
"version": "12.50.0",
"codename": "indigo",
"repository": {
"type": "git",
@@ -46,7 +46,7 @@
"@koa/multer": "3.0.0",
"@koa/router": "9.0.1",
"@sinonjs/fake-timers": "6.0.1",
"@syuilo/aiscript": "0.11.0",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.14.0",
"@types/cbor": "5.0.1",
@@ -92,7 +92,7 @@
"@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.0",
"@types/seedrandom": "2.4.28",
"@types/sharp": "0.25.0",
"@types/sharp": "0.26.0",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/speakeasy": "2.0.5",
"@types/tinycolor2": "1.4.2",
@@ -104,7 +104,7 @@
"@types/websocket": "1.0.1",
"@types/ws": "7.2.7",
"@typescript-eslint/parser": "4.4.0",
"@vue/compiler-sfc": "3.0.0",
"@vue/compiler-sfc": "3.0.2",
"abort-controller": "3.0.0",
"apexcharts": "3.22.0",
"autobind-decorator": "2.4.0",
@@ -123,19 +123,19 @@
"content-disposition": "0.5.3",
"core-js": "3.6.5",
"crc-32": "1.2.0",
"css-loader": "4.3.0",
"css-loader": "5.0.0",
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-entries": "3.1.0",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1",
"eslint": "7.10.0",
"eslint-plugin-vue": "7.0.1",
"eslint": "7.11.0",
"eslint-plugin-vue": "7.1.0",
"eventemitter3": "4.0.7",
"feed": "4.2.1",
"fibers": "5.0.0",
"file-type": "15.0.1",
"file-type": "16.0.0",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"got": "11.7.0",
@@ -159,8 +159,8 @@
"js-yaml": "3.14.0",
"jsdom": "16.4.0",
"json5": "2.1.3",
"json5-loader": "4.0.0",
"jsonld": "3.1.1",
"json5-loader": "4.0.1",
"jsonld": "3.2.0",
"jsrsasign": "8.0.20",
"katex": "0.12.0",
"koa": "2.13.0",
@@ -190,9 +190,9 @@
"parsimmon": "1.16.0",
"pg": "8.4.1",
"portscanner": "2.2.0",
"postcss": "8.1.1",
"postcss-loader": "4.0.3",
"prismjs": "1.21.0",
"postcss": "8.1.3",
"postcss-loader": "4.0.4",
"prismjs": "1.22.0",
"probe-image-size": "5.0.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
@@ -202,8 +202,8 @@
"qrcode": "1.4.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.15.5",
"recaptcha-promise": "0.1.3",
"re2": "1.15.8",
"recaptcha-promise": "1.0.0",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
"redis-lock": "0.1.4",
@@ -216,46 +216,45 @@
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.27.0",
"sass-loader": "10.0.2",
"sass-loader": "10.0.4",
"seedrandom": "3.0.5",
"sharp": "0.26.1",
"sharp": "0.26.2",
"speakeasy": "2.0.0",
"stringz": "2.1.0",
"style-loader": "1.3.0",
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "4.27.8",
"systeminformation": "4.27.10",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.4",
"ts-loader": "8.0.6",
"ts-node": "9.0.0",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.28",
"typescript": "4.0.3",
"ulid": "2.3.0",
"url-loader": "4.1.0",
"url-loader": "4.1.1",
"uuid": "8.3.1",
"v-debounce": "0.1.2",
"vue": "3.0.1",
"vue": "3.0.2",
"vue-color": "2.7.1",
"vue-draggable-next": "1.0.8",
"vue-i18n": "9.0.0-beta.4",
"vue-i18n": "9.0.0-beta.6",
"vue-json-pretty": "1.7.0",
"vue-loader": "16.0.0-beta.7",
"vue-loader": "16.0.0-beta.8",
"vue-prism-editor": "1.2.2",
"vue-router": "4.0.0-beta.13",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader-corejs3": "1.5.0",
"vue-template-compiler": "2.6.12",
"vuex": "4.0.0-beta.4",
"vuex-persistedstate": "3.1.0",
"web-push": "3.4.4",
"webpack": "5.1.3",
"webpack-cli": "3.3.12",
"webpack": "5.2.0",
"webpack-cli": "4.1.0",
"websocket": "1.0.32",
"ws": "7.3.1",
"xev": "2.0.1"

View File

@@ -2,9 +2,9 @@
<span class="eiwwqkts" :class="{ cat }" :title="acct(user)" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick">
<img class="inner" :src="url"/>
</span>
<router-link class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
<MkA class="eiwwqkts" :class="{ cat }" :to="userPage(user)" :title="acct(user)" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
<img class="inner" :src="url"/>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -1,5 +1,5 @@
<template>
<router-link :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`">
<div class="fade"></div>
<div class="name"><Fa :icon="faSatelliteDish"/> {{ channel.name }}</div>
@@ -30,7 +30,7 @@
{{ $t('updatedAt') }}: <MkTime :time="channel.lastNotedAt"/>
</span>
</footer>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -1,102 +0,0 @@
<template>
<div class="eqryymyo">
<div class="header">
<time ref="time" class="_ghost">
<span class="yyyymmdd">{{ yyyy }}/{{ mm }}/{{ dd }}</span>
<br>
<span class="hhnn">{{ hh }}<span :style="{ visibility: now.getSeconds() % 2 == 0 ? 'visible' : 'hidden' }">:</span>{{ nn }}</span>
</time>
</div>
<div class="content _panel _ghost">
<MkClock/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkClock from './analog-clock.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkClock
},
data() {
return {
now: new Date(),
clock: null
};
},
computed: {
yyyy(): number {
return this.now.getFullYear();
},
mm(): string {
return ('0' + (this.now.getMonth() + 1)).slice(-2);
},
dd(): string {
return ('0' + this.now.getDate()).slice(-2);
},
hh(): string {
return ('0' + this.now.getHours()).slice(-2);
},
nn(): string {
return ('0' + this.now.getMinutes()).slice(-2);
}
},
mounted() {
this.tick();
this.clock = setInterval(this.tick, 1000);
},
beforeUnmount() {
clearInterval(this.clock);
},
methods: {
tick() {
this.now = new Date();
}
}
});
</script>
<style lang="scss" scoped>
.eqryymyo {
display: inline-block;
overflow: visible;
> .header {
padding: 0 12px;
padding-top: 4px;
text-align: center;
font-size: 12px;
font-family: Lucida Console, Courier, monospace;
&:hover + .content {
opacity: 1;
}
> time {
display: table-cell;
vertical-align: middle;
height: 48px;
> .yyyymmdd {
opacity: 0.7;
}
}
}
> .content {
opacity: 0;
display: block;
position: absolute;
top: auto;
right: 0;
margin: 16px 0 0 0;
padding: 16px;
width: 230px;
transition: opacity 0.2s ease;
}
}
</style>

View File

@@ -1,6 +1,7 @@
import { App } from 'vue';
import mfm from './misskey-flavored-markdown.vue';
import a from './ui/a.vue';
import acct from './acct.vue';
import avatar from './avatar.vue';
import emoji from './emoji.vue';
@@ -10,10 +11,10 @@ import time from './time.vue';
import url from './url.vue';
import loading from './loading.vue';
import error from './error.vue';
import streamIndicator from './stream-indicator.vue';
export default function(app: App) {
app.component('Mfm', mfm);
app.component('MkA', a);
app.component('MkAcct', acct);
app.component('MkAvatar', avatar);
app.component('MkEmoji', emoji);
@@ -23,5 +24,4 @@ export default function(app: App) {
app.component('MkUrl', url);
app.component('MkLoading', loading);
app.component('MkError', error);
app.component('StreamIndicator', streamIndicator);
}

View File

@@ -1,5 +1,5 @@
<template>
<component :is="self ? 'router-link' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
<component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
:title="url"

View File

@@ -1,11 +1,11 @@
<template>
<router-link class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')">
<MkA class="ldlomzub" :class="{ isMe }" :to="url" v-user-preview="canonical" v-if="url.startsWith('/')">
<span class="me" v-if="isMe">{{ $t('you') }}</span>
<span class="main">
<span class="username">@{{ username }}</span>
<span class="host" v-if="(host != localHost) || $store.state.settings.showFullAcct">@{{ toUnicode(host) }}</span>
</span>
</router-link>
</MkA>
<a class="ldlomzub" :href="url" target="_blank" rel="noopener" v-else>
<span class="main">
<span class="username">@{{ username }}</span>

View File

@@ -9,8 +9,8 @@ import { concat } from '../../prelude/array';
import MkFormula from './formula.vue';
import MkCode from './code.vue';
import MkGoogle from './google.vue';
import MkA from './ui/a.vue';
import { host } from '@/config';
import { RouterLink } from 'vue-router';
export default defineComponent({
props: {
@@ -150,7 +150,7 @@ export default defineComponent({
}
case 'hashtag': {
return [h(RouterLink, {
return [h(MkA, {
key: Math.random(),
to: this.isNote ? `/tags/${encodeURIComponent(token.node.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.node.props.hashtag)}`,
style: 'color:var(--hashtag);'

View File

@@ -1,17 +1,17 @@
<template>
<header class="kkwtjztg">
<router-link class="name" :to="userPage(note.user)" v-user-preview="note.user.id">
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.user.id">
<MkUserName :user="note.user"/>
</router-link>
</MkA>
<span class="is-bot" v-if="note.user.isBot">bot</span>
<span class="username"><MkAcct :user="note.user"/></span>
<span class="admin" v-if="note.user.isAdmin"><Fa :icon="faBookmark"/></span>
<span class="moderator" v-if="!note.user.isAdmin && note.user.isModerator"><Fa :icon="farBookmark"/></span>
<div class="info">
<span class="mobile" v-if="note.viaMobile"><Fa :icon="faMobileAlt"/></span>
<router-link class="created-at" :to="notePage(note)">
<MkA class="created-at" :to="notePage(note)">
<MkTime :time="note.createdAt"/>
</router-link>
</MkA>
<span class="visibility" v-if="note.visibility !== 'public'">
<Fa v-if="note.visibility === 'home'" :icon="faHome"/>
<Fa v-if="note.visibility === 'followers'" :icon="faUnlock"/>

View File

@@ -18,9 +18,9 @@
<Fa :icon="faRetweet"/>
<i18n-t keypath="renotedBy" tag="span">
<template #user>
<router-link class="name" :to="userPage(note.user)" v-user-preview="note.userId">
<MkA class="name" :to="userPage(note.user)" v-user-preview="note.userId">
<MkUserName :user="note.user"/>
</router-link>
</MkA>
</template>
</i18n-t>
<div class="info">
@@ -48,7 +48,7 @@
<div class="content" v-show="appearNote.cw == null || showContent">
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
<router-link class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></router-link>
<MkA class="reply" v-if="appearNote.replyId" :to="`/notes/${appearNote.replyId}`"><Fa :icon="faReply"/></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/>
<a class="rp" v-if="appearNote.renote != null">RN:</a>
</div>
@@ -59,7 +59,7 @@
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div>
</div>
<router-link v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</router-link>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><Fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</MkA>
</div>
<footer class="footer">
<XReactionsViewer :note="appearNote" ref="reactionsViewer"/>
@@ -91,9 +91,9 @@
<div v-else class="_panel muted" @click="muted = false">
<i18n-t keypath="userSaysSomething" tag="small">
<template #name>
<router-link class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
<MkUserName :user="appearNote.user"/>
</router-link>
</MkA>
</template>
</i18n-t>
</div>
@@ -144,7 +144,7 @@ export default defineComponent({
inject: {
inChannel: {
default: null
}
},
},
props: {
@@ -581,11 +581,6 @@ export default defineComponent({
});
menu = [{
type: 'link',
icon: faInfoCircle,
text: this.$t('details'),
to: '/notes/' + this.appearNote.id
}, null, {
icon: faCopy,
text: this.$t('copyContent'),
action: this.copyContent

View File

@@ -18,34 +18,34 @@
</div>
<div class="tail">
<header>
<router-link v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></router-link>
<MkA v-if="notification.user" class="name" :to="userPage(notification.user)" v-user-preview="notification.user.id"><MkUserName :user="notification.user"/></MkA>
<span v-else>{{ notification.header }}</span>
<MkTime :time="notification.createdAt" v-if="withTime"/>
</header>
<router-link v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
<router-link v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
</MkA>
<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.renote.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
<router-link v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
</router-link>
<router-link v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
</MkA>
<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Fa :icon="faQuoteLeft"/>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<Fa :icon="faQuoteRight"/>
</router-link>
</MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>

View File

@@ -1,5 +1,5 @@
<template>
<router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
<article>
<header>
@@ -11,7 +11,7 @@
<p>{{ userName(page.user) }}</p>
</footer>
</article>
</router-link>
</MkA>
</template>
<script lang="ts">

View File

@@ -1,11 +1,18 @@
<template>
<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')">
<XWindow ref="window"
:initial-width="400"
:initial-height="500"
:can-resize="true"
:close-right="true"
:contextmenu="contextmenu"
@closed="$emit('closed')"
>
<template #header>
<XHeader :info="pageInfo" :with-back="false"/>
</template>
<template #buttons>
<button class="_button" @click="expand" v-tooltip="$t('showInPage')"><Fa :icon="faExpandAlt"/></button>
<button class="_button" @click="popout" v-tooltip="$t('popout')"><Fa :icon="faExternalLinkAlt"/></button>
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
</template>
<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);">
<component :is="component" v-bind="props" :ref="changePage"/>
@@ -14,11 +21,13 @@
</template>
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import { faExternalLinkAlt, faExpandAlt } from '@fortawesome/free-solid-svg-icons';
import { defineComponent } from 'vue';
import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import XWindow from '@/components/ui/window.vue';
import XHeader from '@/ui/_common_/header.vue';
import { popout } from '@/scripts/popout';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { resolve } from '@/router';
export default defineComponent({
components: {
@@ -26,6 +35,14 @@ export default defineComponent({
XHeader,
},
provide() {
return {
navHook: (url) => {
this.navigate(url);
}
};
},
props: {
initialUrl: {
type: String,
@@ -38,7 +55,7 @@ export default defineComponent({
initialProps: {
type: Object,
required: false,
default: {},
default: () => {},
},
},
@@ -50,18 +67,39 @@ export default defineComponent({
url: this.initialUrl,
component: this.initialComponent,
props: this.initialProps,
faExternalLinkAlt, faExpandAlt,
history: [],
faChevronLeft,
};
},
provide() {
return {
navHook: (url, component, props) => {
this.url = url;
this.component = markRaw(component);
this.props = props;
}
};
computed: {
contextmenu() {
return [{
type: 'label',
text: this.url,
}, {
icon: faExpandAlt,
text: this.$t('showInPage'),
action: this.expand
}, {
icon: faExternalLinkAlt,
text: this.$t('popout'),
action: this.popout
}, null, {
icon: faExternalLinkAlt,
text: this.$t('openInNewTab'),
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: faLink,
text: this.$t('copyLink'),
action: () => {
copyToClipboard(this.url);
}
}];
},
},
methods: {
@@ -72,6 +110,18 @@ export default defineComponent({
}
},
navigate(url, record = true) {
if (record) this.history.push(this.url);
this.url = url;
const { component, props } = resolve(url);
this.component = component;
this.props = props;
},
back() {
this.navigate(this.history.pop(), false);
},
expand() {
this.$router.push(this.url);
this.$refs.window.close();

View File

@@ -17,12 +17,12 @@
<button class="item _button index active" @click="top()" v-if="$route.name === 'index'">
<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
</button>
<router-link class="item index" active-class="active" to="/" exact v-else>
<MkA class="item index" active-class="active" to="/" exact v-else>
<Fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
</router-link>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'router-link' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to">
<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to">
<Fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $t(menuDef[item].title) }}</span>
<i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i>
</component>
@@ -35,9 +35,9 @@
<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i>
</button>
<router-link class="item" active-class="active" to="/settings">
<MkA class="item" active-class="active" to="/settings">
<Fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
</router-link>
</MkA>
</div>
</nav>
</transition>

View File

@@ -3,9 +3,9 @@
<div class="body">
<span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span>
<router-link class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></router-link>
<MkA class="reply" v-if="note.replyId" :to="`/notes/${note.replyId}`"><Fa :icon="faReply"/></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/>
<router-link class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</router-link>
<MkA class="rp" v-if="note.renoteId" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<details v-if="note.files.length > 0">
<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary>

View File

@@ -0,0 +1,104 @@
<template>
<a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
<slot></slot>
</a>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faExpandAlt, faColumns, faExternalLinkAlt, faLink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { router } from '@/router';
import { deckmode } from '@/config';
export default defineComponent({
inject: {
navHook: {
default: null
},
sideViewHook: {
default: null
}
},
props: {
to: {
type: String,
required: true,
},
activeClass: {
type: String,
required: false,
},
},
computed: {
active() {
if (this.activeClass == null) return false;
const resolved = router.resolve(this.to);
if (resolved.path == this.$route.path) return true;
if (resolved.name == null) return false;
if (this.$route.name == null) return false;
return resolved.name == this.$route.name;
}
},
methods: {
onContextmenu(e) {
if (window.getSelection().toString() !== '') return;
os.contextMenu([{
type: 'label',
text: this.to,
}, {
icon: faWindowMaximize,
text: this.$t('openInWindow'),
action: () => {
os.pageWindow(this.to);
}
}, !this.navHook && this.sideViewHook ? {
icon: faColumns,
text: this.$t('openInSideView'),
action: () => {
this.sideViewHook(this.to);
}
} : undefined, {
icon: faExpandAlt,
text: this.$t('showInPage'),
action: () => {
this.$router.push(this.to);
}
}, null, {
icon: faExternalLinkAlt,
text: this.$t('openInNewTab'),
action: () => {
window.open(this.to, '_blank');
}
}, {
icon: faLink,
text: this.$t('copyLink'),
action: () => {
copyToClipboard(this.to);
}
}], e);
},
nav() {
if (this.navHook) {
this.navHook(this.to);
} else {
if (this.$store.state.device.defaultSideView && this.sideViewHook && this.to !== '/') {
this.sideViewHook(this.to);
return;
}
if (this.$store.state.device.deckNavWindow && deckmode && this.to !== '/') {
os.pageWindow(this.to);
return;
}
this.$router.push(this.to);
}
}
}
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="nvlagfpb">
<div class="nvlagfpb" @contextmenu.prevent.stop="() => {}">
<MkMenu :items="items" @close="$emit('closed')" class="_popup _shadow" :align="'left'"/>
</div>
</template>

View File

@@ -12,12 +12,12 @@
<span v-else-if="item.type === 'pending'" :tabindex="i" class="pending item">
<span><MkEllipsis/></span>
</span>
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item">
<MkA v-else-if="item.type === 'link'" :to="item.to" @click.passive="close()" :tabindex="i" class="_button item">
<Fa v-if="item.icon" :icon="item.icon" fixed-width/>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span>
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
</router-link>
</MkA>
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item">
<Fa v-if="item.icon" :icon="item.icon" fixed-width/>
<span>{{ item.text }}</span>

View File

@@ -51,7 +51,7 @@ export default defineComponent({
.novjtctn {
position: relative;
display: inline-block;
margin: 0 32px 0 0;
margin: 16px 32px 0 0;
cursor: pointer;
transition: all 0.3s;

View File

@@ -2,12 +2,13 @@
<div class="adhpbeos" :class="{ focused, filled, tall, pre }">
<div class="input">
<span class="label" ref="label"><slot></slot></span>
<textarea ref="input"
<textarea ref="input" :class="{ code }"
:value="value"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="!code"
@input="onInput"
@focus="focused = true"
@blur="focused = false"
@@ -20,7 +21,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import * as os from '@/os';
export default defineComponent({
props: {
@@ -43,6 +43,10 @@ export default defineComponent({
type: String,
required: false
},
code: {
type: Boolean,
required: false
},
tall: {
type: Boolean,
required: false,
@@ -159,6 +163,11 @@ export default defineComponent({
outline: none;
box-shadow: none;
color: var(--fg);
&.code {
tab-size: 2;
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
}
}

View File

@@ -2,14 +2,16 @@
<transition :name="$store.state.device.animation ? 'window' : ''" appear @after-leave="$emit('closed')">
<div class="ebkgocck" v-if="showing">
<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown">
<div class="header">
<button class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<div class="header" @contextmenu.prevent.stop="onContextmenu">
<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
<button v-else class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
<slot name="header"></slot>
</span>
<slot name="buttons">
<button class="_button" style="pointer-events: none;"></button>
</slot>
<button v-if="closeRight" class="_button" @click="close()"><Fa :icon="faTimes"/></button>
<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot>
</div>
<div class="body" v-if="padding">
<div class="_section">
@@ -85,6 +87,15 @@ export default defineComponent({
required: false,
default: false,
},
closeRight: {
type: Boolean,
required: false,
default: false,
},
contextmenu: {
type: Array,
required: false,
}
},
emits: ['closed'],
@@ -129,6 +140,12 @@ export default defineComponent({
}
},
onContextmenu(e) {
if (this.contextmenu) {
os.contextMenu(this.contextmenu, e);
}
},
// 最前面へ移動
top() {
let z = 0;

View File

@@ -8,7 +8,7 @@
</div>
<div v-else class="mk-url-preview" v-size="{ max: [400, 350] }">
<transition name="zoom" mode="out-in">
<component :is="self ? 'router-link' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
<component :is="self ? 'MkA' : 'a'" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button class="_button" v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enablePlayer')"><Fa :icon="faPlayCircle"/></button>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<component :is="self ? 'router-link' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
>

View File

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

View File

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

View File

@@ -6,13 +6,13 @@
</div>
<div class="users">
<router-link v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)">
<MkA v-for="item in items" class="user" :key="item.id" :to="userPage(extract ? extract(item) : item)">
<MkAvatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
<div class="body">
<MkUserName :user="extract ? extract(item) : item" class="name"/>
<MkAcct :user="extract ? extract(item) : item" class="acct"/>
</div>
</router-link>
</MkA>
</div>
<button class="more _button" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>

View File

@@ -4,14 +4,13 @@
import '@/style.scss';
import { createApp } from 'vue';
import { createApp, defineAsyncComponent } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import Root from './root.vue';
import widgets from './widgets';
import directives from './directives';
import components from '@/components';
import { version, apiUrl } from '@/config';
import { version, apiUrl, deckmode } from '@/config';
import { store } from './store';
import { router } from './router';
import { applyTheme } from '@/scripts/theme';
@@ -152,7 +151,12 @@ store.dispatch('instance/fetch').then(() => {
stream.init(store.state.i);
const app = createApp(Root);
const app = createApp(await (
window.location.search === '?zen' ? import('@/ui/zen.vue') :
!store.getters.isSignedIn ? import('@/ui/visitor.vue') :
deckmode ? import('@/ui/deck.vue') :
import('@/ui/default.vue')
).then(x => x.default));
if (_DEV_) {
app.config.performance = true;

View File

@@ -5,6 +5,7 @@ import { store } from '@/store';
import { apiUrl } from '@/config';
import MkPostFormDialog from '@/components/post-form-dialog.vue';
import MkWaitingDialog from '@/components/waiting-dialog.vue';
import { resolve } from '@/router';
const ua = navigator.userAgent.toLowerCase();
export const isMobile = /mobile|iphone|ipad|android/.test(ua);
@@ -162,7 +163,8 @@ export function popup(component: Component | typeof import('*.vue'), props: Reco
};
}
export function pageWindow(url: string, component: Component | typeof import('*.vue'), props: Record<string, any>) {
export function pageWindow(url: string) {
const { component, props } = resolve(url);
popup(defineAsyncComponent(() => import('@/components/page-window.vue')), {
initialUrl: url,
initialComponent: markRaw(component),

View File

@@ -4,7 +4,7 @@
<div class="_content">
<ul>
<li v-for="doc in docs" :key="doc.path">
<router-link :to="`/docs/${doc.path}`">{{ doc.title }}</router-link>
<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA>
</li>
</ul>
</div>

View File

@@ -38,8 +38,8 @@
<template #header><Fa :icon="faHashtag" fixed-width style="margin-right: 0.5em;"/>{{ $t('popularTags') }}</template>
<div class="vxjfqztj">
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link>
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link>
<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA>
<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA>
</div>
</MkFolder>

View File

@@ -12,7 +12,7 @@
<MkAvatar class="avatar" :user="req.follower"/>
<div class="body">
<div class="name">
<router-link class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></router-link>
<MkA class="name" :to="userPage(req.follower)" v-user-preview="req.follower.id"><MkUserName :user="req.follower"/></MkA>
<p class="acct">@{{ acct(req.follower) }}</p>
</div>
<div class="description" v-if="req.follower.description" :title="req.follower.description">

View File

@@ -4,13 +4,12 @@
<MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $t('startMessaging') }}</MkButton>
<div class="history" v-if="messages.length > 0">
<router-link v-for="(message, i) in messages"
<MkA v-for="(message, i) in messages"
class="message _panel"
:class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($store.state.i.id) : message.isRead }"
:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
:data-index="i"
:key="message.id"
@click.prevent="go(message)"
>
<div>
<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
@@ -27,7 +26,7 @@
<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p>
</div>
</div>
</router-link>
</MkA>
</div>
<div class="_fullinfo" v-if="!fetching && messages.length == 0">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -90,18 +89,6 @@ export default defineComponent({
},
methods: {
go(message) {
const url = message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(this.isMe(message) ? message.recipient : message.user)}`;
if (this.navHook) {
this.navHook(url, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), {
userAcct: message.groupId ? null : getAcct(this.isMe(message) ? message.recipient : message.user),
groupId: message.groupId
});
} else {
this.$router.push(url);
}
},
getAcct,
isMe(message) {

View File

@@ -317,7 +317,7 @@ const Component = defineComponent({
text: this.$t('openInWindow'),
icon: faWindowMaximize,
action: () => {
os.pageWindow(url, Component, this.$props);
os.pageWindow(url);
this.$router.back();
},
}, this.inWindow ? undefined : {

View File

@@ -10,7 +10,7 @@
<MkPagination :pagination="ownedPagination" #default="{items}" ref="owned">
<div class="_card" v-for="group in items" :key="group.id">
<div class="_title"><router-link :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</router-link></div>
<div class="_title"><MkA :to="`/my/groups/${ group.id }`" class="_link">{{ group.name }}</MkA></div>
<div class="_content"><MkAvatars :user-ids="group.userIds"/></div>
</div>
</MkPagination>

View File

@@ -4,7 +4,7 @@
<MkPagination :pagination="pagination" #default="{items}" class="lists _content" ref="list">
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
<router-link :to="`/my/lists/${ list.id }`">{{ list.name }}</router-link>
<MkA :to="`/my/lists/${ list.id }`">{{ list.name }}</MkA>
</div>
</MkPagination>
</div>

View File

@@ -42,6 +42,12 @@ export default defineComponent({
MkRemoteCaution,
MkButton,
},
props: {
noteId: {
type: String,
required: true
}
},
data() {
return {
INFO: computed(() => this.note ? {
@@ -77,7 +83,7 @@ export default defineComponent({
};
},
watch: {
$route: 'fetch'
noteId: 'fetch'
},
created() {
this.fetch();
@@ -86,7 +92,7 @@ export default defineComponent({
fetch() {
Progress.start();
os.api('notes/show', {
noteId: this.$route.params.note
noteId: this.noteId
}).then(note => {
Promise.all([
os.api('users/notes', {

View File

@@ -12,7 +12,7 @@
</header>
<section>
<router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</router-link>
<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
<MkInput v-model:value="title">
<span>{{ $t('_pages.title') }}</span>

View File

@@ -20,9 +20,9 @@
</div>
<div class="_section links">
<div class="_content">
<router-link :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</router-link>
<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA>
<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId">
<router-link :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</router-link>
<MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button>
<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button>
</template>

View File

@@ -2,6 +2,10 @@
<div class="_section">
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('general') }}</div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch>
</div>
<div class="_content">
<div>{{ $t('whenServerDisconnected') }}</div>
<MkRadio v-model="serverDisconnectedBehavior" value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</MkRadio>
@@ -51,6 +55,10 @@
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faColumns"/> {{ $t('deck') }}</div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
</div>
<div class="_content">
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
{{ $t('_deck.alwaysShowMainColumn') }}
@@ -146,6 +154,16 @@ export default defineComponent({
set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); }
},
defaultSideView: {
get() { return this.$store.state.device.defaultSideView; },
set(value) { this.$store.commit('device/set', { key: 'defaultSideView', value }); }
},
deckNavWindow: {
get() { return this.$store.state.device.deckNavWindow; },
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
},
chatOpenBehavior: {
get() { return this.$store.state.device.chatOpenBehavior; },
set(value) { this.$store.commit('device/set', { key: 'chatOpenBehavior', value }); }

View File

@@ -1,52 +1,57 @@
<template>
<div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
<div class="nav" v-if="!narrow || $route.name === 'settings'">
<div class="nav" v-if="!narrow || page == null">
<div class="menu">
<div class="label">{{ $t('basicSettings') }}</div>
<router-link class="item" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</router-link>
<router-link class="item" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</router-link>
<router-link class="item" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</router-link>
<router-link class="item" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</router-link>
<router-link class="item" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</router-link>
<router-link class="item" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</router-link>
<MkA class="item" :class="{ active: page === 'profile' }" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</MkA>
<MkA class="item" :class="{ active: page === 'privacy' }" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</MkA>
<MkA class="item" :class="{ active: page === 'reaction' }" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</MkA>
<MkA class="item" :class="{ active: page === 'notifications' }" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</MkA>
<MkA class="item" :class="{ active: page === 'integration' }" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</MkA>
<MkA class="item" :class="{ active: page === 'security' }" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('clientSettings') }}</div>
<router-link class="item" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</router-link>
<router-link class="item" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</router-link>
<router-link class="item" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</router-link>
<router-link class="item" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</router-link>
<router-link class="item" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</router-link>
<MkA class="item" :class="{ active: page === 'general' }" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</MkA>
<MkA class="item" :class="{ active: page === 'theme' }" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</MkA>
<MkA class="item" :class="{ active: page === 'sidebar' }" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</MkA>
<MkA class="item" :class="{ active: page === 'sounds' }" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</MkA>
<MkA class="item" :class="{ active: page === 'plugins' }" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('otherSettings') }}</div>
<router-link class="item" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</router-link>
<router-link class="item" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</router-link>
<router-link class="item" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</router-link>
<router-link class="item" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</router-link>
<MkA class="item" :class="{ active: page === 'mute-block' }" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</MkA>
<MkA class="item" :class="{ active: page === 'word-mute' }" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</MkA>
<MkA class="item" :class="{ active: page === 'api' }" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</MkA>
<MkA class="item" :class="{ active: page === 'other' }" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</MkA>
</div>
<div class="menu">
<button class="_button item" @click="logout">{{ $t('logout') }}</button>
</div>
</div>
<div class="main">
<router-view v-slot="{ Component }">
<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in">
<component :is="Component" @info="onInfo"/>
</transition>
</router-view>
<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in">
<component :is="component" @info="onInfo"/>
</transition>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { computed, defineAsyncComponent, defineComponent, onMounted, ref } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey } from '@fortawesome/free-solid-svg-icons';
import { faLaugh, faBell } from '@fortawesome/free-regular-svg-icons';
import { store } from '@/store';
import { i18n } from '@/i18n';
export default defineComponent({
props: {
page: {
type: String,
required: false
}
},
setup(props, context) {
const INFO = ref({
header: [{
@@ -60,6 +65,28 @@ export default defineComponent({
const onInfo = (viewInfo) => {
INFO.value = viewInfo;
};
const component = computed(() => {
switch (props.page) {
case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
case 'reaction': return defineAsyncComponent(() => import('./reaction.vue'));
case 'notifications': return defineAsyncComponent(() => import('./notifications.vue'));
case 'mute-block': return defineAsyncComponent(() => import('./mute-block.vue'));
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case 'api': return defineAsyncComponent(() => import('./api.vue'));
case 'other': return defineAsyncComponent(() => import('./other.vue'));
case 'general': return defineAsyncComponent(() => import('./general.vue'));
case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
case 'plugins': return defineAsyncComponent(() => import('./plugins.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'regedit': return defineAsyncComponent(() => import('./regedit.vue'));
default: return null;
}
});
onMounted(() => {
narrow.value = el.value.offsetWidth < 650;
@@ -71,6 +98,7 @@ export default defineComponent({
view,
el,
onInfo,
component,
logout: () => {
store.dispatch('logout');
location.href = '/';
@@ -121,7 +149,7 @@ export default defineComponent({
//border-top: solid 1px var(--divider);
}
&.router-link-active {
&.active {
color: var(--accent);
padding-left: 42px;
}

View File

@@ -6,9 +6,9 @@
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
<template #default="{items}">
<div class="user" v-for="mute in items" :key="mute.id">
<router-link class="name" :to="userPage(mute.mutee)">
<MkA class="name" :to="userPage(mute.mutee)">
<MkAcct :user="mute.mutee"/>
</router-link>
</MkA>
</div>
</template>
</MkPagination>
@@ -18,9 +18,9 @@
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
<template #default="{items}">
<div class="user" v-for="block in items" :key="block.id">
<router-link class="name" :to="userPage(block.blockee)">
<MkA class="name" :to="userPage(block.blockee)">
<MkAcct :user="block.blockee"/>
</router-link>
</MkA>
</div>
</template>
</MkPagination>

View File

@@ -1,12 +1,17 @@
<template>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</MkSwitch>
<div>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</MkSwitch>
</div>
</div>
</div>
<div class="_section">
<MkA to="/settings/regedit">RegEdit</MkA>
</div>
</div>
</template>

View File

@@ -0,0 +1,77 @@
<template>
<div>
<div class="_section">
<MkInfo warn>{{ $t('editTheseSettingsMayBreakAccount') }}</MkInfo>
</div>
<div class="_section">
<div class="_title">Account</div>
<div class="_content">
<MkTextarea v-model:value="settings" code tall></MkTextarea>
<!--<MkButton @click="saveSettings">Save</MkButton>-->
</div>
</div>
<div class="_section">
<div class="_title">Device</div>
<div class="_content">
<MkTextarea v-model:value="deviceSettings" code tall></MkTextarea>
<MkButton @click="saveDeviceSettings">Save</MkButton>
</div>
</div>
<div class="_section">
<div class="_title">Device (per account)</div>
<div class="_content">
<MkTextarea v-model:value="deviceUserSettings" code tall></MkTextarea>
<MkButton @click="saveDeviceUserSettings">Save</MkButton>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCode } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkInfo from '@/components/ui/info.vue';
import MkButton from '@/components/ui/button.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkInfo, MkButton, MkTextarea
},
emits: ['info'],
data() {
return {
INFO: {
header: [{
title: 'RegEdit',
icon: faCode
}]
},
settings: JSON5.stringify(this.$store.state.settings, null, '\t'),
deviceSettings: JSON5.stringify(this.$store.state.device, null, '\t'),
deviceUserSettings: JSON5.stringify(this.$store.state.deviceUser, null, '\t'),
};
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
saveDeviceSettings() {
const obj = JSON5.parse(this.deviceSettings);
this.$store.commit('device/overwrite', obj);
},
saveDeviceUserSettings() {
const obj = JSON5.parse(this.deviceUserSettings);
this.$store.commit('deviceUser/overwrite', obj);
},
}
});
</script>

View File

@@ -43,7 +43,7 @@
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</MkSelect>
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a><router-link to="/theme-editor" class="_link">{{ $t('_theme.make') }}</router-link>
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a><MkA to="/theme-editor" class="_link">{{ $t('_theme.make') }}</MkA>
</div>
<div class="_content">
<MkButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</MkButton>

View File

@@ -15,11 +15,18 @@ export default defineComponent({
XNotes
},
props: {
tag: {
type: String,
required: true
}
},
data() {
return {
INFO: {
header: [{
title: this.$route.params.tag,
title: this.tag,
icon: faHashtag
}],
},
@@ -27,7 +34,7 @@ export default defineComponent({
endpoint: 'notes/search-by-tag',
limit: 10,
params: () => ({
tag: this.$route.params.tag,
tag: this.tag,
})
},
faHashtag
@@ -35,7 +42,7 @@ export default defineComponent({
},
watch: {
$route() {
tag() {
(this.$refs.notes as any).reload();
}
},

View File

@@ -229,7 +229,7 @@ export default defineComponent({
},
messagingWindowOpen() {
os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue')));
os.pageWindow('/my/messaging');
},
openWaitingDialog(text?) {

View File

@@ -9,7 +9,7 @@
<div class="_content" v-else-if="tutorial === 1">
<div>{{ $t('_tutorial.step2_1') }}</div>
<div>{{ $t('_tutorial.step2_2') }}</div>
<router-link class="_link" to="/settings/profile">{{ $t('editProfile') }}</router-link>
<MkA class="_link" to="/settings/profile">{{ $t('editProfile') }}</MkA>
</div>
<div class="_content" v-else-if="tutorial === 2">
<div>{{ $t('_tutorial.step3_1') }}</div>
@@ -25,10 +25,10 @@
<div>{{ $t('_tutorial.step5_1') }}</div>
<i18n-t keypath="_tutorial.step5_2" tag="div">
<template #featured>
<router-link class="_link" to="/featured">{{ $t('featured') }}</router-link>
<MkA class="_link" to="/featured">{{ $t('featured') }}</MkA>
</template>
<template #explore>
<router-link class="_link" to="/explore">{{ $t('explore') }}</router-link>
<MkA class="_link" to="/explore">{{ $t('explore') }}</MkA>
</template>
</i18n-t>
<div>{{ $t('_tutorial.step5_3') }}</div>
@@ -43,7 +43,7 @@
<div>{{ $t('_tutorial.step7_1') }}</div>
<i18n-t keypath="_tutorial.step7_2" tag="div">
<template #help>
<router-link class="_link" to="/docs">{{ $t('help') }}</router-link>
<MkA class="_link" to="/docs">{{ $t('help') }}</MkA>
</template>
</i18n-t>
<div>{{ $t('_tutorial.step7_3') }}</div>

View File

@@ -10,7 +10,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import parseAcct from '../../../misc/acct/parse';
import MkUserInfo from '@/components/user-info.vue';
import MkPagination from '@/components/ui/pagination.vue';
import { userPage, acct } from '../../filters/user';
@@ -22,10 +21,14 @@ export default defineComponent({
},
props: {
user: {
type: Object,
required: true
},
type: {
type: String,
required: true
}
},
},
data() {
@@ -34,7 +37,7 @@ export default defineComponent({
endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers',
limit: 20,
params: {
...parseAcct(this.$route.params.user),
userId: this.user.id,
}
},
};
@@ -45,7 +48,7 @@ export default defineComponent({
this.$refs.list.reload();
},
'$route'() {
user() {
this.$refs.list.reload();
}
},

View File

@@ -2,11 +2,11 @@
<div class="ujigsodd">
<MkLoading v-if="fetching"/>
<div class="stream" v-if="!fetching && images.length > 0">
<router-link v-for="(image, i) in images" :key="i"
<MkA v-for="image in images"
class="img"
:style="`background-image: url(${thumbnail(image.file)})`"
:to="notePage(image.note)"
></router-link>
></MkA>
</div>
<p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p>
</div>

View File

@@ -67,24 +67,23 @@
</dl>
</div>
<div class="status">
<router-link :to="userPage(user)" :class="{ active: $route.name === 'user' }">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $t('notes') }}</span>
</router-link>
<router-link :to="userPage(user, 'following')" :class="{ active: $route.name === 'userFollowing' }">
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $t('following') }}</span>
</router-link>
<router-link :to="userPage(user, 'followers')" :class="{ active: $route.name === 'userFollowers' }">
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
</MkA>
</div>
</div>
</div>
<router-view :user="user"></router-view>
<template v-if="$route.name == 'user'">
<template v-if="page === 'index'">
<div class="_section">
<div class="_content _vMargin" v-if="user.pinnedNotes.length > 0">
<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/>
@@ -106,6 +105,8 @@
<XUserTimeline :user="user" class="_content"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user"/>
</div>
<div v-else-if="error">
<MkError @retry="fetch()"/>
@@ -128,7 +129,7 @@ import parseAcct from '../../../misc/acct/parse';
import { getScrollPosition } from '@/scripts/scroll';
import { getUserMenu } from '@/scripts/get-user-menu';
import number from '../../filters/number';
import { userPage, acct } from '../../filters/user';
import { userPage, acct as getAcct } from '../../filters/user';
import * as os from '@/os';
export default defineComponent({
@@ -139,10 +140,23 @@ export default defineComponent({
MkContainer,
MkRemoteCaution,
MkFolder,
XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
XPhotos: defineAsyncComponent(() => import('./index.photos.vue')),
XActivity: defineAsyncComponent(() => import('./index.activity.vue')),
},
props: {
acct: {
type: String,
required: true
},
page: {
type: String,
required: false,
default: 'index'
}
},
data() {
return {
INFO: computed(() => this.user ? {
@@ -176,7 +190,7 @@ export default defineComponent({
},
watch: {
$route: 'fetch'
acct: 'fetch'
},
created() {
@@ -192,10 +206,12 @@ export default defineComponent({
},
methods: {
getAcct,
fetch() {
if (this.$route.params.user == null) return;
if (this.acct == null) return;
Progress.start();
os.api('users/show', parseAcct(this.$route.params.user)).then(user => {
os.api('users/show', parseAcct(this.acct)).then(user => {
this.user = user;
}).catch(e => {
this.error = e;

View File

@@ -1,4 +1,4 @@
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, markRaw } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
@@ -18,30 +18,11 @@ export const router = createRouter({
routes: [
// NOTE: MkTimelineをdynamic importするとAsyncComponentWrapperが間に入るせいでkeep-aliveのコンポーネント指定が効かなくなる
{ path: '/', name: 'index', component: store.getters.isSignedIn ? MkTimeline : page('welcome') },
{ path: '/@:user', name: 'user', component: page('user/index'), children: [
{ path: 'following', name: 'userFollowing', component: page('user/follow-list'), props: { type: 'following' } },
{ path: 'followers', name: 'userFollowers', component: page('user/follow-list'), props: { type: 'followers' } },
]},
{ path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) },
{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) },
{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/@:acct/room', props: true, component: page('room/room') },
{ path: '/settings', name: 'settings', component: page('settings/index'), children: [
{ path: 'profile', component: page('settings/profile') },
{ path: 'privacy', component: page('settings/privacy') },
{ path: 'reaction', component: page('settings/reaction') },
{ path: 'notifications', component: page('settings/notifications') },
{ path: 'mute-block', component: page('settings/mute-block') },
{ path: 'word-mute', component: page('settings/word-mute') },
{ path: 'integration', component: page('settings/integration') },
{ path: 'security', component: page('settings/security') },
{ path: 'api', component: page('settings/api') },
{ path: 'other', component: page('settings/other') },
{ path: 'general', component: page('settings/general') },
{ path: 'theme', component: page('settings/theme') },
{ path: 'sidebar', component: page('settings/sidebar') },
{ path: 'sounds', component: page('settings/sounds') },
{ path: 'plugins', component: page('settings/plugins') },
]},
{ path: '/settings/:page?', name: 'settings', component: page('settings/index'), props: route => ({ page: route.params.page || null }) },
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') },
@@ -87,8 +68,8 @@ export const router = createRouter({
{ path: '/instance/relays', component: page('instance/relays') },
{ path: '/instance/announcements', component: page('instance/announcements') },
{ path: '/instance/abuses', component: page('instance/abuses') },
{ path: '/notes/:note', name: 'note', component: page('note') },
{ path: '/tags/:tag', component: page('tag') },
{ path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) },
{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) },
{ path: '/auth/:token', component: page('auth') },
{ path: '/miauth/:session', component: page('miauth') },
{ path: '/authorize-follow', component: page('follow') },
@@ -120,3 +101,13 @@ router.afterEach((to, from) => {
indexScrollPos = window.scrollY;
}
});
export function resolve(path: string) {
const resolved = router.resolve(path);
const route = resolved.matched[0];
return {
component: markRaw(route.components.default),
// TODO: route.propsには関数以外も入る可能性があるのでよしなにハンドリングする
props: route.props?.default ? route.props.default(resolved) : resolved.params
};
}

View File

@@ -7,7 +7,6 @@ import getAcct from '../../misc/acct/render';
import * as os from '@/os';
import { store, userActions } from '@/store';
import { router } from '@/router';
import { defineAsyncComponent } from 'vue';
import { popout } from './popout';
export function getUserMenu(user) {
@@ -137,7 +136,7 @@ export function getUserMenu(user) {
action: () => {
const acct = getAcct(user);
switch (store.state.device.chatOpenBehavior) {
case 'window': { os.pageWindow('/my/messaging/' + acct, defineAsyncComponent(() => import('@/pages/messaging/messaging-room.vue')), { userAcct: acct }); break; }
case 'window': { os.pageWindow('/my/messaging/' + acct); break; }
case 'popout': { popout('/my/messaging'); break; }
default: { router.push('/my/messaging'); break; }
}

View File

@@ -1,6 +1,6 @@
import { faBell, faComments, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { faAt, faBroadcastTower, faCloud, faColumns, faDoorClosed, faFileAlt, faFireAlt, faGamepad, faHashtag, faListUl, faSatellite, faSatelliteDish, faSearch, faStar, faTerminal, faUserClock, faUsers } from '@fortawesome/free-solid-svg-icons';
import { computed, defineAsyncComponent } from 'vue';
import { computed } from 'vue';
import { store } from '@/store';
import { deckmode } from '@/config';
import { search } from '@/scripts/search';
@@ -23,7 +23,7 @@ export const sidebarDef = {
indicated: computed(() => store.getters.isSignedIn && store.state.i.hasUnreadMessagingMessage),
action: () => {
switch (store.state.device.chatOpenBehavior) {
case 'window': { os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue'))); break; }
case 'window': { os.pageWindow('/my/messaging'); break; }
case 'popout': { popout('/my/messaging'); break; }
default: { router.push('/my/messaging'); break; }
}

View File

@@ -70,6 +70,8 @@ export const defaultDeviceSettings = {
animatedMfm: true,
imageNewTab: false,
chatOpenBehavior: 'page',
defaultSideView: false,
deckNavWindow: true,
showFixedPostForm: false,
disablePagesScript: false,
enableInfiniteScroll: true,
@@ -202,6 +204,15 @@ export const store = createStore({
state: defaultDeviceSettings,
mutations: {
overwrite(state, x) {
for (const k of Object.keys(state)) {
if (x[k] === undefined) delete state[k];
}
for (const k of Object.keys(x)) {
state[k] = x[k];
}
},
set(state, x: { key: string; value: any }) {
state[x.key] = x.value;
},
@@ -218,6 +229,15 @@ export const store = createStore({
state: defaultDeviceUserSettings,
mutations: {
overwrite(state, x) {
for (const k of Object.keys(state)) {
if (x[k] === undefined) delete state[k];
}
for (const k of Object.keys(x)) {
state[k] = x[k];
}
},
init(state, x) {
for (const [key, value] of Object.entries(defaultDeviceUserSettings)) {
if (x[key]) {

View File

@@ -1,9 +1,4 @@
<template>
<ZenUI v-if="zen"/>
<VisitorUI v-else-if="!$store.getters.isSignedIn"/>
<DeckUI v-else-if="deckmode"/>
<DefaultUI v-else/>
<component v-for="popup in popups"
:key="popup.id"
:is="popup.component"
@@ -13,27 +8,23 @@
<XUpload v-if="uploads.length > 0"/>
<XStreamIndicator/>
<div id="wait" v-if="pendingApiRequestsCount > 0"></div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { deckmode } from '@/config';
import { popups, uploads, pendingApiRequestsCount } from '@/os';
export default defineComponent({
components: {
DefaultUI: defineAsyncComponent(() => import('@/ui/default.vue')),
DeckUI: defineAsyncComponent(() => import('@/ui/deck.vue')),
ZenUI: defineAsyncComponent(() => import('@/ui/zen.vue')),
VisitorUI: defineAsyncComponent(() => import('@/ui/visitor.vue')),
XUpload: defineAsyncComponent(() => import('@/components/upload.vue')),
XStreamIndicator: defineAsyncComponent(() => import('./stream-indicator.vue')),
XUpload: defineAsyncComponent(() => import('./upload.vue')),
},
setup() {
return {
zen: window.location.search === '?zen',
deckmode,
uploads,
popups,
pendingApiRequestsCount,

View File

@@ -29,7 +29,7 @@
<button v-if="$store.getters.isSignedIn" class="nav _button" @click="showNav()"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button>
<button v-if="$store.getters.isSignedIn" class="post _buttonPrimary" @click="post()"><Fa :icon="faPencilAlt"/></button>
<StreamIndicator v-if="$store.getters.isSignedIn"/>
<XCommon/>
</div>
</template>
@@ -47,9 +47,11 @@ import XHeader from './_common_/header.vue';
import { getScrollContainer } from '@/scripts/scroll';
import * as os from '@/os';
import { sidebarDef } from '@/sidebar';
import XCommon from './_common_/common.vue';
export default defineComponent({
components: {
XCommon,
XSidebar,
XHeader,
DeckColumn,

View File

@@ -0,0 +1,157 @@
<template>
<div class="qvzfzxam _narrow_" v-if="component">
<div class="container">
<header class="header" @contextmenu.prevent.stop="onContextmenu">
<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
<XHeader class="title" :info="pageInfo" :with-back="false"/>
<button class="_button" @click="close()"><Fa :icon="faTimes"/></button>
</header>
<component :is="component" v-bind="props" :ref="changePage"/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faTimes, faChevronLeft, faExpandAlt, faWindowMaximize, faExternalLinkAlt, faLink } from '@fortawesome/free-solid-svg-icons';
import XHeader from './_common_/header.vue';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { resolve } from '@/router';
export default defineComponent({
components: {
XHeader
},
provide() {
return {
navHook: (url) => {
this.navigate(url);
}
};
},
data() {
return {
url: null,
component: null,
props: {},
pageInfo: null,
history: [],
faTimes, faChevronLeft,
};
},
methods: {
changePage(page) {
if (page == null) return;
if (page.INFO) {
this.pageInfo = page.INFO;
}
},
navigate(url, record = true) {
if (record && this.url) this.history.push(this.url);
this.url = url;
const { component, props } = resolve(url);
this.component = component;
this.props = props;
},
back() {
this.navigate(this.history.pop(), false);
},
close() {
this.url = null;
this.component = null;
this.props = {};
},
onContextmenu(e) {
os.contextMenu([{
type: 'label',
text: this.url,
}, {
icon: faExpandAlt,
text: this.$t('showInPage'),
action: () => {
this.$router.push(this.url);
this.close();
}
}, {
icon: faWindowMaximize,
text: this.$t('openInWindow'),
action: () => {
os.pageWindow(this.url);
this.close();
}
}, null, {
icon: faExternalLinkAlt,
text: this.$t('openInNewTab'),
action: () => {
window.open(this.url, '_blank');
this.close();
}
}, {
icon: faLink,
text: this.$t('copyLink'),
action: () => {
copyToClipboard(this.url);
}
}], e);
}
}
});
</script>
<style lang="scss" scoped>
.qvzfzxam {
$header-height: 58px; // TODO: どこかに集約したい
--section-padding: 16px;
--margin: var(--marginHalf);
> .container {
position: fixed;
width: 370px;
height: 100vh;
overflow: auto;
box-sizing: border-box;
> .header {
display: flex;
position: sticky;
z-index: 1000;
top: 0;
height: $header-height;
width: 100%;
line-height: $header-height;
text-align: center;
font-weight: bold;
//background-color: var(--panel);
-webkit-backdrop-filter: blur(32px);
backdrop-filter: blur(32px);
background-color: var(--header);
border-bottom: solid 1px var(--divider);
> ._button {
height: $header-height;
width: $header-height;
&:hover {
color: var(--fgHighlighted);
}
}
> .title {
flex: 1;
position: relative;
}
}
}
}
</style>

View File

@@ -20,6 +20,8 @@
</main>
</div>
<XSide v-if="isDesktop" class="side" ref="side"/>
<div v-if="isDesktop" class="widgets">
<div ref="widgetsSpacer"></div>
<XWidgets @mounted="attachSticky"/>
@@ -47,19 +49,21 @@
<XWidgets v-if="widgetsShowing" class="tray"/>
</transition>
<StreamIndicator/>
<XCommon/>
</div>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import { defineComponent, defineAsyncComponent, markRaw } from 'vue';
import { faLayerGroup, faBars, faHome, faCircle } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config';
import { search } from '@/scripts/search';
import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from '@/components/sidebar.vue';
import XCommon from './_common_/common.vue';
import XHeader from './_common_/header.vue';
import XSide from './default.side.vue';
import * as os from '@/os';
import { sidebarDef } from '@/sidebar';
@@ -67,9 +71,19 @@ const DESKTOP_THRESHOLD = 1100;
export default defineComponent({
components: {
XCommon,
XSidebar,
XHeader,
XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')),
XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
},
provide() {
return {
sideViewHook: (url) => {
this.$refs.side.navigate(url);
}
};
},
data() {
@@ -245,7 +259,7 @@ export default defineComponent({
}
.mk-app {
$header-height: 60px;
$header-height: 58px; // TODO: どこかに集約したい
$ui-font-size: 1em; // TODO: どこかに集約したい
$widgets-hide-threshold: 1090px;
@@ -301,6 +315,12 @@ export default defineComponent({
}
}
> .side {
min-width: 370px;
max-width: 370px;
border-left: solid 1px var(--divider);
}
> .widgets {
padding: 0 var(--margin);
border-left: solid 1px var(--divider);

View File

@@ -1,10 +1,10 @@
<template>
<div class="mk-app">
<header>
<router-link class="link" to="/">{{ $t('home') }}</router-link>
<router-link class="link" to="/announcements">{{ $t('announcements') }}</router-link>
<router-link class="link" to="/channels">{{ $t('channel') }}</router-link>
<router-link class="link" to="/about">{{ $t('aboutX', { x: instanceName || host }) }}</router-link>
<MkA class="link" to="/">{{ $t('home') }}</MkA>
<MkA class="link" to="/announcements">{{ $t('announcements') }}</MkA>
<MkA class="link" to="/channels">{{ $t('channel') }}</MkA>
<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName || host }) }}</MkA>
</header>
<div class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }">
@@ -23,12 +23,12 @@
</router-view>
</main>
<div class="powered-by">
<b><router-link to="/">{{ host }}</router-link></b>
<b><MkA to="/">{{ host }}</MkA></b>
<small>Powered by <a href="https://github.com/syuilo/misskey" target="_blank">Misskey</a></small>
</div>
</div>
<StreamIndicator v-if="$store.getters.isSignedIn"/>
<XCommon/>
</div>
</template>
@@ -39,12 +39,14 @@ import { host, instanceName } from '@/config';
import { search } from '@/scripts/search';
import * as os from '@/os';
import XHeader from './_common_/header.vue';
import XCommon from './_common_/common.vue';
const DESKTOP_THRESHOLD = 1100;
export default defineComponent({
components: {
XHeader
XCommon,
XHeader,
},
data() {
@@ -130,7 +132,7 @@ export default defineComponent({
line-height: 60px;
padding: 0 0.7em;
&.router-link-active {
&.MkA-active {
box-shadow: 0 -2px 0 0 var(--accent) inset;
}
}

View File

@@ -17,7 +17,7 @@
</main>
</div>
<StreamIndicator/>
<XCommon/>
</div>
</template>
@@ -28,10 +28,12 @@ import { faBell } from '@fortawesome/free-regular-svg-icons';
import { host } from '@/config';
import { search } from '@/scripts/search';
import XHeader from './_common_/header.vue';
import XCommon from './_common_/common.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XCommon,
XHeader,
},

View File

@@ -7,7 +7,7 @@
<transition-group tag="div" name="chart" class="tags" v-else>
<div v-for="stat in stats" :key="stat.tag">
<div class="tag">
<router-link class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
<p>{{ $t('nUsersMentioned', { n: stat.usersCount }) }}</p>
</div>
<MkMiniChart class="chart" :src="stat.chart"/>

View File

@@ -3,7 +3,6 @@ import { getConnection } from 'typeorm';
export const meta = {
requireCredential: true as const,
requireAdmin: true,
requireModerator: true,
desc: {

View File

@@ -6,7 +6,6 @@ import redis from '../../../../db/redis';
export const meta = {
requireCredential: true as const,
requireAdmin: true,
requireModerator: true,
desc: {

View File

@@ -50,8 +50,6 @@ module.exports = {
preserveWhitespace: false
}
}
}, {
loader: 'vue-svg-inline-loader-corejs3'
}]
}, {
test: /\.scss?$/,

919
yarn.lock

File diff suppressed because it is too large Load Diff