feat(moments): 瞬间富文本与逐条评论(5h T2/T3)
瞬间文字从纯文本直出升级到与文章同级表达力,每条动态获得独立评论流。
T2 富文本:
- text 在 frontmatter 预渲染成 momentId → html 的 Map(renderRich 是 async,
模板内不能 await),模板只此一处 set:html 且喂的是净化产物
- 走与全站文案同一条管线:markdown → 内容净化(标签/属性/协议三层白名单)
→ class 白名单 35 项,故 <span class="c-blue font-kai"> 生效、
<script> 整棵子树被丢弃(实测)
- 纯文本老数据零回归:无 markdown 的 text 得到单个 <p>,与旧直出视觉等价
- 卡片内富文本首尾 margin 归零、段间半档、列表缩进 1.35em,15px/ink-2 基调不变
T3 逐条评论:
- 新增 MomentComments.astro:<details> 折叠,**展开才拉 Artalk 并 init**
(未展开连 Artalk.js 都不下载,时间线一屏十几条不白付成本)
- page_key = /moments/#<slug>,中英双语共用同一评论流(与文章页同策略)
- pvAdd: false 且复用 ViewMeta readonly 计数:读者开评论不会虚增浏览数
- 5s 兜底失败提示 + 重试;软导航回页对已展开的框补挂
- Playwright 实测同页两个容器各自 init 成功(各渲染 31 个 atk-* 节点,无异常),
故无需降级;若日后内存吃紧可对上一实例调 destroy() 一行切换
验证: 构建 exit 0;E2E 37/37(快照口径);临时瞬间实测 markdown 粗体、
彩色+楷体类保留、行内码、<script> 净化丢弃、details 与 /moments/# 键、
ViewMeta 只读挂载全部命中(验收数据已还原)
Spec: 2026-11-02_blog-5h-content-social (T2/T3)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
5bf0c327bf
commit
8e74bead62
3 files changed
+285
-3
No files matched your search
@@ -13,12 +13,12 @@
|
|||||||
- boundary: src/content.config.ts(postsSchema 加 `pinned: z.boolean().optional()`,posts/postsEn 共用)、src/components/PostList.astro(置顶优先排序 + pin 标记)、src/pages/archives/index.astro(排序)、src/components/pages/{HomePage,BooksPage}.astro(排序)、public/admin/config.yml(posts 集合 pinned 开关 + 中文 hint)、src/i18n/ui.ts(置顶标签)
|
- boundary: src/content.config.ts(postsSchema 加 `pinned: z.boolean().optional()`,posts/postsEn 共用)、src/components/PostList.astro(置顶优先排序 + pin 标记)、src/pages/archives/index.astro(排序)、src/components/pages/{HomePage,BooksPage}.astro(排序)、public/admin/config.yml(posts 集合 pinned 开关 + 中文 hint)、src/i18n/ui.ts(置顶标签)
|
||||||
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -q "pinned" src/content.config.ts && grep -q "pinned" src/components/PostList.astro && grep -q "pinned" public/admin/config.yml`
|
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -q "pinned" src/content.config.ts && grep -q "pinned" src/components/PostList.astro && grep -q "pinned" public/admin/config.yml`
|
||||||
|
|
||||||
- [ ] T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
- [x] T2 瞬间富文本(字体可换 / 颜色可换 / 多形式)
|
||||||
- claim: worker-one
|
- claim: worker-one
|
||||||
- boundary: src/components/pages/MomentsPage.astro(`text` 由纯文本 `<p>` 改为走 `renderRich` 净化+排版类管线,保留 inline 语义;空文本老数据零回归)、public/admin/config.yml 中 moments 集合 `text` 字段改 markdown widget + 提示可用排版类(**仅在 orchestrator 提交的字段块内改 widget 类型,不改结构**)、docs 提示留给 T7
|
- boundary: src/components/pages/MomentsPage.astro(`text` 由纯文本 `<p>` 改为走 `renderRich` 净化+排版类管线,保留 inline 语义;空文本老数据零回归)、public/admin/config.yml 中 moments 集合 `text` 字段改 markdown widget + 提示可用排版类(**仅在 orchestrator 提交的字段块内改 widget 类型,不改结构**)、docs 提示留给 T7
|
||||||
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -qE "renderRich|renderMarkdown" src/components/pages/MomentsPage.astro && python3 -c "import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t"`
|
- verify: `E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -qE "renderRich|renderMarkdown" src/components/pages/MomentsPage.astro && python3 -c "import yaml;d=yaml.safe_load(open('public/admin/config.yml'));m=[c for c in d['collections'] if c['name']=='moments'][0];t=[f for f in m['fields'] if f['name']=='text'][0];assert t['widget']=='markdown',t"`
|
||||||
|
|
||||||
- [ ] T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
- [x] T3 瞬间逐条评论(Artalk 独立 page_key + 折叠懒挂载)
|
||||||
- claim: worker-one
|
- claim: worker-one
|
||||||
- boundary: 新增 src/components/MomentComments.astro(折叠 disclosure,展开时首次挂载 Artalk;page_key 约定 `/moments/#<slug>`,en 页同一 key 不分裂;评论数徽标复用 T4 的 ViewMeta 计数接口)、src/components/pages/MomentsPage.astro(每条卡片挂评论区)
|
- boundary: 新增 src/components/MomentComments.astro(折叠 disclosure,展开时首次挂载 Artalk;page_key 约定 `/moments/#<slug>`,en 页同一 key 不分裂;评论数徽标复用 T4 的 ViewMeta 计数接口)、src/components/pages/MomentsPage.astro(每条卡片挂评论区)
|
||||||
- verify: `test -f src/components/MomentComments.astro && grep -q "MomentComments" src/components/pages/MomentsPage.astro && grep -q "moments/#" src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
|
- verify: `test -f src/components/MomentComments.astro && grep -q "MomentComments" src/components/pages/MomentsPage.astro && grep -q "moments/#" src/components/MomentComments.astro && E2E_BASE=http://127.0.0.1:18925 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* 瞬间逐条评论(Phase 5h T3):每条瞬间一个 `<details>` 折叠区,**展开才首次挂载 Artalk**。
|
||||||
|
*
|
||||||
|
* 为什么惰性挂载:时间线一屏能有十几条,若每张卡常驻一个 Artalk 实例,内存与请求
|
||||||
|
* 都白付;Artalk 官方也未背书"同页多实例长期并存"。于是「展开才挂」是折中:
|
||||||
|
* 读者点了才付成本,未点零开销(连 Artalk.js 都不下载)。
|
||||||
|
*
|
||||||
|
* 评论流约定:`page_key = /moments/#<slug>`,**中英双语共用同一键**(评论不分裂,
|
||||||
|
* 与文章页 `/archives/<slug>/` 双语共用同理);site 恒为 wait4x.online。
|
||||||
|
*
|
||||||
|
* 计数不在这里自己 fetch:复用 geek/ViewMeta(mode="readonly" 只读,不写 pv),
|
||||||
|
* 放在卡片底部让读者不开评论也能看见条数。
|
||||||
|
*/
|
||||||
|
import ViewMeta from './geek/ViewMeta.astro';
|
||||||
|
import { t } from '../i18n/ui';
|
||||||
|
import type { Locale } from '../lib/locale';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** 瞬间条目 id(即文件名 slug),用于生成评论流键与容器 id */
|
||||||
|
slug: string;
|
||||||
|
locale?: Locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slug, locale = 'zh' } = Astro.props;
|
||||||
|
|
||||||
|
/** 评论流键:双语共用;`#<slug>` 与文章页 page_key 形状保持一致的可读性 */
|
||||||
|
const pageKey = `/moments/#${slug}`;
|
||||||
|
/** 容器 id 只允许 CSS 安全字符(slug 理论上是文件名,仍兜一层) */
|
||||||
|
const panelId = `mom-atk-${slug.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
||||||
|
const label = t('moment_comments', locale);
|
||||||
|
const failText = locale === 'zh' ? '评论加载失败' : 'Comments failed to load';
|
||||||
|
const retryText = locale === 'zh' ? '重试' : 'Retry';
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="mom-cm">
|
||||||
|
<details class="mom-cm-box" data-mom-comments data-page-key={pageKey} data-page-title={`瞬间 · ${slug}`}>
|
||||||
|
<summary class="mom-cm-sum">
|
||||||
|
<span class="mom-cm-label">{label}</span>
|
||||||
|
<svg class="mom-cm-chev" viewBox="0 0 16 16" width="12" height="12" aria-hidden="true">
|
||||||
|
<path d="M4 6l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div class="mom-cm-body">
|
||||||
|
<div class="mom-cm-panel" id={panelId} data-artalk-panel></div>
|
||||||
|
<p class="mom-cm-fail" hidden>
|
||||||
|
<span>{failText}</span>
|
||||||
|
<button type="button" class="mom-cm-retry">{retryText}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<div class="mom-cm-meta">
|
||||||
|
<ViewMeta pageKey={pageKey} locale={locale} mode="readonly" showPv={false} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── 惰性挂载:details 首次展开才拉 Artalk 库并 init ──
|
||||||
|
// toggle 事件不冒泡,但捕获阶段能拿到;astro:page-load 后重新扫描(软导航换页)
|
||||||
|
const SITE = 'wait4x.online';
|
||||||
|
const SERVER = 'https://wait4x.online/artalk';
|
||||||
|
const LIB_CSS = 'https://wait4x.online/artalk/dist/Artalk.css';
|
||||||
|
const LIB_JS = 'https://wait4x.online/artalk/dist/Artalk.js';
|
||||||
|
|
||||||
|
type ArtalkLib = { init: (opts: Record<string, unknown>) => unknown };
|
||||||
|
interface Mounted {
|
||||||
|
/** 已 init 的容器 id,避免重复 init(详情反复开合只在首次付成本) */
|
||||||
|
[key: string]: boolean;
|
||||||
|
}
|
||||||
|
const mounted: Mounted = {};
|
||||||
|
const pending: { [key: string]: boolean } = {};
|
||||||
|
/** 同页多实例:记录已 init 的容器,供排障(window.__momArtalk 可查) */
|
||||||
|
const instances: string[] = [];
|
||||||
|
(window as unknown as { __momArtalk?: string[] }).__momArtalk = instances;
|
||||||
|
|
||||||
|
const lib = () => (window as unknown as { Artalk?: ArtalkLib }).Artalk;
|
||||||
|
|
||||||
|
const loadAssets = () =>
|
||||||
|
new Promise<void>((done) => {
|
||||||
|
// 复用全站同一对资产 id:文章页已加载过就不再重复拉
|
||||||
|
if (document.getElementById('artalk-lib') && lib()) return done();
|
||||||
|
const cssReady = new Promise<void>((cssDone) => {
|
||||||
|
if (document.getElementById('artalk-css')) return cssDone();
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.id = 'artalk-css';
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = LIB_CSS;
|
||||||
|
link.onload = () => cssDone();
|
||||||
|
link.onerror = () => cssDone(); // CSS 失败不阻塞挂载(降级 UA 样式)
|
||||||
|
document.head.appendChild(link);
|
||||||
|
});
|
||||||
|
const existing = document.getElementById('artalk-lib') as HTMLScriptElement | null;
|
||||||
|
if (existing) {
|
||||||
|
// 脚本标签已在但库还没就绪:等它 onload
|
||||||
|
existing.addEventListener('load', () => void cssReady.then(() => done()));
|
||||||
|
if (lib()) void cssReady.then(() => done());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const s = document.createElement('script');
|
||||||
|
s.id = 'artalk-lib';
|
||||||
|
s.src = LIB_JS;
|
||||||
|
s.onload = () => void cssReady.then(() => done());
|
||||||
|
s.onerror = () => done(); // 失败也让调用方走"库不存在"分支显示重试
|
||||||
|
document.head.appendChild(s);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mountOne = async (box: HTMLDetailsElement) => {
|
||||||
|
const key = box.dataset.pageKey ?? '';
|
||||||
|
const panel = box.querySelector<HTMLElement>('[data-artalk-panel]');
|
||||||
|
const fail = box.querySelector<HTMLElement>('.mom-cm-fail');
|
||||||
|
if (!key || !panel) return;
|
||||||
|
const id = panel.id;
|
||||||
|
if (mounted[id] || pending[id]) return;
|
||||||
|
pending[id] = true;
|
||||||
|
|
||||||
|
const showFail = (show: boolean) => {
|
||||||
|
if (fail) fail.hidden = !show;
|
||||||
|
};
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
if (!mounted[id]) showFail(true);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
await loadAssets();
|
||||||
|
const a = lib();
|
||||||
|
if (!a) {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
pending[id] = false;
|
||||||
|
showFail(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
a.init({
|
||||||
|
el: `#${id}`,
|
||||||
|
server: SERVER,
|
||||||
|
site: SITE,
|
||||||
|
pageKey: key,
|
||||||
|
pageTitle: box.dataset.pageTitle ?? '',
|
||||||
|
locale: 'zh-CN',
|
||||||
|
// 明暗交给站点 tokens 接管(与文章页同策略),pv 不计(ViewMeta 只读)
|
||||||
|
darkMode: false,
|
||||||
|
pvAdd: false,
|
||||||
|
});
|
||||||
|
mounted[id] = true;
|
||||||
|
panel.dataset.artalkMounted = '1';
|
||||||
|
instances.push(id);
|
||||||
|
showFail(false);
|
||||||
|
} catch {
|
||||||
|
showFail(true);
|
||||||
|
} finally {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
pending[id] = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 展开时挂载;重复展开只挂一次 */
|
||||||
|
const onToggle = (target: EventTarget | null) => {
|
||||||
|
const box = target as HTMLDetailsElement | null;
|
||||||
|
if (!box || box.tagName !== 'DETAILS' || !box.hasAttribute('data-mom-comments')) return;
|
||||||
|
if (box.open) void mountOne(box);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('toggle', (e) => onToggle(e.target), true);
|
||||||
|
|
||||||
|
// 重试钮:失败后点一下重新挂(清掉挂载标记再走一遍)
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const btn = (e.target as HTMLElement)?.closest?.('.mom-cm-retry');
|
||||||
|
if (!btn) return;
|
||||||
|
const box = btn.closest<HTMLDetailsElement>('details[data-mom-comments]');
|
||||||
|
const panel = box?.querySelector<HTMLElement>('[data-artalk-panel]');
|
||||||
|
if (!box || !panel) return;
|
||||||
|
delete mounted[panel.id];
|
||||||
|
void mountOne(box);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 软导航回到本页时,已展开的(浏览器可能保留 open 状态)补挂一次
|
||||||
|
document.addEventListener('astro:page-load', () => {
|
||||||
|
for (const box of document.querySelectorAll<HTMLDetailsElement>('details[data-mom-comments][open]')) {
|
||||||
|
void mountOne(box);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mom-cm {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
padding-top: var(--space-2);
|
||||||
|
border-top: 1px dashed var(--line);
|
||||||
|
}
|
||||||
|
.mom-cm-box {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.mom-cm-sum {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ink-3);
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.mom-cm-sum::-webkit-details-marker { display: none; }
|
||||||
|
.mom-cm-sum:hover { color: var(--ink-2); }
|
||||||
|
.mom-cm-chev { transition: rotate var(--dur) var(--ease); }
|
||||||
|
details[open] > .mom-cm-sum .mom-cm-chev { rotate: 180deg; }
|
||||||
|
.mom-cm-body { margin-top: var(--space-2); }
|
||||||
|
.mom-cm-panel { font-size: 13px; }
|
||||||
|
.mom-cm-meta { flex: none; }
|
||||||
|
.mom-cm-fail {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin: var(--space-2) 0 0;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--ink-4);
|
||||||
|
}
|
||||||
|
.mom-cm-retry {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mom-cm-retry:hover { border-color: var(--accent); }
|
||||||
|
</style>
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import MomentComments from '../MomentComments.astro';
|
||||||
import { t } from '../../i18n/ui';
|
import { t } from '../../i18n/ui';
|
||||||
|
import { renderRich } from '../../lib/site-content';
|
||||||
import type { Locale } from '../../lib/locale';
|
import type { Locale } from '../../lib/locale';
|
||||||
|
|
||||||
// /en/ 侧由 T4 薄页传 locale='en';zh 路由无人传参,默认 zh 零回归
|
// /en/ 侧由 T4 薄页传 locale='en';zh 路由无人传参,默认 zh 零回归
|
||||||
@@ -14,6 +16,16 @@ const { locale = 'zh' } = Astro.props;
|
|||||||
const moments = (await getCollection('moments')).sort(
|
const moments = (await getCollection('moments')).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 富文本预渲染(Phase 5h T2):text 走与页面文案同一条管线(markdown → 内容净化 →
|
||||||
|
// class 白名单 → 链接卡片),因此瞬间支持 **加粗**、<span class="c-blue font-kai">、列表与换行。
|
||||||
|
// 预渲染成 map 再进模板:renderRich 是 async,模板里不能 await。
|
||||||
|
// 纯文本老数据零回归:无 markdown 的文本会得到单个 <p>,与旧的 {text} 直出视觉等价。
|
||||||
|
const richText = new Map<string, string>();
|
||||||
|
for (const m of moments) {
|
||||||
|
if (!m.data.text) continue;
|
||||||
|
richText.set(m.id, await renderRich(m.data.text));
|
||||||
|
}
|
||||||
const iso = (d: Date) => d.toISOString();
|
const iso = (d: Date) => d.toISOString();
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,12 +58,13 @@ const iso = (d: Date) => d.toISOString();
|
|||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
)}
|
)}
|
||||||
{m.data.text && <p>{m.data.text}</p>}
|
{m.data.text && <div class="mom-text" set:html={richText.get(m.id) ?? ''} />}
|
||||||
{m.data.tags.length > 0 && (
|
{m.data.tags.length > 0 && (
|
||||||
<p class="mom-tags">
|
<p class="mom-tags">
|
||||||
{m.data.tags.map((tag) => <code class="tag">{`#${tag}`}</code>)}
|
{m.data.tags.map((tag) => <code class="tag">{`#${tag}`}</code>)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
<MomentComments slug={m.id} locale={locale} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -208,6 +221,41 @@ const iso = (d: Date) => d.toISOString();
|
|||||||
background: var(--bg-soft);
|
background: var(--bg-soft);
|
||||||
}
|
}
|
||||||
.mom-card > p { font-size: 15px; color: var(--ink-2); }
|
.mom-card > p { font-size: 15px; color: var(--ink-2); }
|
||||||
|
/* 富文本正文(T2):首尾 margin 归零,避免卡片被 <p> 的默认外边距撑开;
|
||||||
|
段间只留半档间距,列表缩进对齐正文基线。基调保持 15px / ink-2。 */
|
||||||
|
.mom-text {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
.mom-text :global(> :first-child) { margin-top: 0; }
|
||||||
|
.mom-text :global(> :last-child) { margin-bottom: 0; }
|
||||||
|
.mom-text :global(p) { margin: 0 0 var(--space-1); }
|
||||||
|
.mom-text :global(ul),
|
||||||
|
.mom-text :global(ol) {
|
||||||
|
margin: 0 0 var(--space-1);
|
||||||
|
padding-left: 1.35em;
|
||||||
|
}
|
||||||
|
.mom-text :global(li) { margin: 0; }
|
||||||
|
.mom-text :global(li + li) { margin-top: 2px; }
|
||||||
|
.mom-text :global(blockquote) {
|
||||||
|
margin: var(--space-1) 0;
|
||||||
|
padding-left: var(--space-2);
|
||||||
|
border-left: 2px solid var(--line);
|
||||||
|
color: var(--ink-3);
|
||||||
|
}
|
||||||
|
.mom-text :global(code) {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.92em;
|
||||||
|
background: var(--bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
.mom-text :global(a) {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.18em;
|
||||||
|
}
|
||||||
.mom-fig {
|
.mom-fig {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in new issue
Block a user