Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # locales/en-US.yml # locales/index.d.ts # locales/ja-JP.yml # package.json # packages/backend/src/server/api/endpoints/notes/create.ts # packages/frontend/src/components/MkDrive.file.vue # packages/frontend/src/components/MkNote.vue # packages/frontend/src/components/MkNoteHeader.vue # packages/frontend/src/components/MkPostForm.vue # packages/frontend/src/components/MkReactionsViewer.reaction.vue # packages/frontend/src/components/MkUserSetupDialog.vue # packages/frontend/src/pages/timeline.tutorial.vue # packages/frontend/src/pages/timeline.vue # packages/misskey-js/etc/misskey-js.api.md
This commit is contained in:
commit
cb1005811d
106 changed files with 2036 additions and 963 deletions
|
|
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
|
||||
</div>
|
||||
<FormSection>
|
||||
<div class="_formLinks">
|
||||
<div class="_gaps_s">
|
||||
<FormLink to="https://github.com/misskey-dev/misskey" external>
|
||||
<template #icon><i class="ti ti-code"></i></template>
|
||||
{{ i18n.ts._aboutMisskey.source }}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink>
|
||||
<div class="_formLinks">
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-if="instance.serverRules.length > 0">
|
||||
<template #label>{{ i18n.ts.serverRules }}</template>
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<FormSection>
|
||||
<template #label>Well-known resources</template>
|
||||
<div class="_formLinks">
|
||||
<div class="_gaps_s">
|
||||
<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
|
||||
<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
|
||||
<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="900">
|
||||
<div class="_gaps">
|
||||
<MkInfo>{{ i18n.ts._announcement.shouldNotBeUsedToPresentPermanentInfo }}</MkInfo>
|
||||
<MkInfo v-if="announcements.length > 5" warn>{{ i18n.ts._announcement.tooManyActiveAnnouncementDescription }}</MkInfo>
|
||||
|
||||
<MkFolder v-for="announcement in announcements" :key="announcement.id ?? announcement._id" :defaultOpen="announcement.id == null">
|
||||
|
|
@ -43,6 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="banner">{{ i18n.ts.banner }}</option>
|
||||
<option value="dialog">{{ i18n.ts.dialog }}</option>
|
||||
</MkRadios>
|
||||
<MkInfo v-if="announcement.display === 'dialog'" warn>{{ i18n.ts._announcement.dialogAnnouncementUxWarn }}</MkInfo>
|
||||
<MkSwitch v-model="announcement.forExistingUsers" :helpText="i18n.ts._announcement.forExistingUsersDescription">
|
||||
{{ i18n.ts._announcement.forExistingUsers }}
|
||||
</MkSwitch>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.sensitive }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="allowRenoteToExternal">
|
||||
<template #label>{{ i18n.ts._channel.allowRenoteToExternal }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<div>
|
||||
<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="ti ti-plus"></i> {{ i18n.ts._channel.setBanner }}</MkButton>
|
||||
<div v-else-if="bannerUrl">
|
||||
|
|
@ -76,7 +80,7 @@ import { useRouter } from '@/router.js';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from "@/components/MkSwitch.vue";
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
|
|
@ -93,6 +97,7 @@ let bannerUrl = $ref<string | null>(null);
|
|||
let bannerId = $ref<string | null>(null);
|
||||
let color = $ref('#000');
|
||||
let isSensitive = $ref(false);
|
||||
let allowRenoteToExternal = $ref(true);
|
||||
const pinnedNotes = ref([]);
|
||||
|
||||
watch(() => bannerId, async () => {
|
||||
|
|
@ -121,6 +126,7 @@ async function fetchChannel() {
|
|||
id,
|
||||
}));
|
||||
color = channel.color;
|
||||
allowRenoteToExternal = channel.allowRenoteToExternal;
|
||||
}
|
||||
|
||||
fetchChannel();
|
||||
|
|
@ -150,6 +156,7 @@ function save() {
|
|||
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
|
||||
color: color,
|
||||
isSensitive: isSensitive,
|
||||
allowRenoteToExternal: allowRenoteToExternal,
|
||||
};
|
||||
|
||||
if (props.channelId) {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #key>{{ i18n.ts._fileViewer.size }}</template>
|
||||
<template #value>{{ bytes(file.size) }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue :class="$style.fileMetaDataChildren" :copy="file.url">
|
||||
<template #key>URL</template>
|
||||
<template #value>{{ file.url }}</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="_fullinfo">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<FormSplit>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts._registry.domain }}</template>
|
||||
<template #value>{{ i18n.ts.system }}</template>
|
||||
<template #value>{{ props.domain === '@' ? i18n.ts.system : props.domain.toUpperCase() }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts._registry.scope }}</template>
|
||||
|
|
@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<FormSection v-if="keys">
|
||||
<template #label>{{ i18n.ts.keys }}</template>
|
||||
<div class="_formLinks">
|
||||
<FormLink v-for="key in keys" :to="`/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
|
||||
<div class="_gaps_s">
|
||||
<FormLink v-for="key in keys" :to="`/registry/value/${props.domain}/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
|
@ -46,15 +46,17 @@ import FormSplit from '@/components/form/split.vue';
|
|||
|
||||
const props = defineProps<{
|
||||
path: string;
|
||||
domain: string;
|
||||
}>();
|
||||
|
||||
const scope = $computed(() => props.path.split('/'));
|
||||
const scope = $computed(() => props.path ? props.path.split('/') : []);
|
||||
|
||||
let keys = $ref(null);
|
||||
|
||||
function fetchKeys() {
|
||||
os.api('i/registry/keys-with-type', {
|
||||
scope: scope,
|
||||
domain: props.domain === '@' ? null : props.domain,
|
||||
}).then(res => {
|
||||
keys = Object.entries(res).sort((a, b) => a[0].localeCompare(b[0]));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<FormSplit>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts._registry.domain }}</template>
|
||||
<template #value>{{ i18n.ts.system }}</template>
|
||||
<template #value>{{ props.domain === '@' ? i18n.ts.system : props.domain.toUpperCase() }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts._registry.scope }}</template>
|
||||
|
|
@ -58,6 +58,7 @@ import FormInfo from '@/components/MkInfo.vue';
|
|||
|
||||
const props = defineProps<{
|
||||
path: string;
|
||||
domain: string;
|
||||
}>();
|
||||
|
||||
const scope = $computed(() => props.path.split('/').slice(0, -1));
|
||||
|
|
@ -70,6 +71,7 @@ function fetchValue() {
|
|||
os.api('i/registry/get-detail', {
|
||||
scope,
|
||||
key,
|
||||
domain: props.domain === '@' ? null : props.domain,
|
||||
}).then(res => {
|
||||
value = res;
|
||||
valueForEditor = JSON5.stringify(res.value, null, '\t');
|
||||
|
|
@ -95,6 +97,7 @@ async function save() {
|
|||
scope,
|
||||
key,
|
||||
value: JSON5.parse(valueForEditor),
|
||||
domain: props.domain === '@' ? null : props.domain,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -108,6 +111,7 @@ function del() {
|
|||
os.apiWithDialog('i/registry/remove', {
|
||||
scope,
|
||||
key,
|
||||
domain: props.domain === '@' ? null : props.domain,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSpacer :contentMax="600" :marginMin="16">
|
||||
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
|
||||
|
||||
<FormSection v-if="scopes">
|
||||
<template #label>{{ i18n.ts.system }}</template>
|
||||
<div class="_formLinks">
|
||||
<FormLink v-for="scope in scopes" :to="`/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
<div v-if="scopesWithDomain" class="_gaps_m">
|
||||
<FormSection v-for="domain in scopesWithDomain" :key="domain.domain">
|
||||
<template #label>{{ domain.domain ? domain.domain.toUpperCase() : i18n.ts.system }}</template>
|
||||
<div class="_gaps_s">
|
||||
<FormLink v-for="scope in domain.scopes" :to="`/registry/keys/${domain.domain ?? '@'}/${scope.join('/')}`" class="_monospace">{{ scope.length === 0 ? '(root)' : scope.join('/') }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
|
@ -28,11 +30,11 @@ import FormLink from '@/components/form/link.vue';
|
|||
import FormSection from '@/components/form/section.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
let scopes = $ref(null);
|
||||
let scopesWithDomain = $ref(null);
|
||||
|
||||
function fetchScopes() {
|
||||
os.api('i/registry/scopes').then(res => {
|
||||
scopes = res.slice().sort((a, b) => a.join('/').localeCompare(b.join('/')));
|
||||
os.api('i/registry/scopes-with-domain').then(res => {
|
||||
scopesWithDomain = res;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ function submit() {
|
|||
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.somethingHappened,
|
||||
title: i18n.ts.somethingHappened,
|
||||
text: i18n.ts.signupPendingError,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,124 +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>{{ i18n.t('_timelineTutorial.step1_3', { 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>
|
||||
<MkSpacer :contentMax="800">
|
||||
<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);"/>
|
||||
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
|
|
@ -31,9 +33,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, computed, watch, provide } from 'vue';
|
||||
import { computed, watch, provide } from 'vue';
|
||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import { scroll } from '@/scripts/scroll.js';
|
||||
import * as os from '@/os.js';
|
||||
|
|
@ -48,8 +51,6 @@ import { deviceKind } from '@/scripts/device-kind.js';
|
|||
|
||||
provide('shouldOmitHeaderTitle', true);
|
||||
|
||||
const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
|
||||
|
||||
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
||||
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable && defaultStore.state.showGlobalTimeline) || ($i != null && $i.policies.gtlAvailable && defaultStore.state.showGlobalTimeline);
|
||||
const keymap = {
|
||||
|
|
@ -141,6 +142,13 @@ function focus(): void {
|
|||
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 tmp = [
|
||||
{icon: 'ti ti-dots',
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MarqueeText :duration="40">
|
||||
<MkA v-for="instance in instances" :key="instance.id" :class="$style.federationInstance" :to="`/instance-info/${instance.host}`" behavior="window">
|
||||
<!--<MkInstanceCardMini :instance="instance"/>-->
|
||||
<img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/>
|
||||
<img v-if="instance.iconUrl" class="icon" :src="getInstanceIcon(instance)" alt=""/>
|
||||
<span class="name _monospace">{{ instance.host }}</span>
|
||||
</MkA>
|
||||
</MarqueeText>
|
||||
|
|
@ -46,10 +46,15 @@ import { instance } from '@/instance.js';
|
|||
import number from '@/filters/number.js';
|
||||
import MkNumber from '@/components/MkNumber.vue';
|
||||
import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
|
||||
import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
|
||||
|
||||
let meta = $ref<Misskey.entities.Instance>();
|
||||
let instances = $ref<any[]>();
|
||||
|
||||
function getInstanceIcon(instance): string {
|
||||
return getProxiedImageUrl(instance.iconUrl, 'preview');
|
||||
}
|
||||
|
||||
os.api('meta', { detail: true }).then(_meta => {
|
||||
meta = _meta;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue