mirror of
https://iceshrimp.dev/Crimekillz/jointrashposs.git
synced 2024-11-24 09:49:06 +01:00
(add) mfm preview
This commit is contained in:
parent
1125dd7923
commit
447024f3c1
199
assets/css/mfm.scss
Normal file
199
assets/css/mfm.scss
Normal file
@ -0,0 +1,199 @@
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; transform: scale(1); }
|
||||
30% { opacity: 1; transform: scale(1); }
|
||||
90% { opacity: 0; transform: scale(0.5); }
|
||||
}
|
||||
|
||||
@keyframes tada {
|
||||
from {
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
10%,
|
||||
20% {
|
||||
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
|
||||
}
|
||||
|
||||
30%,
|
||||
50%,
|
||||
70%,
|
||||
90% {
|
||||
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
|
||||
}
|
||||
|
||||
40%,
|
||||
60%,
|
||||
80% {
|
||||
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
._anime_bounce {
|
||||
will-change: transform;
|
||||
animation: bounce ease 0.7s;
|
||||
animation-iteration-count: 1;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
._anime_bounce_ready {
|
||||
will-change: transform;
|
||||
transform: scaleX(0.90) scaleY(0.90) ;
|
||||
}
|
||||
._anime_bounce_standBy {
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0% {
|
||||
transform: scaleX(0.90) scaleY(0.90) ;
|
||||
}
|
||||
19% {
|
||||
transform: scaleX(1.10) scaleY(1.10) ;
|
||||
}
|
||||
48% {
|
||||
transform: scaleX(0.95) scaleY(0.95) ;
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1.00) scaleY(1.00) ;
|
||||
}
|
||||
}
|
||||
|
||||
// MFM -----------------------------
|
||||
|
||||
._mfm_blur_ {
|
||||
filter: blur(6px);
|
||||
transition: filter 0.3s;
|
||||
|
||||
&:hover {
|
||||
filter: blur(0px);
|
||||
}
|
||||
}
|
||||
|
||||
.mfm-x2 {
|
||||
--mfm-zoom-size: 200%;
|
||||
}
|
||||
|
||||
.mfm-x3 {
|
||||
--mfm-zoom-size: 400%;
|
||||
}
|
||||
|
||||
.mfm-x4 {
|
||||
--mfm-zoom-size: 600%;
|
||||
}
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
font-size: var(--mfm-zoom-size);
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
/* only half effective */
|
||||
font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
/* disabled */
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mfm-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes mfm-spinX {
|
||||
0% { transform: perspective(128px) rotateX(0deg); }
|
||||
100% { transform: perspective(128px) rotateX(360deg); }
|
||||
}
|
||||
|
||||
@keyframes mfm-spinY {
|
||||
0% { transform: perspective(128px) rotateY(0deg); }
|
||||
100% { transform: perspective(128px) rotateY(360deg); }
|
||||
}
|
||||
|
||||
@keyframes mfm-jump {
|
||||
0% { transform: translateY(0); }
|
||||
25% { transform: translateY(-16px); }
|
||||
50% { transform: translateY(0); }
|
||||
75% { transform: translateY(-8px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes mfm-bounce {
|
||||
0% { transform: translateY(0) scale(1, 1); }
|
||||
25% { transform: translateY(-16px) scale(1, 1); }
|
||||
50% { transform: translateY(0) scale(1, 1); }
|
||||
75% { transform: translateY(0) scale(1.5, 0.75); }
|
||||
100% { transform: translateY(0) scale(1, 1); }
|
||||
}
|
||||
|
||||
// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`;
|
||||
// let css = '';
|
||||
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
|
||||
@keyframes mfm-twitch {
|
||||
0% { transform: translate(7px, -2px) }
|
||||
5% { transform: translate(-3px, 1px) }
|
||||
10% { transform: translate(-7px, -1px) }
|
||||
15% { transform: translate(0px, -1px) }
|
||||
20% { transform: translate(-8px, 6px) }
|
||||
25% { transform: translate(-4px, -3px) }
|
||||
30% { transform: translate(-4px, -6px) }
|
||||
35% { transform: translate(-8px, -8px) }
|
||||
40% { transform: translate(4px, 6px) }
|
||||
45% { transform: translate(-3px, 1px) }
|
||||
50% { transform: translate(2px, -10px) }
|
||||
55% { transform: translate(-7px, 0px) }
|
||||
60% { transform: translate(-2px, 4px) }
|
||||
65% { transform: translate(3px, -8px) }
|
||||
70% { transform: translate(6px, 7px) }
|
||||
75% { transform: translate(-7px, -2px) }
|
||||
80% { transform: translate(-7px, -8px) }
|
||||
85% { transform: translate(9px, 3px) }
|
||||
90% { transform: translate(-3px, -2px) }
|
||||
95% { transform: translate(-10px, 2px) }
|
||||
100% { transform: translate(-2px, -6px) }
|
||||
}
|
||||
|
||||
// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`;
|
||||
// let css = '';
|
||||
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
|
||||
@keyframes mfm-shake {
|
||||
0% { transform: translate(-3px, -1px) rotate(-8deg) }
|
||||
5% { transform: translate(0px, -1px) rotate(-10deg) }
|
||||
10% { transform: translate(1px, -3px) rotate(0deg) }
|
||||
15% { transform: translate(1px, 1px) rotate(11deg) }
|
||||
20% { transform: translate(-2px, 1px) rotate(1deg) }
|
||||
25% { transform: translate(-1px, -2px) rotate(-2deg) }
|
||||
30% { transform: translate(-1px, 2px) rotate(-3deg) }
|
||||
35% { transform: translate(2px, 1px) rotate(6deg) }
|
||||
40% { transform: translate(-2px, -3px) rotate(-9deg) }
|
||||
45% { transform: translate(0px, -1px) rotate(-12deg) }
|
||||
50% { transform: translate(1px, 2px) rotate(10deg) }
|
||||
55% { transform: translate(0px, -3px) rotate(8deg) }
|
||||
60% { transform: translate(1px, -1px) rotate(8deg) }
|
||||
65% { transform: translate(0px, -1px) rotate(-7deg) }
|
||||
70% { transform: translate(-1px, -3px) rotate(6deg) }
|
||||
75% { transform: translate(0px, -2px) rotate(4deg) }
|
||||
80% { transform: translate(-2px, -1px) rotate(3deg) }
|
||||
85% { transform: translate(1px, -3px) rotate(-10deg) }
|
||||
90% { transform: translate(1px, 0px) rotate(3deg) }
|
||||
95% { transform: translate(-2px, 0px) rotate(-3deg) }
|
||||
100% { transform: translate(2px, 1px) rotate(2deg) }
|
||||
}
|
||||
|
||||
@keyframes mfm-rubberBand {
|
||||
from { transform: scale3d(1, 1, 1); }
|
||||
30% { transform: scale3d(1.25, 0.75, 1); }
|
||||
40% { transform: scale3d(0.75, 1.25, 1); }
|
||||
50% { transform: scale3d(1.15, 0.85, 1); }
|
||||
65% { transform: scale3d(0.95, 1.05, 1); }
|
||||
75% { transform: scale3d(1.05, 0.95, 1); }
|
||||
to { transform: scale3d(1, 1, 1); }
|
||||
}
|
||||
|
||||
@keyframes mfm-rainbow {
|
||||
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
|
||||
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
|
||||
}
|
13
components/content/MfmPreview.vue
Normal file
13
components/content/MfmPreview.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-slate-200 dark:border-slate-800 p-6 mfm-root mb-4">
|
||||
<MkMfm :text="text" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MkMfm from '@/components/mk/Mfm';
|
||||
|
||||
defineProps<{
|
||||
text: string;
|
||||
}>();
|
||||
</script>
|
@ -21,7 +21,6 @@ let realTarget = props.target;
|
||||
|
||||
try {
|
||||
const url = new URL(props.href);
|
||||
console.log(url);
|
||||
if (!url.hostname || rootDomain.hostname === url.hostname) {
|
||||
realHref = localePath(realHref);
|
||||
}
|
||||
|
151
components/docs/AsideTree.vue
Normal file
151
components/docs/AsideTree.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
links: {
|
||||
type: Array as PropType<any>,
|
||||
default: () => [],
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
parent: {
|
||||
type: Object as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const collapsedMap = useState(
|
||||
`docus-docs-aside-collapse-map-${props.parent?._path || "/"}`,
|
||||
() => {
|
||||
if (props.level === 0) {
|
||||
return {};
|
||||
}
|
||||
return (props.links as any[])
|
||||
.filter((link) => !!link.children)
|
||||
.reduce((map, link) => {
|
||||
map[link._path] = true;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
);
|
||||
|
||||
const isActive = (link: any) => {
|
||||
return route.path === link._path;
|
||||
};
|
||||
|
||||
const isCollapsed = (link: any) => {
|
||||
if (link.children) {
|
||||
// Directory has been toggled manually, use its state
|
||||
if (typeof collapsedMap.value[link._path] !== "undefined") {
|
||||
return collapsedMap.value[link._path];
|
||||
}
|
||||
|
||||
// Check if aside.collapsed has been set in YML
|
||||
if ([true, false].includes(link?.aside?.collapsed)) {
|
||||
return link.aside.collapsed;
|
||||
}
|
||||
|
||||
// Return value grabbed from the link
|
||||
if (link?.collapsed) {
|
||||
return link?.collapsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const toggleCollapse = (link: any) =>
|
||||
(collapsedMap.value[link._path] = !isCollapsed(link));
|
||||
|
||||
const hasNesting = computed(() =>
|
||||
props.links.some((link: any) => link.children)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="docs-aside-tree">
|
||||
<li
|
||||
v-for="link in links"
|
||||
:key="link._path"
|
||||
:class="{
|
||||
'has-parent-icon': parent?.icon,
|
||||
'has-children': level > 0 && link.children,
|
||||
bordered: level > 0 || !hasNesting,
|
||||
active: isActive(link),
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-if="link.children"
|
||||
class="title-collapsible-button"
|
||||
@click="toggleCollapse(link)"
|
||||
>
|
||||
<span class="content">
|
||||
<Icon
|
||||
v-if="link?.navigation?.icon || link.icon"
|
||||
:name="link?.navigation?.icon || link.icon"
|
||||
class="icon"
|
||||
/>
|
||||
<span>{{
|
||||
link?.navigation?.title || link.title || link._path
|
||||
}}</span>
|
||||
</span>
|
||||
<span>
|
||||
<Icon
|
||||
:name="
|
||||
isCollapsed(link)
|
||||
? 'lucide:chevrons-up-down'
|
||||
: 'lucide:chevrons-down-up'
|
||||
"
|
||||
class="collapsible-icon"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<NuxtLink
|
||||
v-else
|
||||
:to="link.redirect ? link.redirect : link._path"
|
||||
class="link"
|
||||
:exact="link.exact"
|
||||
:class="{
|
||||
padded: level > 0 || !hasNesting,
|
||||
active: isActive(link),
|
||||
}"
|
||||
>
|
||||
<span class="content">
|
||||
<Icon
|
||||
v-if="link?.navigation?.icon || link.icon"
|
||||
:name="link?.navigation?.icon || link.icon"
|
||||
class="icon"
|
||||
/>
|
||||
<span>{{
|
||||
link?.navigation?.title || link.title || link._path
|
||||
}}</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
|
||||
<DocsAsideTree
|
||||
v-show="!isCollapsed(link)"
|
||||
v-if="
|
||||
link.children?.length && (max === null || level + 1 < max)
|
||||
"
|
||||
:links="link.children"
|
||||
:level="level + 1"
|
||||
:parent="link"
|
||||
:max="max"
|
||||
class="recursive"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped lang="ts">
|
||||
</style>
|
54
components/mk/Google.vue
Normal file
54
components/mk/Google.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<input v-model="query" :class="$style.input" type="search" :placeholder="q">
|
||||
<button :class="$style.button" @click="search"><SearchIco /> 検索</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import SearchIco from 'bi/search.svg';
|
||||
|
||||
const props = defineProps<{
|
||||
q: string;
|
||||
}>();
|
||||
|
||||
const query = ref(props.q);
|
||||
|
||||
const search = () => {
|
||||
window.open(`https://www.google.com/search?q=${query.value}`, '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply border-slate-200;
|
||||
flex-shrink: 1;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
border: solid 1px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply border-slate-200;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
border: solid 1px;
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
|
||||
}
|
||||
}
|
||||
</style>
|
333
components/mk/Mfm.ts
Normal file
333
components/mk/Mfm.ts
Normal file
@ -0,0 +1,333 @@
|
||||
import { VNode, h } from 'vue';
|
||||
import * as mfm from 'mfm-js';
|
||||
import MkGoogle from '@/components/mk/Google.vue';
|
||||
import MkSparkle from '@/components/mk/Sparkle.vue';
|
||||
import NuxtLink from '@/components/g/NuxtLink';
|
||||
import ProseAVue from '@/components/content/ProseA.vue';
|
||||
|
||||
const QUOTE_STYLE = `
|
||||
display: block;
|
||||
margin: 8px;
|
||||
padding: 6px 0 6px 12px;
|
||||
color: var(--fg);
|
||||
border-left: solid 3px var(--fg);
|
||||
opacity: 0.7;
|
||||
`.split('\n').join(' ');
|
||||
|
||||
export default function(props: {
|
||||
text: string;
|
||||
plain?: boolean;
|
||||
nowrap?: boolean;
|
||||
isNote?: boolean;
|
||||
emojiUrls?: string[];
|
||||
rootScale?: number;
|
||||
}) {
|
||||
const isNote = props.isNote !== undefined ? props.isNote : true;
|
||||
|
||||
if (props.text == null || props.text === '') return;
|
||||
|
||||
const ast = (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
|
||||
|
||||
const validTime = (t: string | null | undefined) => {
|
||||
if (t == null) return null;
|
||||
return t.match(/^[0-9.]+s$/) ? t : null;
|
||||
};
|
||||
|
||||
const useAnim = true;
|
||||
|
||||
/**
|
||||
* Gen Vue Elements from MFM AST
|
||||
* @param ast MFM AST
|
||||
* @param scale How times large the text is
|
||||
*/
|
||||
const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
|
||||
switch (token.type) {
|
||||
case 'text': {
|
||||
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||
|
||||
if (!props.plain) {
|
||||
const res: (VNode | string)[] = [];
|
||||
for (const t of text.split('\n')) {
|
||||
res.push(h('br'));
|
||||
res.push(t);
|
||||
}
|
||||
res.shift();
|
||||
return res;
|
||||
} else {
|
||||
return [text.replace(/\n/g, ' ')];
|
||||
}
|
||||
}
|
||||
|
||||
case 'bold': {
|
||||
return [h('b', genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
case 'strike': {
|
||||
return [h('del', genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
case 'italic': {
|
||||
return h('i', {
|
||||
style: 'font-style: oblique;',
|
||||
}, genEl(token.children, scale));
|
||||
}
|
||||
|
||||
case 'fn': {
|
||||
// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
|
||||
let style;
|
||||
switch (token.props.name) {
|
||||
case 'tada': {
|
||||
const speed = validTime(token.props.args.speed) ?? '1s';
|
||||
style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : '');
|
||||
break;
|
||||
}
|
||||
case 'jelly': {
|
||||
const speed = validTime(token.props.args.speed) ?? '1s';
|
||||
style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : '');
|
||||
break;
|
||||
}
|
||||
case 'twitch': {
|
||||
const speed = validTime(token.props.args.speed) ?? '0.5s';
|
||||
style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'shake': {
|
||||
const speed = validTime(token.props.args.speed) ?? '0.5s';
|
||||
style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'spin': {
|
||||
const direction =
|
||||
token.props.args.left ? 'reverse' :
|
||||
token.props.args.alternate ? 'alternate' :
|
||||
'normal';
|
||||
const anime =
|
||||
token.props.args.x ? 'mfm-spinX' :
|
||||
token.props.args.y ? 'mfm-spinY' :
|
||||
'mfm-spin';
|
||||
const speed = validTime(token.props.args.speed) ?? '1.5s';
|
||||
style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : '';
|
||||
break;
|
||||
}
|
||||
case 'jump': {
|
||||
const speed = validTime(token.props.args.speed) ?? '0.75s';
|
||||
style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'bounce': {
|
||||
const speed = validTime(token.props.args.speed) ?? '0.75s';
|
||||
style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
|
||||
break;
|
||||
}
|
||||
case 'flip': {
|
||||
const transform =
|
||||
(token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' :
|
||||
token.props.args.v ? 'scaleY(-1)' :
|
||||
'scaleX(-1)';
|
||||
style = `transform: ${transform};`;
|
||||
break;
|
||||
}
|
||||
case 'x2': {
|
||||
return h('span', {
|
||||
class: 'mfm-x2',
|
||||
}, genEl(token.children, scale * 2));
|
||||
}
|
||||
case 'x3': {
|
||||
return h('span', {
|
||||
class: 'mfm-x3',
|
||||
}, genEl(token.children, scale * 3));
|
||||
}
|
||||
case 'x4': {
|
||||
return h('span', {
|
||||
class: 'mfm-x4',
|
||||
}, genEl(token.children, scale * 4));
|
||||
}
|
||||
case 'font': {
|
||||
const family =
|
||||
token.props.args.serif ? 'serif' :
|
||||
token.props.args.monospace ? 'monospace' :
|
||||
token.props.args.cursive ? 'cursive' :
|
||||
token.props.args.fantasy ? 'fantasy' :
|
||||
token.props.args.emoji ? 'emoji' :
|
||||
token.props.args.math ? 'math' :
|
||||
null;
|
||||
if (family) style = `font-family: ${family};`;
|
||||
break;
|
||||
}
|
||||
case 'blur': {
|
||||
return h('span', {
|
||||
class: '_mfm_blur_',
|
||||
}, genEl(token.children, scale));
|
||||
}
|
||||
case 'rainbow': {
|
||||
const speed = validTime(token.props.args.speed) ?? '1s';
|
||||
style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'sparkle': {
|
||||
if (!useAnim) {
|
||||
return genEl(token.children, scale);
|
||||
}
|
||||
return h(MkSparkle, {}, genEl(token.children, scale));
|
||||
}
|
||||
case 'rotate': {
|
||||
const degrees = parseFloat(token.props.args.deg ?? '90');
|
||||
style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
|
||||
break;
|
||||
}
|
||||
case 'position': {
|
||||
const x = parseFloat(token.props.args.x ?? '0');
|
||||
const y = parseFloat(token.props.args.y ?? '0');
|
||||
style = `transform: translateX(${x}em) translateY(${y}em);`;
|
||||
break;
|
||||
}
|
||||
case 'scale': {
|
||||
const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
|
||||
const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
|
||||
style = `transform: scale(${x}, ${y});`;
|
||||
scale = scale * Math.max(x, y);
|
||||
break;
|
||||
}
|
||||
case 'fg': {
|
||||
let color = token.props.args.color;
|
||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
||||
style = `color: #${color};`;
|
||||
break;
|
||||
}
|
||||
case 'bg': {
|
||||
let color = token.props.args.color;
|
||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
||||
style = `background-color: #${color};`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (style == null) {
|
||||
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
|
||||
} else {
|
||||
return h('span', {
|
||||
style: 'display: inline-block; ' + style,
|
||||
}, genEl(token.children, scale));
|
||||
}
|
||||
}
|
||||
|
||||
case 'small': {
|
||||
return [h('small', {
|
||||
style: 'opacity: 0.7;',
|
||||
}, genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
case 'center': {
|
||||
return [h('div', {
|
||||
style: 'text-align:center;',
|
||||
}, genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
case 'url': {
|
||||
return [h(ProseAVue, {
|
||||
key: Math.random(),
|
||||
href: token.props.url,
|
||||
rel: 'nofollow noopener',
|
||||
}, token.props.url)];
|
||||
}
|
||||
|
||||
case 'link': {
|
||||
return [h(NuxtLink, {
|
||||
key: Math.random(),
|
||||
to: token.props.url,
|
||||
rel: 'nofollow noopener',
|
||||
}, genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
case 'mention': {
|
||||
return [h(MkMention, {
|
||||
key: Math.random(),
|
||||
host: (token.props.host) || host,
|
||||
username: token.props.username,
|
||||
})];
|
||||
}
|
||||
|
||||
case 'hashtag': {
|
||||
return [h(NuxtLink, {
|
||||
key: Math.random(),
|
||||
to: `https://misskey.io/tags/${encodeURIComponent(token.props.hashtag)}`,
|
||||
style: 'color:rgb(255, 145, 86);',
|
||||
}, `#${token.props.hashtag}`)];
|
||||
}
|
||||
|
||||
case 'quote': {
|
||||
if (!props.nowrap) {
|
||||
return [h('div', {
|
||||
style: QUOTE_STYLE,
|
||||
}, genEl(token.children, scale))];
|
||||
} else {
|
||||
return [h('span', {
|
||||
style: QUOTE_STYLE,
|
||||
}, genEl(token.children, scale))];
|
||||
}
|
||||
}
|
||||
|
||||
case 'emojiCode': {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (props.author?.host == null) {
|
||||
return [h(MkCustomEmoji, {
|
||||
key: Math.random(),
|
||||
name: token.props.name,
|
||||
normal: props.plain,
|
||||
host: null,
|
||||
useOriginalSize: scale >= 2.5,
|
||||
})];
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (props.emojiUrls && (props.emojiUrls[token.props.name] == null)) {
|
||||
return [h('span', `:${token.props.name}:`)];
|
||||
} else {
|
||||
return [h(MkCustomEmoji, {
|
||||
key: Math.random(),
|
||||
name: token.props.name,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
url: props.emojiUrls ? props.emojiUrls[token.props.name] : null,
|
||||
normal: props.plain,
|
||||
host: props.author.host,
|
||||
useOriginalSize: scale >= 2.5,
|
||||
})];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'unicodeEmoji': {
|
||||
return [h('span', token.props.emoji)];
|
||||
}
|
||||
|
||||
case 'mathInline': {
|
||||
return [h('code', token.props.formula)];
|
||||
}
|
||||
|
||||
case 'mathBlock': {
|
||||
return [h('code', token.props.formula)];
|
||||
}
|
||||
|
||||
case 'search': {
|
||||
return [h(MkGoogle, {
|
||||
key: Math.random(),
|
||||
q: token.props.query,
|
||||
})];
|
||||
}
|
||||
|
||||
case 'plain': {
|
||||
return [h('span', genEl(token.children, scale))];
|
||||
}
|
||||
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
console.error('unrecognized ast type:', (token as any).type);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}).flat(Infinity) as (VNode | string)[];
|
||||
|
||||
return h('span', {
|
||||
// https://codeday.me/jp/qa/20190424/690106.html
|
||||
style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
|
||||
}, genEl(ast, props.rootScale ?? 1));
|
||||
}
|
135
components/mk/Sparkle.vue
Normal file
135
components/mk/Sparkle.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<span :class="$style.root">
|
||||
<span ref="el" style="display: inline-block">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<!-- なぜか path に対する key が機能しないため
|
||||
<svg :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg">
|
||||
<path v-for="particle in particles" :key="particle.id" style="transform-origin: center; transform-box: fill-box;"
|
||||
:transform="`translate(${particle.x} ${particle.y})`"
|
||||
:fill="particle.color"
|
||||
d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="0 0 0"
|
||||
to="360 0 0"
|
||||
:dur="`${particle.dur}ms`"
|
||||
repeatCount="indefinite"
|
||||
additive="sum"
|
||||
/>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
:values="`0; ${particle.size}; 0`"
|
||||
:dur="`${particle.dur}ms`"
|
||||
repeatCount="indefinite"
|
||||
additive="sum"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
-->
|
||||
<!-- MFMで上位レイヤーに表示されるため、リンクをクリックできるようにstyleにpointer-events: none;を付与。 -->
|
||||
<svg
|
||||
v-for="particle in particles"
|
||||
:key="particle.id"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:viewBox="`0 0 ${width} ${height}`"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style="
|
||||
position: absolute;
|
||||
top: -32px;
|
||||
left: -32px;
|
||||
pointer-events: none;
|
||||
"
|
||||
>
|
||||
<path
|
||||
style="transform-origin: center; transform-box: fill-box"
|
||||
:transform="`translate(${particle.x} ${particle.y})`"
|
||||
:fill="particle.color"
|
||||
d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="0 0 0"
|
||||
to="360 0 0"
|
||||
:dur="`${particle.dur}ms`"
|
||||
repeatCount="1"
|
||||
additive="sum"
|
||||
/>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
:values="`0; ${particle.size}; 0`"
|
||||
:dur="`${particle.dur}ms`"
|
||||
repeatCount="1"
|
||||
additive="sum"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, shallowRef } from "vue";
|
||||
|
||||
const particles = ref([]);
|
||||
const el = shallowRef<HTMLElement>();
|
||||
const width = ref(0);
|
||||
const height = ref(0);
|
||||
const colors = ["#FF1493", "#00FFFF", "#FFE202", "#FFE202", "#FFE202"];
|
||||
let stop = false;
|
||||
let ro: ResizeObserver | undefined;
|
||||
|
||||
onMounted(() => {
|
||||
ro = new ResizeObserver((entries, observer) => {
|
||||
width.value = el.value?.offsetWidth + 64;
|
||||
height.value = el.value?.offsetHeight + 64;
|
||||
});
|
||||
ro.observe(el.value);
|
||||
const add = () => {
|
||||
if (stop) return;
|
||||
const x = Math.random() * (width.value - 64);
|
||||
const y = Math.random() * (height.value - 64);
|
||||
const sizeFactor = Math.random();
|
||||
const particle = {
|
||||
id: Math.random().toString(),
|
||||
x,
|
||||
y,
|
||||
size: 0.2 + (sizeFactor / 10) * 3,
|
||||
dur: 1000 + sizeFactor * 1000,
|
||||
color: colors[Math.floor(Math.random() * colors.length)],
|
||||
};
|
||||
particles.value.push(particle);
|
||||
window.setTimeout(() => {
|
||||
particles.value = particles.value.filter(
|
||||
(x) => x.id !== particle.id
|
||||
);
|
||||
}, particle.dur - 100);
|
||||
|
||||
window.setTimeout(() => {
|
||||
add();
|
||||
}, 500 + Math.random() * 500);
|
||||
};
|
||||
add();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ro) ro.disconnect();
|
||||
stop = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
@ -22,10 +22,10 @@ MFMは、Markup language For Misskeyの略で、Misskeyの様々な場所で使
|
||||
メンションについての詳細は[こちら](./mention.md)を参照してください。
|
||||
:::
|
||||
|
||||
```:no-line-numbers
|
||||
```
|
||||
@alice
|
||||
```
|
||||
```:no-line-numbers
|
||||
```
|
||||
@alice@example.com
|
||||
```
|
||||
|
||||
@ -35,65 +35,79 @@ MFMは、Markup language For Misskeyの略で、Misskeyの様々な場所で使
|
||||
ハッシュタグについての詳細は[こちら](./hashtag.md)を参照してください。
|
||||
:::
|
||||
|
||||
```:no-line-numbers
|
||||
```
|
||||
#misskey
|
||||
```
|
||||
|
||||
<MfmPreview text="#misskey"></MfmPreview>
|
||||
|
||||
### URL
|
||||
URLを示すことができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
https://example.com
|
||||
```
|
||||
|
||||
<MfmPreview text="https://example.com"></MfmPreview>
|
||||
|
||||
### リンク
|
||||
文章の特定の範囲を、URLに紐づけることができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
[example link](https://example.com)
|
||||
```
|
||||
|
||||
<MfmPreview text="[example link](https://example.com)"></MfmPreview>
|
||||
|
||||
### カスタム絵文字
|
||||
コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。
|
||||
:::tip
|
||||
カスタム絵文字についての詳細は[こちら](./custom-emoji.md)を参照してください。
|
||||
:::
|
||||
|
||||
```:no-line-numbers
|
||||
```
|
||||
:misskey:
|
||||
```
|
||||
|
||||
### 太字
|
||||
文字を太く表示して強調することができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
**太字**
|
||||
```
|
||||
|
||||
<MfmPreview text="**太字**"></MfmPreview>
|
||||
|
||||
### 目立たなくする
|
||||
内容を小さく・薄く表示させることができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
<small>MisskeyでFediverseの世界が広がります</small>
|
||||
```
|
||||
|
||||
<MfmPreview text="<small>MisskeyでFediverseの世界が広がります</small>"></MfmPreview>
|
||||
|
||||
### 引用
|
||||
内容が引用であることを示すことができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
> MisskeyでFediverseの世界が広がります
|
||||
```
|
||||
|
||||
<MfmPreview text="> MisskeyでFediverseの世界が広がります"></MfmPreview>
|
||||
|
||||
### 中央寄せ
|
||||
内容を中央寄せで表示させることができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
<center>MisskeyでFediverseの世界が広がります</center>
|
||||
```
|
||||
|
||||
<MfmPreview text="<center>MisskeyでFediverseの世界が広がります</center>"></MfmPreview>
|
||||
|
||||
### コード(インライン)
|
||||
プログラムなどのコードをインラインでシンタックスハイライトします。
|
||||
```:no-line-numbers
|
||||
```
|
||||
`<: "Hello, world!"`
|
||||
```
|
||||
|
||||
### コード(ブロック)
|
||||
複数行のプログラムなどのコードをブロックでシンタックスハイライトします。
|
||||
```:no-line-numbers
|
||||
```
|
||||
~ (#i, 100) {
|
||||
<: ? ((i % 15) = 0) "FizzBuzz"
|
||||
.? ((i % 3) = 0) "Fizz"
|
||||
@ -104,33 +118,189 @@ https://example.com
|
||||
|
||||
### 反転
|
||||
内容を上下または左右に反転させます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
$[flip MisskeyでFediverseの世界が広がります]
|
||||
$[flip.v MisskeyでFediverseの世界が広がります]
|
||||
$[flip.h,v MisskeyでFediverseの世界が広がります]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[flip MisskeyでFediverseの世界が広がります]
|
||||
$[flip.v MisskeyでFediverseの世界が広がります]
|
||||
$[flip.h,v MisskeyでFediverseの世界が広がります]"></MfmPreview>
|
||||
|
||||
### フォント
|
||||
内容のフォントを指定することができます。
|
||||
```:no-line-numbers
|
||||
```
|
||||
$[font.serif MisskeyでFediverseの世界が広がります]
|
||||
$[font.monospace MisskeyでFediverseの世界が広がります]
|
||||
$[font.cursive MisskeyでFediverseの世界が広がります]
|
||||
$[font.fantasy MisskeyでFediverseの世界が広がります]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[font.serif MisskeyでFediverseの世界が広がります]
|
||||
$[font.monospace MisskeyでFediverseの世界が広がります]
|
||||
$[font.cursive MisskeyでFediverseの世界が広がります]
|
||||
$[font.fantasy MisskeyでFediverseの世界が広がります]"></MfmPreview>
|
||||
|
||||
### ぼかし
|
||||
内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。
|
||||
```:no-line-numbers
|
||||
```
|
||||
$[blur MisskeyでFediverseの世界が広がります]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[blur MisskeyでFediverseの世界が広がります]"></MfmPreview>
|
||||
|
||||
### 検索
|
||||
検索ボックスを表示できます。
|
||||
|
||||
```
|
||||
misskey 検索
|
||||
```
|
||||
|
||||
<MfmPreview text="misskey 検索"></MfmPreview>
|
||||
|
||||
|
||||
### 文字色・背景色
|
||||
文字色と背景色を変更することができます。
|
||||
|
||||
3,4,6桁のカラーコードで色を表現します。
|
||||
|
||||
```
|
||||
$[fg.color=f00 赤字]
|
||||
$[bg.color=ff0 黄背景]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[fg.color=f00 赤字]
|
||||
$[bg.color=ff0 黄背景]"></MfmPreview>
|
||||
|
||||
### 角度変更
|
||||
指定した角度で回転させます。
|
||||
|
||||
```
|
||||
$[rotate.deg=30 misskey]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[rotate.deg=30 misskey]"></MfmPreview>
|
||||
|
||||
### 位置変更
|
||||
位置をずらすことができます。
|
||||
|
||||
```
|
||||
😏$[position.x=0.8,y=0.5 🍮]😀
|
||||
```
|
||||
|
||||
<MfmPreview text="😏$[position.x=0.8,y=0.5 🍮]😀"></MfmPreview>
|
||||
|
||||
### 拡大
|
||||
文字を引き延ばして表示します。
|
||||
|
||||
```
|
||||
$[scale.x=4,y=2 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[scale.x=4,y=2 🍮]"></MfmPreview>
|
||||
|
||||
```
|
||||
$[x2 x2]
|
||||
$[x3 x3]
|
||||
$[x4 x4]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 x2]
|
||||
$[x3 x3]
|
||||
$[x4 x4]"></MfmPreview>
|
||||
|
||||
### アニメーション(びよんびよん)
|
||||
|
||||
```
|
||||
$[jelly 🍮] $[jelly.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[jelly 🍮] $[jelly.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(じゃーん)
|
||||
|
||||
```
|
||||
$[tada 🍮] $[tada.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[tada 🍮] $[tada.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(ジャンプ)
|
||||
|
||||
```
|
||||
$[jump 🍮] $[jump.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[jump 🍮] $[jump.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(バウンド)
|
||||
|
||||
```
|
||||
$[bounce 🍮] $[bounce.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[bounce 🍮] $[bounce.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(回転)
|
||||
|
||||
```
|
||||
$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]
|
||||
$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]
|
||||
$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]
|
||||
|
||||
$[spin.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]
|
||||
$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]
|
||||
$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]
|
||||
$[spin.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(ぶるぶる)
|
||||
|
||||
```
|
||||
$[shake 🍮] $[shake.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[shake 🍮] $[shake.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### アニメーション(ブレ)
|
||||
|
||||
```
|
||||
$[twitch 🍮] $[twitch.speed=5s 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[twitch 🍮] $[twitch.speed=5s 🍮]]"></MfmPreview>
|
||||
|
||||
### レインボー
|
||||
|
||||
```
|
||||
$[rainbow 🍮] $[rainbow.speed=5s 🍮]
|
||||
$[rainbow 色なし文字]
|
||||
$[rainbow $[fg.color=f0f 色付き文字]]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[rainbow 🍮] $[rainbow.speed=5s 🍮]
|
||||
$[rainbow 色なし文字]
|
||||
$[rainbow $[fg.color=f0f 色付き文字]]"></MfmPreview>
|
||||
|
||||
### キラキラ
|
||||
|
||||
```
|
||||
$[sparkle 🍮]
|
||||
```
|
||||
|
||||
<MfmPreview text="$[x2 $[sparkle 🍮]]"></MfmPreview>
|
||||
|
||||
### プレーン
|
||||
内側の構文を全て無効にします。
|
||||
```:no-line-numbers
|
||||
```
|
||||
<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>
|
||||
```
|
||||
|
||||
<MfmPreview text="<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>"></MfmPreview>
|
||||
|
||||
## 開発者向け情報
|
||||
MFMのパーサー実装はライブラリとして公開されており、簡単にクライアントにMFMを組み込むことが可能です。
|
||||
- [misskey-dev/mfm.js](https://github.com/misskey-dev/mfm.js) - JavaScriptパーサー実装
|
||||
|
@ -23,6 +23,7 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
css: [
|
||||
"@/assets/css/mfm.scss",
|
||||
"github-markdown-css/github-markdown.css",
|
||||
"@/assets/css/tailwind.css",
|
||||
"@/assets/css/bootstrap-forms.scss",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"meshline": "^3.1.6",
|
||||
"mfm-js": "^0.23.3",
|
||||
"nuxt": "^3.6.2",
|
||||
"postcss": "^8.4.25",
|
||||
"sass": "^1.63.6",
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="relative container mx-auto max-w-screen-xl p-6 lg:py-0 grid docs-root pb-12">
|
||||
<div class="hidden lg:block">
|
||||
<div class="sticky top-16 h-[calc(100vh-4rem)] overflow-y-scroll border-r border-slate-200 dark:border-slate-700 py-6 pr-6">
|
||||
あ
|
||||
<DocsAsideTree :links="navigation" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="lg:p-6 w-full overflow-x-hidden">
|
||||
@ -41,6 +41,7 @@ const currentLocaleISO = () => {
|
||||
}
|
||||
|
||||
const { data } = await useAsyncData(`blog-${locale.value}-${slugs.join('-')}`, () => queryContent(`/${locale.value}/docs/${slugs.join('/')}`).findOne());
|
||||
const { navigation } = await useAsyncData('navigation', () => fetchContentNavigation());
|
||||
|
||||
route.meta.title = data.value?.title;
|
||||
</script>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<IndexDonation />
|
||||
<IndexSponsors />
|
||||
</main>
|
||||
<GFooter class="relative bg-transparent dark:bg-transparent" />
|
||||
<GFooter class="relative !bg-transparent dark:!bg-transparent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -41,6 +41,9 @@ devDependencies:
|
||||
meshline:
|
||||
specifier: ^3.1.6
|
||||
version: 3.1.6(three@0.154.0)
|
||||
mfm-js:
|
||||
specifier: ^0.23.3
|
||||
version: 0.23.3
|
||||
nuxt:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2(@types/node@18.0.0)(sass@1.63.6)(typescript@5.1.6)
|
||||
@ -3897,6 +3900,12 @@ packages:
|
||||
three: 0.154.0
|
||||
dev: true
|
||||
|
||||
/mfm-js@0.23.3:
|
||||
resolution: {integrity: sha512-o8scYmbey6rMUmWAlT3k3ntt6khaCLdxlmHhAWV5wTTMj2OK1atQvZfRUq0SIVm1Jig08qlZg/ps71xUqrScNA==}
|
||||
dependencies:
|
||||
twemoji-parser: 14.0.0
|
||||
dev: true
|
||||
|
||||
/micromark-core-commonmark@1.1.0:
|
||||
resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
|
||||
dependencies:
|
||||
@ -5873,6 +5882,10 @@ packages:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
dev: true
|
||||
|
||||
/twemoji-parser@14.0.0:
|
||||
resolution: {integrity: sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==}
|
||||
dev: true
|
||||
|
||||
/type-fest@0.21.3:
|
||||
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
|
||||
engines: {node: '>=10'}
|
||||
|
Loading…
Reference in New Issue
Block a user