(wip) ssr using pug

This commit is contained in:
kakkokari-gtyih 2023-04-09 04:43:38 +09:00
parent ae22b5b5da
commit 73d552814a
17 changed files with 884 additions and 8 deletions

View File

@ -36,7 +36,11 @@ gulp.task('copy:frontend:locales', cb => {
});
gulp.task('build:backend:script', () => {
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
return gulp.src([
'./packages/backend/src/server/web/boot.js',
'./packages/backend/src/server/web/bios.js',
'./packages/backend/src/server/web/cli.js',
])
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(terser({
toplevel: true
@ -45,7 +49,12 @@ gulp.task('build:backend:script', () => {
});
gulp.task('build:backend:style', () => {
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
return gulp.src([
'./packages/backend/src/server/web/style.css',
'./packages/backend/src/server/web/bios.css',
'./packages/backend/src/server/web/cli.css',
'./packages/backend/src/server/web/embed.css'
])
.pipe(cssnano({
zindex: false
}))

View File

@ -4,13 +4,14 @@
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
"url": "https://github.com/kakkokari-gtyih/misskey.git"
},
"packageManager": "pnpm@8.1.1",
"workspaces": [
"packages/frontend",
"packages/backend",
"packages/sw"
"packages/sw",
"packages/misskey-js"
],
"private": true,
"scripts": {
@ -66,4 +67,4 @@
"optionalDependencies": {
"@tensorflow/tfjs-core": "4.2.0"
}
}
}

View File

@ -103,6 +103,7 @@ export type Mixin = {
driveUrl: string;
userAgent: string;
clientEntry: string;
clientEmbedEntry: string;
clientManifestExists: boolean;
mediaProxy: string;
externalMediaProxyEnabled: boolean;
@ -133,7 +134,10 @@ export function loadConfig() {
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
const clientManifest = clientManifestExists ?
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
: { 'src/init.ts': { file: 'src/init.ts' } };
: {
'src/init.ts': { file: 'src/init.ts' },
'src/embed/init.ts': { file: 'src/embed/init.ts' },
};
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
const mixin = {} as Mixin;
@ -155,6 +159,7 @@ export function loadConfig() {
mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
mixin.clientEntry = clientManifest['src/init.ts'];
mixin.clientEmbedEntry = clientManifest['src/embed/init.ts'];
mixin.clientManifestExists = clientManifestExists;
const externalMediaProxy = config.mediaProxy ?

View File

@ -344,6 +344,17 @@ export class ClientServerService {
});
};
const renderEmbed404 = async (reply: FastifyReply) => {
reply.status(404);
const meta = await this.metaService.fetch();
return await reply.view('embed/404', {
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
url: this.config.url,
});
};
// URL preview endpoint
fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply));
@ -469,13 +480,42 @@ export class ClientServerService {
summary: getNoteSummary(_note),
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
themeColor: meta.themeColor
});
} else {
return await renderBase(reply);
}
});
// Note Embed
fastify.get<{ Params: { note: string; } }>('/notes/:note/embed', async (request, reply) => {
vary(reply.raw, 'Accept');
const note = await this.notesRepository.findOneBy({
id: request.params.note,
visibility: In(['public', 'home']),
});
if (note) {
const _note = await this.noteEntityService.pack(note);
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('embed/note', {
note: _note,
profile,
avatarUrl: _note.user.avatarUrl,
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
themeColor: meta.themeColor,
});
} else {
return await renderEmbed404(reply);
}
});
// Page
fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
const { username, host } = Acct.parse(request.params.user);

View File

@ -0,0 +1,21 @@
html,body {
max-width: 650px;
}
#splash {
max-width: 650px;
width: 100%;
height: 100%;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}

View File

@ -0,0 +1,16 @@
extends ./base
block style
style.
block content
div#error
div
div#instance-info
a.click-anime(href=url target='_blank')
img(src= icon || '/static-assets/splash.png')
span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`)
img.main(src='https://xn--931a.moe/assets/not-found.jpg')
h2(data-mi-i18n='notFound')
p(data-mi-i18n='notFoundDescription')

View File

@ -0,0 +1,98 @@
block vars
block loadClientEntry
- const clientEntry = config.clientEmbedEntry;
doctype html
//
-
_____ _ _
| |_|___ ___| |_ ___ _ _
| | | | |_ -|_ -| '_| -_| | |
|_|_|_|_|___|___|_,_|___|_ |
|___|
Thank you for using Misskey!
If you are reading this message... how about joining the development?
https://github.com/misskey-dev/misskey
html
head
meta(charset='utf-8')
meta(name='application-name' content='Misskey')
meta(name='referrer' content='origin')
meta(name='theme-color' content= themeColor || '#86b300')
meta(name='theme-color-orig' content= themeColor || '#86b300')
meta(property='twitter:card' content='summary')
meta(property='og:site_name' content= instanceName || 'Misskey')
meta(name='viewport' content='width=device-width, initial-scale=1')
link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png')
link(rel='manifest' href='/manifest.json')
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg')
link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
//- https://github.com/misskey-dev/misskey/issues/9842
link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.12.0')
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists
script(type="module" src="/vite/@vite/client")
if Array.isArray(clientEntry.css)
each href in clientEntry.css
link(rel='stylesheet' href=`/vite/${href}`)
title
block title
= title || 'Misskey'
block desc
meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
block meta
block og
meta(property='og:title' content= title || 'Misskey')
meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨')
meta(property='og:image' content= img)
style
include ../../style.css
include ../../embed.css
block style
script.
var VERSION = "#{version}";
var CLIENT_ENTRY = "#{clientEntry.file}";
var EMBED = true;
script
include ../../boot.js
body
noscript: p
| JavaScriptを有効にしてください
br
| Please turn on your JavaScript
div#splash
img#splashIcon(src= icon || '/static-assets/splash.png')
div#splashSpinner
<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1,0,0,1,12,12)">
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
</g>
</svg>
<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1,0,0,1,12,12)">
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
</g>
</svg>
div#container
block content

View File

@ -0,0 +1,53 @@
extends ./base
block vars
- const user = note.user;
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`;
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
- const displayUser = isRenote ? note.renote.user: note.user;
block meta
if !user.host
link(rel='alternate' href=url type='application/activity+json')
if note.uri
link(rel='alternate' href=note.uri type='application/activity+json')
script(type='application/json') !{JSON.stringify(note)}
block content
div#note
header
div.wrapper
if isRenote
div#renote
a.avatar(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer")
img(src=note.user.avatarUrl)
i.ti.ti-repeat
span(data-mi-i18n='renotedBy' data-mi-i18n-ctx=`{"user": "${note.user.name || note.user.username}"}`)
a(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer")
b(data-mi-i18n-target='user')
div.author
a.avatar(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer")
img(src=displayUser.avatarUrl)
div.user-info
a.user-name(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer") #{displayUser.name || displayUser.username}
div.user-id @#{displayUser.username}
div#instance-info
a.click-anime(href=config.url target='_blank')
img(src= icon || '/static-assets/splash.png')
span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`)
main
div.mfm !{isRenote ? note.renote.text : note.text}
if (!isRenote && note.renote)
div#quote.mfm !{note.renote.text}
hr
pre(style='white-space: pre-wrap;') !{JSON.stringify(note, null, 2)}

View File

@ -39,6 +39,7 @@
"eventemitter3": "5.0.0",
"gsap": "3.11.5",
"idb-keyval": "6.2.0",
"iframe-resizer": "^4.3.6",
"insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2",
"json5": "2.2.3",
@ -97,6 +98,7 @@
"@types/estree": "1.0.0",
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1",
"@types/iframe-resizer": "^3.5.9",
"@types/matter-js": "0.18.2",
"@types/micromatch": "3.1.1",
"@types/node": "18.15.11",

View File

@ -0,0 +1,125 @@
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}:where([hidden]:not([hidden='until-found'])){display:none!important}
html,body {
background-color: transparent;
}
#container {
padding: 1.5rem 2rem;
border-radius: var(--radius);
border: 1px solid var(--divider);
background: var(--bg);
}
#instance-info img {
height: 2.5rem;
width: auto;
}
.custom-emoji {
height: 2em;
width: auto;
display: inline-block;
vertical-align: center;
transition: transform .2s ease;
&:hover {
transform: scale(1.2);
}
}
#error {
padding: 0 0 3.5rem;
text-align: center;
#instance-info {
text-align: end;
}
img.main {
max-width: 128px;
width: 100%;
height: auto;
border-radius: 16px;
margin-bottom: .5rem;
}
h2 {
font-size: 1.25rem;
margin-bottom: .5rem;
}
p {
}
}
#note {
display: block;
position: relative;
header {
display: flex;
margin-bottom: 1rem;
.wrapper {
#renote {
display: flex;
align-items: center;
margin-bottom: .5rem;
color: var(--renote);
>.avatar {
display: block;
margin-right: 1rem;
>img {
width: 28px;
height: 28px;
border-radius: 50%;
}
}
}
.author {
display: flex;
align-items: center;
>.avatar {
display: block;
margin-right: 1rem;
>img {
width: 54px;
height: 54px;
border-radius: 50%;
}
}
>.user-info {
>.user-name {
font-weight: 700;
font-size: 1.1rem;
}
}
}
}
#instance-info {
flex-shrink: 0;
margin-left: auto;
}
}
main {
font-size: 1.05rem;
#quote {
font-size: .95rem;
padding: .75rem;
margin: 1rem 0;
border-radius: var(--radius);
border: dashed 1px var(--renote);
}
}
}

View File

@ -0,0 +1,82 @@
import { miLocalStorage } from '@/local-storage';
import { version, lang, updateLocale } from '@/config';
import { updateI18n } from '@/i18n';
import { embedInitI18n } from './scripts/embed-i18n';
import '@/style.scss';
import './embed.scss';
import 'iframe-resizer/js/iframeResizer.contentWindow';
import { embedInitLinkAnime } from './scripts/link-anime';
import { parseMfm } from './scripts/parse-mfm';
console.info(`Misskey (Embed Sandbox) v${version}`);
if (_DEV_) {
console.warn('Development mode!!!');
window.addEventListener('error', event => {
console.error(event);
/*
alert({
type: 'error',
title: 'DEV: Unhandled error',
text: event.message
});
*/
});
window.addEventListener('unhandledrejection', event => {
console.error(event);
/*
alert({
type: 'error',
title: 'DEV: Unhandled promise rejection',
text: event.reason
});
*/
});
}
//#region Detect language & fetch translations
const localeVersion = miLocalStorage.getItem('localeVersion');
const localeOutdated = (localeVersion == null || localeVersion !== version);
if (localeOutdated) {
const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
if (res.status === 200) {
const newLocale = await res.text();
const parsedNewLocale = JSON.parse(newLocale);
miLocalStorage.setItem('locale', newLocale);
miLocalStorage.setItem('localeVersion', version);
updateLocale(parsedNewLocale);
updateI18n(parsedNewLocale);
}
}
//#endregion
// タッチデバイスでCSSの:hoverを機能させる
document.addEventListener('touchend', () => {}, { passive: true });
//#region Set lang attr
const html = document.documentElement;
html.setAttribute('lang', lang);
//#endregion
embedInitI18n();
document.querySelectorAll(".mfm").forEach((e) => {
e.innerHTML = parseMfm(e.innerHTML).outerHTML;
});
//#region ロード画面解除
const splash = document.getElementById('splash');
// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
if (splash) splash.addEventListener('transitionend', () => {
splash.remove();
});
if (splash) {
splash.style.opacity = '0';
splash.style.pointerEvents = 'none';
}
//#endregion
embedInitLinkAnime();

View File

@ -0,0 +1,46 @@
import { i18n } from "@/i18n";
/**
* vueページ向け翻訳適用関数
*
* :
* ```html
* <span data-mi-i18n="翻訳key必須" data-mi-i18n-ctx=" *JSON Objectで動的な値を指定任意* "></span>
* ```
*/
export function embedInitI18n() {
const els: NodeListOf<HTMLElement> = document.querySelectorAll("[data-mi-i18n]");
els.forEach((tag: HTMLElement) => {
const key: string[] | null = tag.dataset.miI18n?.split('.') || null;
const translationContext: Record<string, string | number> | null = JSON.parse(tag.dataset.miI18nCtx ?? 'null');
if (!key) {
console.warn("[i18n] Key doesn't exist!", tag);
} else if (translationContext) {
let hasTranslationTarget: boolean = false;
let output: string = key.reduce((o, i) => o[i], i18n.ts);
Object.keys(translationContext).forEach((item) => {
const templateTag: NodeListOf<HTMLElement> = tag.querySelectorAll(`[data-mi-i18n-target="${item}"]`);
if (templateTag.length > 0) {
hasTranslationTarget = true;
templateTag.forEach((target: HTMLElement) => {
target.innerText = translationContext[item].toString();
let parent: HTMLElement = target;
while (parent.parentElement != null && parent.parentElement !== tag) {
if (parent.parentElement != null) {
parent = parent.parentElement;
}
}
output = output.replace(new RegExp(`{\s*${item}\s*}`), parent.outerHTML);
});
}
});
if (!hasTranslationTarget) {
tag.innerText = i18n.t(key.join('.'), translationContext);
} else {
tag.innerHTML = output;
}
} else {
tag.innerText = key.reduce((o, i) => o[i], i18n.ts);
}
});
}

View File

@ -0,0 +1,33 @@
export function embedInitLinkAnime() {
const animeEl: NodeListOf<HTMLElement> = document.querySelectorAll("a.click-anime,button.click-anime");
if (animeEl.length > 0) {
animeEl.forEach((el: HTMLElement) => {
const target = el.children[0];
if (target == null) return;
target.classList.add('_anime_bounce_standBy');
el.addEventListener('mousedown', () => {
target.classList.remove('_anime_bounce');
target.classList.add('_anime_bounce_standBy');
target.classList.add('_anime_bounce_ready');
target.addEventListener('mouseleave', () => {
target.classList.remove('_anime_bounce_ready');
});
});
el.addEventListener('click', () => {
target.classList.add('_anime_bounce');
target.classList.remove('_anime_bounce_ready');
});
el.addEventListener('animationend', () => {
target.classList.remove('_anime_bounce');
target.classList.add('_anime_bounce_standBy');
});
});
}
}

View File

@ -0,0 +1,329 @@
import * as mfm from 'mfm-js';
import { toUnicode } from 'punycode';
import { host as localHost } from '@/config';
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(' ');
interface MfmFn extends mfm.MfmFn {
props: {
name: string;
args: Record<string, any>;
}
};
export function parseMfm(text: string): HTMLDivElement {
const ast = mfm.parse(text);
const el = document.createElement("div");
function genEl(ast: (MfmFn | mfm.MfmNode)[]) {
return ast.map((token: (MfmFn | mfm.MfmNode)) => {
switch (token.type) {
case 'text': {
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
const res: HTMLElement[] = [];
for (const t of text.split('\n')) {
res.push(document.createElement('br'));
const el = document.createElement('span');
el.innerText = t;
res.push(el);
}
res.shift();
return res;
}
case 'bold': {
const el = document.createElement("b");
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'strike': {
const el = document.createElement("del");
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'italic': {
const el = document.createElement("i");
el.style.fontStyle = 'oblique';
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'fn': {
// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
let style;
switch (token.props.name) {
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': {
const el = document.createElement("span");
el.classList.add('mfm-x2');
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
})
return [el];
}
case 'x3': {
const el = document.createElement("span");
el.classList.add('mfm-x3');
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
})
return [el];
}
case 'x4': {
const el = document.createElement("span");
el.classList.add('mfm-x4');
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
})
return [el];
}
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': {
const el = document.createElement("span");
el.classList.add('_mfm_blur_');
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
})
return [el];
}
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});`;
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) {
const el = document.createElement("span");
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
el.innerHTML = `$[${token.props.name} ${el.innerHTML}]`;
return [el];
} else {
const el = document.createElement("span");
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
el.setAttribute('style', `display: inline-block; ${style}`);
return [el];
}
}
case 'small': {
const el = document.createElement("small");
el.style.opacity = '.7';
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'center': {
const el = document.createElement("div");
el.style.textAlign = "center";
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'url': {
const el = document.createElement("a");
el.href = token.props.url;
el.target = '_blank';
el.rel = 'nofollow noopener';
el.innerText = token.props.url;
return [el];
}
case 'link': {
const el = document.createElement("a");
el.href = token.props.url;
el.target = '_blank';
el.rel = 'nofollow noopener';
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'mention': {
const el = document.createElement("a");
const canonical = token.props.host === localHost ? `@${token.props.username}` : `@${token.props.username}@${toUnicode(token.props.host ?? '')}`;
el.href = `/${canonical}`;
el.target = '_blank';
el.rel = 'nofollow noopener';
el.style.display = 'inline-block';
el.style.padding = '4px 8px 4px 4px';
el.style.borderRadius = '999px';
el.style.color = 'var(--mention)';
el.style.fontWeight = '700';
el.innerText = `@${canonical}`;
return [el];
}
case 'hashtag': {
const el = document.createElement("a");
el.href = `/tags/${encodeURIComponent(token.props.hashtag)}`;
el.target = '_blank';
el.rel = 'nofollow noopener';
el.style.color = 'var(--hashtag)';
el.innerText = `#${token.props.hashtag}`;
return [el];
}
case 'blockCode': {
const el = document.createElement('pre');
el.style.overflowX = 'scroll';
el.style.width = '100%';
const elc = document.createElement('code');
elc.innerText = token.props.code;
el.appendChild(elc);
return [el];
}
case 'inlineCode': {
const el = document.createElement('code');
el.innerText = token.props.code;
return [el];
}
case 'quote': {
const el = document.createElement('div');
el.setAttribute('style', QUOTE_STYLE);
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
case 'emojiCode': {
const el = document.createElement('span');
el.classList.add('custom-emoji', 'needs-replacing');
el.innerText = `:${token.props.name}:`;
return [el];
}
case 'unicodeEmoji': {
const el = document.createElement('span');
el.classList.add('emoji');
el.innerText = token.props.emoji;
return [el];
}
case 'mathInline': {
const el = document.createElement('code');
el.innerText = token.props.formula;
return [el];
}
case 'mathBlock': {
const el = document.createElement('code');
el.innerText = token.props.formula;
return [el];
}
case 'search': {
const el = document.createElement('form');
el.action = 'https://www.google.com/search';
el.method = 'GET';
const text = document.createElement('input');
text.type = 'search';
text.value = token.props.query;
el.appendChild(text);
const submit = document.createElement('button');
submit.type = 'submit';
submit.innerHTML = '<i class="ti ti-search"></i><span data-mi-i18n-';
el.appendChild(submit);
return [el];
}
case 'plain': {
const el = document.createElement('span');
genEl(token.children).forEach((e) => {
el.appendChild(e as HTMLElement);
});
return [el];
}
default: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
console.error('unrecognized ast type:', (token as any).type);
return [];
}
}
}).flat(Infinity);
}
genEl(ast).forEach((element) => {
el.appendChild(element as HTMLElement);
});
return el;
}

View File

@ -43,7 +43,7 @@
".eslintrc.js",
"./**/*.ts",
"./**/*.vue"
],
, "src/embed/scripts/parse-mfm.ts" ],
"exclude": [
".storybook/**/*",
]

View File

@ -100,6 +100,7 @@ export function getConfig(): UserConfig {
rollupOptions: {
input: {
app: './src/init.ts',
embed: './src/embed/init.ts'
},
output: {
manualChunks: {

View File

@ -663,6 +663,9 @@ importers:
idb-keyval:
specifier: 6.2.0
version: 6.2.0
iframe-resizer:
specifier: ^4.3.6
version: 4.3.6
insert-text-at-cursor:
specifier: 0.3.0
version: 0.3.0
@ -832,6 +835,9 @@ importers:
'@types/gulp-rename':
specifier: 2.0.1
version: 2.0.1
'@types/iframe-resizer':
specifier: ^3.5.9
version: 3.5.9
'@types/matter-js':
specifier: 0.18.2
version: 0.18.2
@ -6593,6 +6599,10 @@ packages:
/@types/http-cache-semantics@4.0.1:
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
/@types/iframe-resizer@3.5.9:
resolution: {integrity: sha512-RQUBI75F+uXruB95BFpC/8V8lPgJg4MQ6HxOCtAZYBB/h0FNCfrFfb4I+u2pZJIV7sKeszZbFqy1UnGeBMrvsA==}
dev: true
/@types/ioredis@4.28.10:
resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==}
dependencies:
@ -12526,6 +12536,11 @@ packages:
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
/iframe-resizer@4.3.6:
resolution: {integrity: sha512-wz0WodRIF6eP0oGQa5NIP1yrITAZ59ZJvVaVJqJRjaeCtfm461vy2C3us6CKx0e7pooqpIGLpVMSTzrfAjX9Sg==}
engines: {node: '>=0.8.0'}
dev: false
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}