better onboarding experience
This commit is contained in:
parent
4dd4a11cef
commit
9f306cc493
49
locales/index.d.ts
vendored
49
locales/index.d.ts
vendored
|
@ -1174,10 +1174,46 @@ export interface Locale {
|
||||||
"pushNotificationDescription": string;
|
"pushNotificationDescription": string;
|
||||||
"initialAccountSettingCompleted": string;
|
"initialAccountSettingCompleted": string;
|
||||||
"haveFun": string;
|
"haveFun": string;
|
||||||
"ifYouNeedLearnMore": string;
|
"youCanContinueTutorial": string;
|
||||||
|
"startTutorial": string;
|
||||||
"skipAreYouSure": string;
|
"skipAreYouSure": string;
|
||||||
"laterAreYouSure": string;
|
"laterAreYouSure": string;
|
||||||
};
|
};
|
||||||
|
"_initialTutorial": {
|
||||||
|
"title": string;
|
||||||
|
"wellDone": string;
|
||||||
|
"_note": {
|
||||||
|
"description": string;
|
||||||
|
"date": string;
|
||||||
|
"reply": string;
|
||||||
|
"renote": string;
|
||||||
|
"reaction": string;
|
||||||
|
"howToNote": string;
|
||||||
|
};
|
||||||
|
"_reaction": {
|
||||||
|
"description": string;
|
||||||
|
"letsTryReacting": string;
|
||||||
|
"reactDone": string;
|
||||||
|
};
|
||||||
|
"_timeline": {
|
||||||
|
"description1": string;
|
||||||
|
"home": string;
|
||||||
|
"local": string;
|
||||||
|
"social": string;
|
||||||
|
"global": string;
|
||||||
|
"description2": string;
|
||||||
|
};
|
||||||
|
"_done": {
|
||||||
|
"title": string;
|
||||||
|
"description": string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"_timelineDescription": {
|
||||||
|
"home": string;
|
||||||
|
"local": string;
|
||||||
|
"social": string;
|
||||||
|
"global": string;
|
||||||
|
};
|
||||||
"_serverRules": {
|
"_serverRules": {
|
||||||
"description": string;
|
"description": string;
|
||||||
};
|
};
|
||||||
|
@ -1851,17 +1887,6 @@ export interface Locale {
|
||||||
"hour": string;
|
"hour": string;
|
||||||
"day": string;
|
"day": string;
|
||||||
};
|
};
|
||||||
"_timelineTutorial": {
|
|
||||||
"title": string;
|
|
||||||
"step1_1": string;
|
|
||||||
"step1_2": string;
|
|
||||||
"step2_1": string;
|
|
||||||
"step2_2": string;
|
|
||||||
"step3_1": string;
|
|
||||||
"step3_2": string;
|
|
||||||
"step4_1": string;
|
|
||||||
"step4_2": string;
|
|
||||||
};
|
|
||||||
"_2fa": {
|
"_2fa": {
|
||||||
"alreadyRegistered": string;
|
"alreadyRegistered": string;
|
||||||
"registerTOTP": string;
|
"registerTOTP": string;
|
||||||
|
|
|
@ -1172,10 +1172,42 @@ _initialAccountSetting:
|
||||||
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。"
|
pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。"
|
||||||
initialAccountSettingCompleted: "初期設定が完了しました!"
|
initialAccountSettingCompleted: "初期設定が完了しました!"
|
||||||
haveFun: "{name}をお楽しみください!"
|
haveFun: "{name}をお楽しみください!"
|
||||||
ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
|
youCanContinueTutorial: "このまま{name}(Misskey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。"
|
||||||
|
startTutorial: "チュートリアルを開始"
|
||||||
skipAreYouSure: "初期設定をスキップしますか?"
|
skipAreYouSure: "初期設定をスキップしますか?"
|
||||||
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
laterAreYouSure: "初期設定をあとでやり直しますか?"
|
||||||
|
|
||||||
|
_initialTutorial:
|
||||||
|
title: "チュートリアル"
|
||||||
|
wellDone: "お見事!"
|
||||||
|
_note:
|
||||||
|
description: "Misskeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。"
|
||||||
|
date: "日付をクリックすることで、ノートの詳細ページに移動することができます。"
|
||||||
|
reply: "このボタンから返信できます。もちろん返信に返信することもできるので、気が済むまで会話を続けることができます。"
|
||||||
|
renote: "このボタンを押すと、このノートを自分のタイムラインに投稿することができます。引用リノートも可能です。"
|
||||||
|
reaction: "リアクションをつけることができます。詳しくは次のページで解説します。"
|
||||||
|
howToNote: "ノートは、下のようなボタンから簡単に行なえます。チュートリアルが終わったら早速やってみてくださいね!"
|
||||||
|
_reaction:
|
||||||
|
description: "ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。"
|
||||||
|
letsTryReacting: "リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのノートに好きなリアクションをつけてみましょう!"
|
||||||
|
reactDone: "リアクションを外すときは、「ー」ボタンを押します。実際に試してみてくださいね。"
|
||||||
|
_timeline:
|
||||||
|
description1: "Misskeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。"
|
||||||
|
home: "あなたがフォローしているアカウントの投稿を見られます。"
|
||||||
|
local: "このサーバーにいるユーザー全員の投稿を見られます。"
|
||||||
|
social: "ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
||||||
|
global: "接続している他のすべてのサーバーからの投稿を見られます。"
|
||||||
|
description2: "その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。"
|
||||||
|
_done:
|
||||||
|
title: "チュートリアルは終了です🎉"
|
||||||
|
description: "ここで紹介した機能は、ほんの一部です!Misskeyの使い方をより詳しく知るには、{link}をご覧ください。"
|
||||||
|
|
||||||
|
_timelineDescription:
|
||||||
|
home: "ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。"
|
||||||
|
local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
|
||||||
|
social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
||||||
|
global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||||
|
|
||||||
|
@ -1768,17 +1800,6 @@ _time:
|
||||||
hour: "時間"
|
hour: "時間"
|
||||||
day: "日"
|
day: "日"
|
||||||
|
|
||||||
_timelineTutorial:
|
|
||||||
title: "Misskeyの使い方"
|
|
||||||
step1_1: "この画面は「タイムライン」です。{name}に投稿された「ノート」が時系列で表示されます。"
|
|
||||||
step1_2: "タイムラインにはいくつか種類があり、例えば「ホームタイムライン」にはあなたがフォローしている人のノートが流れ、「ローカルタイムライン」には{name}全体のノートが流れます。"
|
|
||||||
step2_1: "試しに、何かノートを投稿してみましょう。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
|
|
||||||
step2_2: "初めてのノートの内容は、あなたの自己紹介や「{name}始めました」などがおすすめです。"
|
|
||||||
step3_1: "投稿できましたか?"
|
|
||||||
step3_2: "あなたのノートがタイムラインに表示されていれば成功です。"
|
|
||||||
step4_1: "ノートには、「リアクション」を付けることができます。"
|
|
||||||
step4_2: "リアクションを付けるには、ノートの「+」マークをクリックして、好きな絵文字を選択します。"
|
|
||||||
|
|
||||||
_2fa:
|
_2fa:
|
||||||
alreadyRegistered: "既に設定は完了しています。"
|
alreadyRegistered: "既に設定は完了しています。"
|
||||||
registerTOTP: "認証アプリの設定を開始"
|
registerTOTP: "認証アプリの設定を開始"
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
|
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
|
||||||
<i v-else class="ti ti-info-circle" :class="$style.i"></i>
|
<i v-else class="ti ti-info-circle" :class="$style.i"></i>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
<button v-if="closable" :class="$style.button" class="_button" @click="close()"><i class="ti ti-x"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,11 +17,23 @@ import { } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
warn?: boolean;
|
warn?: boolean;
|
||||||
|
closable?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'close'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
// こいつの中では非表示動作は行わない
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
background: var(--infoBg);
|
background: var(--infoBg);
|
||||||
|
@ -37,4 +50,9 @@ const props = defineProps<{
|
||||||
.i {
|
.i {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -47,9 +47,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||||
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
||||||
<MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/>
|
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
||||||
<div :class="$style.main">
|
<div :class="$style.main">
|
||||||
<MkNoteHeader :note="appearNote" :mini="true"/>
|
<MkNoteHeader :note="appearNote" :mock="mock" :mini="true"/>
|
||||||
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
|
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
|
||||||
<div style="container-type: inline-size;">
|
<div style="container-type: inline-size;">
|
||||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||||
|
@ -136,7 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent } from 'vue';
|
import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent, watch } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkNoteSub from '@/components/MkNoteSub.vue';
|
import MkNoteSub from '@/components/MkNoteSub.vue';
|
||||||
|
@ -170,9 +170,17 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||||
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
|
mock?: boolean;
|
||||||
|
}>(), {
|
||||||
|
mock: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'reaction', emoji: string): void;
|
||||||
|
(ev: 'removeReaction', emoji: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const inChannel = inject('inChannel', null);
|
const inChannel = inject('inChannel', null);
|
||||||
|
@ -229,13 +237,20 @@ const keymap = {
|
||||||
's': () => showContent.value !== showContent.value,
|
's': () => showContent.value !== showContent.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (props.mock) {
|
||||||
|
watch(() => props.note, (to) => {
|
||||||
|
note = deepClone(to);
|
||||||
|
}, { deep: true });
|
||||||
|
} else {
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: $$(appearNote),
|
note: $$(appearNote),
|
||||||
pureNote: $$(note),
|
pureNote: $$(note),
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.mock) {
|
||||||
useTooltip(renoteButton, async (showing) => {
|
useTooltip(renoteButton, async (showing) => {
|
||||||
const renotes = await os.api('notes/renotes', {
|
const renotes = await os.api('notes/renotes', {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
|
@ -253,6 +268,7 @@ useTooltip(renoteButton, async (showing) => {
|
||||||
targetElement: renoteButton.value,
|
targetElement: renoteButton.value,
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type Visibility = 'public' | 'home' | 'followers' | 'specified';
|
type Visibility = 'public' | 'home' | 'followers' | 'specified';
|
||||||
|
|
||||||
|
@ -284,21 +300,25 @@ function renote(viaKeyboard = false) {
|
||||||
os.popup(MkRippleEffect, { x, y }, {}, 'end');
|
os.popup(MkRippleEffect, { x, y }, {}, 'end');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.mock) {
|
||||||
os.api('notes/create', {
|
os.api('notes/create', {
|
||||||
renoteId: appearNote.id,
|
renoteId: appearNote.id,
|
||||||
channelId: appearNote.channelId,
|
channelId: appearNote.channelId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
os.toast(i18n.ts.renoted);
|
os.toast(i18n.ts.renoted);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.inChannelQuote,
|
text: i18n.ts.inChannelQuote,
|
||||||
icon: 'ti ti-quote',
|
icon: 'ti ti-quote',
|
||||||
action: () => {
|
action: () => {
|
||||||
|
if (!props.mock) {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote,
|
renote: appearNote,
|
||||||
channel: appearNote.channel,
|
channel: appearNote.channel,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}, null]);
|
}, null]);
|
||||||
}
|
}
|
||||||
|
@ -324,6 +344,7 @@ function renote(viaKeyboard = false) {
|
||||||
visibility = smallerVisibility(visibility, 'home');
|
visibility = smallerVisibility(visibility, 'home');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.mock) {
|
||||||
os.api('notes/create', {
|
os.api('notes/create', {
|
||||||
localOnly,
|
localOnly,
|
||||||
visibility,
|
visibility,
|
||||||
|
@ -331,8 +352,9 @@ function renote(viaKeyboard = false) {
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
os.toast(i18n.ts.renoted);
|
os.toast(i18n.ts.renoted);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}, {
|
}, (props.mock) ? undefined : {
|
||||||
text: i18n.ts.quote,
|
text: i18n.ts.quote,
|
||||||
icon: 'ti ti-quote',
|
icon: 'ti ti-quote',
|
||||||
action: () => {
|
action: () => {
|
||||||
|
@ -349,6 +371,9 @@ function renote(viaKeyboard = false) {
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
os.post({
|
os.post({
|
||||||
reply: appearNote,
|
reply: appearNote,
|
||||||
channel: appearNote.channel,
|
channel: appearNote.channel,
|
||||||
|
@ -362,6 +387,10 @@ function react(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
os.api('notes/reactions/create', {
|
os.api('notes/reactions/create', {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
reaction: '❤️',
|
reaction: '❤️',
|
||||||
|
@ -376,6 +405,11 @@ function react(viaKeyboard = false): void {
|
||||||
} else {
|
} else {
|
||||||
blur();
|
blur();
|
||||||
reactionPicker.show(reactButton.value, reaction => {
|
reactionPicker.show(reactButton.value, reaction => {
|
||||||
|
if (props.mock) {
|
||||||
|
emit('reaction', reaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
os.api('notes/reactions/create', {
|
os.api('notes/reactions/create', {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
|
@ -392,12 +426,22 @@ function react(viaKeyboard = false): void {
|
||||||
function undoReact(note): void {
|
function undoReact(note): void {
|
||||||
const oldReaction = note.myReaction;
|
const oldReaction = note.myReaction;
|
||||||
if (!oldReaction) return;
|
if (!oldReaction) return;
|
||||||
|
|
||||||
|
if (props.mock) {
|
||||||
|
emit('removeReaction', oldReaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
os.api('notes/reactions/delete', {
|
os.api('notes/reactions/delete', {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent): void {
|
function onContextmenu(ev: MouseEvent): void {
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isLink = (el: HTMLElement) => {
|
const isLink = (el: HTMLElement) => {
|
||||||
if (el.tagName === 'A') return true;
|
if (el.tagName === 'A') return true;
|
||||||
// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
|
// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
|
||||||
|
@ -419,6 +463,10 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
|
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
|
||||||
os.popupMenu(menu, menuButton.value, {
|
os.popupMenu(menu, menuButton.value, {
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
|
@ -426,10 +474,18 @@ function menu(viaKeyboard = false): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clip() {
|
async function clip() {
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRenoteMenu(viaKeyboard = false): void {
|
function showRenoteMenu(viaKeyboard = false): void {
|
||||||
|
if (props.mock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function getUnrenote(): MenuItem {
|
function getUnrenote(): MenuItem {
|
||||||
return {
|
return {
|
||||||
text: i18n.ts.unrenote,
|
text: i18n.ts.unrenote,
|
||||||
|
|
|
@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header :class="$style.root">
|
<header :class="$style.root">
|
||||||
<MkA v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
|
<div v-if="mock" :class="$style.name">
|
||||||
|
<MkUserName :user="note.user"/>
|
||||||
|
</div>
|
||||||
|
<MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
|
||||||
<MkUserName :user="note.user"/>
|
<MkUserName :user="note.user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
|
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
|
||||||
|
@ -14,7 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
|
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.info">
|
<div :class="$style.info">
|
||||||
<MkA :to="notePage(note)">
|
<div v-if="mock">
|
||||||
|
<MkTime :time="note.createdAt" colored/>
|
||||||
|
</div>
|
||||||
|
<MkA v-else :to="notePage(note)">
|
||||||
<MkTime :time="note.createdAt" colored/>
|
<MkTime :time="note.createdAt" colored/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
||||||
|
@ -37,6 +43,7 @@ import { userPage } from '@/filters/user.js';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
mock?: boolean;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="phase === 'aboutNote'" class="_gaps">
|
||||||
|
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._note.description }}</div>
|
||||||
|
<MkNote :class="$style.exampleNoteRoot" style="pointer-events: none;" :note="exampleNote" :mock="true"/>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<div><small><MkTime :time="exampleNote.createdAt" colored></MkTime></small> … {{ i18n.ts._initialTutorial._note.date }}</div>
|
||||||
|
<div><i class="ti ti-arrow-back-up"></i> <b>{{ i18n.ts.reply }}</b> … {{ i18n.ts._initialTutorial._note.reply }}</div>
|
||||||
|
<div><i class="ti ti-repeat"></i> <b>{{ i18n.ts.renote }}</b> … {{ i18n.ts._initialTutorial._note.renote }}</div>
|
||||||
|
<div><i class="ti ti-plus"></i> <b>{{ i18n.ts.reaction }}</b> … {{ i18n.ts._initialTutorial._note.reaction }}</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.divider"></div>
|
||||||
|
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._note.howToNote }}</div>
|
||||||
|
<div style="width: 250px; margin: 0 auto;">
|
||||||
|
<div :class="[$style.post]">
|
||||||
|
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else-if="phase === 'howToReact'" class="_gaps">
|
||||||
|
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._reaction.description }}</div>
|
||||||
|
<div>{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}</div>
|
||||||
|
<MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/>
|
||||||
|
<div v-if="onceReacted"><b style="color: var(--accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactDone }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkTime from '@/components/global/MkTime.vue';
|
||||||
|
import MkNote from '@/components/MkNote.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
phase: 'aboutNote' | 'howToReact';
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const exampleNote = reactive<Misskey.entities.Note>({
|
||||||
|
id: '0000000000',
|
||||||
|
createdAt: '2019-04-14T17:30:49.181Z',
|
||||||
|
userId: '0000000001',
|
||||||
|
user: {
|
||||||
|
id: '0000000001',
|
||||||
|
name: 'しゅいろ',
|
||||||
|
username: 'syuilo',
|
||||||
|
host: null,
|
||||||
|
avatarDecorations: [],
|
||||||
|
avatarUrl: 'https://proxy.misskeyusercontent.com/avatar.webp?url=https%3A%2F%2Fs3.arkjp.net%2Fmisskey%2Fwebpublic-b2dc591e-58b6-4df7-b7c9-1cba199f6619.png&avatar=1',
|
||||||
|
avatarBlurhash: 'yFF5Kq0L00?a^*IBNG01^j-pV@D*o|xt58WB}@9at7s.Ip~AWB57%Laes:xaOEoLnis:ofIpoJr?NHtRV@oLoeNHNI%1M{kCWCjuxZ',
|
||||||
|
isBot: false,
|
||||||
|
isCat: true,
|
||||||
|
emojis: {},
|
||||||
|
onlineStatus: null,
|
||||||
|
badgeRoles: [],
|
||||||
|
},
|
||||||
|
text: 'just setting up my msky',
|
||||||
|
cw: null,
|
||||||
|
visibility: 'public',
|
||||||
|
localOnly: false,
|
||||||
|
reactionAcceptance: null,
|
||||||
|
renoteCount: 0,
|
||||||
|
repliesCount: 1,
|
||||||
|
reactions: {},
|
||||||
|
reactionEmojis: {},
|
||||||
|
fileIds: [],
|
||||||
|
files: [],
|
||||||
|
replyId: null,
|
||||||
|
renoteId: null,
|
||||||
|
});
|
||||||
|
const onceReacted = ref<boolean>(false);
|
||||||
|
|
||||||
|
function addReaction(emoji) {
|
||||||
|
onceReacted.value = true;
|
||||||
|
exampleNote.reactions[emoji] = 1;
|
||||||
|
exampleNote.myReaction = emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeReaction(emoji) {
|
||||||
|
delete exampleNote.reactions[emoji];
|
||||||
|
exampleNote.myReaction = undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.exampleNoteRoot {
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: var(--panelBorder);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
color: var(--fgOnAccent);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 38px);
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.postIcon {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postText {
|
||||||
|
position: relative;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,77 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._timeline.description1 }}</div>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<div><i class="ti ti-home"></i> <b>{{ i18n.ts._timelines.home }}</b> … {{ i18n.ts._initialTutorial._timeline.home }}</div>
|
||||||
|
<div><i class="ti ti-planet"></i> <b>{{ i18n.ts._timelines.local }}</b> … {{ i18n.ts._initialTutorial._timeline.local }}</div>
|
||||||
|
<div><i class="ti ti-universe"></i> <b>{{ i18n.ts._timelines.social }}</b> … {{ i18n.ts._initialTutorial._timeline.social }}</div>
|
||||||
|
<div><i class="ti ti-whirl"></i> <b>{{ i18n.ts._timelines.global }}</b> … {{ i18n.ts._initialTutorial._timeline.global }}</div>
|
||||||
|
</div>
|
||||||
|
<I18n :src="i18n.ts._initialTutorial._timeline.description2" tag="div" style="padding: 0 16px;">
|
||||||
|
<template #link>
|
||||||
|
<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.exampleNoteRoot {
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: var(--panelBorder);
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
color: var(--fgOnAccent);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 38px);
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.postIcon {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 30px;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postText {
|
||||||
|
position: relative;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -17,10 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template v-else-if="page === 3" #header><i class="ti ti-user-plus"></i> {{ i18n.ts.follow }}</template>
|
<template v-else-if="page === 3" #header><i class="ti ti-user-plus"></i> {{ i18n.ts.follow }}</template>
|
||||||
<template v-else-if="page === 4" #header><i class="ti ti-bell-plus"></i> {{ i18n.ts.pushNotification }}</template>
|
<template v-else-if="page === 4" #header><i class="ti ti-bell-plus"></i> {{ i18n.ts.pushNotification }}</template>
|
||||||
<template v-else-if="page === 5" #header>{{ i18n.ts.done }}</template>
|
<template v-else-if="page === 5" #header>{{ i18n.ts.done }}</template>
|
||||||
|
<template v-else-if="page >= 6" #header>{{ i18n.ts._initialTutorial.title }}</template>
|
||||||
<template v-else #header>{{ i18n.ts.initialAccountSetting }}</template>
|
<template v-else #header>{{ i18n.ts.initialAccountSetting }}</template>
|
||||||
|
|
||||||
<div style="overflow-x: clip;">
|
<div style="overflow-x: clip;">
|
||||||
<div :class="$style.progressBar">
|
<div v-if="page <= 5" :class="$style.progressBar">
|
||||||
<div :class="$style.progressBarValue" :style="{ width: `${(page / 5) * 100}%` }"></div>
|
<div :class="$style.progressBarValue" :style="{ width: `${(page / 5) * 100}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
|
@ -102,8 +103,65 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps" style="text-align: center;">
|
<div class="_gaps" style="text-align: center;">
|
||||||
<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
||||||
<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
|
<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
|
||||||
<I18n :src="i18n.ts._initialAccountSetting.ifYouNeedLearnMore" tag="div" style="padding: 0 16px;">
|
<div>{{ i18n.t('_initialAccountSetting.youCanContinueTutorial', { name: instance.name ?? host }) }}</div>
|
||||||
<template #name>{{ instance.name ?? host }}</template>
|
<div class="_buttonsCenter" style="margin-top: 16px;">
|
||||||
|
<MkButton rounded primary gradate data-cy-user-setup-continue @click="() => { setupComplete(); page++; }">{{ i18n.ts._initialAccountSetting.startTutorial }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
|
<MkButton rounded primary data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="page === 6">
|
||||||
|
<div style="height: 100cqh; overflow: auto;">
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
|
<XTutorialNote phase="aboutNote"/>
|
||||||
|
</MkSpacer>
|
||||||
|
<div :class="$style.pageFooter">
|
||||||
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
|
<MkButton primary rounded gradate style="" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="page === 7">
|
||||||
|
<div style="height: 100cqh; overflow: auto;">
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
|
<XTutorialNote phase="howToReact"/>
|
||||||
|
</MkSpacer>
|
||||||
|
<div :class="$style.pageFooter">
|
||||||
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
|
<MkButton primary rounded gradate style="" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="page === 8">
|
||||||
|
<div style="height: 100cqh; overflow: auto;">
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
|
<XTutorialTimeline phase="howToReact"/>
|
||||||
|
</MkSpacer>
|
||||||
|
<div :class="$style.pageFooter">
|
||||||
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
|
<MkButton primary rounded gradate style="" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="page === 9">
|
||||||
|
<div :class="$style.centerPage">
|
||||||
|
<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
|
<div class="_gaps" style="text-align: center;">
|
||||||
|
<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
||||||
|
<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div>
|
||||||
|
<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
|
||||||
<template #link>
|
<template #link>
|
||||||
<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -111,7 +169,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
|
||||||
<div class="_buttonsCenter" style="margin-top: 16px;">
|
<div class="_buttonsCenter" style="margin-top: 16px;">
|
||||||
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
<MkButton primary rounded gradate data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
|
<MkButton rounded primary gradate data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -129,6 +187,8 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
|
import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
|
||||||
import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
|
import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
|
||||||
import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
|
import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
|
||||||
|
import XTutorialNote from '@/components/MkUserSetupDialog.NoteTutorial.vue';
|
||||||
|
import XTutorialTimeline from '@/components/MkUserSetupDialog.TimelineTutorial.vue';
|
||||||
import MkAnimBg from '@/components/MkAnimBg.vue';
|
import MkAnimBg from '@/components/MkAnimBg.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
@ -146,7 +206,9 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
const page = ref(defaultStore.state.accountSetupWizard);
|
const page = ref(defaultStore.state.accountSetupWizard);
|
||||||
|
|
||||||
watch(page, () => {
|
watch(page, () => {
|
||||||
|
if (page.value <= 5) {
|
||||||
defaultStore.set('accountSetupWizard', page.value);
|
defaultStore.set('accountSetupWizard', page.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function close(skip: boolean) {
|
async function close(skip: boolean) {
|
||||||
|
@ -162,6 +224,10 @@ async function close(skip: boolean) {
|
||||||
defaultStore.set('accountSetupWizard', -1);
|
defaultStore.set('accountSetupWizard', -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupComplete() {
|
||||||
|
defaultStore.set('accountSetupWizard', -1);
|
||||||
|
}
|
||||||
|
|
||||||
async function later(later: boolean) {
|
async function later(later: boolean) {
|
||||||
if (later) {
|
if (later) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="$style.container">
|
|
||||||
<div :class="$style.title">
|
|
||||||
<div :class="$style.titleText"><i class="ti ti-info-circle"></i> {{ i18n.ts._timelineTutorial.title }}</div>
|
|
||||||
<div :class="$style.step">
|
|
||||||
<button class="_button" :class="$style.stepArrow" :disabled="tutorial === 0" @click="tutorial--">
|
|
||||||
<i class="ti ti-chevron-left"></i>
|
|
||||||
</button>
|
|
||||||
<span :class="$style.stepNumber">{{ tutorial + 1 }} / {{ tutorialsNumber }}</span>
|
|
||||||
<button class="_button" :class="$style.stepArrow" :disabled="tutorial === tutorialsNumber - 1" @click="tutorial++">
|
|
||||||
<i class="ti ti-chevron-right"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="tutorial === 0" :class="$style.body">
|
|
||||||
<div>{{ i18n.t('_timelineTutorial.step1_1', { name: instance.name ?? host }) }}</div>
|
|
||||||
<div>{{ i18n.t('_timelineTutorial.step1_2', { name: instance.name ?? host }) }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tutorial === 1" :class="$style.body">
|
|
||||||
<div>{{ i18n.ts._timelineTutorial.step2_1 }}</div>
|
|
||||||
<div>{{ i18n.t('_timelineTutorial.step2_2', { name: instance.name ?? host }) }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tutorial === 2" :class="$style.body">
|
|
||||||
<div>{{ i18n.ts._timelineTutorial.step3_1 }}</div>
|
|
||||||
<div>{{ i18n.ts._timelineTutorial.step3_2 }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tutorial === 3" :class="$style.body">
|
|
||||||
<div>{{ i18n.ts._timelineTutorial.step4_1 }}</div>
|
|
||||||
<div>{{ i18n.ts._timelineTutorial.step4_2 }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="$style.footer">
|
|
||||||
<template v-if="tutorial === tutorialsNumber - 1">
|
|
||||||
<MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial = -1">{{ i18n.ts.done }} <i class="ti ti-check"></i></MkButton>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial++">{{ i18n.ts.next }} <i class="ti ti-arrow-right"></i></MkButton>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import { instance } from '@/instance.js';
|
|
||||||
import { host } from '@/config.js';
|
|
||||||
|
|
||||||
const tutorialsNumber = 4;
|
|
||||||
|
|
||||||
const tutorial = computed({
|
|
||||||
get() { return defaultStore.reactiveState.timelineTutorial.value || 0; },
|
|
||||||
set(value) { defaultStore.set('timelineTutorial', value); },
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.small {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
border: solid 2px var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 22px 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&Text {
|
|
||||||
margin: 4px 0;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step {
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
&Arrow {
|
|
||||||
padding: 4px;
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
&:first-child {
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&Number {
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
padding: 0 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: right;
|
|
||||||
padding: 22px 32px;
|
|
||||||
|
|
||||||
&Item {
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -8,7 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div ref="rootEl" v-hotkey.global="keymap">
|
<div ref="rootEl" v-hotkey.global="keymap">
|
||||||
<XTutorial v-if="$i && defaultStore.reactiveState.timelineTutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
|
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
|
||||||
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
|
</MkInfo>
|
||||||
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||||
|
|
||||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||||
|
@ -34,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { defineAsyncComponent, computed, watch, provide } from 'vue';
|
import { defineAsyncComponent, computed, watch, provide } from 'vue';
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
import { scroll } from '@/scripts/scroll.js';
|
import { scroll } from '@/scripts/scroll.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -47,8 +50,6 @@ import { antennasCache, userListsCache } from '@/cache.js';
|
||||||
|
|
||||||
provide('shouldOmitHeaderTitle', true);
|
provide('shouldOmitHeaderTitle', true);
|
||||||
|
|
||||||
const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
|
|
||||||
|
|
||||||
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
||||||
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
||||||
const keymap = {
|
const keymap = {
|
||||||
|
@ -139,6 +140,13 @@ function focus(): void {
|
||||||
tlComponent.focus();
|
tlComponent.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeTutorial(): void {
|
||||||
|
if (!['home', 'local', 'social', 'global'].includes(src)) return;
|
||||||
|
const before = defaultStore.state.timelineTutorials;
|
||||||
|
before[src] = true;
|
||||||
|
defaultStore.set('timelineTutorials', before);
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = $computed(() => [{
|
const headerActions = $computed(() => [{
|
||||||
icon: 'ti ti-dots',
|
icon: 'ti ti-dots',
|
||||||
text: i18n.ts.options,
|
text: i18n.ts.options,
|
||||||
|
|
|
@ -49,9 +49,14 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
timelineTutorial: {
|
timelineTutorials: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: 0,
|
default: {
|
||||||
|
home: false,
|
||||||
|
local: false,
|
||||||
|
social: false,
|
||||||
|
global: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
keepCw: {
|
keepCw: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
|
|
Loading…
Reference in a new issue