Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
90a9cf376e | |||
16d6c55407 | |||
f3508d15a3 | |||
0add490097 | |||
2d2d1bd58d | |||
7813c8a942 | |||
ac5453232f | |||
e78e5274d3 | |||
982520bcef | |||
7abd91f031 | |||
b93bfb7e5c | |||
2b20c34c1e | |||
0f63acea5b | |||
e600fb7096 | |||
b63fc71865 | |||
23e7650983 | |||
01b5ccfdc6 | |||
cc72f91465 | |||
45cb5ff4ef | |||
390279a4a8 | |||
851dececab | |||
482afa93a2 | |||
25bdbd7ae0 | |||
527a639242 |
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,6 +1,26 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
12.5.0 (2020/02/09)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* チュートリアルを実装
|
||||
* 検索のキーボードショートカットを追加
|
||||
* タイムラインを遡っている状況でないときに、誰かをフォローまたはフォロー解除したときにタイムラインをリロードするように
|
||||
|
||||
### 🐛Fixes
|
||||
* グループチャットが開始できない問題を修正
|
||||
* Renoteメニューが開けない問題を修正
|
||||
* 誕生日設定が崩れていたのを修正
|
||||
* キャッシュが削除できない問題を修正
|
||||
|
||||
12.4.1 (2020/02/09)
|
||||
--------------------
|
||||
### 🐛Fixes
|
||||
* グループの招待をacceptもrejectも出来ない問題を修正
|
||||
* 非ログイン時に検索欄がズレていたのを修正
|
||||
* バックグラウンドで受信したタイムラインの投稿のリアクションが受信されていない問題を修正
|
||||
|
||||
12.4.0 (2020/02/09)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
|
@ -215,7 +215,7 @@ remove: "削除"
|
||||
removed: "削除しました"
|
||||
removeAreYouSure: "「{x}」を削除しますか?"
|
||||
saved: "保存しました"
|
||||
messaging: "トーク"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
fromDrive: "ドライブから"
|
||||
fromUrl: "URLから"
|
||||
@ -226,7 +226,7 @@ games: "Misskey Games"
|
||||
messageRead: "既読"
|
||||
recentUsedEmojis: "最近使用した絵文字"
|
||||
noMoreHistory: "これより過去の履歴はありません"
|
||||
startMessaging: "トークを開始"
|
||||
startMessaging: "チャットを開始"
|
||||
nUsersRead: "{n}人が読みました"
|
||||
agreeTo: "{0}に同意"
|
||||
tos: "利用規約"
|
||||
@ -358,7 +358,7 @@ uploadFolder: "既定アップロード先"
|
||||
cacheClear: "キャッシュを削除"
|
||||
markAsReadAllNotifications: "すべての通知を既読にする"
|
||||
markAsReadAllUnreadNotes: "すべての投稿を既読にする"
|
||||
markAsReadAllTalkMessages: "すべてのトークを既読にする"
|
||||
markAsReadAllTalkMessages: "すべてのチャットを既読にする"
|
||||
help: "ヘルプ"
|
||||
inputMessageHere: "ここにメッセージを入力"
|
||||
close: "閉じる"
|
||||
@ -371,8 +371,35 @@ invites: "招待"
|
||||
groupName: "グループ名"
|
||||
members: "メンバー"
|
||||
transfer: "譲渡"
|
||||
messagingWithUser: "ユーザーとトーク"
|
||||
messagingWithGroup: "グループでトーク"
|
||||
messagingWithUser: "ユーザーとチャット"
|
||||
messagingWithGroup: "グループでチャット"
|
||||
enable: "有効にする"
|
||||
next: "次"
|
||||
retype: "再入力"
|
||||
|
||||
_tutorial:
|
||||
title: "Misskeyの使い方"
|
||||
step1_1: "ようこそ。"
|
||||
step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
|
||||
step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
|
||||
step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
|
||||
step2_2: "あなたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなります。"
|
||||
step3_1: "プロフィール設定はうまくできましたか?"
|
||||
step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
|
||||
step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
|
||||
step3_4: "内容が思いつかない?「Misskey始めました」というのはいかがでしょう。"
|
||||
step4_1: "投稿できましたか?"
|
||||
step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
|
||||
step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
|
||||
step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます。"
|
||||
step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
|
||||
step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
|
||||
step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
|
||||
step6_2: "他の人のノートには、「リアクション」を付けることができ、簡単にあなたの反応を伝えられます。"
|
||||
step6_3: "リアクションを付けるには、ノートの「+」マークをクリックして、好きなリアクションを選択します。"
|
||||
step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
|
||||
step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。"
|
||||
step7_3: "では、Misskeyをお楽しみください🚀"
|
||||
|
||||
_2fa:
|
||||
alreadyRegistered: "既に設定は完了しています。"
|
||||
@ -395,8 +422,8 @@ _permissions:
|
||||
"write:favorites": "お気に入りを操作する"
|
||||
"read:following": "フォローの情報を見る"
|
||||
"write:following": "フォロー・フォロー解除する"
|
||||
"read:messaging": "トークを見る"
|
||||
"write:messaging": "トークを操作する"
|
||||
"read:messaging": "チャットを見る"
|
||||
"write:messaging": "チャットを操作する"
|
||||
"read:mutes": "ミュートを見る"
|
||||
"write:mutes": "ミュートを操作する"
|
||||
"write:notes": "ノートを作成・削除する"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.4.0",
|
||||
"version": "12.5.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -193,6 +193,7 @@ export default Vue.extend({
|
||||
return {
|
||||
'p': this.post,
|
||||
'n': this.post,
|
||||
's': this.search,
|
||||
'h|/': this.help
|
||||
};
|
||||
},
|
||||
@ -697,6 +698,8 @@ export default Vue.extend({
|
||||
> .sub {
|
||||
$post-button-size: 42px;
|
||||
$post-button-margin: (($header-height - $post-button-size) / 2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 16px;
|
||||
@ -736,7 +739,7 @@ export default Vue.extend({
|
||||
> .post {
|
||||
width: $post-button-size;
|
||||
height: $post-button-size;
|
||||
margin: $post-button-margin 0 $post-button-margin $post-button-margin;
|
||||
margin-left: $post-button-margin;
|
||||
border-radius: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="user in us" :key="user.id" style="width:32px;height:32px;margin-right:8px;">
|
||||
<div v-for="user in us" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
|
||||
<mk-avatar :user="user" style="width:32px;height:32px;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,6 +45,8 @@ export default Vue.extend({
|
||||
height: 150px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }" v-once/>
|
||||
<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -19,7 +19,7 @@
|
||||
</router-link>
|
||||
</i18n>
|
||||
<div class="info">
|
||||
<button class="_button time" @click="showRenoteMenu"><mk-time :time="note.createdAt"/></button>
|
||||
<button class="_button time" @click="showRenoteMenu()" ref="renoteTime"><mk-time :time="note.createdAt"/></button>
|
||||
<span class="visibility" v-if="note.visibility != 'public'">
|
||||
<fa v-if="note.visibility == 'home'" :icon="faHome"/>
|
||||
<fa v-if="note.visibility == 'followers'" :icon="faUnlock"/>
|
||||
@ -265,14 +265,8 @@ export default Vue.extend({
|
||||
methods: {
|
||||
capture(withHandler = false) {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
if (document.body.contains(this.$el)) {
|
||||
this.connection.send('sn', { id: this.appearNote.id });
|
||||
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
|
||||
} else {
|
||||
this.$once('hook:activated', () => {
|
||||
this.capture(withHandler);
|
||||
});
|
||||
}
|
||||
this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
|
||||
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
|
||||
}
|
||||
},
|
||||
|
||||
@ -564,7 +558,7 @@ export default Vue.extend({
|
||||
}).then(this.focus);
|
||||
},
|
||||
|
||||
showRenoteMenu(ev) {
|
||||
showRenoteMenu(viaKeyboard = false) {
|
||||
if (!this.$store.getters.isSignedIn || (this.$store.state.i.id !== this.note.userId)) return;
|
||||
this.$root.menu({
|
||||
items: [{
|
||||
@ -577,7 +571,7 @@ export default Vue.extend({
|
||||
Vue.set(this.note, 'deletedAt', new Date());
|
||||
}
|
||||
}],
|
||||
source: ev.currentTarget || ev.target,
|
||||
source: this.$refs.renoteTime,
|
||||
viaKeyboard: viaKeyboard
|
||||
});
|
||||
},
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="mk-notes" v-size="[{ max: 500 }]">
|
||||
<div class="empty _panel" v-if="empty">
|
||||
<div class="empty" v-if="empty">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
|
||||
<div>{{ $t('noNotes') }}</div>
|
||||
</div>
|
||||
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note, i }">
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }">
|
||||
<x-note :note="note" :detail="detail" :key="note.id"/>
|
||||
</x-list>
|
||||
|
||||
@ -94,6 +94,8 @@ export default Vue.extend({
|
||||
height: 128px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ export default Vue.extend({
|
||||
this.$t('_postForm._placeholders.f')
|
||||
];
|
||||
const x = xs[Math.floor(Math.random() * xs.length)];
|
||||
|
||||
|
||||
return this.renote
|
||||
? this.$t('_postForm.quotePlaceholder')
|
||||
: this.reply
|
||||
@ -713,7 +713,7 @@ export default Vue.extend({
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
font-family: initial;
|
||||
font-family: inherit;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
padding: 0 16px;
|
||||
|
@ -27,6 +27,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
connection2: null,
|
||||
pagination: null,
|
||||
baseQuery: {
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
@ -40,6 +41,7 @@ export default Vue.extend({
|
||||
created() {
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
this.connection.dispose();
|
||||
if (this.connection2) this.connection2.dispose();
|
||||
});
|
||||
|
||||
const prepend = note => {
|
||||
@ -54,6 +56,12 @@ export default Vue.extend({
|
||||
(this.$refs.tl as any).reload();
|
||||
};
|
||||
|
||||
const onChangeFollowing = () => {
|
||||
if (!this.$refs.tl.backed) {
|
||||
this.$refs.tl.reload();
|
||||
}
|
||||
};
|
||||
|
||||
let endpoint;
|
||||
|
||||
if (this.src == 'antenna') {
|
||||
@ -67,13 +75,12 @@ export default Vue.extend({
|
||||
this.connection.on('note', prepend);
|
||||
} else if (this.src == 'home') {
|
||||
endpoint = 'notes/timeline';
|
||||
const onChangeFollowing = () => {
|
||||
this.fetch();
|
||||
};
|
||||
this.connection = this.$root.stream.useSharedConnection('homeTimeline');
|
||||
this.connection.on('note', prepend);
|
||||
this.connection.on('follow', onChangeFollowing);
|
||||
this.connection.on('unfollow', onChangeFollowing);
|
||||
|
||||
this.connection2 = this.$root.stream.useSharedConnection('main');
|
||||
this.connection2.on('follow', onChangeFollowing);
|
||||
this.connection2.on('unfollow', onChangeFollowing);
|
||||
} else if (this.src == 'local') {
|
||||
endpoint = 'notes/local-timeline';
|
||||
this.connection = this.$root.stream.useSharedConnection('localTimeline');
|
||||
|
@ -254,7 +254,7 @@ export default Vue.extend({
|
||||
|
||||
> .input {
|
||||
position: relative;
|
||||
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
@ -327,14 +327,16 @@ export default Vue.extend({
|
||||
}
|
||||
|
||||
> input {
|
||||
$height: 32px;
|
||||
display: block;
|
||||
height: $height;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
line-height: $height;
|
||||
color: var(--inputText);
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
127
src/client/pages/index.home.tutorial.vue
Normal file
127
src/client/pages/index.home.tutorial.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="_card tbkwesmv">
|
||||
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('_tutorial.title') }}</div>
|
||||
<div class="_content" v-if="tutorial === 0">
|
||||
<div>{{ $t('_tutorial.step1_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step1_3') }}</div>
|
||||
</div>
|
||||
<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="/my/settings">{{ $t('editProfile') }}</router-link>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 2">
|
||||
<div>{{ $t('_tutorial.step3_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step3_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step3_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 3">
|
||||
<div>{{ $t('_tutorial.step4_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step4_2') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 4">
|
||||
<div>{{ $t('_tutorial.step5_1') }}</div>
|
||||
<i18n path="_tutorial.step5_2" tag="div">
|
||||
<router-link class="_link" place="featured" to="/featured">{{ $t('featured') }}</router-link>
|
||||
<router-link class="_link" place="explore" to="/explore">{{ $t('explore') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step5_3') }}</div>
|
||||
<small>{{ $t('_tutorial.step5_4') }}</small>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 5">
|
||||
<div>{{ $t('_tutorial.step6_1') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_2') }}</div>
|
||||
<div>{{ $t('_tutorial.step6_3') }}</div>
|
||||
</div>
|
||||
<div class="_content" v-else-if="tutorial === 6">
|
||||
<div>{{ $t('_tutorial.step7_1') }}</div>
|
||||
<i18n path="_tutorial.step7_2" tag="div">
|
||||
<router-link class="_link" place="help" to="/docs">{{ $t('help') }}</router-link>
|
||||
</i18n>
|
||||
<div>{{ $t('_tutorial.step7_3') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="_footer navigation">
|
||||
<div class="step">
|
||||
<button class="arrow _button" @click="tutorial--" :disabled="tutorial === 0">
|
||||
<fa :icon="faChevronLeft"/>
|
||||
</button>
|
||||
<span>{{ tutorial + 1 }} / 7</span>
|
||||
<button class="arrow _button" @click="tutorial++" :disabled="tutorial === 6">
|
||||
<fa :icon="faChevronRight"/>
|
||||
</button>
|
||||
</div>
|
||||
<mk-button class="ok" @click="tutorial = -1" primary v-if="tutorial === 6"><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>
|
||||
<mk-button class="ok" @click="tutorial++" primary v-else><fa :icon="faCheck"/> {{ $t('next') }}</mk-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faInfoCircle, faChevronLeft, faChevronRight, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import MkButton from '../components/ui/button.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
MkButton,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
faInfoCircle, faChevronLeft, faChevronRight, faCheck
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tutorial: {
|
||||
get() { return this.$store.state.settings.tutorial || 0; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'tutorial', value }); }
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tbkwesmv {
|
||||
> ._content {
|
||||
> small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
> .navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
> .step {
|
||||
> .arrow {
|
||||
padding: 4px;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
> .ok {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,6 +13,9 @@
|
||||
<fa :icon="menuOpened ? faAngleUp : faAngleDown" style="margin-left: 8px;"/>
|
||||
</button>
|
||||
</portal>
|
||||
|
||||
<x-tutorial class="tutorial" v-if="$store.state.settings.tutorial != -1"/>
|
||||
|
||||
<x-timeline ref="tl" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src" :src="src" :list="list" :antenna="antenna" @before="before()" @after="after()"/>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,6 +26,7 @@ import { faAngleDown, faAngleUp, faHome, faShareAlt, faGlobe, faListUl, faSatell
|
||||
import { faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import Progress from '../scripts/loading';
|
||||
import XTimeline from '../components/timeline.vue';
|
||||
import XTutorial from './index.home.tutorial.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
metaInfo() {
|
||||
@ -32,7 +36,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
components: {
|
||||
XTimeline
|
||||
XTimeline,
|
||||
XTutorial,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -57,7 +62,7 @@ export default Vue.extend({
|
||||
return {
|
||||
't': this.focus
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -172,7 +177,13 @@ export default Vue.extend({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.mk-home {
|
||||
> .tutorial {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
._kjvfvyph_ {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
@ -120,30 +120,30 @@
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faTwitter"/> {{ $t('twitter-integration-config') }}</header>
|
||||
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</mk-switch>
|
||||
<header><fa :icon="faTwitter"/> Twitter</header>
|
||||
<mk-switch v-model="enableTwitterIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableTwitterIntegration">
|
||||
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-key') }}</mk-input>
|
||||
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('twitter-integration-consumer-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/tw/cb` }}</mk-info>
|
||||
<mk-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Key</mk-input>
|
||||
<mk-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa :icon="faKey"/></template>Consumer Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faGithub"/> {{ $t('github-integration-config') }}</header>
|
||||
<mk-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</mk-switch>
|
||||
<header><fa :icon="faGithub"/> GitHub</header>
|
||||
<mk-switch v-model="enableGithubIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableGithubIntegration">
|
||||
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-id') }}</mk-input>
|
||||
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('github-integration-client-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/gh/cb` }}</mk-info>
|
||||
<mk-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||
<mk-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<header><fa :icon="faDiscord"/> {{ $t('discord-integration-config') }}</header>
|
||||
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</mk-switch>
|
||||
<header><fa :icon="faDiscord"/> Discord</header>
|
||||
<mk-switch v-model="enableDiscordIntegration">{{ $t('enable') }}</mk-switch>
|
||||
<template v-if="enableDiscordIntegration">
|
||||
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-id') }}</mk-input>
|
||||
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>{{ $t('discord-integration-client-secret') }}</mk-input>
|
||||
<mk-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</mk-info>
|
||||
<mk-info>Callback URL: {{ `${url}/api/dc/cb` }}</mk-info>
|
||||
<mk-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client ID</mk-input>
|
||||
<mk-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa :icon="faKey"/></template>Client Secret</mk-input>
|
||||
</template>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
@ -180,7 +180,7 @@ import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
import MkInfo from '../../components/ui/info.vue';
|
||||
import MkUserSelect from '../../components/user-select.vue';
|
||||
import { version } from '../../config';
|
||||
import { version, url } from '../../config';
|
||||
import i18n from '../../i18n';
|
||||
import getAcct from '../../../misc/acct/render';
|
||||
|
||||
@ -204,6 +204,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
version,
|
||||
url,
|
||||
meta: null,
|
||||
stats: null,
|
||||
serverInfo: null,
|
||||
|
@ -247,6 +247,7 @@ export default Vue.extend({
|
||||
padding: 16px 16px 0 16px;
|
||||
resize: none;
|
||||
font-size: 1em;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
@ -8,6 +8,7 @@
|
||||
<portal to="avatar"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
|
||||
</template>
|
||||
<template v-if="!fetching && group">
|
||||
<portal to="icon"><fa :icon="faUsers"/></portal>
|
||||
<portal to="title">{{ group.name }}</portal>
|
||||
</template>
|
||||
|
||||
@ -35,7 +36,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faArrowCircleDown, faFlag } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faArrowCircleDown, faFlag, faUsers } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import XList from '../components/date-separated-list.vue';
|
||||
import XMessage from './messaging-room.message.vue';
|
||||
@ -63,7 +64,7 @@ export default Vue.extend({
|
||||
connection: null,
|
||||
showIndicator: false,
|
||||
timer: null,
|
||||
faArrowCircleDown, faFlag
|
||||
faArrowCircleDown, faFlag, faUsers
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -150,7 +150,7 @@ export default Vue.extend({
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
this.navigateGroup(group);
|
||||
this.$router.push(`/my/messaging/group/${group.id}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
<mk-container :body-togglable="true">
|
||||
<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
|
||||
<mk-pagination :pagination="invitePagination" #default="{items}">
|
||||
<mk-pagination :pagination="invitePagination" #default="{items}" ref="invites">
|
||||
<div class="_frame" v-for="invite in items" :key="invite.id">
|
||||
<div class="_title">{{ invite.group.name }}</div>
|
||||
<div class="_content"><mk-avatars :user-ids="invite.group.userIds"/></div>
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
<mk-container :body-togglable="true">
|
||||
<template #header><fa :icon="faUsers"/> {{ $t('joinedGroups') }}</template>
|
||||
<mk-pagination :pagination="joinedPagination" #default="{items}">
|
||||
<mk-pagination :pagination="joinedPagination" #default="{items}" ref="joined">
|
||||
<div class="_frame" v-for="group in items" :key="group.id">
|
||||
<div class="_title">{{ group.name }}</div>
|
||||
<div class="_content"><mk-avatars :user-ids="group.userIds"/></div>
|
||||
@ -95,6 +95,25 @@ export default Vue.extend({
|
||||
iconOnly: true, autoClose: true
|
||||
});
|
||||
},
|
||||
acceptInvite(invite) {
|
||||
this.$root.api('users/groups/invitations/accept', {
|
||||
inviteId: invite.id
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
iconOnly: true, autoClose: true
|
||||
});
|
||||
this.$refs.invites.reload();
|
||||
this.$refs.joined.reload();
|
||||
});
|
||||
},
|
||||
rejectInvite(invite) {
|
||||
this.$root.api('users/groups/invitations/reject', {
|
||||
inviteId: invite.id
|
||||
}).then(() => {
|
||||
this.$refs.invites.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -45,6 +45,8 @@ export default Vue.extend({
|
||||
height: 150px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ export const router = new VueRouter({
|
||||
{ path: '/my/mentions', component: page('mentions') },
|
||||
{ path: '/my/messaging', name: 'messaging', component: page('messaging') },
|
||||
{ path: '/my/messaging/:user', component: page('messaging-room') },
|
||||
{ path: '/my/messaging/group/:group', component: page('messaging-room') },
|
||||
{ path: '/my/drive', name: 'drive', component: page('drive') },
|
||||
{ path: '/my/drive/folder/:folder', component: page('drive') },
|
||||
{ path: '/my/pages', name: 'pages', component: page('pages') },
|
||||
|
@ -8,7 +8,8 @@ export default (opts) => ({
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
inited: false,
|
||||
more: false
|
||||
more: false,
|
||||
backed: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -78,6 +79,7 @@ export default (opts) => ({
|
||||
async fetchMore() {
|
||||
if (!this.more || this.moreFetching || this.items.length === 0) return;
|
||||
this.moreFetching = true;
|
||||
this.backed = true;
|
||||
let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params;
|
||||
if (params && params.then) params = await params;
|
||||
const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint;
|
||||
|
@ -5,6 +5,7 @@ import * as nestedProperty from 'nested-property';
|
||||
import MiOS from './mios';
|
||||
|
||||
const defaultSettings = {
|
||||
tutorial: 0,
|
||||
keepCw: false,
|
||||
showFullAcct: false,
|
||||
rememberNoteVisibility: false,
|
||||
|
@ -18,7 +18,7 @@ self.addEventListener('install', ev => {
|
||||
caches.open(cacheName)
|
||||
.then(cache => {
|
||||
return cache.addAll([
|
||||
'/'
|
||||
`/?v=${version}`
|
||||
]);
|
||||
})
|
||||
.then(() => self.skipWaiting())
|
||||
@ -45,7 +45,7 @@ self.addEventListener('fetch', ev => {
|
||||
return response || fetch(ev.request);
|
||||
})
|
||||
.catch(() => {
|
||||
return caches.match('/');
|
||||
return caches.match(`/?v=${version}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -10,6 +10,7 @@
|
||||
<tr><td><kbd class="key">P</kbd>, <kbd class="key">N</kbd></td><td>新規投稿</td><td><b>P</b>ost, <b>N</b>ew, <b>N</b>ote</td></tr>
|
||||
<tr><td><kbd class="key">T</kbd></td><td>タイムラインの最も新しい投稿にフォーカス</td><td><b>T</b>imeline, <b>T</b>op</td></tr>
|
||||
<tr><td><kbd class="group"><kbd class="key">Shift</kbd> + <kbd class="key">N</kbd></kbd></td><td>通知を表示/隠す</td><td><b>N</b>otifications</td></tr>
|
||||
<tr><td><kbd class="key">S</kbd></td><td>検索</td><td><b>S</b>earch</td></tr>
|
||||
<tr><td><kbd class="key">H</kbd>, <kbd class="key">?</kbd></td><td>ヘルプを表示</td><td><b>H</b>elp</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -13,6 +13,11 @@ export const meta = {
|
||||
default: 10
|
||||
},
|
||||
|
||||
withUnreads: {
|
||||
validator: $.optional.boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
@ -38,5 +43,5 @@ export default define(meta, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
return announcements;
|
||||
return ps.withUnreads ? announcements.filter((a: any) => !a.isRead) : announcements;
|
||||
});
|
||||
|
@ -60,8 +60,9 @@ export default class Connection {
|
||||
switch (type) {
|
||||
case 'api': this.onApiRequest(body); break;
|
||||
case 'readNotification': this.onReadNotification(body); break;
|
||||
case 'subNote': this.onSubscribeNote(body); break;
|
||||
case 'sn': this.onSubscribeNote(body); break; // alias
|
||||
case 'subNote': this.onSubscribeNote(body, true); break;
|
||||
case 'sn': this.onSubscribeNote(body, true); break; // alias
|
||||
case 's': this.onSubscribeNote(body, false); break;
|
||||
case 'unsubNote': this.onUnsubscribeNote(body); break;
|
||||
case 'un': this.onUnsubscribeNote(body); break; // alias
|
||||
case 'connect': this.onChannelConnectRequested(body); break;
|
||||
@ -107,7 +108,7 @@ export default class Connection {
|
||||
* 投稿購読要求時
|
||||
*/
|
||||
@autobind
|
||||
private onSubscribeNote(payload: any) {
|
||||
private onSubscribeNote(payload: any, read: boolean) {
|
||||
if (!payload.id) return;
|
||||
|
||||
if (this.subscribingNotes[payload.id] == null) {
|
||||
@ -120,7 +121,7 @@ export default class Connection {
|
||||
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
|
||||
}
|
||||
|
||||
if (this.user) {
|
||||
if (this.user && read) {
|
||||
readNote(this.user.id, payload.id);
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +327,10 @@ const override = (source: string, target: string, depth: number = 0) =>
|
||||
router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1)));
|
||||
router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games')));
|
||||
|
||||
router.get('/flush', async ctx => {
|
||||
await ctx.render('flush');
|
||||
});
|
||||
|
||||
// Render base html for all requests
|
||||
router.get('*', async ctx => {
|
||||
const meta = await fetchMeta();
|
||||
|
20
src/server/web/views/flush.pug
Normal file
20
src/server/web/views/flush.pug
Normal file
@ -0,0 +1,20 @@
|
||||
doctype html
|
||||
|
||||
html
|
||||
script.
|
||||
localStorage.removeItem('locale');
|
||||
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
||||
}).then(() => {
|
||||
location = '/';
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location = '/';
|
||||
}, 10000)
|
||||
}
|
Reference in New Issue
Block a user