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:
mattyatea 2023-11-04 01:24:44 +09:00
commit cb1005811d
106 changed files with 2036 additions and 963 deletions

View file

@ -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 }}

View file

@ -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>

View file

@ -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>

View file

@ -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) {

View file

@ -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">

View file

@ -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]));
});

View file

@ -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,
});
});
}

View file

@ -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;
});
}

View file

@ -51,7 +51,8 @@ function submit() {
os.alert({
type: 'error',
text: i18n.ts.somethingHappened,
title: i18n.ts.somethingHappened,
text: i18n.ts.signupPendingError,
});
});
}

View file

@ -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>

View file

@ -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',

View file

@ -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;
});