summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-05-23 10:46:42 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2025-05-23 10:46:42 +0900
commit2bfbbbf16ac1f085efa897c98913297fe09eef61 (patch)
treec88edb1ca495c3f48e92c2a14744a06c006f6a2f /packages/frontend/src
parentfix(backend): admin側のエンドポイントで作成した招待コード... (diff)
downloadmisskey-2bfbbbf16ac1f085efa897c98913297fe09eef61.tar.gz
misskey-2bfbbbf16ac1f085efa897c98913297fe09eef61.tar.bz2
misskey-2bfbbbf16ac1f085efa897c98913297fe09eef61.zip
enhance(frontend): improve tips
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/MkDrive.vue9
-rw-r--r--packages/frontend/src/components/MkUploaderDialog.vue4
-rw-r--r--packages/frontend/src/components/global/MkTip.vue48
-rw-r--r--packages/frontend/src/components/index.ts3
-rw-r--r--packages/frontend/src/pages/admin/abuses.vue9
-rw-r--r--packages/frontend/src/pages/my-clips/index.vue5
-rw-r--r--packages/frontend/src/pages/my-lists/index.vue4
-rw-r--r--packages/frontend/src/pages/settings/other.vue20
-rw-r--r--packages/frontend/src/pages/timeline.vue12
-rw-r--r--packages/frontend/src/store.ts31
10 files changed, 103 insertions, 42 deletions
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 5604f0226f..7e955f1529 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -60,9 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@drop.prevent.stop="onDrop"
@contextmenu.stop="onContextmenu"
>
- <div v-if="!store.r.readDriveTip.value" style="padding: 8px;">
- <MkInfo closable @close="closeTip()"><div v-html="i18n.ts.driveAboutTip"></div></MkInfo>
- </div>
+ <MkTip k="drive"><div v-html="i18n.ts.driveAboutTip"></div></MkTip>
<div :class="$style.folders">
<XFolder
@@ -135,7 +133,6 @@ SPDX-License-Identifier: AGPL-3.0-only
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, computed, TransitionGroup } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from './MkButton.vue';
-import MkInfo from './MkInfo.vue';
import type { MenuItem } from '@/types/menu.js';
import XNavFolder from '@/components/MkDrive.navFolder.vue';
import XFolder from '@/components/MkDrive.folder.vue';
@@ -661,10 +658,6 @@ function onContextmenu(ev: MouseEvent) {
os.contextMenu(getMenu(), ev);
}
-function closeTip() {
- store.set('readDriveTip', true);
-}
-
useGlobalEvent('driveFileCreated', (file) => {
if (file.folderId === (folder.value?.id ?? null)) {
filesPaginator.prepend(file);
diff --git a/packages/frontend/src/components/MkUploaderDialog.vue b/packages/frontend/src/components/MkUploaderDialog.vue
index fb27dcbf58..eb8026a480 100644
--- a/packages/frontend/src/components/MkUploaderDialog.vue
+++ b/packages/frontend/src/components/MkUploaderDialog.vue
@@ -19,6 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="[$style.overallProgress, canRetry ? $style.overallProgressError : null]" :style="{ '--op': `${overallProgress}%` }"></div>
<div class="_gaps_s _spacer">
+ <MkTip k="uploader">
+ {{ i18n.ts._uploader.tip }}
+ </MkTip>
+
<div class="_gaps_s">
<div
v-for="ctx in items"
diff --git a/packages/frontend/src/components/global/MkTip.vue b/packages/frontend/src/components/global/MkTip.vue
new file mode 100644
index 0000000000..384511a0ed
--- /dev/null
+++ b/packages/frontend/src/components/global/MkTip.vue
@@ -0,0 +1,48 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div v-if="!store.r.tips.value[props.k]" :class="[$style.root, { [$style.warn]: warn }]" class="_selectable _gaps_s">
+ <div style="font-weight: bold;"><i class="ti ti-bulb"></i> {{ i18n.ts.tip }}:</div>
+ <div><slot></slot></div>
+ <MkButton primary rounded small @click="closeTip()"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { i18n } from '@/i18n.js';
+import { store } from '@/store.js';
+import MkButton from '@/components/MkButton.vue';
+
+const props = withDefaults(defineProps<{
+ k: keyof (typeof store['s']['tips']);
+ warn?: boolean;
+}>(), {
+ warn: false,
+});
+
+function closeTip() {
+ store.set('tips', {
+ ...store.r.tips.value,
+ [props.k]: true,
+ });
+}
+</script>
+
+<style lang="scss" module>
+.root {
+ padding: 12px 14px;
+ font-size: 90%;
+ background: var(--MI_THEME-infoBg);
+ color: var(--MI_THEME-infoFg);
+ border-radius: var(--MI-radius);
+
+ &.warn {
+ background: var(--MI_THEME-infoWarnBg);
+ color: var(--MI_THEME-infoWarnFg);
+ }
+}
+
+</style>
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 9981772ae8..19766e8575 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -26,6 +26,7 @@ import MkStickyContainer from './global/MkStickyContainer.vue';
import MkLazy from './global/MkLazy.vue';
import MkResult from './global/MkResult.vue';
import MkSystemIcon from './global/MkSystemIcon.vue';
+import MkTip from './global/MkTip.vue';
import PageWithHeader from './global/PageWithHeader.vue';
import PageWithAnimBg from './global/PageWithAnimBg.vue';
import SearchMarker from './global/SearchMarker.vue';
@@ -65,6 +66,7 @@ export const components = {
MkLazy: MkLazy,
MkResult: MkResult,
MkSystemIcon: MkSystemIcon,
+ MkTip: MkTip,
PageWithHeader: PageWithHeader,
PageWithAnimBg: PageWithAnimBg,
SearchMarker: SearchMarker,
@@ -98,6 +100,7 @@ declare module '@vue/runtime-core' {
MkLazy: typeof MkLazy;
MkResult: typeof MkResult;
MkSystemIcon: typeof MkSystemIcon;
+ MkTip: typeof MkTip;
PageWithHeader: typeof PageWithHeader;
PageWithAnimBg: typeof PageWithAnimBg;
SearchMarker: typeof SearchMarker;
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 14e8e600b0..4dbb573ceb 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton>
</div>
- <MkInfo v-if="!store.r.abusesTutorial.value" closable @close="closeTutorial()">
+ <MkTip k="abuses">
{{ i18n.ts._abuseUserReport.resolveTutorial }}
- </MkInfo>
+ </MkTip>
<div :class="$style.inputs" class="_gaps">
<MkSelect v-model="state" style="margin: 0; flex: 1;">
@@ -65,7 +65,6 @@ import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
-import MkInfo from '@/components/MkInfo.vue';
import { store } from '@/store.js';
const reports = useTemplateRef('reports');
@@ -90,10 +89,6 @@ function resolved(reportId) {
reports.value?.paginator.removeItem(reportId);
}
-function closeTutorial() {
- store.set('abusesTutorial', false);
-}
-
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 4dafd87b80..c386ed7239 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
- <div class="_spacer" style="--MI_SPACER-w: 700px;">
+ <div class="_spacer _gaps" style="--MI_SPACER-w: 700px;">
+ <MkTip k="clips">
+ {{ i18n.ts._clip.tip }}
+ </MkTip>
<div v-if="tab === 'my'" class="_gaps">
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 41afabff99..fb31cd542c 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -7,6 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px;">
<div class="_gaps">
+ <MkTip k="userLists">
+ {{ i18n.ts._userLists.tip }}
+ </MkTip>
+
<MkResult v-if="items.length === 0" type="empty"/>
<MkButton primary rounded style="margin: 0 auto;" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton>
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 7b6ad5e56e..f09cc9c9bc 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -123,6 +123,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<hr>
+ <MkButton @click="resetAllTips"><i class="ti ti-bulb"></i> {{ i18n.ts.redisplayAllTips }}</MkButton>
+ <MkButton @click="hideAllTips"><i class="ti ti-bulb-off"></i> {{ i18n.ts.hideAllTips }}</MkButton>
+
+ <hr>
+
<FormSlot>
<MkButton danger @click="migrate"><i class="ti ti-refresh"></i> {{ i18n.ts.migrateOldSettings }}</MkButton>
<template #caption>{{ i18n.ts.migrateOldSettings_description }}</template>
@@ -152,6 +157,7 @@ import { prefer } from '@/preferences.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import { signout } from '@/signout.js';
import { migrateOldSettings } from '@/pref-migrate.js';
+import { store, TIPS } from '@/store.js';
const $i = ensureSignin();
@@ -194,6 +200,20 @@ function migrate() {
migrateOldSettings();
}
+function resetAllTips() {
+ store.set('tips', {});
+ os.success();
+}
+
+function hideAllTips() {
+ const v = {};
+ for (const k of TIPS) {
+ v[k] = true;
+ }
+ store.set('tips', v);
+ os.success();
+}
+
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 453a48d1bc..5696d1dd89 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :swipable="true" :displayMyAvatar="true">
<div class="_spacer" style="--MI_SPACER-w: 800px;">
- <MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
+ <MkTip v-if="isBasicTimeline(src)" :k="`tl.${src}`" style="margin-bottom: var(--MI-margin);">
{{ i18n.ts._timelineDescription[src] }}
- </MkInfo>
+ </MkTip>
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
<MkStreamingNotesTimeline
ref="tlComponent"
@@ -32,7 +32,6 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
import type { MenuItem } from '@/types/menu.js';
import type { BasicTimelineType } from '@/timelines.js';
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
-import MkInfo from '@/components/MkInfo.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import * as os from '@/os.js';
import { store } from '@/store.js';
@@ -204,13 +203,6 @@ function focus(): void {
tlComponent.value.focus();
}
-function closeTutorial(): void {
- if (!isBasicTimeline(src.value)) return;
- const before = store.s.timelineTutorials;
- before[src.value] = true;
- store.set('timelineTutorials', before);
-}
-
function switchTlIfNeeded() {
if (isBasicTimeline(src.value) && !isAvailableBasicTimeline(src.value)) {
src.value = availableBasicTimelines()[0];
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 06c2f9149c..6f9b5786ee 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -14,6 +14,18 @@ import { miLocalStorage } from '@/local-storage.js';
import { Pizzax } from '@/lib/pizzax.js';
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
+export const TIPS = [
+ 'drive',
+ 'uploader',
+ 'clips',
+ 'userLists',
+ 'tl.home',
+ 'tl.local',
+ 'tl.social',
+ 'tl.global',
+ 'abuses',
+] as const;
+
/**
* 「状態」を管理するストア(not「設定」)
*/
@@ -22,22 +34,9 @@ export const store = markRaw(new Pizzax('base', {
where: 'account',
default: 0,
},
- timelineTutorials: {
- where: 'account',
- default: {
- home: false,
- local: false,
- social: false,
- global: false,
- },
- },
- abusesTutorial: {
- where: 'account',
- default: false,
- },
- readDriveTip: {
- where: 'account',
- default: false,
+ tips: {
+ where: 'device',
+ default: {} as Partial<Record<typeof TIPS[number], boolean>>, // true = 既読
},
memo: {
where: 'account',