feat: クライアントのアップデートがある場合の通知

This commit is contained in:
NoriDev 2023-10-02 02:30:02 +09:00
parent 9e32d84a2e
commit e8d249f62b
14 changed files with 200 additions and 0 deletions

View file

@ -35,6 +35,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
- 마우스를 움직이거나 화면을 터치하고 있으면 이미지를 재생
- 일정 시간이 경과하면 이미지 재생을 중지
- Feat: 미디어가 포함된 모든 노트를 접을 수 있음
- Feat: 클라이언트 업데이트가 있으면 알림
- Fix: 로그인하지 않은 상태에서 노트 상세 페이지의 노트 작성 폼을 조작할 수 있음
### Server

View file

@ -1,5 +1,7 @@
---
_lang_: "English"
youAreRunningBetaClient: "Unreleased version of CherryPick in use!"
cherrypickUpdate: "CherryPick Update"
allMediaNoteCollapse: "Collapse all media notes"
showingAnimatedImagesDescription: "When set to \"Animate on interaction\", the image will play when you hover over it or touch it."
showFixedPostFormInReplies: "Show posting form in replies"

2
locales/index.d.ts vendored
View file

@ -3,6 +3,8 @@
// Do not edit this file directly.
export interface Locale {
"_lang_": string;
"youAreRunningBetaClient": string;
"cherrypickUpdate": string;
"allMediaNoteCollapse": string;
"showingAnimatedImagesDescription": string;
"showFixedPostFormInReplies": string;

View file

@ -1,5 +1,7 @@
_lang_: "日本語"
youAreRunningBetaClient: "未発売バージョンのCherryPickを利用しています"
cherrypickUpdate: "CherryPickアップデート"
allMediaNoteCollapse: "すべてのメディアノートを省略して表示"
showingAnimatedImagesDescription: "「インタラクト時に再生」に設定すると、画像の上にマウスを置いたり、画像をタッチすると再生されます。"
showFixedPostFormInReplies: "返信に投稿フォームを表示する"

View file

@ -1,5 +1,7 @@
---
_lang_: "한국어"
youAreRunningBetaClient: "아직 출시되지 않은 버전의 CherryPick를 이용하고 있어요!"
cherrypickUpdate: "CherryPick 업데이트"
allMediaNoteCollapse: "모든 미디어 노트 간략화하기"
showingAnimatedImagesDescription: "'건드리면 움직임'으로 설정하면 이미지 위에 마우스를 올리거나 이미지를 터치하면 움직여요."
showFixedPostFormInReplies: "답글에 글 작성란 표시"

View file

@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="noMaintainerInformation" warn class="info">{{ i18n.ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noBotProtection" warn class="info">{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noEmailServer" warn class="info">{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="updateAvailable" warn class="info">{{ i18n.ts.newVersionOfClientAvailable }} <MkA to="/admin/update" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
</div>
@ -37,6 +38,7 @@ import * as os from '@/os.js';
import { lookupUser } from '@/scripts/lookup-user.js';
import { useRouter } from '@/router.js';
import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import { version } from '@/config.js';
const isEmpty = (x: string | null) => x == null || x === '';
@ -61,6 +63,8 @@ let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha
let noEmailServer = !instance.enableEmail;
let thereIsUnresolvedAbuseReport = $ref(false);
let currentPage = $computed(() => router.currentRef.value.child);
let updateAvailable = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -69,6 +73,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) thereIsUnresolvedAbuseReport = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) updateAvailable = true;
});
const NARROW_THRESHOLD = 600;
const ro = new ResizeObserver((entries, observer) => {
if (entries.length === 0) return;

View file

@ -9,6 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
<div class="_gaps">
<FormLink to="/admin/update"><template #icon><i class="ti ti-refresh"></i></template>{{ i18n.ts.cherrypickUpdate }}</FormLink>
<div class="_panel" style="padding: 16px;">
<MkSwitch v-model="enableServerMachineStats">
<template #label>{{ i18n.ts.enableServerMachineStats }}</template>
@ -57,6 +59,7 @@ import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkSwitch from '@/components/MkSwitch.vue';
import FormLink from '@/components/form/link.vue';
let enableServerMachineStats: boolean = $ref(false);
let enableIdenticonGeneration: boolean = $ref(false);

View file

@ -0,0 +1,122 @@
<!--
SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer>
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<div class="_gaps_m">
<template v-if="meta">
<FormInfo v-if="version > releasesCherryPick[0].tag_name">{{ i18n.ts.youAreRunningBetaClient }}</FormInfo>
<FormInfo v-else-if="version === (meta.version && releasesCherryPick[0].tag_name)">{{ i18n.ts.youAreRunningUpToDateClient }}</FormInfo>
<FormInfo v-else warn>{{ i18n.ts.newVersionOfClientAvailable }}</FormInfo>
</template>
<FormSection first>
<template #label>{{ instanceName }}</template>
<MkKeyValue @click="whatIsNewCherryPick">
<template #key>{{ i18n.ts.currentVersion }} <i class="ti ti-external-link"></i></template>
<template #value>{{ version }}</template>
</MkKeyValue>
<MkKeyValue style="margin-top: 10px;" @click="whatIsNewLatestCherryPick">
<template #key>{{ i18n.ts.latestVersion }} <i class="ti ti-external-link"></i></template>
<template v-if="releasesCherryPick" #value>{{ releasesCherryPick[0].tag_name }}</template>
<template v-else #value><MkEllipsis/></template>
</MkKeyValue>
</FormSection>
<FormSection @click="whatIsNewLatestCherryPick">
<template #label>CherryPick <i class="ti ti-external-link"></i></template>
<MkKeyValue>
<template #key>{{ i18n.ts.latestVersion }}</template>
<template v-if="releasesCherryPick" #value>{{ releasesCherryPick[0].tag_name }}</template>
<template v-else #value><MkEllipsis/></template>
</MkKeyValue>
<MkKeyValue style="margin: 8px 0 0; color: var(--fgTransparentWeak); font-size: 0.85em;">
<template v-if="releasesCherryPick" #value><MkTime :time="releasesCherryPick[0].published_at" mode="detail"/></template>
<template v-else #value><MkEllipsis/></template>
</MkKeyValue>
</FormSection>
<FormSection @click="whatIsNewLatestMisskey">
<template #label>Misskey <i class="ti ti-external-link"></i></template>
<MkKeyValue>
<template #key>{{ i18n.ts.latestVersion }}</template>
<template v-if="releasesMisskey" #value>{{ releasesMisskey[0].tag_name }}</template>
<template v-else #value><MkEllipsis/></template>
</MkKeyValue>
<MkKeyValue style="margin: 8px 0 0; color: var(--fgTransparentWeak); font-size: 0.85em;">
<template v-if="releasesMisskey" #value><MkTime :time="releasesMisskey[0].published_at" mode="detail"/></template>
<template v-else #value><MkEllipsis/></template>
</MkKeyValue>
</FormSection>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import * as Misskey from 'cherrypick-js';
import * as os from '@/os.js';
import FormInfo from '@/components/MkInfo.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { version, instanceName, basedMisskeyVersion } from '@/config.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import XHeader from '@/pages/admin/_header_.vue';
let meta = $ref<Misskey.entities.LiteInstanceMetadata | null>(null);
let releasesCherryPick = $ref(null);
let releasesMisskey = $ref(null);
onMounted(() => {
os.api('meta', {
detail: false,
}).then(res => {
meta = res;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
});
fetch('https://api.github.com/repos/misskey-dev/misskey/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesMisskey = res;
});
});
const whatIsNewCherryPick = () => {
window.open(`https://github.com/kokonect-link/cherrypick/blob/develop/CHANGELOG_CHERRYPICK.md#${version.replace(/\./g, '')}`, '_blank');
};
const whatIsNewLatestCherryPick = () => {
window.open(`https://github.com/kokonect-link/cherrypick/blob/develop/CHANGELOG_CHERRYPICK.md#${releasesCherryPick[0].tag_name.replace(/\./g, '')}`, '_blank');
};
const whatIsNewMisskey = () => {
window.open(`https://misskey-hub.net/docs/releases.html#_${basedMisskeyVersion.replace(/\./g, '-')}`, '_blank');
};
const whatIsNewLatestMisskey = () => {
window.open(`https://misskey-hub.net/docs/releases.html#_${releasesMisskey[0].tag_name.replace(/\./g, '-')}`, '_blank');
};
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.cherrypickUpdate,
icon: 'ti ti-refresh',
});
</script>

View file

@ -459,6 +459,10 @@ export const routes = [{
path: '/invites',
name: 'invites',
component: page(() => import('./pages/admin/invites.vue')),
}, {
path: '/update',
name: 'update',
component: page(() => import('./pages/admin/update.vue')),
}, {
path: '/',
component: page(() => import('./pages/_empty_.vue')),

View file

@ -57,6 +57,7 @@ import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { version } from '@/config.js';
const menu = toRef(defaultStore.state, 'menu');
const otherMenuItemIndicated = computed(() => {
@ -67,6 +68,7 @@ const otherMenuItemIndicated = computed(() => {
return false;
});
let controlPanelIndicated = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -75,6 +77,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) controlPanelIndicated = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) controlPanelIndicated = true;
});
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: true,

View file

@ -69,6 +69,7 @@ import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { version } from '@/config.js';
const iconOnly = ref(false);
@ -81,6 +82,7 @@ const otherMenuItemIndicated = computed(() => {
return false;
});
let controlPanelIndicated = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -89,6 +91,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) controlPanelIndicated = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) controlPanelIndicated = true;
});
const calcViewState = () => {
iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon');
};

View file

@ -60,6 +60,7 @@ import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { version } from '@/config.js';
const WINDOW_THRESHOLD = 1400;
@ -78,6 +79,7 @@ let el = $shallowRef<HTMLElement>();
let iconOnly = $ref(false);
let settingsWindowed = $ref(false);
let controlPanelIndicated = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -86,6 +88,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) controlPanelIndicated = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) controlPanelIndicated = true;
});
function calcViewState() {
iconOnly = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
settingsWindowed = (window.innerWidth > WINDOW_THRESHOLD);

View file

@ -61,6 +61,7 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { mainRouter } from '@/router.js';
import { version } from '@/config.js';
const menu = toRef(defaultStore.state, 'menu');
const otherMenuItemIndicated = computed(() => {
@ -71,6 +72,7 @@ const otherMenuItemIndicated = computed(() => {
return false;
});
let controlPanelIndicated = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -79,6 +81,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) controlPanelIndicated = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) controlPanelIndicated = true;
});
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: true,

View file

@ -76,6 +76,7 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { mainRouter } from '@/router.js';
import { version } from '@/config.js';
const iconOnly = ref(false);
@ -88,6 +89,7 @@ const otherMenuItemIndicated = computed(() => {
return false;
});
let controlPanelIndicated = $ref(false);
let releasesCherryPick = $ref(null);
os.api('admin/abuse-user-reports', {
state: 'unresolved',
@ -96,6 +98,14 @@ os.api('admin/abuse-user-reports', {
if (reports.length > 0) controlPanelIndicated = true;
});
fetch('https://api.github.com/repos/kokonect-link/cherrypick/releases', {
method: 'GET',
}).then(res => res.json())
.then(res => {
releasesCherryPick = res;
if (version < releasesCherryPick[0].tag_name) controlPanelIndicated = true;
});
const calcViewState = () => {
iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon');
};