feat: ノート・ユーザTL埋め込み
This commit is contained in:
parent
f80c5d26b5
commit
e1a541d60b
|
@ -764,9 +764,9 @@ export class ClientServerService {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region embed pages
|
//#region embed pages
|
||||||
fastify.get('/embed/:path(.*)', async (request, reply) => {
|
fastify.get('/embed/*', async (request, reply) => {
|
||||||
reply.removeHeader('X-Frame-Options');
|
reply.removeHeader('X-Frame-Options');
|
||||||
return await renderBase(reply, { noindex: true });
|
return await renderBase(reply, { noindex: true, embed: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get('/_info_card_', async (request, reply) => {
|
fastify.get('/_info_card_', async (request, reply) => {
|
||||||
|
|
|
@ -9,6 +9,12 @@ html {
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.embed {
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
#splash {
|
#splash {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
@ -22,6 +28,13 @@ html {
|
||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.embed #splash {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 300px;
|
||||||
|
border-radius: var(--radius, 12px);
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
#splashIcon {
|
#splashIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -77,7 +77,7 @@ html
|
||||||
script
|
script
|
||||||
include ../boot.js
|
include ../boot.js
|
||||||
|
|
||||||
body
|
body(class=embed && 'embed')
|
||||||
noscript: p
|
noscript: p
|
||||||
| JavaScriptを有効にしてください
|
| JavaScriptを有効にしてください
|
||||||
br
|
br
|
||||||
|
|
|
@ -7,21 +7,33 @@
|
||||||
import 'vite/modulepreload-polyfill';
|
import 'vite/modulepreload-polyfill';
|
||||||
|
|
||||||
import '@/style.scss';
|
import '@/style.scss';
|
||||||
|
import type { CommonBootOptions } from '@/boot/common.js';
|
||||||
import { mainBoot } from '@/boot/main-boot.js';
|
import { mainBoot } from '@/boot/main-boot.js';
|
||||||
import { subBoot } from '@/boot/sub-boot.js';
|
import { subBoot } from '@/boot/sub-boot.js';
|
||||||
import { isEmbedPage } from '@/scripts/embed-page.js';
|
import { isEmbedPage } from '@/scripts/embed-page.js';
|
||||||
|
import { setIframeId, postMessageToParentWindow } from '@/scripts/post-message.js';
|
||||||
|
|
||||||
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete', '/embed'];
|
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete'];
|
||||||
|
|
||||||
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
|
if (isEmbedPage()) {
|
||||||
if (isEmbedPage()) {
|
const bootOptions: Partial<CommonBootOptions> = {};
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
const color = params.get('color');
|
const params = new URLSearchParams(location.search);
|
||||||
if (color && ['light', 'dark'].includes(color)) {
|
const color = params.get('color');
|
||||||
subBoot({ forceColorMode: color as 'light' | 'dark' });
|
if (color && ['light', 'dark'].includes(color)) {
|
||||||
}
|
bootOptions.forceColorMode = color as 'light' | 'dark';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
if (event.data?.type === 'misskey:embedParent:registerIframeId' && event.data.payload?.iframeId != null) {
|
||||||
|
setIframeId(event.data.payload.iframeId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subBoot(bootOptions, true).then(() => {
|
||||||
|
postMessageToParentWindow('misskey:embed:ready');
|
||||||
|
});
|
||||||
|
} else if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
|
||||||
subBoot();
|
subBoot();
|
||||||
} else {
|
} else {
|
||||||
mainBoot();
|
mainBoot();
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||||
import { setupRouter } from '@/router/definition.js';
|
import { setupRouter } from '@/router/definition.js';
|
||||||
|
|
||||||
export type CommonBootOptions = {
|
export type CommonBootOptions = {
|
||||||
forceColorMode?: 'dark' | 'light' | 'auto';
|
forceColorMode: 'dark' | 'light' | 'auto';
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultCommonBootOptions: CommonBootOptions = {
|
const defaultCommonBootOptions: CommonBootOptions = {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { createApp, defineAsyncComponent } from 'vue';
|
||||||
import { common } from './common.js';
|
import { common } from './common.js';
|
||||||
import type { CommonBootOptions } from './common.js';
|
import type { CommonBootOptions } from './common.js';
|
||||||
|
|
||||||
export async function subBoot(options?: CommonBootOptions) {
|
export async function subBoot(options?: Partial<CommonBootOptions>, isEmbedPage?: boolean) {
|
||||||
const { isClientUpdated } = await common(() => createApp(
|
const { isClientUpdated } = await common(() => createApp(
|
||||||
defineAsyncComponent(() => import('@/ui/minimum.vue')),
|
defineAsyncComponent(() => isEmbedPage ? import('@/ui/embed.vue') : import('@/ui/minimum.vue')),
|
||||||
), options);
|
), options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<template>
|
|
||||||
<div></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
</style>
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.noteEmbedRoot">
|
||||||
|
<MkLoading v-if="loading"/>
|
||||||
|
<MkNote v-else-if="note" :note="note"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkNote from '@/components/MkNote.vue';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
noteId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const note = ref<Misskey.entities.Note | null>(null);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
misskeyApi('notes/show', {
|
||||||
|
noteId: props.noteId,
|
||||||
|
}).then(res => {
|
||||||
|
note.value = res;
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.noteEmbedRoot {
|
||||||
|
background-color: var(--panel);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.userTimelineRoot">
|
||||||
|
<MkLoading v-if="loading"/>
|
||||||
|
<template v-else-if="user">
|
||||||
|
<div v-if="normalizedShowHeader" :class="$style.userHeader">
|
||||||
|
<MkAvatar :user="user"/>{{ user.name }} のノート
|
||||||
|
</div>
|
||||||
|
<MkNotes :class="$style.userTimelineNotes" :pagination="pagination" :noGap="true"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
username: string;
|
||||||
|
showHeader?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const normalizedShowHeader = computed(() => props.showHeader !== 'false');
|
||||||
|
|
||||||
|
const user = ref<Misskey.entities.UserLite | null>(null);
|
||||||
|
const pagination = computed(() => ({
|
||||||
|
endpoint: 'users/notes',
|
||||||
|
params: {
|
||||||
|
userId: user.value?.id,
|
||||||
|
},
|
||||||
|
} as Paging));
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
misskeyApi('users/show', {
|
||||||
|
username: props.username,
|
||||||
|
}).then(res => {
|
||||||
|
user.value = res;
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.userTimelineRoot {
|
||||||
|
background-color: var(--panel);
|
||||||
|
height: 100%;
|
||||||
|
max-height: var(--embedMaxHeight, none);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userTimelineNotes {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -556,9 +556,14 @@ const routes: RouteDef[] = [{
|
||||||
component: page(() => import('@/pages/reversi/game.vue')),
|
component: page(() => import('@/pages/reversi/game.vue')),
|
||||||
loginRequired: false,
|
loginRequired: false,
|
||||||
}, {
|
}, {
|
||||||
path: '/embed',
|
path: '/embed/notes/:noteId',
|
||||||
component: page(() => import('@/pages/embed/index.vue')),
|
component: page(() => import('@/pages/embed/note.vue')),
|
||||||
// children: [],
|
}, {
|
||||||
|
path: '/embed/user-timeline/@:username',
|
||||||
|
component: page(() => import('@/pages/embed/user-timeline.vue')),
|
||||||
|
query: {
|
||||||
|
header: 'showHeader',
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
path: '/timeline',
|
path: '/timeline',
|
||||||
component: page(() => import('@/pages/timeline.vue')),
|
component: page(() => import('@/pages/timeline.vue')),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
export const postMessageEventTypes = [
|
export const postMessageEventTypes = [
|
||||||
'misskey:shareForm:shareCompleted',
|
'misskey:shareForm:shareCompleted',
|
||||||
|
'misskey:embed:ready',
|
||||||
'misskey:embed:changeHeight',
|
'misskey:embed:changeHeight',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
@ -12,16 +13,29 @@ export type PostMessageEventType = typeof postMessageEventTypes[number];
|
||||||
|
|
||||||
export type MiPostMessageEvent = {
|
export type MiPostMessageEvent = {
|
||||||
type: PostMessageEventType;
|
type: PostMessageEventType;
|
||||||
|
iframeId?: string;
|
||||||
payload?: any;
|
payload?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let defaultIframeId: string | null = null;
|
||||||
|
|
||||||
|
export function setIframeId(id: string): void {
|
||||||
|
if (_DEV_) console.log('setIframeId', id);
|
||||||
|
defaultIframeId = id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 親フレームにイベントを送信
|
* 親フレームにイベントを送信
|
||||||
*/
|
*/
|
||||||
export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void {
|
export function postMessageToParentWindow(type: PostMessageEventType, payload?: any, iframeId: string | null = null): void {
|
||||||
if (_DEV_) console.log('postMessageToParentWindow', type, payload);
|
let _iframeId = iframeId;
|
||||||
|
if (_iframeId == null) {
|
||||||
|
_iframeId = defaultIframeId;
|
||||||
|
}
|
||||||
|
if (_DEV_) console.log('postMessageToParentWindow', type, _iframeId, payload);
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
type,
|
type,
|
||||||
|
iframeId: _iframeId,
|
||||||
payload,
|
payload,
|
||||||
}, '*');
|
}, '*');
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,9 +93,16 @@ html {
|
||||||
|
|
||||||
&.embed {
|
&.embed {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.embed,
|
||||||
|
html.embed body,
|
||||||
|
html.embed #misskey_app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
html._themeChanging_ {
|
html._themeChanging_ {
|
||||||
&, * {
|
&, * {
|
||||||
transition: background 1s ease, border 1s ease !important;
|
transition: background 1s ease, border 1s ease !important;
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="rootEl"
|
||||||
|
:class="[
|
||||||
|
$style.rootForEmbedPage,
|
||||||
|
{
|
||||||
|
[$style.rounded]: embedRounded,
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:style="maxHeight > 0 ? { maxHeight: `${maxHeight}px`, '--embedMaxHeight': `${maxHeight}px` } : {}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="$style.routerViewContainer"
|
||||||
|
>
|
||||||
|
<RouterView/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<XCommon/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, provide, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
||||||
|
import XCommon from './_common_/common.vue';
|
||||||
|
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import { instanceName } from '@/config.js';
|
||||||
|
import { mainRouter } from '@/router/main.js';
|
||||||
|
import { postMessageToParentWindow } from '@/scripts/post-message';
|
||||||
|
|
||||||
|
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
|
||||||
|
|
||||||
|
const pageMetadata = ref<null | PageMetadata>(null);
|
||||||
|
|
||||||
|
provide('router', mainRouter);
|
||||||
|
provideMetadataReceiver((metadataGetter) => {
|
||||||
|
const info = metadataGetter();
|
||||||
|
pageMetadata.value = info;
|
||||||
|
if (pageMetadata.value) {
|
||||||
|
if (isRoot.value && pageMetadata.value.title === instanceName) {
|
||||||
|
document.title = pageMetadata.value.title;
|
||||||
|
} else {
|
||||||
|
document.title = `${pageMetadata.value.title} | ${instanceName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
provideReactiveMetadata(pageMetadata);
|
||||||
|
|
||||||
|
//#region Embed Style
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const embedRounded = ref(params.get('rounded') !== '0');
|
||||||
|
const maxHeight = ref(params.get('maxHeight') ? parseInt(params.get('maxHeight')!) : 0);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Embed Resizer
|
||||||
|
const rootEl = shallowRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
let resizeMessageThrottleTimer: number | null = null;
|
||||||
|
let resizeMessageThrottleFlag = false;
|
||||||
|
let previousHeight = 0;
|
||||||
|
const resizeObserver = new ResizeObserver(async () => {
|
||||||
|
const height = rootEl.value!.scrollHeight + 2; // border 上下1px
|
||||||
|
if (resizeMessageThrottleFlag && Math.abs(previousHeight - height) < 30) return;
|
||||||
|
if (resizeMessageThrottleTimer) window.clearTimeout(resizeMessageThrottleTimer);
|
||||||
|
|
||||||
|
postMessageToParentWindow('misskey:embed:changeHeight', {
|
||||||
|
height: (maxHeight.value > 0 && height > maxHeight.value) ? maxHeight.value : height,
|
||||||
|
});
|
||||||
|
previousHeight = height;
|
||||||
|
|
||||||
|
resizeMessageThrottleFlag = true;
|
||||||
|
|
||||||
|
resizeMessageThrottleTimer = window.setTimeout(() => {
|
||||||
|
resizeMessageThrottleFlag = false; // 収縮をやりすぎるとチカチカする
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
resizeObserver.observe(rootEl.value!);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
document.documentElement.style.maxWidth = '500px';
|
||||||
|
|
||||||
|
// サーバー起動の場合はもとから付与されているためdevのみ
|
||||||
|
if (_DEV_) document.documentElement.classList.add('embed');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.rootForEmbedPage {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid var(--divider);
|
||||||
|
background-color: var(--bg);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.rounded {
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.routerViewContainer {
|
||||||
|
container-type: inline-size;
|
||||||
|
max-height: var(--embedMaxHeight, none);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,15 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="$style.root">
|
||||||
ref="rootEl"
|
|
||||||
:class="isEmbed ? [
|
|
||||||
$style.rootForEmbedPage,
|
|
||||||
{
|
|
||||||
[$style.rounded]: embedRounded,
|
|
||||||
}
|
|
||||||
] : [$style.root]"
|
|
||||||
>
|
|
||||||
<div style="container-type: inline-size;">
|
<div style="container-type: inline-size;">
|
||||||
<RouterView/>
|
<RouterView/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,15 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, provide, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
import { computed, provide, ref } from 'vue';
|
||||||
import XCommon from './_common_/common.vue';
|
import XCommon from './_common_/common.vue';
|
||||||
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { instanceName } from '@/config.js';
|
import { instanceName } from '@/config.js';
|
||||||
import { mainRouter } from '@/router/main.js';
|
import { mainRouter } from '@/router/main.js';
|
||||||
import { isEmbedPage } from '@/scripts/embed-page.js';
|
|
||||||
import { postMessageToParentWindow } from '@/scripts/post-message';
|
|
||||||
|
|
||||||
const isEmbed = isEmbedPage();
|
|
||||||
|
|
||||||
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
|
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
|
||||||
|
|
||||||
|
@ -50,35 +38,7 @@ provideMetadataReceiver((metadataGetter) => {
|
||||||
});
|
});
|
||||||
provideReactiveMetadata(pageMetadata);
|
provideReactiveMetadata(pageMetadata);
|
||||||
|
|
||||||
//#region Embed Style
|
document.documentElement.style.overflowY = 'scroll';
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
const embedRounded = ref(params.get('rounded') !== '0');
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Embed Resizer
|
|
||||||
const rootEl = shallowRef<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
if (isEmbed) {
|
|
||||||
const resizeObserver = new ResizeObserver(async () => {
|
|
||||||
postMessageToParentWindow('misskey:embed:changeHeight', {
|
|
||||||
height: rootEl.value!.scrollHeight + 2, // border 上下1px
|
|
||||||
});
|
|
||||||
});
|
|
||||||
onMounted(() => {
|
|
||||||
resizeObserver.observe(rootEl.value!);
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
resizeObserver.disconnect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
if (isEmbed) {
|
|
||||||
document.documentElement.style.maxWidth = '500px';
|
|
||||||
document.documentElement.classList.add('embed');
|
|
||||||
} else {
|
|
||||||
document.documentElement.style.overflowY = 'scroll';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -86,16 +46,4 @@ if (isEmbed) {
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rootForEmbedPage {
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid var(--divider);
|
|
||||||
background-color: var(--bg);
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.rounded {
|
|
||||||
border-radius: var(--radius);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue