forked from FoundKeyGang/FoundKey
Merge branch 'develop' into pr/4549
This commit is contained in:
commit
05862a420f
47 changed files with 441 additions and 83 deletions
|
@ -36,7 +36,10 @@ npm run migrate
|
||||||
|
|
||||||
11.24.0 (2019/07/05)
|
11.24.0 (2019/07/05)
|
||||||
--------------------
|
--------------------
|
||||||
注意: このアップデート後に、`node built/tools/accept-migration Init 1000000000000`してください。
|
### 注意
|
||||||
|
- このアップデート後に、`node built/tools/accept-migration Init 1000000000000`してください。
|
||||||
|
- プロセスを起動(もしくは再起動)する前に[マイグレーション](#migration)の手順を実行してください
|
||||||
|
|
||||||
|
|
||||||
### ✨Improvements
|
### ✨Improvements
|
||||||
* WebAuthnサポート
|
* WebAuthnサポート
|
||||||
|
|
|
@ -220,8 +220,7 @@ const user = await Users.findOne(userId).then(ensure);
|
||||||
|
|
||||||
### Migration作成方法
|
### Migration作成方法
|
||||||
```
|
```
|
||||||
npm i -g ts-node
|
npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
|
||||||
ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前
|
|
||||||
```
|
```
|
||||||
|
|
||||||
作成されたスクリプトは不必要な変更を含むため除去してください。
|
作成されたスクリプトは不必要な変更を含むため除去してください。
|
||||||
|
|
|
@ -967,7 +967,7 @@ desktop/views/components/drive.file.vue:
|
||||||
unmark-as-sensitive: "閲覧注意を解除"
|
unmark-as-sensitive: "閲覧注意を解除"
|
||||||
copy-url: "URLをコピー"
|
copy-url: "URLをコピー"
|
||||||
download: "ダウンロード"
|
download: "ダウンロード"
|
||||||
else-files: "その他..."
|
else-files: "その他"
|
||||||
set-as-avatar: "アイコンに設定"
|
set-as-avatar: "アイコンに設定"
|
||||||
set-as-banner: "バナーに設定"
|
set-as-banner: "バナーに設定"
|
||||||
open-in-app: "アプリで開く"
|
open-in-app: "アプリで開く"
|
||||||
|
@ -978,15 +978,20 @@ desktop/views/components/drive.file.vue:
|
||||||
copied-url-to-clipboard: "URLをクリップボードにコピーしました"
|
copied-url-to-clipboard: "URLをクリップボードにコピーしました"
|
||||||
|
|
||||||
desktop/views/components/drive.folder.vue:
|
desktop/views/components/drive.folder.vue:
|
||||||
|
upload-folder: "既定アップロード先"
|
||||||
unable-to-process: "操作を完了できません"
|
unable-to-process: "操作を完了できません"
|
||||||
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
|
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
|
||||||
unhandled-error: "不明なエラー"
|
unhandled-error: "不明なエラー"
|
||||||
|
unable-to-delete: "削除できません"
|
||||||
|
has-child-files-or-folders: "このフォルダは空でないため、削除できません。"
|
||||||
contextmenu:
|
contextmenu:
|
||||||
move-to-this-folder: "このフォルダへ移動"
|
move-to-this-folder: "このフォルダへ移動"
|
||||||
show-in-new-window: "新しいウィンドウで表示"
|
show-in-new-window: "新しいウィンドウで表示"
|
||||||
rename: "名前を変更"
|
rename: "名前を変更"
|
||||||
rename-folder: "フォルダ名の変更"
|
rename-folder: "フォルダ名の変更"
|
||||||
input-new-folder-name: "新しいフォルダ名を入力してください"
|
input-new-folder-name: "新しいフォルダ名を入力してください"
|
||||||
|
else-folders: "その他"
|
||||||
|
set-as-upload-folder: "既定アップロード先に設定"
|
||||||
|
|
||||||
desktop/views/components/drive.vue:
|
desktop/views/components/drive.vue:
|
||||||
search: "検索"
|
search: "検索"
|
||||||
|
@ -1150,6 +1155,9 @@ common/views/components/drive-settings.vue:
|
||||||
max: "容量"
|
max: "容量"
|
||||||
in-use: "使用中"
|
in-use: "使用中"
|
||||||
stats: "統計"
|
stats: "統計"
|
||||||
|
default-upload-folder: "既定のアップロード先フォルダ"
|
||||||
|
default-upload-folder-name: "フォルダ"
|
||||||
|
change-default-upload-folder: "フォルダを変更"
|
||||||
|
|
||||||
common/views/components/mute-and-block.vue:
|
common/views/components/mute-and-block.vue:
|
||||||
mute-and-block: "ミュートとブロック"
|
mute-and-block: "ミュートとブロック"
|
||||||
|
@ -1916,6 +1924,7 @@ deck/deck.user-column.vue:
|
||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
pinned-notes: "ピン留めされた投稿"
|
pinned-notes: "ピン留めされた投稿"
|
||||||
|
pinned-page: "ピン留めされたページ"
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
|
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
|
||||||
|
@ -1955,6 +1964,8 @@ pages:
|
||||||
are-you-sure-delete: "このページを削除しますか?"
|
are-you-sure-delete: "このページを削除しますか?"
|
||||||
page-deleted: "ページを削除しました"
|
page-deleted: "ページを削除しました"
|
||||||
edit-this-page: "このページを編集"
|
edit-this-page: "このページを編集"
|
||||||
|
pin-this-page: "ピン留め"
|
||||||
|
unpin-this-page: "ピン留め解除"
|
||||||
view-source: "ソースを表示"
|
view-source: "ソースを表示"
|
||||||
view-page: "ページを見る"
|
view-page: "ページを見る"
|
||||||
like: "いいね"
|
like: "いいね"
|
||||||
|
@ -1973,6 +1984,7 @@ pages:
|
||||||
url: "ページURL"
|
url: "ページURL"
|
||||||
summary: "ページの要約"
|
summary: "ページの要約"
|
||||||
align-center: "中央寄せ"
|
align-center: "中央寄せ"
|
||||||
|
hide-title-when-pinned: "ピン留めされているときにタイトルを非表示"
|
||||||
font: "フォント"
|
font: "フォント"
|
||||||
fontSerif: "セリフ"
|
fontSerif: "セリフ"
|
||||||
fontSansSerif: "サンセリフ"
|
fontSansSerif: "サンセリフ"
|
||||||
|
@ -2045,6 +2057,8 @@ pages:
|
||||||
_pushEvent:
|
_pushEvent:
|
||||||
event: "イベント名"
|
event: "イベント名"
|
||||||
message: "押したときに表示するメッセージ"
|
message: "押したときに表示するメッセージ"
|
||||||
|
variable: "送信する変数"
|
||||||
|
no-variable: "なし"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
categories:
|
categories:
|
||||||
|
|
17
migration/1562444565093-PinnedPage.ts
Normal file
17
migration/1562444565093-PinnedPage.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class PinnedPage1562444565093 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD "pinnedPageId" character varying(32)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27" UNIQUE ("pinnedPageId")`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27" FOREIGN KEY ("pinnedPageId") REFERENCES "page"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "pinnedPageId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
migration/1562448332510-PageTitleHideOption.ts
Normal file
13
migration/1562448332510-PageTitleHideOption.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class PageTitleHideOption1562448332510 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" ADD "hideTitleWhenPinned" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "hideTitleWhenPinned"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -151,9 +151,16 @@ export default (opts) => ({
|
||||||
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
||||||
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
|
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
|
||||||
this.visibility = this.reply.visibility;
|
this.visibility = this.reply.visibility;
|
||||||
|
if (this.reply.visibility === 'specified') {
|
||||||
|
this.$root.api('users/show', {
|
||||||
|
userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$store.state.i.id && uid !== this.reply.userId)
|
||||||
|
}).then(users => {
|
||||||
|
this.visibleUsers.push(...users);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply) {
|
if (this.reply && this.reply.userId !== this.$store.state.i.id) {
|
||||||
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
||||||
this.visibleUsers.push(user);
|
this.visibleUsers.push(user);
|
||||||
});
|
});
|
||||||
|
@ -238,7 +245,7 @@ export default (opts) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
upload(file) {
|
upload(file) {
|
||||||
(this.$refs.uploader as any).upload(file);
|
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeUploadings(uploads) {
|
onChangeUploadings(uploads) {
|
||||||
|
|
|
@ -158,7 +158,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
upload(file) {
|
upload(file) {
|
||||||
(this.$refs.uploader as any).upload(file);
|
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
|
||||||
},
|
},
|
||||||
|
|
||||||
onUploaded(file) {
|
onUploaded(file) {
|
||||||
|
|
|
@ -30,7 +30,10 @@ export default Vue.extend({
|
||||||
} else if (this.value.action === 'pushEvent') {
|
} else if (this.value.action === 'pushEvent') {
|
||||||
this.$root.api('page-push', {
|
this.$root.api('page-push', {
|
||||||
pageId: this.script.page.id,
|
pageId: this.script.page.id,
|
||||||
event: this.value.event
|
event: this.value.event,
|
||||||
|
...(this.value.var ? {
|
||||||
|
var: this.script.vars[this.value.var]
|
||||||
|
} : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
@ -46,7 +49,7 @@ export default Vue.extend({
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.kudkigyw
|
.kudkigyw
|
||||||
display inline-block
|
display inline-block
|
||||||
min-width 300px
|
min-width 200px
|
||||||
max-width 450px
|
max-width 450px
|
||||||
margin 8px 0
|
margin 8px 0
|
||||||
</style>
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="page" class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }" :key="path">
|
<div class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }">
|
||||||
<header>
|
<header v-if="showTitle">
|
||||||
<div class="title">{{ page.title }}</div>
|
<div class="title">{{ page.title }}</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -8,9 +8,13 @@
|
||||||
<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/>
|
<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer v-if="showFooter">
|
||||||
<small>@{{ page.user.username }}</small>
|
<small>@{{ page.user.username }}</small>
|
||||||
<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
|
<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId">
|
||||||
|
<router-link :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
|
||||||
|
<a v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)">{{ $t('unpin-this-page') }}</a>
|
||||||
|
<a v-else @click="pin(true)">{{ $t('pin-this-page') }}</a>
|
||||||
|
</template>
|
||||||
<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
|
<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
|
||||||
<div class="like">
|
<div class="like">
|
||||||
<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button>
|
<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button>
|
||||||
|
@ -25,7 +29,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
|
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faHeart, faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
import { faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||||
import XBlock from './page.block.vue';
|
import XBlock from './page.block.vue';
|
||||||
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
|
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
|
||||||
import { collectPageVars } from '../../../scripts/collect-page-vars';
|
import { collectPageVars } from '../../../scripts/collect-page-vars';
|
||||||
|
@ -69,64 +73,43 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
pageName: {
|
page: {
|
||||||
type: String,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
username: {
|
showTitle: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
required: true
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
page: null,
|
|
||||||
script: null,
|
script: null,
|
||||||
faHeartS, faHeart
|
faHeartS, faHeart
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
path(): string {
|
|
||||||
return this.username + '/' + this.pageName;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
path() {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.fetch();
|
const pageVars = this.getPageVars();
|
||||||
|
this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, {
|
||||||
|
randomSeed: Math.random(),
|
||||||
|
user: this.page.user,
|
||||||
|
visitor: this.$store.state.i,
|
||||||
|
page: this.page,
|
||||||
|
url: url
|
||||||
|
}), e => {
|
||||||
|
console.dir(e);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetch() {
|
|
||||||
this.$root.api('pages/show', {
|
|
||||||
name: this.pageName,
|
|
||||||
username: this.username,
|
|
||||||
}).then(page => {
|
|
||||||
this.page = page;
|
|
||||||
this.$emit('init', {
|
|
||||||
title: this.page.title,
|
|
||||||
icon: faStickyNote
|
|
||||||
});
|
|
||||||
const pageVars = this.getPageVars();
|
|
||||||
this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, {
|
|
||||||
randomSeed: Math.random(),
|
|
||||||
user: page.user,
|
|
||||||
visitor: this.$store.state.i,
|
|
||||||
page: page,
|
|
||||||
url: url
|
|
||||||
}), e => {
|
|
||||||
console.dir(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getPageVars() {
|
getPageVars() {
|
||||||
return collectPageVars(this.page.content);
|
return collectPageVars(this.page.content);
|
||||||
},
|
},
|
||||||
|
@ -147,6 +130,17 @@ export default Vue.extend({
|
||||||
this.page.isLiked = false;
|
this.page.isLiked = false;
|
||||||
this.page.likedCount--;
|
this.page.likedCount--;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
pin(pin) {
|
||||||
|
this.$root.api('i/update', {
|
||||||
|
pinnedPageId: pin ? this.page.id : null,
|
||||||
|
}).then(() => {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'success',
|
||||||
|
splash: true
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -11,6 +11,12 @@
|
||||||
<header>{{ $t('stats') }}</header>
|
<header>{{ $t('stats') }}</header>
|
||||||
<div ref="chart" style="margin-bottom: -16px; margin-left: -8px; color: #000;"></div>
|
<div ref="chart" style="margin-bottom: -16px; margin-left: -8px; color: #000;"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>{{ $t('default-upload-folder') }}</header>
|
||||||
|
<ui-input v-model="uploadFolderName" readonly>{{ $t('default-upload-folder-name') }}</ui-input>
|
||||||
|
<ui-button @click="chooseUploadFolder()">{{ $t('change-default-upload-folder') }}</ui-button>
|
||||||
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,7 +32,8 @@ export default Vue.extend({
|
||||||
return {
|
return {
|
||||||
fetching: true,
|
fetching: true,
|
||||||
usage: null,
|
usage: null,
|
||||||
capacity: null
|
capacity: null,
|
||||||
|
uploadFolderName: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,10 +47,25 @@ export default Vue.extend({
|
||||||
l: 0.5
|
l: 0.5
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
uploadFolder: {
|
||||||
|
get() { return this.$store.state.settings.uploadFolder; },
|
||||||
|
set(value) { this.$store.dispatch('settings/set', { key: 'uploadFolder', value }); }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (this.uploadFolder == null) {
|
||||||
|
this.uploadFolderName = this.$t('@._settings.root');
|
||||||
|
} else {
|
||||||
|
this.$root.api('drive/folders/show', {
|
||||||
|
folderId: this.uploadFolder
|
||||||
|
}).then(folder => {
|
||||||
|
this.uploadFolderName = folder.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.$root.api('drive').then(info => {
|
this.$root.api('drive').then(info => {
|
||||||
this.capacity = info.capacity;
|
this.capacity = info.capacity;
|
||||||
this.usage = info.usage;
|
this.usage = info.usage;
|
||||||
|
@ -89,6 +111,9 @@ export default Vue.extend({
|
||||||
height: 150,
|
height: 150,
|
||||||
zoom: {
|
zoom: {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
|
@ -152,6 +177,13 @@ export default Vue.extend({
|
||||||
|
|
||||||
chart.render();
|
chart.render();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
chooseUploadFolder() {
|
||||||
|
this.$chooseDriveFolder().then(folder => {
|
||||||
|
this.uploadFolder = folder ? folder.id : null;
|
||||||
|
this.uploadFolderName = folder ? folder.name : this.$t('@._settings.root');
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :custom-emojis="notification.note.renote.emojis"/>
|
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
69
src/client/app/common/views/deck/deck.page-column.vue
Normal file
69
src/client/app/common/views/deck/deck.page-column.vue
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<x-column>
|
||||||
|
<template #header>
|
||||||
|
<fa :icon="faStickyNote"/>{{ page ? page.name : '' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="page">
|
||||||
|
<x-page :page="page" :key="page.id"/>
|
||||||
|
</div>
|
||||||
|
</x-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import XColumn from './deck.column.vue';
|
||||||
|
import XPage from '../../../common/views/components/page/page.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n(),
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XColumn,
|
||||||
|
XPage
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
pageName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: null,
|
||||||
|
faStickyNote
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route: 'fetch'
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.$root.api('pages/show', {
|
||||||
|
name: this.pageName,
|
||||||
|
username: this.username,
|
||||||
|
}).then(page => {
|
||||||
|
this.page = page;
|
||||||
|
this.$emit('init', {
|
||||||
|
title: this.page.title,
|
||||||
|
icon: faStickyNote
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<ui-container v-if="user.pinnedPage" :body-togglable="true">
|
||||||
|
<template #header><fa icon="thumbtack"/> {{ $t('pinned-page') }}</template>
|
||||||
|
<div>
|
||||||
|
<x-page :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/>
|
||||||
|
</div>
|
||||||
|
</ui-container>
|
||||||
<ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true">
|
<ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true">
|
||||||
<template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template>
|
<template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template>
|
||||||
<div>
|
<div>
|
||||||
|
@ -48,6 +54,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
XNotes,
|
XNotes,
|
||||||
|
XPage: () => import('../../../common/views/components/page/page.vue').then(m => m.default),
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -17,6 +17,17 @@
|
||||||
<template v-else-if="value.action === 'pushEvent'">
|
<template v-else-if="value.action === 'pushEvent'">
|
||||||
<ui-input v-model="value.event"><span>{{ $t('blocks._button._action._pushEvent.event') }}</span></ui-input>
|
<ui-input v-model="value.event"><span>{{ $t('blocks._button._action._pushEvent.event') }}</span></ui-input>
|
||||||
<ui-input v-model="value.message"><span>{{ $t('blocks._button._action._pushEvent.message') }}</span></ui-input>
|
<ui-input v-model="value.message"><span>{{ $t('blocks._button._action._pushEvent.message') }}</span></ui-input>
|
||||||
|
<ui-select v-model="value.var">
|
||||||
|
<template #label>{{ $t('blocks._button._action._pushEvent.variable') }}</template>
|
||||||
|
<option :value="null">{{ $t('blocks._button._action._pushEvent.no-variable') }}</option>
|
||||||
|
<option v-for="v in aiScript.getVarsByType()" :value="v.name">{{ v.name }}</option>
|
||||||
|
<optgroup :label="$t('script.pageVariables')">
|
||||||
|
<option v-for="v in aiScript.getPageVarsByType()" :value="v">{{ v }}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup :label="$t('script.enviromentVariables')">
|
||||||
|
<option v-for="v in aiScript.getEnvVarsByType()" :value="v">{{ v }}</option>
|
||||||
|
</optgroup>
|
||||||
|
</ui-select>
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
</x-container>
|
</x-container>
|
||||||
|
@ -39,6 +50,9 @@ export default Vue.extend({
|
||||||
value: {
|
value: {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
aiScript: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -53,7 +67,8 @@ export default Vue.extend({
|
||||||
if (this.value.content == null) Vue.set(this.value, 'content', null);
|
if (this.value.content == null) Vue.set(this.value, 'content', null);
|
||||||
if (this.value.event == null) Vue.set(this.value, 'event', null);
|
if (this.value.event == null) Vue.set(this.value, 'event', null);
|
||||||
if (this.value.message == null) Vue.set(this.value, 'message', null);
|
if (this.value.message == null) Vue.set(this.value, 'message', null);
|
||||||
if (this.value.message == null) Vue.set(this.value, 'primary', false);
|
if (this.value.primary == null) Vue.set(this.value, 'primary', false);
|
||||||
|
if (this.value.var == null) Vue.set(this.value, 'var', null);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
<option value="sans-serif">{{ $t('fontSansSerif') }}</option>
|
<option value="sans-serif">{{ $t('fontSansSerif') }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
|
|
||||||
|
<ui-switch v-model="hideTitleWhenPinned">{{ $t('hide-title-when-pinned') }}</ui-switch>
|
||||||
|
|
||||||
<div class="eyeCatch">
|
<div class="eyeCatch">
|
||||||
<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button>
|
<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button>
|
||||||
<div v-else-if="eyeCatchingImage">
|
<div v-else-if="eyeCatchingImage">
|
||||||
|
@ -140,6 +142,7 @@ export default Vue.extend({
|
||||||
font: 'sans-serif',
|
font: 'sans-serif',
|
||||||
content: [],
|
content: [],
|
||||||
alignCenter: false,
|
alignCenter: false,
|
||||||
|
hideTitleWhenPinned: false,
|
||||||
variables: [],
|
variables: [],
|
||||||
aiScript: null,
|
aiScript: null,
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
|
@ -192,6 +195,7 @@ export default Vue.extend({
|
||||||
this.currentName = this.page.name;
|
this.currentName = this.page.name;
|
||||||
this.summary = this.page.summary;
|
this.summary = this.page.summary;
|
||||||
this.font = this.page.font;
|
this.font = this.page.font;
|
||||||
|
this.hideTitleWhenPinned = this.page.hideTitleWhenPinned;
|
||||||
this.alignCenter = this.page.alignCenter;
|
this.alignCenter = this.page.alignCenter;
|
||||||
this.content = this.page.content;
|
this.content = this.page.content;
|
||||||
this.variables = this.page.variables;
|
this.variables = this.page.variables;
|
||||||
|
@ -223,6 +227,7 @@ export default Vue.extend({
|
||||||
name: this.name.trim(),
|
name: this.name.trim(),
|
||||||
summary: this.summary,
|
summary: this.summary,
|
||||||
font: this.font,
|
font: this.font,
|
||||||
|
hideTitleWhenPinned: this.hideTitleWhenPinned,
|
||||||
alignCenter: this.alignCenter,
|
alignCenter: this.alignCenter,
|
||||||
content: this.content,
|
content: this.content,
|
||||||
variables: this.variables,
|
variables: this.variables,
|
||||||
|
@ -240,6 +245,7 @@ export default Vue.extend({
|
||||||
name: this.name.trim(),
|
name: this.name.trim(),
|
||||||
summary: this.summary,
|
summary: this.summary,
|
||||||
font: this.font,
|
font: this.font,
|
||||||
|
hideTitleWhenPinned: this.hideTitleWhenPinned,
|
||||||
alignCenter: this.alignCenter,
|
alignCenter: this.alignCenter,
|
||||||
content: this.content,
|
content: this.content,
|
||||||
variables: this.variables,
|
variables: this.variables,
|
||||||
|
|
63
src/client/app/common/views/pages/page.vue
Normal file
63
src/client/app/common/views/pages/page.vue
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<x-page v-if="page" :page="page" :key="page.id" :show-footer="true"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import XPage from '../components/page/page.vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XPage
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
pageName: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
path(): string {
|
||||||
|
return this.username + '/' + this.pageName;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
path() {
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.$root.api('pages/show', {
|
||||||
|
name: this.pageName,
|
||||||
|
username: this.username,
|
||||||
|
}).then(page => {
|
||||||
|
this.page = page;
|
||||||
|
this.$emit('init', {
|
||||||
|
title: this.page.title,
|
||||||
|
icon: faStickyNote
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -122,7 +122,7 @@ export default define({
|
||||||
},
|
},
|
||||||
|
|
||||||
upload(file) {
|
upload(file) {
|
||||||
(this.$refs.uploader as any).upload(file);
|
(this.$refs.uploader as any).upload(file, this.$store.state.settings.uploadFolder);
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragover(e) {
|
onDragover(e) {
|
||||||
|
|
|
@ -148,6 +148,7 @@ init(async (launch, os) => {
|
||||||
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
|
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
|
||||||
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
|
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
|
||||||
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
|
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
|
||||||
|
{ path: '/@:username/pages/:pageName', name: 'page', props: true, component: () => import('../common/views/deck/deck.page-column.vue').then(m => m.default) },
|
||||||
]}
|
]}
|
||||||
: { path: '/', component: MkHome, children: [
|
: { path: '/', component: MkHome, children: [
|
||||||
{ path: '', name: 'index', component: MkHomeTimeline },
|
{ path: '', name: 'index', component: MkHomeTimeline },
|
||||||
|
@ -171,12 +172,11 @@ init(async (launch, os) => {
|
||||||
{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) },
|
{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) },
|
||||||
{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
|
{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
|
||||||
{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
|
{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
|
||||||
{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) },
|
{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) },
|
||||||
{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
|
{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
|
||||||
]},
|
]},
|
||||||
{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
|
{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) },
|
||||||
{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
|
{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) },
|
||||||
{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) },
|
|
||||||
{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
|
{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
|
||||||
{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
|
{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
|
||||||
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
{ path: '/i/messaging/:user', component: MkMessagingRoom },
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
<template v-if="!hover"><fa :icon="['far', 'folder']" fixed-width/></template>
|
<template v-if="!hover"><fa :icon="['far', 'folder']" fixed-width/></template>
|
||||||
{{ folder.name }}
|
{{ folder.name }}
|
||||||
</p>
|
</p>
|
||||||
|
<p class="upload" v-if="$store.state.settings.uploadFolder == folder.id">
|
||||||
|
{{ $t('upload-folder') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -73,6 +76,14 @@ export default Vue.extend({
|
||||||
text: this.$t('@.delete'),
|
text: this.$t('@.delete'),
|
||||||
icon: ['far', 'trash-alt'],
|
icon: ['far', 'trash-alt'],
|
||||||
action: this.deleteFolder
|
action: this.deleteFolder
|
||||||
|
}, null, {
|
||||||
|
type: 'nest',
|
||||||
|
text: this.$t('contextmenu.else-folders'),
|
||||||
|
menu: [{
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.set-as-upload-folder'),
|
||||||
|
action: this.setAsUploadFolder
|
||||||
|
}]
|
||||||
}], {
|
}], {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
this.isContextmenuShowing = false;
|
this.isContextmenuShowing = false;
|
||||||
|
@ -213,8 +224,37 @@ export default Vue.extend({
|
||||||
deleteFolder() {
|
deleteFolder() {
|
||||||
this.$root.api('drive/folders/delete', {
|
this.$root.api('drive/folders/delete', {
|
||||||
folderId: this.folder.id
|
folderId: this.folder.id
|
||||||
|
}).then(() => {
|
||||||
|
if (this.$store.state.settings.uploadFolder === this.folder.id) {
|
||||||
|
this.$store.dispatch('settings/set', {
|
||||||
|
key: 'uploadFolder',
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
switch(err.id) {
|
||||||
|
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
title: this.$t('unable-to-delete'),
|
||||||
|
text: this.$t('has-child-files-or-folders')
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('unable-to-delete')
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
setAsUploadFolder() {
|
||||||
|
this.$store.dispatch('settings/set', {
|
||||||
|
key: 'uploadFolder',
|
||||||
|
value: this.folder.id
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -264,4 +304,10 @@ export default Vue.extend({
|
||||||
margin-left 2px
|
margin-left 2px
|
||||||
text-align left
|
text-align left
|
||||||
|
|
||||||
|
> .upload
|
||||||
|
margin 4px 4px
|
||||||
|
font-size 0.8em
|
||||||
|
text-align right
|
||||||
|
color var(--desktopDriveFolderFg)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</p>
|
</p>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
</p>
|
</p>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :custom-emojis="notification.note.renote.emojis"/>
|
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
</router-link></p>
|
</router-link></p>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="lnctpgve">
|
<div class="lnctpgve">
|
||||||
|
<x-page v-if="user.pinnedPage" :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/>
|
||||||
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||||
<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
|
<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
|
||||||
<div class="activity">
|
<div class="activity">
|
||||||
|
@ -21,13 +22,15 @@ import i18n from '../../../../i18n';
|
||||||
import XTimeline from './user.timeline.vue';
|
import XTimeline from './user.timeline.vue';
|
||||||
import XPhotos from './user.photos.vue';
|
import XPhotos from './user.photos.vue';
|
||||||
import XActivity from '../../../../common/views/components/activity.vue';
|
import XActivity from '../../../../common/views/components/activity.vue';
|
||||||
|
import XPage from '../../../../common/views/components/page/page.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XTimeline,
|
XTimeline,
|
||||||
XPhotos,
|
XPhotos,
|
||||||
XActivity
|
XActivity,
|
||||||
|
XPage,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
|
|
|
@ -129,6 +129,7 @@ init((launch, os) => {
|
||||||
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
|
{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
|
||||||
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
|
{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
|
||||||
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
|
{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
|
||||||
|
{ path: '/@:username/pages/:pageName', name: 'page', props: true, component: () => import('../common/views/deck/deck.page-column.vue').then(m => m.default) },
|
||||||
]}]
|
]}]
|
||||||
: [
|
: [
|
||||||
{ path: '/', name: 'index', component: MkIndex },
|
{ path: '/', name: 'index', component: MkIndex },
|
||||||
|
@ -163,7 +164,7 @@ init((launch, os) => {
|
||||||
{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
|
{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
|
||||||
{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
|
{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
|
||||||
]},
|
]},
|
||||||
{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
|
{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) },
|
||||||
{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
|
{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) },
|
||||||
{ path: '/notes/:note', component: MkNote },
|
{ path: '/notes/:note', component: MkNote },
|
||||||
{ path: '/authorize-follow', component: MkFollow },
|
{ path: '/authorize-follow', component: MkFollow },
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note.renote)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :custom-emojis="notification.note.renote.emojis"/>
|
<mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :custom-emojis="notification.note.renote.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
</header>
|
</header>
|
||||||
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
<router-link class="note-ref" :to="notification.note | notePage" :title="getNoteSummary(notification.note)">
|
||||||
<fa icon="quote-left"/>
|
<fa icon="quote-left"/>
|
||||||
<mfm :text="getNoteSummary(notification.note)" :plain="true" :custom-emojis="notification.note.emojis"/>
|
<mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :custom-emojis="notification.note.emojis"/>
|
||||||
<fa icon="quote-right"/>
|
<fa icon="quote-right"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="wojmldye">
|
<div class="wojmldye">
|
||||||
|
<x-page class="page" v-if="user.pinnedPage" :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/>
|
||||||
<mk-note-detail class="note" v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
<mk-note-detail class="note" v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||||
<ui-container :body-togglable="true">
|
<ui-container :body-togglable="true">
|
||||||
<template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template>
|
<template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template>
|
||||||
|
@ -33,6 +34,7 @@ export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
XNotes,
|
XNotes,
|
||||||
XPhotos,
|
XPhotos,
|
||||||
|
XPage: () => import('../../../../common/views/components/page/page.vue').then(m => m.default),
|
||||||
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
|
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
props: ['user'],
|
props: ['user'],
|
||||||
|
@ -53,6 +55,12 @@ export default Vue.extend({
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.wojmldye
|
.wojmldye
|
||||||
|
> .page
|
||||||
|
margin 0 0 8px 0
|
||||||
|
|
||||||
|
@media (min-width 500px)
|
||||||
|
margin 0 0 16px 0
|
||||||
|
|
||||||
> .note
|
> .note
|
||||||
margin 0 0 8px 0
|
margin 0 0 8px 0
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ const defaultSettings = {
|
||||||
homeProfiles: {},
|
homeProfiles: {},
|
||||||
mobileHomeProfiles: {},
|
mobileHomeProfiles: {},
|
||||||
deckProfiles: {},
|
deckProfiles: {},
|
||||||
|
uploadFolder: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultDeviceSettings = {
|
const defaultDeviceSettings = {
|
||||||
|
|
|
@ -150,11 +150,9 @@ export function initDb(justBorrow = false, sync = false, log = false) {
|
||||||
options: {
|
options: {
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
options: {
|
password: config.redis.pass,
|
||||||
password: config.redis.pass,
|
prefix: config.redis.prefix,
|
||||||
prefix: config.redis.prefix,
|
db: config.redis.db || 0
|
||||||
db: config.redis.db || 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} : false,
|
} : false,
|
||||||
logging: log,
|
logging: log,
|
||||||
|
|
|
@ -40,6 +40,11 @@ export class Page {
|
||||||
@Column('boolean')
|
@Column('boolean')
|
||||||
public alignCenter: boolean;
|
public alignCenter: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
public hideTitleWhenPinned: boolean;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 32,
|
length: 32,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||||
import { id } from '../id';
|
import { id } from '../id';
|
||||||
import { User } from './user';
|
import { User } from './user';
|
||||||
|
import { Page } from './page';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class UserProfile {
|
export class UserProfile {
|
||||||
|
@ -118,6 +119,18 @@ export class UserProfile {
|
||||||
})
|
})
|
||||||
public carefulBot: boolean;
|
public carefulBot: boolean;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
|
public pinnedPageId: Page['id'] | null;
|
||||||
|
|
||||||
|
@OneToOne(type => Page, {
|
||||||
|
onDelete: 'SET NULL'
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public pinnedPage: Page | null;
|
||||||
|
|
||||||
//#region Linking
|
//#region Linking
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -71,6 +71,7 @@ export class PageRepository extends Repository<Page> {
|
||||||
title: page.title,
|
title: page.title,
|
||||||
name: page.name,
|
name: page.name,
|
||||||
summary: page.summary,
|
summary: page.summary,
|
||||||
|
hideTitleWhenPinned: page.hideTitleWhenPinned,
|
||||||
alignCenter: page.alignCenter,
|
alignCenter: page.alignCenter,
|
||||||
font: page.font,
|
font: page.font,
|
||||||
eyeCatchingImageId: page.eyeCatchingImageId,
|
eyeCatchingImageId: page.eyeCatchingImageId,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import { EntityRepository, Repository, In } from 'typeorm';
|
import { EntityRepository, Repository, In } from 'typeorm';
|
||||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings } from '..';
|
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages } from '..';
|
||||||
import { ensure } from '../../prelude/ensure';
|
import { ensure } from '../../prelude/ensure';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { SchemaType } from '../../misc/schema';
|
import { SchemaType } from '../../misc/schema';
|
||||||
|
@ -155,6 +155,8 @@ export class UserRepository extends Repository<User> {
|
||||||
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
|
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
|
||||||
detail: true
|
detail: true
|
||||||
}),
|
}),
|
||||||
|
pinnedPageId: profile!.pinnedPageId,
|
||||||
|
pinnedPage: profile!.pinnedPageId ? Pages.pack(profile!.pinnedPageId, meId) : null,
|
||||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||||
securityKeys: profile!.twoFactorEnabled
|
securityKeys: profile!.twoFactorEnabled
|
||||||
|
|
|
@ -10,7 +10,7 @@ import extractHashtags from '../../../../misc/extract-hashtags';
|
||||||
import * as langmap from 'langmap';
|
import * as langmap from 'langmap';
|
||||||
import { updateHashtag } from '../../../../services/update-hashtag';
|
import { updateHashtag } from '../../../../services/update-hashtag';
|
||||||
import { ApiError } from '../../error';
|
import { ApiError } from '../../error';
|
||||||
import { Users, DriveFiles, UserProfiles } from '../../../../models';
|
import { Users, DriveFiles, UserProfiles, Pages } from '../../../../models';
|
||||||
import { User } from '../../../../models/entities/user';
|
import { User } from '../../../../models/entities/user';
|
||||||
import { UserProfile } from '../../../../models/entities/user-profile';
|
import { UserProfile } from '../../../../models/entities/user-profile';
|
||||||
import { ensure } from '../../../../prelude/ensure';
|
import { ensure } from '../../../../prelude/ensure';
|
||||||
|
@ -125,6 +125,13 @@ export const meta = {
|
||||||
'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか'
|
'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pinnedPageId: {
|
||||||
|
validator: $.optional.nullable.type(ID),
|
||||||
|
desc: {
|
||||||
|
'ja-JP': 'ピン留めするページID'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
@ -150,7 +157,13 @@ export const meta = {
|
||||||
message: 'The file specified as a banner is not an image.',
|
message: 'The file specified as a banner is not an image.',
|
||||||
code: 'BANNER_NOT_AN_IMAGE',
|
code: 'BANNER_NOT_AN_IMAGE',
|
||||||
id: '75aedb19-2afd-4e6d-87fc-67941256fa60'
|
id: '75aedb19-2afd-4e6d-87fc-67941256fa60'
|
||||||
}
|
},
|
||||||
|
|
||||||
|
noSuchPage: {
|
||||||
|
message: 'No such page.',
|
||||||
|
code: 'NO_SUCH_PAGE',
|
||||||
|
id: '8e01b590-7eb9-431b-a239-860e086c408e'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -203,6 +216,16 @@ export default define(meta, async (ps, user, app) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.pinnedPageId) {
|
||||||
|
const page = await Pages.findOne(ps.pinnedPageId);
|
||||||
|
|
||||||
|
if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
|
||||||
|
|
||||||
|
profileUpdates.pinnedPageId = page.id;
|
||||||
|
} else if (ps.pinnedPageId === null) {
|
||||||
|
profileUpdates.pinnedPageId = null;
|
||||||
|
}
|
||||||
|
|
||||||
//#region emojis/tags
|
//#region emojis/tags
|
||||||
|
|
||||||
let emojis = [] as string[];
|
let emojis = [] as string[];
|
||||||
|
|
|
@ -16,6 +16,10 @@ export const meta = {
|
||||||
|
|
||||||
event: {
|
event: {
|
||||||
validator: $.str
|
validator: $.str
|
||||||
|
},
|
||||||
|
|
||||||
|
var: {
|
||||||
|
validator: $.optional.nullable.any
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -37,6 +41,7 @@ export default define(meta, async (ps, user) => {
|
||||||
publishMainStream(user.id, 'pageEvent', {
|
publishMainStream(user.id, 'pageEvent', {
|
||||||
pageId: ps.pageId,
|
pageId: ps.pageId,
|
||||||
event: ps.event,
|
event: ps.event,
|
||||||
|
var: ps.var,
|
||||||
user: await Users.pack(user, page.userId, {
|
user: await Users.pack(user, page.userId, {
|
||||||
detail: true
|
detail: true
|
||||||
})
|
})
|
||||||
|
|
|
@ -57,6 +57,11 @@ export const meta = {
|
||||||
validator: $.optional.bool,
|
validator: $.optional.bool,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hideTitleWhenPinned: {
|
||||||
|
validator: $.optional.bool,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
@ -100,6 +105,7 @@ export default define(meta, async (ps, user) => {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
alignCenter: ps.alignCenter,
|
alignCenter: ps.alignCenter,
|
||||||
|
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
||||||
font: ps.font
|
font: ps.font
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,10 @@ export const meta = {
|
||||||
alignCenter: {
|
alignCenter: {
|
||||||
validator: $.optional.bool,
|
validator: $.optional.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hideTitleWhenPinned: {
|
||||||
|
validator: $.optional.bool,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
@ -113,6 +117,7 @@ export default define(meta, async (ps, user) => {
|
||||||
content: ps.content,
|
content: ps.content,
|
||||||
variables: ps.variables,
|
variables: ps.variables,
|
||||||
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
|
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
|
||||||
|
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
||||||
font: ps.font === undefined ? page.font : ps.font,
|
font: ps.font === undefined ? page.font : ps.font,
|
||||||
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
||||||
? null
|
? null
|
||||||
|
|
|
@ -221,5 +221,4 @@ export default async (ctx: Koa.BaseContext) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await fail();
|
await fail();
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue