perf(embed): improve embed performance (#14613)
* wip * wip * wip * refactor * refactor --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
parent
2aebdb8cc5
commit
3f0aaaa41e
|
@ -785,6 +785,72 @@ export class ClientServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region embed pages
|
//#region embed pages
|
||||||
|
fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => {
|
||||||
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const user = await this.usersRepository.findOneBy({
|
||||||
|
id: request.params.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user == null) return;
|
||||||
|
if (user.host != null) return;
|
||||||
|
|
||||||
|
const _user = await this.userEntityService.pack(user);
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
user: _user,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
|
||||||
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const note = await this.notesRepository.findOneBy({
|
||||||
|
id: request.params.note,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note == null) return;
|
||||||
|
if (note.visibility !== 'public') return;
|
||||||
|
if (note.userHost != null) return;
|
||||||
|
|
||||||
|
const _note = await this.noteEntityService.pack(note, null, { detail: true });
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
note: _note,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
|
||||||
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
const clip = await this.clipsRepository.findOneBy({
|
||||||
|
id: request.params.clip,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clip == null) return;
|
||||||
|
|
||||||
|
const _clip = await this.clipEntityService.pack(clip);
|
||||||
|
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('base-embed', {
|
||||||
|
title: this.meta.name ?? 'Misskey',
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
embedCtx: htmlSafeJsonStringify({
|
||||||
|
clip: _clip,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
fastify.get('/embed/*', async (request, reply) => {
|
fastify.get('/embed/*', async (request, reply) => {
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ html(class='embed')
|
||||||
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
||||||
!= metaJson
|
!= metaJson
|
||||||
|
|
||||||
|
script(type='application/json' id='misskey_embedCtx' data-generated-at=now)
|
||||||
|
!= embedCtx
|
||||||
|
|
||||||
script
|
script
|
||||||
include ../boot.embed.js
|
include ../boot.embed.js
|
||||||
|
|
||||||
|
|
|
@ -20,16 +20,19 @@ import { serverMetadata } from '@/server-metadata.js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { parseEmbedParams } from '@@/js/embed-page.js';
|
import { parseEmbedParams } from '@@/js/embed-page.js';
|
||||||
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
||||||
|
import { serverContext } from '@/server-context.js';
|
||||||
|
|
||||||
import type { Theme } from '@/theme.js';
|
import type { Theme } from '@/theme.js';
|
||||||
|
|
||||||
console.log('Misskey Embed');
|
console.log('Misskey Embed');
|
||||||
|
|
||||||
|
//#region Embedパラメータの取得・パース
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const embedParams = parseEmbedParams(params);
|
const embedParams = parseEmbedParams(params);
|
||||||
|
|
||||||
if (_DEV_) console.log(embedParams);
|
if (_DEV_) console.log(embedParams);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region テーマ
|
||||||
function parseThemeOrNull(theme: string | null): Theme | null {
|
function parseThemeOrNull(theme: string | null): Theme | null {
|
||||||
if (theme == null) return null;
|
if (theme == null) return null;
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +68,7 @@ if (embedParams.colorMode === 'dark') {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// サイズの制限
|
// サイズの制限
|
||||||
document.documentElement.style.maxWidth = '500px';
|
document.documentElement.style.maxWidth = '500px';
|
||||||
|
@ -89,6 +93,10 @@ const app = createApp(
|
||||||
|
|
||||||
app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
|
app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
|
||||||
|
|
||||||
|
app.provide(DI.serverMetadata, serverMetadata);
|
||||||
|
|
||||||
|
app.provide(DI.serverContext, serverContext);
|
||||||
|
|
||||||
app.provide(DI.embedParams, embedParams);
|
app.provide(DI.embedParams, embedParams);
|
||||||
|
|
||||||
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
||||||
|
|
|
@ -142,8 +142,8 @@ import EmAcct from '@/components/EmAcct.vue';
|
||||||
import { userPage } from '@/utils.js';
|
import { userPage } from '@/utils.js';
|
||||||
import { notePage } from '@/utils.js';
|
import { notePage } from '@/utils.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
import { shouldCollapsed } from '@@/js/collapsed.js';
|
import { shouldCollapsed } from '@@/js/collapsed.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import EmMfm from '@/components/EmMfm.js';
|
import EmMfm from '@/components/EmMfm.js';
|
||||||
|
|
||||||
|
@ -151,6 +151,8 @@ const props = defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
const inChannel = inject('inChannel', null);
|
const inChannel = inject('inChannel', null);
|
||||||
|
|
||||||
const note = ref(props.note);
|
const note = ref(props.note);
|
||||||
|
|
|
@ -20,12 +20,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from 'vue';
|
import { useTemplateRef } from 'vue';
|
||||||
import EmNote from '@/components/EmNote.vue';
|
import EmNote from '@/components/EmNote.vue';
|
||||||
import EmPagination, { Paging } from '@/components/EmPagination.vue';
|
import EmPagination, { Paging } from '@/components/EmPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: Paging;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
disableAutoLoad?: boolean;
|
disableAutoLoad?: boolean;
|
||||||
|
@ -34,7 +34,7 @@ const props = withDefaults(defineProps<{
|
||||||
ad: true,
|
ad: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagingComponent = shallowRef<InstanceType<typeof EmPagination>>();
|
const pagingComponent = useTemplateRef('pagingComponent');
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
pagingComponent,
|
pagingComponent,
|
||||||
|
|
|
@ -7,9 +7,11 @@ import type { InjectionKey } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { MediaProxy } from '@@/js/media-proxy.js';
|
import { MediaProxy } from '@@/js/media-proxy.js';
|
||||||
import type { ParsedEmbedParams } from '@@/js/embed-page.js';
|
import type { ParsedEmbedParams } from '@@/js/embed-page.js';
|
||||||
|
import type { ServerContext } from '@/server-context.js';
|
||||||
|
|
||||||
export const DI = {
|
export const DI = {
|
||||||
serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>,
|
serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>,
|
||||||
embedParams: Symbol() as InjectionKey<ParsedEmbedParams>,
|
embedParams: Symbol() as InjectionKey<ParsedEmbedParams>,
|
||||||
|
serverContext: Symbol() as InjectionKey<ServerContext>,
|
||||||
mediaProxy: Symbol() as InjectionKey<MediaProxy>,
|
mediaProxy: Symbol() as InjectionKey<MediaProxy>,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<EmLoading v-if="loading"/>
|
<EmTimelineContainer v-if="clip" :showHeader="embedParams.header">
|
||||||
<EmTimelineContainer v-else-if="clip" :showHeader="embedParams.header">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div :class="$style.clipHeader">
|
<div :class="$style.clipHeader">
|
||||||
<div :class="$style.headerClipIconRoot">
|
<div :class="$style.headerClipIconRoot">
|
||||||
|
@ -39,20 +38,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef, inject } from 'vue';
|
import { ref, computed, inject, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { scrollToTop } from '@@/js/scroll.js';
|
import { scrollToTop } from '@@/js/scroll.js';
|
||||||
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
|
import { isLink } from '@@/js/is-link.js';
|
||||||
|
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
import { assertServerContext } from '@/server-context.js';
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
|
||||||
import { isLink } from '@@/js/is-link.js';
|
|
||||||
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -61,16 +59,30 @@ const props = defineProps<{
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const clip = ref<Misskey.entities.Clip | null>(null);
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
|
const serverContext = inject(DI.serverContext)!;
|
||||||
|
|
||||||
|
const clip = ref<Misskey.entities.Clip | null>();
|
||||||
|
|
||||||
|
if (assertServerContext(serverContext, 'clip')) {
|
||||||
|
clip.value = serverContext.clip;
|
||||||
|
} else {
|
||||||
|
clip.value = await misskeyApi('clips/show', {
|
||||||
|
clipId: props.clipId,
|
||||||
|
}).catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
endpoint: 'clips/notes',
|
endpoint: 'clips/notes',
|
||||||
params: {
|
params: {
|
||||||
clipId: props.clipId,
|
clipId: props.clipId,
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
function top(ev: MouseEvent) {
|
function top(ev: MouseEvent) {
|
||||||
const target = ev.target as HTMLElement | null;
|
const target = ev.target as HTMLElement | null;
|
||||||
|
@ -80,16 +92,6 @@ function top(ev: MouseEvent) {
|
||||||
scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
|
scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
misskeyApi('clips/show', {
|
|
||||||
clipId: props.clipId,
|
|
||||||
}).then(res => {
|
|
||||||
clip.value = res;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,40 +5,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.noteEmbedRoot">
|
<div :class="$style.noteEmbedRoot">
|
||||||
<EmLoading v-if="loading"/>
|
<EmNoteDetailed v-if="note" :note="note"/>
|
||||||
<EmNoteDetailed v-else-if="note" :note="note"/>
|
|
||||||
<XNotFound v-else/>
|
<XNotFound v-else/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { inject, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
|
import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
|
import { assertServerContext } from '@/server-context';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const note = ref<Misskey.entities.Note | null>(null);
|
const serverContext = inject(DI.serverContext)!;
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
// TODO: クライアント側でAPIを叩くのは二度手間なので予めHTMLに埋め込んでおく
|
const note = ref<Misskey.entities.Note | null>(null);
|
||||||
misskeyApi('notes/show', {
|
|
||||||
noteId: props.noteId,
|
if (assertServerContext(serverContext, 'note')) {
|
||||||
}).then(res => {
|
note.value = serverContext.note;
|
||||||
// リモートのノートは埋め込ませない
|
} else {
|
||||||
if (res.url == null && res.uri == null) {
|
note.value = await misskeyApi('notes/show', {
|
||||||
note.value = res;
|
noteId: props.noteId,
|
||||||
}
|
}).catch(() => {
|
||||||
loading.value = false;
|
return null;
|
||||||
}).catch(err => {
|
});
|
||||||
console.error(err);
|
}
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -38,14 +38,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, shallowRef, inject } from 'vue';
|
import { computed, inject, useTemplateRef } from 'vue';
|
||||||
import { scrollToTop } from '@@/js/scroll.js';
|
import { scrollToTop } from '@@/js/scroll.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
@ -55,6 +54,8 @@ const props = defineProps<{
|
||||||
tag: string;
|
tag: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
|
@ -64,7 +65,7 @@ const pagination = computed(() => ({
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
function top(ev: MouseEvent) {
|
function top(ev: MouseEvent) {
|
||||||
const target = ev.target as HTMLElement | null;
|
const target = ev.target as HTMLElement | null;
|
||||||
|
|
|
@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<EmLoading v-if="loading"/>
|
<EmTimelineContainer v-if="user && !prohibited" :showHeader="embedParams.header">
|
||||||
<EmTimelineContainer v-else-if="user" :showHeader="embedParams.header">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div :class="$style.userHeader">
|
<div :class="$style.userHeader">
|
||||||
<a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink">
|
<a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink">
|
||||||
|
@ -46,21 +45,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef, inject } from 'vue';
|
import { ref, computed, inject, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { url, instanceName } from '@@/js/config.js';
|
||||||
|
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
||||||
import type { Paging } from '@/components/EmPagination.vue';
|
import type { Paging } from '@/components/EmPagination.vue';
|
||||||
import EmNotes from '@/components/EmNotes.vue';
|
import EmNotes from '@/components/EmNotes.vue';
|
||||||
import EmAvatar from '@/components/EmAvatar.vue';
|
import EmAvatar from '@/components/EmAvatar.vue';
|
||||||
import EmLoading from '@/components/EmLoading.vue';
|
|
||||||
import EmUserName from '@/components/EmUserName.vue';
|
import EmUserName from '@/components/EmUserName.vue';
|
||||||
import I18n from '@/components/I18n.vue';
|
import I18n from '@/components/I18n.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { serverMetadata } from '@/server-metadata.js';
|
import { assertServerContext } from '@/server-context.js';
|
||||||
import { url, instanceName } from '@@/js/config.js';
|
|
||||||
import { defaultEmbedParams } from '@@/js/embed-page.js';
|
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -69,26 +67,37 @@ const props = defineProps<{
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
||||||
const user = ref<Misskey.entities.UserLite | null>(null);
|
const serverMetadata = inject(DI.serverMetadata)!;
|
||||||
|
|
||||||
|
const serverContext = inject(DI.serverContext)!;
|
||||||
|
|
||||||
|
const user = ref<Misskey.entities.UserLite | null>();
|
||||||
|
|
||||||
|
const prohibited = ref(false);
|
||||||
|
|
||||||
|
if (assertServerContext(serverContext, 'user')) {
|
||||||
|
user.value = serverContext.user;
|
||||||
|
} else {
|
||||||
|
user.value = await misskeyApi('users/show', {
|
||||||
|
userId: props.userId,
|
||||||
|
}).catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.value?.host != null) {
|
||||||
|
// リモートサーバーのユーザーは弾く
|
||||||
|
prohibited.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
const pagination = computed(() => ({
|
const pagination = computed(() => ({
|
||||||
endpoint: 'users/notes',
|
endpoint: 'users/notes',
|
||||||
params: {
|
params: {
|
||||||
userId: user.value?.id,
|
userId: user.value?.id,
|
||||||
},
|
},
|
||||||
} as Paging));
|
} as Paging));
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
|
const notesEl = useTemplateRef('notesEl');
|
||||||
|
|
||||||
misskeyApi('users/show', {
|
|
||||||
userId: props.userId,
|
|
||||||
}).then(res => {
|
|
||||||
user.value = res;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
|
const providedContextEl = document.getElementById('misskey_embedCtx');
|
||||||
|
|
||||||
|
export type ServerContext = {
|
||||||
|
clip?: Misskey.entities.Clip;
|
||||||
|
note?: Misskey.entities.Note;
|
||||||
|
user?: Misskey.entities.UserLite;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
// NOTE: devモードのときしか embedCtx が null になることは無い
|
||||||
|
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
|
||||||
|
|
||||||
|
export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
|
||||||
|
if (ctx == null) return false;
|
||||||
|
return entity in ctx;
|
||||||
|
}
|
|
@ -18,11 +18,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div
|
<div
|
||||||
:class="$style.routerViewContainer"
|
:class="$style.routerViewContainer"
|
||||||
>
|
>
|
||||||
<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
|
<Suspense :timeout="0">
|
||||||
<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
|
<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
|
||||||
<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
|
<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
|
||||||
<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
|
<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
|
||||||
<XNotFound v-else/>
|
<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
|
||||||
|
<XNotFound v-else/>
|
||||||
|
<template #fallback>
|
||||||
|
<EmLoading/>
|
||||||
|
</template>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,6 +42,7 @@ import EmUserTimelinePage from '@/pages/user-timeline.vue';
|
||||||
import EmClipPage from '@/pages/clip.vue';
|
import EmClipPage from '@/pages/clip.vue';
|
||||||
import EmTagPage from '@/pages/tag.vue';
|
import EmTagPage from '@/pages/tag.vue';
|
||||||
import XNotFound from '@/pages/not-found.vue';
|
import XNotFound from '@/pages/not-found.vue';
|
||||||
|
import EmLoading from '@/components/EmLoading.vue';
|
||||||
|
|
||||||
const page = location.pathname.split('/')[2];
|
const page = location.pathname.split('/')[2];
|
||||||
const contentId = location.pathname.split('/')[3];
|
const contentId = location.pathname.split('/')[3];
|
||||||
|
|
Loading…
Reference in New Issue