Compare commits

...

4 Commits

Author SHA1 Message Date
syuilo 98462ccbaf Update pnpm-lock.yaml 2023-09-06 18:35:40 +09:00
syuilo ca00a08e6e feat: introduce aidx and make it default
Co-Authored-By: MeiMei <30769358+mei23@users.noreply.github.com>
2023-09-06 18:33:51 +09:00
syuilo 54c6fb762a pnpm@8.7.4 2023-09-06 18:21:51 +09:00
かっこかり 22d966e92d
enhance(frontend): データセーバーモードで隠れる画像を増やす等 (#11779)
* enhance datasaver mode

* サムネイルがないとき変な角丸にならんようにする

* Avoid using wildcard selector

* Avoid wildcard

* Update MkMediaImage.vue

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-09-06 17:36:14 +09:00
20 changed files with 159 additions and 57 deletions

View File

@ -114,6 +114,7 @@ redis:
# Available methods: # Available methods:
# aid ... Short, Millisecond accuracy # aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy # ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility # objectid ... This is left for backward compatibility
@ -121,7 +122,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT! # ID SETTINGS AFTER THAT!
id: 'aid' id: 'aidx'
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────

View File

@ -125,6 +125,7 @@ redis:
# Available methods: # Available methods:
# aid ... Short, Millisecond accuracy # aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy # ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility # objectid ... This is left for backward compatibility
@ -132,7 +133,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT! # ID SETTINGS AFTER THAT!
id: 'aid' id: 'aidx'
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────

View File

@ -114,6 +114,7 @@ redis:
# Available methods: # Available methods:
# aid ... Short, Millisecond accuracy # aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy # ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility # objectid ... This is left for backward compatibility
@ -121,7 +122,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT! # ID SETTINGS AFTER THAT!
id: 'aid' id: 'aidx'
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────

View File

@ -12,4 +12,4 @@ db:
redis: redis:
host: 127.0.0.1 host: 127.0.0.1
port: 56312 port: 56312
id: aid id: aidx

View File

@ -35,6 +35,7 @@
- Enhance: ノート検索にローカルのみ検索可能なオプションの追加 - Enhance: ノート検索にローカルのみ検索可能なオプションの追加
- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように - Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように
- Enhance: Renote自体を通報できるように - Enhance: Renote自体を通報できるように
- Enhance: データセーバーモードの強化
- Enhance: Renoteを管理者権限で削除可能に - Enhance: Renoteを管理者権限で削除可能に
- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました - `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました
- Playの操作を行うAPI TokenをAPIコンソールから発行できるように - Playの操作を行うAPI TokenをAPIコンソールから発行できるように

View File

@ -135,6 +135,7 @@ redis:
# Available methods: # Available methods:
# aid ... Short, Millisecond accuracy # aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy # ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility # objectid ... This is left for backward compatibility
@ -142,7 +143,7 @@ redis:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT! # ID SETTINGS AFTER THAT!
id: "aid" id: "aidx"
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────

View File

@ -6,7 +6,7 @@
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@8.7.1", "packageManager": "pnpm@8.7.4",
"workspaces": [ "workspaces": [
"packages/frontend", "packages/frontend",
"packages/backend", "packages/backend",

View File

@ -120,6 +120,7 @@
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nanoid": "4.0.2",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.9.4", "nodemailer": "6.9.4",

View File

@ -8,6 +8,7 @@ import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { genAid, parseAid } from '@/misc/id/aid.js'; import { genAid, parseAid } from '@/misc/id/aid.js';
import { genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, parseMeid } from '@/misc/id/meid.js'; import { genMeid, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, parseMeidg } from '@/misc/id/meidg.js'; import { genMeidg, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, parseObjectId } from '@/misc/id/object-id.js'; import { genObjectId, parseObjectId } from '@/misc/id/object-id.js';
@ -31,6 +32,7 @@ export class IdService {
switch (this.method) { switch (this.method) {
case 'aid': return genAid(date); case 'aid': return genAid(date);
case 'aidx': return genAidx(date);
case 'meid': return genMeid(date); case 'meid': return genMeid(date);
case 'meidg': return genMeidg(date); case 'meidg': return genMeidg(date);
case 'ulid': return ulid(date.getTime()); case 'ulid': return ulid(date.getTime());
@ -43,6 +45,7 @@ export class IdService {
public parse(id: string): { date: Date; } { public parse(id: string): { date: Date; } {
switch (this.method) { switch (this.method) {
case 'aid': return parseAid(id); case 'aid': return parseAid(id);
case 'aidx': return parseAidx(id);
case 'objectid': return parseObjectId(id); case 'objectid': return parseObjectId(id);
case 'meid': return parseMeid(id); case 'meid': return parseMeid(id);
case 'meidg': return parseMeidg(id); case 'meidg': return parseMeidg(id);

View File

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
// AIDX
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ4の[個体ID] + 長さ4の[カウンタ]
// (c) mei23
// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24
import { customAlphabet } from 'nanoid';
export const aidxRegExp = /^[0-9a-z]{16}$/;
const TIME2000 = 946684800000;
const TIME_LENGTH = 8;
const NODE_LENGTH = 4;
const NOISE_LENGTH = 4;
const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)();
let counter = 0;
function getTime(time: number): string {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36).padStart(TIME_LENGTH, '0').slice(-TIME_LENGTH);
}
function getNoise(): string {
return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH);
}
export function genAidx(date: Date): string {
const t = date.getTime();
if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date');
counter++;
return getTime(t) + nodeId + getNoise();
}
export function parseAidx(id: string): { date: Date; } {
const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000;
return { date: new Date(time) };
}

View File

@ -12,7 +12,7 @@ import { GlobalModule } from '@/GlobalModule.js';
import { AnnouncementService } from '@/core/AnnouncementService.js'; import { AnnouncementService } from '@/core/AnnouncementService.js';
import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/index.js'; import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/index.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { genAid } from '@/misc/id/aid.js'; import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -33,7 +33,7 @@ describe('AnnouncementService', () => {
function createUser(data: Partial<MiUser> = {}) { function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16); const un = secureRndstr(16);
return usersRepository.insert({ return usersRepository.insert({
id: genAid(new Date()), id: genAidx(new Date()),
createdAt: new Date(), createdAt: new Date(),
username: un, username: un,
usernameLower: un, usernameLower: un,
@ -44,7 +44,7 @@ describe('AnnouncementService', () => {
function createAnnouncement(data: Partial<MiAnnouncement> = {}) { function createAnnouncement(data: Partial<MiAnnouncement> = {}) {
return announcementsRepository.insert({ return announcementsRepository.insert({
id: genAid(new Date()), id: genAidx(new Date()),
createdAt: new Date(), createdAt: new Date(),
updatedAt: null, updatedAt: null,
title: 'Title', title: 'Title',

View File

@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js';
import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/index.js'; import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/index.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { genAid } from '@/misc/id/aid.js'; import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -37,7 +37,7 @@ describe('RoleService', () => {
function createUser(data: Partial<MiUser> = {}) { function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16); const un = secureRndstr(16);
return usersRepository.insert({ return usersRepository.insert({
id: genAid(new Date()), id: genAidx(new Date()),
createdAt: new Date(), createdAt: new Date(),
username: un, username: un,
usernameLower: un, usernameLower: un,
@ -48,7 +48,7 @@ describe('RoleService', () => {
function createRole(data: Partial<MiRole> = {}) { function createRole(data: Partial<MiRole> = {}) {
return rolesRepository.insert({ return rolesRepository.insert({
id: genAid(new Date()), id: genAidx(new Date()),
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
lastUsedAt: new Date(), lastUsedAt: new Date(),

View File

@ -6,6 +6,7 @@
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { describe, test, expect } from '@jest/globals'; import { describe, test, expect } from '@jest/globals';
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js'; import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js'; import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js'; import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js'; import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
@ -19,6 +20,13 @@ describe('misc:id', () => {
expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); expect(parseAid(gotAid).date.getTime()).toBe(date.getTime());
}); });
test('aidx', () => {
const date = new Date();
const gotAidx = genAidx(date);
expect(gotAidx).toMatch(aidxRegExp);
expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime());
});
test('meid', () => { test('meid', () => {
const date = new Date(); const date = new Date();
const gotMeid = genMeid(date); const gotMeid = genMeid(date);

View File

@ -4,34 +4,41 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> <div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click.stop="onclick">
<a <component
:class="$style.imageContainer" :is="disableImageLink ? 'div' : 'a'"
:href="image.url" v-bind="disableImageLink ? {
:title="image.name" title: image.name,
class: $style.imageContainer,
} : {
title: image.name,
class: $style.imageContainer,
href: image.url,
style: 'cursor: zoom-in;'
}"
> >
<ImgWithBlurhash <ImgWithBlurhash
:hash="image.blurhash" :hash="image.blurhash"
:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url" :src="(defaultStore.state.enableDataSaverMode && hide) ? null : url"
:forceBlurhash="hide" :forceBlurhash="hide"
:cover="hide" :cover="hide || cover"
:alt="image.comment || image.name" :alt="image.comment || image.name"
:title="image.comment || image.name" :title="image.comment || image.name"
:width="image.properties.width" :width="image.properties.width"
:height="image.properties.height" :height="image.properties.height"
:style="hide ? 'filter: brightness(0.5);' : null" :style="hide ? 'filter: brightness(0.5);' : null"
/> />
</a> </component>
<template v-if="hide"> <template v-if="hide">
<div :class="$style.hiddenText"> <div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper"> <div :class="$style.hiddenTextWrapper">
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span> <span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div> </div>
</div> </div>
</template> </template>
<template v-else> <template v-else-if="controls">
<div :class="$style.indicators"> <div :class="$style.indicators">
<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
<div v-if="image.comment" :class="$style.indicator">ALT</div> <div v-if="image.comment" :class="$style.indicator">ALT</div>
@ -54,10 +61,17 @@ import { i18n } from '@/i18n';
import * as os from '@/os'; import * as os from '@/os';
import { iAmModerator } from '@/account'; import { iAmModerator } from '@/account';
const props = defineProps<{ const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile; image: Misskey.entities.DriveFile;
raw?: boolean; raw?: boolean;
}>(); cover?: boolean;
disableImageLink?: boolean;
controls?: boolean;
}>(), {
cover: false,
disableImageLink: false,
controls: true,
});
let hide = $ref(true); let hide = $ref(true);
let darkMode: boolean = $ref(defaultStore.state.darkMode); let darkMode: boolean = $ref(defaultStore.state.darkMode);
@ -70,6 +84,9 @@ const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
); );
function onclick() { function onclick() {
if (!props.controls) {
return;
}
if (hide) { if (hide) {
hide = false; hide = false;
} }
@ -167,7 +184,6 @@ function showMenu(ev: MouseEvent) {
.imageContainer { .imageContainer {
display: block; display: block;
cursor: zoom-in;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -5,7 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> <MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
<div v-if="page.eyeCatchingImage" class="thumbnail" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div> <div v-if="page.eyeCatchingImage" class="thumbnail">
<MediaImage
:image="page.eyeCatchingImage"
:disableImageLink="true"
:controls="false"
:cover="true"
:class="$style.eyeCatchingImageRoot"
/>
</div>
<article> <article>
<header> <header>
<h1 :title="page.title">{{ page.title }}</h1> <h1 :title="page.title">{{ page.title }}</h1>
@ -23,12 +31,22 @@ SPDX-License-Identifier: AGPL-3.0-only
import { } from 'vue'; import { } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { userName } from '@/filters/user'; import { userName } from '@/filters/user';
import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{ const props = defineProps<{
page: Misskey.entities.Page; page: Misskey.entities.Page;
}>(); }>();
</script> </script>
<style module>
.eyeCatchingImageRoot {
width: 100%;
height: 200px;
border-radius: var(--radius) var(--radius) 0 0;
overflow: hidden;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.vhpxefrj { .vhpxefrj {
display: block; display: block;
@ -39,32 +57,15 @@ const props = defineProps<{
} }
> .thumbnail { > .thumbnail {
width: 100%;
height: 200px;
background-position: center;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
> button {
font-size: 3.5em;
opacity: 0.7;
&:hover {
font-size: 4em;
opacity: 0.9;
}
}
& + article { & + article {
left: 100px; border-radius: 0 0 var(--radius) var(--radius);
width: calc(100% - 100px);
} }
} }
> article { > article {
background-color: var(--panel);
padding: 16px; padding: 16px;
border-radius: var(--radius);
> header { > header {
margin-bottom: 8px; margin-bottom: 8px;

View File

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<div v-else> <div v-else>
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`"> <div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.enableDataSaverMode ? '' : `background-image: url('${thumbnail}')`">
</div> </div>
<article :class="$style.body"> <article :class="$style.body">
<header :class="$style.header"> <header :class="$style.header">
@ -260,6 +260,7 @@ onUnmounted(() => {
height: 100%; height: 100%;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-color: var(--bg);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -5,20 +5,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div> <div>
<ImgWithBlurhash v-if="image" style="max-width: 100%;" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :width="image.properties.width" :height="image.properties.height" :cover="false"/> <MediaImage
v-if="image"
:image="image"
:disableImageLink="true"
/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { ImageBlock } from './block.type'; import { ImageBlock } from './block.type';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{ const props = defineProps<{
block: ImageBlock, block: ImageBlock,
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();
const image = props.page.attachedFiles.find(x => x.id === props.block.fileId); const image = ref<Misskey.entities.DriveFile>(props.page.attachedFiles.find(x => x.id === props.block.fileId));
</script> </script>

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }"> <div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }" class="_gaps_s">
<XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/> <XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/>
</div> </div>
</template> </template>

View File

@ -16,7 +16,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
--> -->
<div class="banner"> <div class="banner">
<img v-if="page.eyeCatchingImageId" :src="page.eyeCatchingImage.url"/> <MkMediaImage
v-if="page.eyeCatchingImageId"
:image="page.eyeCatchingImage"
:cover="true"
:disableImageLink="true"
class="thumbnail"
/>
</div> </div>
<div class="content"> <div class="content">
<XPage :page="page"/> <XPage :page="page"/>
@ -74,6 +80,7 @@ import XPage from '@/components/page/page.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { url } from '@/config'; import { url } from '@/config';
import MkMediaImage from '@/components/MkMediaImage.vue';
import MkFollowButton from '@/components/MkFollowButton.vue'; import MkFollowButton from '@/components/MkFollowButton.vue';
import MkContainer from '@/components/MkContainer.vue'; import MkContainer from '@/components/MkContainer.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination from '@/components/MkPagination.vue';
@ -204,11 +211,14 @@ definePageMetadata(computed(() => page ? {
} }
> .banner { > .banner {
> img { > .thumbnail {
// TODO: // TODO:
display: block; display: block;
width: 100%; width: 100%;
height: 150px; height: auto;
aspect-ratio: 3/1;
border-radius: var(--radius);
overflow: hidden;
object-fit: cover; object-fit: cover;
} }
} }

View File

@ -260,6 +260,9 @@ importers:
ms: ms:
specifier: 3.0.0-canary.1 specifier: 3.0.0-canary.1
version: 3.0.0-canary.1 version: 3.0.0-canary.1
nanoid:
specifier: 4.0.2
version: 4.0.2
nested-property: nested-property:
specifier: 4.0.0 specifier: 4.0.0
version: 4.0.0 version: 4.0.0
@ -6998,7 +7001,7 @@ packages:
ts-dedent: 2.2.0 ts-dedent: 2.2.0
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.3.4 vue: 3.3.4
vue-component-type-helpers: 1.8.8 vue-component-type-helpers: 1.8.10
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- supports-color - supports-color
@ -16069,6 +16072,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/nanoid@4.0.2:
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
engines: {node: ^14 || ^16 || >=18}
hasBin: true
dev: false
/nanomatch@1.2.13: /nanomatch@1.2.13:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -21040,8 +21049,8 @@ packages:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/vue-component-type-helpers@1.8.8: /vue-component-type-helpers@1.8.10:
resolution: {integrity: sha512-Ohv9HQY92nSbpReC6WhY0X4YkOszHzwUHaaN/lev5tHQLM1AEw+LrLeB2bIGIyKGDU7ZVrncXcv/oBny4rjbYg==} resolution: {integrity: sha512-FJtmfw2Gn6eQ8kAVNEhw9nYIzWmVQJjdyQRtJXZ7tgXh/FoZhQnZ2KyxR+NuF9U4iZLBvSspeetIpnP9yxxyMw==}
dev: true dev: true
/vue-docgen-api@4.64.1(vue@3.3.4): /vue-docgen-api@4.64.1(vue@3.3.4):