mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 05:51:44 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cc2e640b0 | |||
| e2fa3065f7 | |||
| 8787cbe85b | |||
| 226d501e9e |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.42.1"
|
"version": "1.42.2"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.42.2 - 2026-03-01
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- `baoyu-markdown-to-html`: inline rendering pipeline (no subprocess), fix CJK emphasis order, enhance modern theme with GFM alerts and improved typography
|
||||||
|
- `baoyu-post-to-wechat`: internalize markdown conversion with modular renderer, add color support, simplify publishing workflow
|
||||||
|
|
||||||
## 1.42.1 - 2026-02-28
|
## 1.42.1 - 2026-02-28
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.42.2 - 2026-03-01
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- `baoyu-markdown-to-html`:内联渲染管线(移除子进程),修复 CJK 强调符号处理顺序,增强 modern 主题(GFM 警告块、排版改进)
|
||||||
|
- `baoyu-post-to-wechat`:内置 Markdown 转换模块化渲染器,新增颜色支持,简化发布流程
|
||||||
|
|
||||||
## 1.42.1 - 2026-02-28
|
## 1.42.1 - 2026-02-28
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
SRC="$REPO_ROOT/skills/baoyu-markdown-to-html/scripts/md/"
|
||||||
|
DEST="$REPO_ROOT/skills/baoyu-post-to-wechat/scripts/md/"
|
||||||
|
|
||||||
|
echo "Syncing: $SRC → $DEST"
|
||||||
|
|
||||||
|
rsync -av --delete \
|
||||||
|
--exclude 'node_modules/' \
|
||||||
|
--exclude 'package-lock.json' \
|
||||||
|
"$SRC" "$DEST"
|
||||||
|
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
cd "$DEST" && npm install
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { writeFile } from 'node:fs/promises';
|
|
||||||
import os from 'node:os';
|
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import { spawnSync } from 'node:child_process';
|
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
import type { StyleConfig, HtmlDocumentMeta } from './md/types.js';
|
||||||
|
import { DEFAULT_STYLE, THEME_STYLE_DEFAULTS } from './md/constants.js';
|
||||||
|
import { loadThemeCss, normalizeThemeCss } from './md/themes.js';
|
||||||
|
import { initRenderer, renderMarkdown, postProcessHtml } from './md/renderer.js';
|
||||||
|
import {
|
||||||
|
buildCss, loadCodeThemeCss, buildHtmlDocument,
|
||||||
|
inlineCss, normalizeInlineCss, modifyHtmlStructure, removeFirstHeading,
|
||||||
|
} from './md/html-builder.js';
|
||||||
|
|
||||||
interface ImageInfo {
|
interface ImageInfo {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
@@ -187,33 +191,23 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
|
|
||||||
const modifiedMarkdown = `---\n${Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n${modifiedBody}`;
|
const modifiedMarkdown = `---\n${Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n${modifiedBody}`;
|
||||||
|
|
||||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'markdown-to-html-'));
|
|
||||||
const tempMdPath = path.join(tempDir, 'temp-article.md');
|
|
||||||
await writeFile(tempMdPath, modifiedMarkdown, 'utf-8');
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const renderScript = path.join(__dirname, 'md', 'render.ts');
|
|
||||||
|
|
||||||
console.error(`[markdown-to-html] Rendering with theme: ${theme}, keepTitle: ${keepTitle}`);
|
console.error(`[markdown-to-html] Rendering with theme: ${theme}, keepTitle: ${keepTitle}`);
|
||||||
|
|
||||||
const args = ['-y', 'bun', renderScript, tempMdPath, '--theme', theme];
|
const themeDefaults = THEME_STYLE_DEFAULTS[theme] ?? {};
|
||||||
if (keepTitle) args.push('--keep-title');
|
const style: StyleConfig = { ...DEFAULT_STYLE, ...themeDefaults };
|
||||||
|
const { baseCss, themeCss } = loadThemeCss(theme);
|
||||||
|
const css = normalizeThemeCss(buildCss(baseCss, themeCss, style));
|
||||||
|
const codeThemeCss = loadCodeThemeCss('github');
|
||||||
|
|
||||||
const result = spawnSync('npx', args, {
|
const renderer = initRenderer({});
|
||||||
stdio: ['inherit', 'pipe', 'pipe'],
|
const { html: baseHtml, readingTime } = renderMarkdown(modifiedMarkdown, renderer);
|
||||||
cwd: baseDir,
|
let htmlContent = postProcessHtml(baseHtml, readingTime, renderer);
|
||||||
});
|
if (!keepTitle) htmlContent = removeFirstHeading(htmlContent);
|
||||||
|
|
||||||
if (result.status !== 0) {
|
const meta: HtmlDocumentMeta = { title, author, description: summary };
|
||||||
const stderr = result.stderr?.toString() || '';
|
const fullHtml = buildHtmlDocument(meta, css, htmlContent, codeThemeCss);
|
||||||
throw new Error(`Render failed: ${stderr}`);
|
const inlinedHtml = normalizeInlineCss(await inlineCss(fullHtml), style);
|
||||||
}
|
const renderedHtml = modifyHtmlStructure(inlinedHtml);
|
||||||
|
|
||||||
const tempHtmlPath = tempMdPath.replace(/\.md$/i, '.html');
|
|
||||||
if (!fs.existsSync(tempHtmlPath)) {
|
|
||||||
throw new Error(`HTML file not generated: ${tempHtmlPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalHtmlPath = markdownPath.replace(/\.md$/i, '.html');
|
const finalHtmlPath = markdownPath.replace(/\.md$/i, '.html');
|
||||||
let backupPath: string | undefined;
|
let backupPath: string | undefined;
|
||||||
@@ -224,11 +218,16 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
fs.renameSync(finalHtmlPath, backupPath);
|
fs.renameSync(finalHtmlPath, backupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.copyFileSync(tempHtmlPath, finalHtmlPath);
|
fs.writeFileSync(finalHtmlPath, renderedHtml, 'utf-8');
|
||||||
|
|
||||||
const contentImages: ImageInfo[] = [];
|
const contentImages: ImageInfo[] = [];
|
||||||
|
let tempDir: string | undefined;
|
||||||
for (const img of images) {
|
for (const img of images) {
|
||||||
const localPath = await resolveImagePath(img.src, baseDir, tempDir);
|
if (!tempDir && (img.src.startsWith('http://') || img.src.startsWith('https://'))) {
|
||||||
|
const os = await import('node:os');
|
||||||
|
tempDir = fs.mkdtempSync(path.join(os.default.tmpdir(), 'markdown-to-html-'));
|
||||||
|
}
|
||||||
|
const localPath = await resolveImagePath(img.src, baseDir, tempDir ?? baseDir);
|
||||||
contentImages.push({
|
contentImages.push({
|
||||||
placeholder: img.placeholder,
|
placeholder: img.placeholder,
|
||||||
localPath,
|
localPath,
|
||||||
@@ -236,12 +235,12 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let htmlContent = fs.readFileSync(finalHtmlPath, 'utf-8');
|
let finalContent = fs.readFileSync(finalHtmlPath, 'utf-8');
|
||||||
for (const img of contentImages) {
|
for (const img of contentImages) {
|
||||||
const imgTag = `<img src="${img.placeholder}" data-local-path="${img.localPath}" style="display: block; width: 100%; margin: 1.5em auto;">`;
|
const imgTag = `<img src="${img.originalPath}" data-local-path="${img.localPath}" style="display: block; width: 100%; margin: 1.5em auto;">`;
|
||||||
htmlContent = htmlContent.replace(img.placeholder, imgTag);
|
finalContent = finalContent.replace(img.placeholder, imgTag);
|
||||||
}
|
}
|
||||||
fs.writeFileSync(finalHtmlPath, htmlContent, 'utf-8');
|
fs.writeFileSync(finalHtmlPath, finalContent, 'utf-8');
|
||||||
|
|
||||||
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
|
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
|
||||||
|
|
||||||
|
|||||||
@@ -400,10 +400,10 @@ export function renderMarkdown(raw: string, renderer: RendererAPI): {
|
|||||||
html: string;
|
html: string;
|
||||||
readingTime: ReadTimeResults;
|
readingTime: ReadTimeResults;
|
||||||
} {
|
} {
|
||||||
const preprocessed = preprocessCjkEmphasis(raw);
|
|
||||||
const { markdownContent, readingTime: readingTimeResult } =
|
const { markdownContent, readingTime: readingTimeResult } =
|
||||||
renderer.parseFrontMatterAndContent(preprocessed);
|
renderer.parseFrontMatterAndContent(raw);
|
||||||
const html = marked.parse(markdownContent) as string;
|
const preprocessed = preprocessCjkEmphasis(markdownContent);
|
||||||
|
const html = marked.parse(preprocessed) as string;
|
||||||
return { html, readingTime: readingTimeResult };
|
return { html, readingTime: readingTimeResult };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MD 现代主题 (modern)
|
* MD 现代主题 (modern)
|
||||||
* 大圆角、药丸形标题、宽松行距、现代感
|
* 大圆角、药丸形标题、宽松行距、现代感
|
||||||
|
* 如需使用主题色,请使用 var(--md-primary-color) 代替颜色值
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ==================== 容器样式覆盖 ==================== */
|
/* ==================== 容器样式覆盖 ==================== */
|
||||||
@@ -9,6 +10,8 @@ container {
|
|||||||
font-family: var(--md-font-family);
|
font-family: var(--md-font-family);
|
||||||
font-size: var(--md-font-size);
|
font-size: var(--md-font-size);
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
font-weight: 400;
|
||||||
background-color: var(--md-container-bg);
|
background-color: var(--md-container-bg);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.01);
|
border: 1px solid rgba(255, 255, 255, 0.01);
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
@@ -44,15 +47,22 @@ h2 {
|
|||||||
color: var(--md-primary-color);
|
color: var(--md-primary-color);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.578px;
|
||||||
|
line-height: 1.7;
|
||||||
border-bottom: 2px solid var(--md-accent-color);
|
border-bottom: 2px solid var(--md-accent-color);
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 三级标题 ==================== */
|
/* ==================== 三级标题 ==================== */
|
||||||
h3 {
|
h3 {
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid var(--md-primary-color);
|
||||||
|
border-radius: 2px;
|
||||||
margin: 0 8px 10px;
|
margin: 0 8px 10px;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 四级标题 ==================== */
|
/* ==================== 四级标题 ==================== */
|
||||||
@@ -67,7 +77,7 @@ h4 {
|
|||||||
h5 {
|
h5 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 8px 10px;
|
margin: 0 8px 10px;
|
||||||
padding: 4px 10px;
|
padding: 4px 12px;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
background: rgba(255, 255, 255, 0.7);
|
background: rgba(255, 255, 255, 0.7);
|
||||||
border: 1px solid rgb(189, 224, 254);
|
border: 1px solid rgb(189, 224, 254);
|
||||||
@@ -87,86 +97,300 @@ h6 {
|
|||||||
/* ==================== 段落 ==================== */
|
/* ==================== 段落 ==================== */
|
||||||
p {
|
p {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
letter-spacing: 0px;
|
letter-spacing: 0px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 引用块 ==================== */
|
/* ==================== 引用块 ==================== */
|
||||||
blockquote {
|
blockquote {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
padding: 15px 12px;
|
padding: 15px 0;
|
||||||
|
margin: 12px 0;
|
||||||
border-left: 7px solid var(--md-accent-color);
|
border-left: 7px solid var(--md-accent-color);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
background-color: var(--blockquote-background);
|
background-color: var(--blockquote-background);
|
||||||
margin: 12px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote > p {
|
blockquote > p {
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
font-size: 14px;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== GFM 警告块 ==================== */
|
||||||
|
.alert-title-note,
|
||||||
|
.alert-title-tip,
|
||||||
|
.alert-title-info,
|
||||||
|
.alert-title-important,
|
||||||
|
.alert-title-warning,
|
||||||
|
.alert-title-caution,
|
||||||
|
.alert-title-abstract,
|
||||||
|
.alert-title-summary,
|
||||||
|
.alert-title-tldr,
|
||||||
|
.alert-title-todo,
|
||||||
|
.alert-title-success,
|
||||||
|
.alert-title-done,
|
||||||
|
.alert-title-question,
|
||||||
|
.alert-title-help,
|
||||||
|
.alert-title-faq,
|
||||||
|
.alert-title-failure,
|
||||||
|
.alert-title-fail,
|
||||||
|
.alert-title-missing,
|
||||||
|
.alert-title-danger,
|
||||||
|
.alert-title-error,
|
||||||
|
.alert-title-bug,
|
||||||
|
.alert-title-example,
|
||||||
|
.alert-title-quote,
|
||||||
|
.alert-title-cite {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-note {
|
||||||
|
color: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-tip {
|
||||||
|
color: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-info {
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-important {
|
||||||
|
color: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-warning {
|
||||||
|
color: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-caution {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-abstract,
|
||||||
|
.alert-title-summary,
|
||||||
|
.alert-title-tldr {
|
||||||
|
color: #00bfff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-todo {
|
||||||
|
color: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-success,
|
||||||
|
.alert-title-done {
|
||||||
|
color: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-question,
|
||||||
|
.alert-title-help,
|
||||||
|
.alert-title-faq {
|
||||||
|
color: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-failure,
|
||||||
|
.alert-title-fail,
|
||||||
|
.alert-title-missing {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-danger,
|
||||||
|
.alert-title-error {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-bug {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-example {
|
||||||
|
color: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-quote,
|
||||||
|
.alert-title-cite {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GFM Alert SVG 图标颜色 */
|
||||||
|
.alert-icon-note {
|
||||||
|
fill: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-tip {
|
||||||
|
fill: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-info {
|
||||||
|
fill: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-important {
|
||||||
|
fill: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-warning {
|
||||||
|
fill: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-caution {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-abstract,
|
||||||
|
.alert-icon-summary,
|
||||||
|
.alert-icon-tldr {
|
||||||
|
fill: #00bfff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-todo {
|
||||||
|
fill: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-success,
|
||||||
|
.alert-icon-done {
|
||||||
|
fill: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-question,
|
||||||
|
.alert-icon-help,
|
||||||
|
.alert-icon-faq {
|
||||||
|
fill: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-failure,
|
||||||
|
.alert-icon-fail,
|
||||||
|
.alert-icon-missing {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-danger,
|
||||||
|
.alert-icon-error {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-bug {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-example {
|
||||||
|
fill: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-quote,
|
||||||
|
.alert-icon-cite {
|
||||||
|
fill: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==================== 代码块 ==================== */
|
/* ==================== 代码块 ==================== */
|
||||||
pre.code__pre,
|
pre.code__pre,
|
||||||
.hljs.code__pre {
|
.hljs.code__pre {
|
||||||
|
font-size: 90%;
|
||||||
|
overflow-x: auto;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 10px 8px;
|
||||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
|
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 图片 ==================== */
|
/* ==================== 图片 ==================== */
|
||||||
img {
|
img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0.1em auto 0.5em;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin: 5px auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 列表 ==================== */
|
/* ==================== 列表 ==================== */
|
||||||
ol {
|
ol {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
margin: 15px 0;
|
margin-left: 0;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: circle;
|
||||||
padding-left: 0;
|
padding-left: 1em;
|
||||||
margin: 15px 0;
|
margin-left: 0;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin: 0.2em 0;
|
display: block;
|
||||||
|
margin: 0.2em 8px;
|
||||||
color: hsl(var(--foreground));
|
color: hsl(var(--foreground));
|
||||||
font-size: 15px;
|
}
|
||||||
|
|
||||||
|
/* ==================== 脚注 ==================== */
|
||||||
|
p.footnotes {
|
||||||
|
margin: 0.5em 8px;
|
||||||
|
font-size: 80%;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 图表 ==================== */
|
||||||
|
figure {
|
||||||
|
margin: 1.5em 8px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption,
|
||||||
|
.md-figcaption {
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 分隔线 ==================== */
|
/* ==================== 分隔线 ==================== */
|
||||||
hr {
|
hr {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px 0 0;
|
border-width: 1px 0 0;
|
||||||
border-color: var(--md-primary-color);
|
border-color: var(--md-accent-color);
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 行内代码 ==================== */
|
||||||
|
code {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #d14;
|
||||||
|
background: rgba(27, 31, 35, 0.05);
|
||||||
|
padding: 3px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码块内的 code 标签需要特殊处理(覆盖行内 code 样式) */
|
||||||
|
pre.code__pre > code,
|
||||||
|
.hljs.code__pre > code {
|
||||||
|
display: -webkit-box;
|
||||||
|
padding: 0.5em 1em 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
text-indent: 0;
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
white-space: nowrap;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 强调 ==================== */
|
/* ==================== 强调 ==================== */
|
||||||
strong {
|
em {
|
||||||
color: hsl(var(--foreground));
|
font-style: italic;
|
||||||
font-weight: bold;
|
font-size: inherit;
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 标记高亮 ==================== */
|
|
||||||
.markup-highlight {
|
|
||||||
background-color: hsl(var(--foreground));
|
|
||||||
padding: 10px;
|
|
||||||
color: var(--md-container-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markup-underline {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: var(--md-accent-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 链接 ==================== */
|
/* ==================== 链接 ==================== */
|
||||||
@@ -175,7 +399,67 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== 粗体 ==================== */
|
||||||
|
strong {
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==================== 表格 ==================== */
|
/* ==================== 表格 ==================== */
|
||||||
|
table {
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
font-weight: bold;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
|
border: 1px solid #dfdfdf;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
word-break: keep-all;
|
||||||
background: color-mix(in srgb, var(--md-primary-color) 10%, transparent);
|
background: color-mix(in srgb, var(--md-primary-color) 10%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid #dfdfdf;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== KaTeX 公式 ==================== */
|
||||||
|
.katex-inline {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.katex-block {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 标记高亮 ==================== */
|
||||||
|
.markup-highlight {
|
||||||
|
background-color: var(--md-primary-color);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup-underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: var(--md-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup-wavyline {
|
||||||
|
text-decoration: underline wavy;
|
||||||
|
text-decoration-color: var(--md-primary-color);
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ description: Posts content to WeChat Official Account (微信公众号) via API
|
|||||||
| `scripts/wechat-browser.ts` | Image-text posts (图文) |
|
| `scripts/wechat-browser.ts` | Image-text posts (图文) |
|
||||||
| `scripts/wechat-article.ts` | Article posting via browser (文章) |
|
| `scripts/wechat-article.ts` | Article posting via browser (文章) |
|
||||||
| `scripts/wechat-api.ts` | Article posting via API (文章) |
|
| `scripts/wechat-api.ts` | Article posting via API (文章) |
|
||||||
|
| `scripts/md-to-wechat.ts` | Markdown → WeChat-ready HTML with image placeholders |
|
||||||
| `scripts/check-permissions.ts` | Verify environment & permissions |
|
| `scripts/check-permissions.ts` | Verify environment & permissions |
|
||||||
|
|
||||||
## Preferences (EXTEND.md)
|
## Preferences (EXTEND.md)
|
||||||
@@ -103,7 +104,7 @@ Checks: Chrome, profile isolation, Bun, Accessibility, clipboard, paste keystrok
|
|||||||
| Clipboard copy | Ensure Swift/AppKit available (macOS Xcode CLI tools: `xcode-select --install`) |
|
| Clipboard copy | Ensure Swift/AppKit available (macOS Xcode CLI tools: `xcode-select --install`) |
|
||||||
| Paste keystroke (macOS) | Same as Accessibility fix above |
|
| Paste keystroke (macOS) | Same as Accessibility fix above |
|
||||||
| Paste keystroke (Linux) | Install `xdotool` (X11) or `ydotool` (Wayland) |
|
| Paste keystroke (Linux) | Install `xdotool` (X11) or `ydotool` (Wayland) |
|
||||||
| API credentials | Follow guided setup in Step 5, or manually set in `.baoyu-skills/.env` |
|
| API credentials | Follow guided setup in Step 2, or manually set in `.baoyu-skills/.env` |
|
||||||
|
|
||||||
## Image-Text Posting (图文)
|
## Image-Text Posting (图文)
|
||||||
|
|
||||||
@@ -124,12 +125,10 @@ Copy this checklist and check off items as you complete them:
|
|||||||
Publishing Progress:
|
Publishing Progress:
|
||||||
- [ ] Step 0: Load preferences (EXTEND.md)
|
- [ ] Step 0: Load preferences (EXTEND.md)
|
||||||
- [ ] Step 1: Determine input type
|
- [ ] Step 1: Determine input type
|
||||||
- [ ] Step 2: Check markdown-to-html skill
|
- [ ] Step 2: Select method and configure credentials
|
||||||
- [ ] Step 3: Convert to HTML
|
- [ ] Step 3: Resolve theme/color and validate metadata
|
||||||
- [ ] Step 4: Validate metadata (title, summary, cover)
|
- [ ] Step 4: Publish to WeChat
|
||||||
- [ ] Step 5: Select method and configure credentials
|
- [ ] Step 5: Report completion
|
||||||
- [ ] Step 6: Publish to WeChat
|
|
||||||
- [ ] Step 7: Report completion
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 0: Load Preferences
|
### Step 0: Load Preferences
|
||||||
@@ -149,9 +148,9 @@ Resolve and store these defaults for later steps:
|
|||||||
|
|
||||||
| Input Type | Detection | Action |
|
| Input Type | Detection | Action |
|
||||||
|------------|-----------|--------|
|
|------------|-----------|--------|
|
||||||
| HTML file | Path ends with `.html`, file exists | Skip to Step 4 |
|
| HTML file | Path ends with `.html`, file exists | Skip to Step 3 |
|
||||||
| Markdown file | Path ends with `.md`, file exists | Continue to Step 2 |
|
| Markdown file | Path ends with `.md`, file exists | Continue to Step 2 |
|
||||||
| Plain text | Not a file path, or file doesn't exist | Save to markdown, then Step 2 |
|
| Plain text | Not a file path, or file doesn't exist | Save to markdown, continue to Step 2 |
|
||||||
|
|
||||||
**Plain Text Handling**:
|
**Plain Text Handling**:
|
||||||
|
|
||||||
@@ -169,82 +168,7 @@ mkdir -p "$(pwd)/post-to-wechat/$(date +%Y-%m-%d)"
|
|||||||
- "Understanding AI Models" → `understanding-ai-models`
|
- "Understanding AI Models" → `understanding-ai-models`
|
||||||
- "人工智能的未来" → `ai-future` (translate to English for slug)
|
- "人工智能的未来" → `ai-future` (translate to English for slug)
|
||||||
|
|
||||||
### Step 2: Check Markdown-to-HTML Skill
|
### Step 2: Select Publishing Method and Configure
|
||||||
|
|
||||||
**Skip if**: Input is `.html` file
|
|
||||||
|
|
||||||
**Skill Discovery**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if baoyu-markdown-to-html exists
|
|
||||||
test -f skills/baoyu-markdown-to-html/SKILL.md && echo "found"
|
|
||||||
```
|
|
||||||
|
|
||||||
| Result | Action |
|
|
||||||
|--------|--------|
|
|
||||||
| Found | Read its SKILL.md, continue to Step 3 |
|
|
||||||
| Multiple skills | AskUserQuestion to choose |
|
|
||||||
| Not found | Show installation suggestion |
|
|
||||||
|
|
||||||
**When Not Found**:
|
|
||||||
|
|
||||||
```
|
|
||||||
No markdown-to-html skill found.
|
|
||||||
|
|
||||||
Suggested installation:
|
|
||||||
https://github.com/JimLiu/baoyu-skills/blob/main/skills/baoyu-markdown-to-html/SKILL.md
|
|
||||||
|
|
||||||
Options:
|
|
||||||
A) Cancel - install the skill first
|
|
||||||
B) Continue - provide HTML file manually
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Convert Markdown to HTML
|
|
||||||
|
|
||||||
**Skip if**: Input is `.html` file
|
|
||||||
|
|
||||||
1. **Resolve theme** (first match wins, do NOT ask user if resolved):
|
|
||||||
- CLI `--theme` argument
|
|
||||||
- EXTEND.md `default_theme` (loaded in Step 0)
|
|
||||||
- Fallback: `default`
|
|
||||||
|
|
||||||
2. **Resolve color** (first match wins):
|
|
||||||
- CLI `--color` argument
|
|
||||||
- EXTEND.md `default_color` (loaded in Step 0)
|
|
||||||
- Omit if not set (theme default applies)
|
|
||||||
|
|
||||||
3. **Execute conversion** (using the discovered skill), **always pass `--theme`**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx -y bun ${MD_TO_HTML_SKILL_DIR}/scripts/main.ts <markdown_file> --theme <theme> [--color <color>]
|
|
||||||
```
|
|
||||||
|
|
||||||
**CRITICAL**: Always include `--theme` parameter. Never omit it, even if using `default`. Only include `--color` if explicitly set by user or EXTEND.md.
|
|
||||||
|
|
||||||
3. **Parse JSON output** to get: `htmlPath`, `title`, `author`, `summary`, `contentImages`
|
|
||||||
|
|
||||||
### Step 4: Validate Metadata
|
|
||||||
|
|
||||||
Check extracted metadata from Step 3 (or HTML meta tags if direct HTML input).
|
|
||||||
|
|
||||||
| Field | If Missing |
|
|
||||||
|-------|------------|
|
|
||||||
| Title | Prompt: "Enter title, or press Enter to auto-generate from content" |
|
|
||||||
| Summary | Prompt: "Enter summary, or press Enter to auto-generate (recommended for SEO)" |
|
|
||||||
| Author | Use fallback chain: CLI `--author` → frontmatter `author` → EXTEND.md `default_author` |
|
|
||||||
|
|
||||||
**Auto-Generation Logic**:
|
|
||||||
- **Title**: First H1/H2 heading, or first sentence
|
|
||||||
- **Summary**: First paragraph, truncated to 120 characters
|
|
||||||
|
|
||||||
**Cover Image Check** (required for `article_type=news`):
|
|
||||||
1. Use CLI `--cover` if provided.
|
|
||||||
2. Else use frontmatter (`coverImage`, `featureImage`, `cover`, `image`).
|
|
||||||
3. Else check article directory default path: `imgs/cover.png`.
|
|
||||||
4. Else fallback to first inline content image.
|
|
||||||
5. If still missing, stop and request a cover image before publishing.
|
|
||||||
|
|
||||||
### Step 5: Select Publishing Method and Configure
|
|
||||||
|
|
||||||
**Ask publishing method** (unless specified in EXTEND.md or CLI):
|
**Ask publishing method** (unless specified in EXTEND.md or CLI):
|
||||||
|
|
||||||
@@ -285,14 +209,49 @@ WECHAT_APP_ID=<user_input>
|
|||||||
WECHAT_APP_SECRET=<user_input>
|
WECHAT_APP_SECRET=<user_input>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 6: Publish to WeChat
|
### Step 3: Resolve Theme/Color and Validate Metadata
|
||||||
|
|
||||||
**API method**:
|
1. **Resolve theme** (first match wins, do NOT ask user if resolved):
|
||||||
|
- CLI `--theme` argument
|
||||||
|
- EXTEND.md `default_theme` (loaded in Step 0)
|
||||||
|
- Fallback: `default`
|
||||||
|
|
||||||
|
2. **Resolve color** (first match wins):
|
||||||
|
- CLI `--color` argument
|
||||||
|
- EXTEND.md `default_color` (loaded in Step 0)
|
||||||
|
- Omit if not set (theme default applies)
|
||||||
|
|
||||||
|
3. **Validate metadata** from frontmatter (markdown) or HTML meta tags (HTML input):
|
||||||
|
|
||||||
|
| Field | If Missing |
|
||||||
|
|-------|------------|
|
||||||
|
| Title | Prompt: "Enter title, or press Enter to auto-generate from content" |
|
||||||
|
| Summary | Prompt: "Enter summary, or press Enter to auto-generate (recommended for SEO)" |
|
||||||
|
| Author | Use fallback chain: CLI `--author` → frontmatter `author` → EXTEND.md `default_author` |
|
||||||
|
|
||||||
|
**Auto-Generation Logic**:
|
||||||
|
- **Title**: First H1/H2 heading, or first sentence
|
||||||
|
- **Summary**: First paragraph, truncated to 120 characters
|
||||||
|
|
||||||
|
4. **Cover Image Check** (required for API `article_type=news`):
|
||||||
|
1. Use CLI `--cover` if provided.
|
||||||
|
2. Else use frontmatter (`coverImage`, `featureImage`, `cover`, `image`).
|
||||||
|
3. Else check article directory default path: `imgs/cover.png`.
|
||||||
|
4. Else fallback to first inline content image.
|
||||||
|
5. If still missing, stop and request a cover image before publishing.
|
||||||
|
|
||||||
|
### Step 4: Publish to WeChat
|
||||||
|
|
||||||
|
**CRITICAL**: Publishing scripts handle markdown conversion internally. Do NOT pre-convert markdown to HTML — pass the original markdown file directly. This ensures the API method renders images as `<img>` tags (for API upload) while the browser method uses placeholders (for paste-and-replace workflow).
|
||||||
|
|
||||||
|
**API method** (accepts `.md` or `.html`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx -y bun ${SKILL_DIR}/scripts/wechat-api.ts <html_file> [--title <title>] [--summary <summary>] [--author <author>] [--cover <cover_path>]
|
npx -y bun ${SKILL_DIR}/scripts/wechat-api.ts <file> --theme <theme> [--color <color>] [--title <title>] [--summary <summary>] [--author <author>] [--cover <cover_path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**CRITICAL**: Always include `--theme` parameter. Never omit it, even if using `default`. Only include `--color` if explicitly set by user or EXTEND.md.
|
||||||
|
|
||||||
**`draft/add` payload rules**:
|
**`draft/add` payload rules**:
|
||||||
- Use endpoint: `POST https://api.weixin.qq.com/cgi-bin/draft/add?access_token=ACCESS_TOKEN`
|
- Use endpoint: `POST https://api.weixin.qq.com/cgi-bin/draft/add?access_token=ACCESS_TOKEN`
|
||||||
- `article_type`: `news` (default) or `newspic`
|
- `article_type`: `news` (default) or `newspic`
|
||||||
@@ -304,13 +263,14 @@ npx -y bun ${SKILL_DIR}/scripts/wechat-api.ts <html_file> [--title <title>] [--s
|
|||||||
|
|
||||||
If script parameters do not expose the two comment fields, still ensure final API request body includes resolved values.
|
If script parameters do not expose the two comment fields, still ensure final API request body includes resolved values.
|
||||||
|
|
||||||
**Browser method**:
|
**Browser method** (accepts `--markdown` or `--html`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --markdown <markdown_file> --theme <theme> [--color <color>]
|
||||||
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --html <html_file>
|
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --html <html_file>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 7: Completion Report
|
### Step 5: Completion Report
|
||||||
|
|
||||||
**For API method**, include draft management link:
|
**For API method**, include draft management link:
|
||||||
|
|
||||||
@@ -374,7 +334,7 @@ Files created:
|
|||||||
|---------|------------|---------------|-------------------|
|
|---------|------------|---------------|-------------------|
|
||||||
| Plain text input | ✗ | ✓ | ✓ |
|
| Plain text input | ✗ | ✓ | ✓ |
|
||||||
| HTML input | ✗ | ✓ | ✓ |
|
| HTML input | ✗ | ✓ | ✓ |
|
||||||
| Markdown input | Title/content | ✓ (via skill) | ✓ (via skill) |
|
| Markdown input | Title/content | ✓ | ✓ |
|
||||||
| Multiple images | ✓ (up to 9) | ✓ (inline) | ✓ (inline) |
|
| Multiple images | ✓ (up to 9) | ✓ (inline) | ✓ (inline) |
|
||||||
| Themes | ✗ | ✓ | ✓ |
|
| Themes | ✗ | ✓ | ✓ |
|
||||||
| Auto-generate metadata | ✗ | ✓ | ✓ |
|
| Auto-generate metadata | ✗ | ✓ | ✓ |
|
||||||
@@ -388,16 +348,12 @@ Files created:
|
|||||||
|
|
||||||
**For API method**:
|
**For API method**:
|
||||||
- WeChat Official Account API credentials
|
- WeChat Official Account API credentials
|
||||||
- Guided setup in Step 5, or manually set in `.baoyu-skills/.env`
|
- Guided setup in Step 2, or manually set in `.baoyu-skills/.env`
|
||||||
|
|
||||||
**For Browser method**:
|
**For Browser method**:
|
||||||
- Google Chrome
|
- Google Chrome
|
||||||
- First run: log in to WeChat Official Account (session preserved)
|
- First run: log in to WeChat Official Account (session preserved)
|
||||||
|
|
||||||
**For Markdown conversion**:
|
|
||||||
- A markdown-to-html skill (e.g., `baoyu-markdown-to-html`)
|
|
||||||
- If not installed, the workflow will suggest installation
|
|
||||||
|
|
||||||
**Config File Locations** (priority order):
|
**Config File Locations** (priority order):
|
||||||
1. Environment variables
|
1. Environment variables
|
||||||
2. `<cwd>/.baoyu-skills/.env`
|
2. `<cwd>/.baoyu-skills/.env`
|
||||||
@@ -407,8 +363,7 @@ Files created:
|
|||||||
|
|
||||||
| Issue | Solution |
|
| Issue | Solution |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| No markdown-to-html skill | Install `baoyu-markdown-to-html` from suggested URL |
|
| Missing API credentials | Follow guided setup in Step 2 |
|
||||||
| Missing API credentials | Follow guided setup in Step 5 |
|
|
||||||
| Access token error | Check if API credentials are valid and not expired |
|
| Access token error | Check if API credentials are valid and not expired |
|
||||||
| Not logged in (browser) | First run opens browser - scan QR to log in |
|
| Not logged in (browser) | First run opens browser - scan QR to log in |
|
||||||
| Chrome not found | Set `WECHAT_BROWSER_CHROME_PATH` env var |
|
| Chrome not found | Set `WECHAT_BROWSER_CHROME_PATH` env var |
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|||||||
return { frontmatter, body: match[2]! };
|
return { frontmatter, body: match[2]! };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function convertMarkdown(markdownPath: string, options?: { title?: string; theme?: string }): Promise<ParsedResult> {
|
export async function convertMarkdown(markdownPath: string, options?: { title?: string; theme?: string; color?: string }): Promise<ParsedResult> {
|
||||||
const baseDir = path.dirname(markdownPath);
|
const baseDir = path.dirname(markdownPath);
|
||||||
const content = fs.readFileSync(markdownPath, 'utf-8');
|
const content = fs.readFileSync(markdownPath, 'utf-8');
|
||||||
const theme = options?.theme ?? 'default';
|
const theme = options?.theme ?? 'default';
|
||||||
@@ -183,9 +183,12 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const renderScript = path.join(__dirname, 'md', 'render.ts');
|
const renderScript = path.join(__dirname, 'md', 'render.ts');
|
||||||
|
|
||||||
console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}`);
|
const renderArgs = ['-y', 'bun', renderScript, tempMdPath, '--theme', theme];
|
||||||
|
if (options?.color) renderArgs.push('--color', options.color);
|
||||||
|
|
||||||
const result = spawnSync('npx', ['-y', 'bun', renderScript, tempMdPath, '--theme', theme], {
|
console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}${options?.color ? `, color: ${options.color}` : ''}`);
|
||||||
|
|
||||||
|
const result = spawnSync('npx', renderArgs, {
|
||||||
stdio: ['inherit', 'pipe', 'pipe'],
|
stdio: ['inherit', 'pipe', 'pipe'],
|
||||||
cwd: baseDir,
|
cwd: baseDir,
|
||||||
});
|
});
|
||||||
@@ -227,7 +230,8 @@ Usage:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--title <title> Override title
|
--title <title> Override title
|
||||||
--theme <name> Theme name (default, grace, simple)
|
--theme <name> Theme name (default, grace, simple, modern)
|
||||||
|
--color <name|hex> Primary color (blue, green, vermilion, etc. or hex)
|
||||||
--help Show this help
|
--help Show this help
|
||||||
|
|
||||||
Output JSON format:
|
Output JSON format:
|
||||||
@@ -246,6 +250,7 @@ Output JSON format:
|
|||||||
Example:
|
Example:
|
||||||
npx -y bun md-to-wechat.ts article.md
|
npx -y bun md-to-wechat.ts article.md
|
||||||
npx -y bun md-to-wechat.ts article.md --theme grace
|
npx -y bun md-to-wechat.ts article.md --theme grace
|
||||||
|
npx -y bun md-to-wechat.ts article.md --theme modern --color blue
|
||||||
`);
|
`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
@@ -259,6 +264,7 @@ async function main(): Promise<void> {
|
|||||||
let markdownPath: string | undefined;
|
let markdownPath: string | undefined;
|
||||||
let title: string | undefined;
|
let title: string | undefined;
|
||||||
let theme: string | undefined;
|
let theme: string | undefined;
|
||||||
|
let color: string | undefined;
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
const arg = args[i]!;
|
const arg = args[i]!;
|
||||||
@@ -266,6 +272,8 @@ async function main(): Promise<void> {
|
|||||||
title = args[++i];
|
title = args[++i];
|
||||||
} else if (arg === '--theme' && args[i + 1]) {
|
} else if (arg === '--theme' && args[i + 1]) {
|
||||||
theme = args[++i];
|
theme = args[++i];
|
||||||
|
} else if (arg === '--color' && args[i + 1]) {
|
||||||
|
color = args[++i];
|
||||||
} else if (!arg.startsWith('-')) {
|
} else if (!arg.startsWith('-')) {
|
||||||
markdownPath = arg;
|
markdownPath = arg;
|
||||||
}
|
}
|
||||||
@@ -281,7 +289,7 @@ async function main(): Promise<void> {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await convertMarkdown(markdownPath, { title, theme });
|
const result = await convertMarkdown(markdownPath, { title, theme, color });
|
||||||
console.log(JSON.stringify(result, null, 2));
|
console.log(JSON.stringify(result, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import type { CliOptions, ThemeName } from "./types.js";
|
||||||
|
import {
|
||||||
|
FONT_FAMILY_MAP,
|
||||||
|
FONT_SIZE_OPTIONS,
|
||||||
|
COLOR_PRESETS,
|
||||||
|
CODE_BLOCK_THEMES,
|
||||||
|
} from "./constants.js";
|
||||||
|
import { THEME_NAMES } from "./themes.js";
|
||||||
|
import { loadExtendConfig } from "./extend-config.js";
|
||||||
|
|
||||||
|
export function printUsage(): void {
|
||||||
|
console.error(
|
||||||
|
[
|
||||||
|
"Usage:",
|
||||||
|
" npx tsx src/md/render.ts <markdown_file> [options]",
|
||||||
|
"",
|
||||||
|
"Options:",
|
||||||
|
` --theme <name> Theme (${THEME_NAMES.join(", ")})`,
|
||||||
|
` --color <name|hex> Primary color: ${Object.keys(COLOR_PRESETS).join(", ")}, or hex`,
|
||||||
|
` --font-family <name> Font: ${Object.keys(FONT_FAMILY_MAP).join(", ")}, or CSS value`,
|
||||||
|
` --font-size <N> Font size: ${FONT_SIZE_OPTIONS.join(", ")} (default: 16px)`,
|
||||||
|
` --code-theme <name> Code highlight theme (default: github)`,
|
||||||
|
` --mac-code-block Show Mac-style code block header`,
|
||||||
|
` --line-number Show line numbers in code blocks`,
|
||||||
|
` --cite Enable footnote citations`,
|
||||||
|
` --count Show reading time / word count`,
|
||||||
|
` --legend <value> Image caption: title-alt, alt-title, title, alt, none`,
|
||||||
|
` --keep-title Keep the first heading in output`,
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgValue(argv: string[], i: number, flag: string): string | null {
|
||||||
|
const arg = argv[i]!;
|
||||||
|
if (arg.includes("=")) {
|
||||||
|
return arg.slice(flag.length + 1);
|
||||||
|
}
|
||||||
|
const next = argv[i + 1];
|
||||||
|
return next ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFontFamily(value: string): string {
|
||||||
|
return FONT_FAMILY_MAP[value] ?? value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveColor(value: string): string {
|
||||||
|
return COLOR_PRESETS[value] ?? value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseArgs(argv: string[]): CliOptions | null {
|
||||||
|
const ext = loadExtendConfig();
|
||||||
|
|
||||||
|
let inputPath = "";
|
||||||
|
let theme: ThemeName = ext.default_theme ?? "default";
|
||||||
|
let keepTitle = ext.keep_title ?? false;
|
||||||
|
let primaryColor: string | undefined = ext.default_color ? resolveColor(ext.default_color) : undefined;
|
||||||
|
let fontFamily: string | undefined = ext.default_font_family ? resolveFontFamily(ext.default_font_family) : undefined;
|
||||||
|
let fontSize: string | undefined = ext.default_font_size ?? undefined;
|
||||||
|
let codeTheme = ext.default_code_theme ?? "github";
|
||||||
|
let isMacCodeBlock = ext.mac_code_block ?? true;
|
||||||
|
let isShowLineNumber = ext.show_line_number ?? false;
|
||||||
|
let citeStatus = ext.cite ?? false;
|
||||||
|
let countStatus = ext.count ?? false;
|
||||||
|
let legend = ext.legend ?? "alt";
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const arg = argv[i]!;
|
||||||
|
|
||||||
|
if (!arg.startsWith("--") && !inputPath) {
|
||||||
|
inputPath = arg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--help" || arg === "-h") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--keep-title") { keepTitle = true; continue; }
|
||||||
|
if (arg === "--mac-code-block") { isMacCodeBlock = true; continue; }
|
||||||
|
if (arg === "--no-mac-code-block") { isMacCodeBlock = false; continue; }
|
||||||
|
if (arg === "--line-number") { isShowLineNumber = true; continue; }
|
||||||
|
if (arg === "--cite") { citeStatus = true; continue; }
|
||||||
|
if (arg === "--count") { countStatus = true; continue; }
|
||||||
|
|
||||||
|
if (arg === "--theme" || arg.startsWith("--theme=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--theme");
|
||||||
|
if (!val) { console.error("Missing value for --theme"); return null; }
|
||||||
|
theme = val as ThemeName;
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--color" || arg.startsWith("--color=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--color");
|
||||||
|
if (!val) { console.error("Missing value for --color"); return null; }
|
||||||
|
primaryColor = resolveColor(val);
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--font-family" || arg.startsWith("--font-family=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--font-family");
|
||||||
|
if (!val) { console.error("Missing value for --font-family"); return null; }
|
||||||
|
fontFamily = resolveFontFamily(val);
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--font-size" || arg.startsWith("--font-size=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--font-size");
|
||||||
|
if (!val) { console.error("Missing value for --font-size"); return null; }
|
||||||
|
fontSize = val.endsWith("px") ? val : `${val}px`;
|
||||||
|
if (!FONT_SIZE_OPTIONS.includes(fontSize)) {
|
||||||
|
console.error(`Invalid font size: ${fontSize}. Valid: ${FONT_SIZE_OPTIONS.join(", ")}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--code-theme" || arg.startsWith("--code-theme=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--code-theme");
|
||||||
|
if (!val) { console.error("Missing value for --code-theme"); return null; }
|
||||||
|
codeTheme = val;
|
||||||
|
if (!CODE_BLOCK_THEMES.includes(codeTheme)) {
|
||||||
|
console.error(`Unknown code theme: ${codeTheme}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--legend" || arg.startsWith("--legend=")) {
|
||||||
|
const val = parseArgValue(argv, i, "--legend");
|
||||||
|
if (!val) { console.error("Missing value for --legend"); return null; }
|
||||||
|
const valid = ["title-alt", "alt-title", "title", "alt", "none"];
|
||||||
|
if (!valid.includes(val)) {
|
||||||
|
console.error(`Invalid legend: ${val}. Valid: ${valid.join(", ")}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
legend = val;
|
||||||
|
if (!arg.includes("=")) i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Unknown argument: ${arg}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!THEME_NAMES.includes(theme)) {
|
||||||
|
console.error(`Unknown theme: ${theme}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputPath, theme, keepTitle, primaryColor, fontFamily, fontSize,
|
||||||
|
codeTheme, isMacCodeBlock, isShowLineNumber, citeStatus, countStatus, legend,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: 1c-light
|
||||||
|
Description: Style IDE 1C:Enterprise 8
|
||||||
|
Author: (c) Barilko Vitaliy <barilkovetal@gmail.com>
|
||||||
|
Maintainer: @Diversus23
|
||||||
|
Website: https://softonit.ru/
|
||||||
|
License: see project LICENSE
|
||||||
|
Touched: 2023
|
||||||
|
*/.hljs{color:#00f;background:#fff}.hljs-comment{color:green}.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-function,.hljs-keyword,.hljs-name,.hljs-punctuation,.hljs-selector-tag{color:red}.hljs-params,.hljs-type{color:#00f}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-symbol,.hljs-template-tag{color:#000}.hljs-section,.hljs-title{color:#00f}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:red}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#00f}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#963200}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: a11y-dark
|
||||||
|
Author: @ericwbailey
|
||||||
|
Maintainer: @ericwbailey
|
||||||
|
|
||||||
|
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||||
|
*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: a11y-light
|
||||||
|
Author: @ericwbailey
|
||||||
|
Maintainer: @ericwbailey
|
||||||
|
|
||||||
|
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||||
|
*/.hljs{background:#fefefe;color:#545454}.hljs-comment,.hljs-quote{color:#696969}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#d91e18}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#aa5d00}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:green}.hljs-section,.hljs-title{color:#007faa}.hljs-keyword,.hljs-selector-tag{color:#7928a1}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: Agate
|
||||||
|
Author: (c) Taufik Nurrohman <hi@taufik-nurrohman.com>
|
||||||
|
Maintainer: @taufik-nurrohman
|
||||||
|
Updated: 2021-04-24
|
||||||
|
|
||||||
|
#333
|
||||||
|
#62c8f3
|
||||||
|
#7bd694
|
||||||
|
#888
|
||||||
|
#a2fca2
|
||||||
|
#ade5fc
|
||||||
|
#b8d8a2
|
||||||
|
#c6b4f0
|
||||||
|
#d36363
|
||||||
|
#fc9b9b
|
||||||
|
#fcc28c
|
||||||
|
#ffa
|
||||||
|
#fff
|
||||||
|
*/.hljs{background:#333;color:#fff}.hljs-doctag,.hljs-meta-keyword,.hljs-name,.hljs-strong{font-weight:700}.hljs-code,.hljs-emphasis{font-style:italic}.hljs-section,.hljs-tag{color:#62c8f3}.hljs-selector-class,.hljs-selector-id,.hljs-template-variable,.hljs-variable{color:#ade5fc}.hljs-meta-string,.hljs-string{color:#a2fca2}.hljs-attr,.hljs-quote,.hljs-selector-attr{color:#7bd694}.hljs-tag .hljs-attr{color:inherit}.hljs-attribute,.hljs-title,.hljs-type{color:#ffa}.hljs-number,.hljs-symbol{color:#d36363}.hljs-bullet,.hljs-template-tag{color:#b8d8a2}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#fcc28c}.hljs-code,.hljs-comment,.hljs-formula{color:#888}.hljs-link,.hljs-regexp,.hljs-selector-pseudo{color:#c6b4f0}.hljs-meta{color:#fc9b9b}.hljs-deletion{background:#fc9b9b;color:#333}.hljs-addition{background:#a2fca2;color:#333}.hljs-subst{color:#fff}.hljs a{color:inherit}.hljs a:focus,.hljs a:hover{color:inherit;text-decoration:underline}.hljs mark{background:#555;color:inherit}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: An Old Hope – Star Wars Syntax
|
||||||
|
Author: (c) Gustavo Costa <gusbemacbe@gmail.com>
|
||||||
|
Maintainer: @gusbemacbe
|
||||||
|
|
||||||
|
Original theme - Ocean Dark Theme – by https://github.com/gavsiu
|
||||||
|
Based on Jesse Leite's Atom syntax theme 'An Old Hope'
|
||||||
|
https://github.com/JesseLeite/an-old-hope-syntax-atom
|
||||||
|
*/.hljs{background:#1c1d21;color:#c0c5ce}.hljs-comment,.hljs-quote{color:#b6b18b}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#eb3c54}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#e7ce56}.hljs-attribute{color:#ee7c2b}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#4fb4d7}.hljs-section,.hljs-title{color:#78bb65}.hljs-keyword,.hljs-selector-tag{color:#b45ea4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#a9b7c6;background:#282b2e}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-symbol{color:#6897bb}.hljs-deletion,.hljs-keyword,.hljs-selector-tag{color:#cc7832}.hljs-link,.hljs-template-variable,.hljs-variable{color:#629755}.hljs-comment,.hljs-quote{color:grey}.hljs-meta{color:#bbb529}.hljs-addition,.hljs-attribute,.hljs-string{color:#6a8759}.hljs-section,.hljs-title,.hljs-type{color:#ffc66d}.hljs-name,.hljs-selector-class,.hljs-selector-id{color:#e8bf6a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#434f54}.hljs-subst{color:#434f54}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-selector-tag{color:#00979d}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-literal{color:#d35400}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#00979d}.hljs-deletion,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#005c5f}.hljs-comment{color:rgba(149,165,166,.8)}.hljs-meta .hljs-keyword{color:#728e00}.hljs-meta{color:#434f54}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-function{color:#728e00}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-number{color:#8a7b52}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:#aaa}.hljs-subst{color:#aaa}.hljs-section{color:#fff}.hljs-comment,.hljs-meta,.hljs-quote{color:#444}.hljs-bullet,.hljs-regexp,.hljs-string,.hljs-symbol{color:#fc3}.hljs-addition,.hljs-number{color:#0c6}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-literal,.hljs-template-variable,.hljs-type{color:#32aaee}.hljs-keyword,.hljs-name,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag{color:#64a}.hljs-deletion,.hljs-template-tag,.hljs-title,.hljs-variable{color:#b16}.hljs-doctag,.hljs-section,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-link,.hljs-section,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#888}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#ccc}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-keyword,.hljs-operator,.hljs-pattern-match{color:#f92672}.hljs-function,.hljs-pattern-match .hljs-constructor{color:#61aeee}.hljs-function .hljs-params{color:#a6e22e}.hljs-function .hljs-params .hljs-typing{color:#fd971f}.hljs-module-access .hljs-module{color:#7e57c2}.hljs-constructor{color:#e2b93d}.hljs-constructor .hljs-string{color:#9ccc65}.hljs-comment,.hljs-quote{color:#b18eb1;font-style:italic}.hljs-doctag,.hljs-formula{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#363c69;background:url(./brown-papersq.png) #b7a68e}.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#059}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-link,.hljs-name,.hljs-section,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#2c009f}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#802022}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:#fff}.hljs-comment,.hljs-quote{color:#777}.hljs-built_in,.hljs-bullet,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-regexp,.hljs-symbol,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ab875d}.hljs-attribute,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-title,.hljs-type{color:#9b869b}.hljs-addition,.hljs-keyword,.hljs-selector-tag,.hljs-string{color:#8f9c6c}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-addition,.hljs-meta,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable{color:#756bb1}.hljs-comment,.hljs-quote{color:#636363}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp{color:#31a354}.hljs-deletion,.hljs-variable{color:#88f}.hljs-built_in,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-tag,.hljs-title,.hljs-type{color:#3182bd}.hljs-emphasis{font-style:italic}.hljs-attribute{color:#e6550d}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#ddd;background:#303030}.hljs-keyword,.hljs-link,.hljs-literal,.hljs-section,.hljs-selector-tag{color:#fff}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-name,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#d88}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#979797}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
Theme: Default
|
||||||
|
Description: Original highlight.js style
|
||||||
|
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
|
||||||
|
Maintainer: @highlightjs/core-team
|
||||||
|
Website: https://highlightjs.org/
|
||||||
|
License: see project LICENSE
|
||||||
|
Touched: 2021
|
||||||
|
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: devibeans (dark)
|
||||||
|
Author: @terminaldweller
|
||||||
|
Maintainer: @terminaldweller
|
||||||
|
|
||||||
|
Inspired by vim's jellybeans theme (https://github.com/nanotech/jellybeans.vim)
|
||||||
|
*/.hljs{background:#000;color:#a39e9b}.hljs-attr,.hljs-template-tag{color:#8787d7}.hljs-comment,.hljs-doctag,.hljs-quote{color:#396}.hljs-params{color:#a39e9b}.hljs-regexp{color:#d700ff}.hljs-literal,.hljs-number,.hljs-selector-id,.hljs-tag{color:#ef5350}.hljs-meta,.hljs-meta .hljs-keyword{color:#0087ff}.hljs-code,.hljs-formula,.hljs-keyword,.hljs-link,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-variable{color:#64b5f6}.hljs-built_in,.hljs-deletion,.hljs-title{color:#ff8700}.hljs-attribute,.hljs-function,.hljs-name,.hljs-property,.hljs-section,.hljs-type{color:#ffd75f}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-string,.hljs-subst,.hljs-symbol{color:#558b2f}.hljs-selector-tag{color:#96f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#408080;font-style:italic}.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-subst{color:#954121}.hljs-number{color:#40a070}.hljs-doctag,.hljs-string{color:#219161}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-type{color:#19469d}.hljs-params{color:#00f}.hljs-title{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-template-variable,.hljs-variable{color:teal}.hljs-link,.hljs-regexp{color:#b68}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#0ff;background:navy}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable{color:#ff0}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type,.hljs-variable{color:#fff}.hljs-comment,.hljs-deletion,.hljs-doctag,.hljs-quote{color:#888}.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp{color:#0f0}.hljs-meta{color:teal}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
* Theme: FelipeC
|
||||||
|
* Author: (c) 2021 Felipe Contreras <felipe.contreras@gmail.com>
|
||||||
|
* Website: https://github.com/felipec/vim-felipec
|
||||||
|
*
|
||||||
|
* Autogenerated with vim-felipec's generator.
|
||||||
|
*/.hljs{color:#dedde4;background-color:#1d1c21}.hljs ::selection,.hljs::selection{color:#1d1c21;background-color:#ba9cef}.hljs-code,.hljs-comment,.hljs-quote{color:#9e9da4}.hljs-deletion,.hljs-literal,.hljs-number{color:#f09080}.hljs-doctag,.hljs-meta,.hljs-operator,.hljs-punctuation,.hljs-selector-attr,.hljs-subst,.hljs-template-variable{color:#ffbb7b}.hljs-type{color:#fddb7c}.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-title{color:#c4da7d}.hljs-addition,.hljs-regexp,.hljs-string{color:#93e4a4}.hljs-class,.hljs-property{color:#65e7d1}.hljs-name,.hljs-selector-tag{color:#30c2d8}.hljs-built_in,.hljs-keyword{color:#5fb8f2}.hljs-bullet,.hljs-section{color:#90aafa}.hljs-selector-pseudo{color:#ba9cef}.hljs-attr,.hljs-attribute,.hljs-params,.hljs-variable{color:#d991d2}.hljs-link,.hljs-symbol{color:#ec8dab}.hljs-literal,.hljs-strong,.hljs-title{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#eee;color:#000}.hljs-addition,.hljs-attribute,.hljs-emphasis,.hljs-link{color:#070}.hljs-emphasis{font-style:italic}.hljs-deletion,.hljs-string,.hljs-strong{color:#d14}.hljs-strong{font-weight:700}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-section,.hljs-title{color:#900}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{color:#458}.hljs-template-variable,.hljs-variable{color:#369}.hljs-bullet{color:#970}.hljs-meta{color:#34b}.hljs-code,.hljs-keyword,.hljs-literal,.hljs-number,.hljs-selector-tag{color:#099}.hljs-regexp{background-color:#fff0ff;color:#808}.hljs-symbol{color:#990073}.hljs-name,.hljs-selector-class,.hljs-selector-id,.hljs-tag{color:#070}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: GitHub Dark Dimmed
|
||||||
|
Description: Dark dimmed theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Colors taken from GitHub's CSS
|
||||||
|
*/.hljs{color:#adbac7;background:#22272e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: GitHub Dark
|
||||||
|
Description: Dark theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||||
|
Current colors taken from GitHub's CSS
|
||||||
|
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: GitHub
|
||||||
|
Description: Light theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Outdated base version: https://github.com/primer/github-syntax-light
|
||||||
|
Current colors taken from GitHub's CSS
|
||||||
|
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:silver}.hljs-keyword{color:#ffb871;font-weight:700}.hljs-built_in{color:#ffb871}.hljs-literal{color:#ff8080}.hljs-symbol{color:#58e55a}.hljs-comment{color:#5b995b}.hljs-string{color:#ff0}.hljs-number{color:#ff8080}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-code,.hljs-deletion,.hljs-doctag,.hljs-function,.hljs-link,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-name,.hljs-quote,.hljs-regexp,.hljs-section,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:silver}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-comment,.hljs-quote{color:#800}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-title{color:#008}.hljs-template-variable,.hljs-variable{color:#660}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#080}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-symbol{color:#066}.hljs-attr,.hljs-built_in,.hljs-doctag,.hljs-params,.hljs-title,.hljs-type{color:#606}.hljs-attribute,.hljs-subst{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-selector-class,.hljs-selector-id{color:#9b703f}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#652487;background-image:linear-gradient(160deg,#652487 0,#443ac3 35%,#0174b7 68%,#04988e 100%);color:#e7e4eb}.hljs-subtr{color:#e7e4eb}.hljs-comment,.hljs-doctag,.hljs-meta,.hljs-quote{color:#af8dd9}.hljs-attr,.hljs-regexp,.hljs-selector-id,.hljs-selector-tag,.hljs-tag,.hljs-template-tag{color:#aefbff}.hljs-bullet,.hljs-params,.hljs-selector-class{color:#f19fff}.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-symbol,.hljs-type{color:#17fc95}.hljs-addition,.hljs-link,.hljs-number{color:#c5fe00}.hljs-string{color:#38c0ff}.hljs-addition,.hljs-attribute{color:#e7ff9f}.hljs-template-variable,.hljs-variable{color:#e447ff}.hljs-built_in,.hljs-class,.hljs-formula,.hljs-function,.hljs-name,.hljs-title{color:#ffc800}.hljs-deletion,.hljs-literal,.hljs-selector-pseudo{color:#ff9e44}.hljs-emphasis,.hljs-quote{font-style:italic}.hljs-keyword,.hljs-params,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-template-tag{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#f9ccff;background-image:linear-gradient(295deg,#f9ccff 0,#e6bbf9 11%,#9ec6f9 32%,#55e6ee 60%,#91f5d1 74%,#f9ffbf 98%);color:#250482}.hljs-subtr{color:#01958b}.hljs-comment,.hljs-doctag,.hljs-meta,.hljs-quote{color:#cb7200}.hljs-attr,.hljs-regexp,.hljs-selector-id,.hljs-selector-tag,.hljs-tag,.hljs-template-tag{color:#07bd5f}.hljs-bullet,.hljs-params,.hljs-selector-class{color:#43449f}.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-symbol,.hljs-type{color:#7d2801}.hljs-addition,.hljs-link,.hljs-number{color:#7f0096}.hljs-string{color:#2681ab}.hljs-addition,.hljs-attribute{color:#296562}.hljs-template-variable,.hljs-variable{color:#025c8f}.hljs-built_in,.hljs-class,.hljs-formula,.hljs-function,.hljs-name,.hljs-title{color:#529117}.hljs-deletion,.hljs-literal,.hljs-selector-pseudo{color:#ad13ff}.hljs-emphasis,.hljs-quote{font-style:italic}.hljs-keyword,.hljs-params,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-template-tag{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#333;background:#fff}.hljs-comment,.hljs-quote{color:#777;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number{color:#777}.hljs-doctag,.hljs-formula,.hljs-string{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC)}.hljs-section,.hljs-selector-id,.hljs-title{color:#000;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-name,.hljs-title.class_,.hljs-type{color:#333;font-weight:700}.hljs-tag{color:#333}.hljs-regexp{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==)}.hljs-bullet,.hljs-link,.hljs-symbol{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==)}.hljs-built_in{color:#000;text-decoration:underline}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{color:#fff;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==)}.hljs-addition{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#1d1f21;color:#c5c8c6}.hljs span::selection,.hljs::selection{background:#373b41}.hljs span::-moz-selection,.hljs::-moz-selection{background:#373b41}.hljs-name,.hljs-title{color:#f0c674}.hljs-comment,.hljs-meta,.hljs-meta .hljs-keyword{color:#707880}.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol{color:#c66}.hljs-addition,.hljs-doctag,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#b5bd68}.hljs-attribute,.hljs-code,.hljs-selector-id{color:#b294bb}.hljs-bullet,.hljs-keyword,.hljs-selector-tag,.hljs-tag{color:#81a2be}.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-variable{color:#8abeb7}.hljs-built_in,.hljs-quote,.hljs-section,.hljs-selector-class,.hljs-type{color:#de935f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-comment,.hljs-quote{color:grey;font-style:italic}.hljs-meta{color:olive}.hljs-tag{background:#efefef}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type{font-weight:700;color:navy}.hljs-attribute,.hljs-link,.hljs-number,.hljs-regexp{font-weight:700;color:#00f}.hljs-link,.hljs-number,.hljs-regexp{font-weight:400}.hljs-string{color:green;font-weight:700}.hljs-bullet,.hljs-formula,.hljs-symbol{color:#000;background:#d0eded;font-style:italic}.hljs-doctag{text-decoration:underline}.hljs-template-variable,.hljs-variable{color:#660e7a}.hljs-addition{background:#baeeba}.hljs-deletion{background:#ffc8bd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-title.function_{color:#7a7a43}.hljs-code,.hljs-comment,.hljs-quote{color:#8c8c8c;font-style:italic}.hljs-meta{color:#9e880d}.hljs-section{color:#871094}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-symbol,.hljs-template-tag,.hljs-type,.hljs-variable.language_{color:#0033b3}.hljs-attr,.hljs-property{color:#871094}.hljs-attribute{color:#174ad4}.hljs-number{color:#1750eb}.hljs-regexp{color:#264eff}.hljs-link{text-decoration:underline;color:#006dcc}.hljs-meta .hljs-string,.hljs-string{color:#067d17}.hljs-char.escape_{color:#0037a6}.hljs-doctag{text-decoration:underline}.hljs-template-variable{color:#248f8f}.hljs-addition{background:#bee6be}.hljs-deletion{background:#d6d6d6}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#000;color:#f8f8f8}.hljs-comment,.hljs-meta,.hljs-quote{color:#7c7c7c}.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#96cbfe}.hljs-attribute,.hljs-selector-id{color:#ffffb6}.hljs-addition,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#a8ff60}.hljs-subst{color:#daefa3}.hljs-link,.hljs-regexp{color:#e9c062}.hljs-doctag,.hljs-section,.hljs-title,.hljs-type{color:#ffffb6}.hljs-bullet,.hljs-literal,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#c6c5fe}.hljs-deletion,.hljs-number{color:#ff73fd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#404040}.hljs,.hljs-subst{color:#f0f0f0}.hljs-comment{color:#b5b5b5;font-style:italic}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{color:#f0f0f0;font-weight:700}.hljs-string{color:#97bf0d}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#f0f0f0}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#e2c696}.hljs-built_in,.hljs-literal{color:#97bf0d;font-weight:700}.hljs-addition,.hljs-bullet,.hljs-code{color:#397300}.hljs-class{color:#ce9d4d;font-weight:700}.hljs-section,.hljs-title{color:#df471e}.hljs-title>.hljs-built_in{color:#81bce9;font-weight:400}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-subst{color:#000}.hljs-comment{color:#555;font-style:italic}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{color:#000;font-weight:700}.hljs-string{color:navy}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#000}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#5e1700}.hljs-built_in,.hljs-literal{color:navy;font-weight:700}.hljs-addition,.hljs-bullet,.hljs-code{color:#397300}.hljs-class{color:#6f1c00;font-weight:700}.hljs-section,.hljs-title{color:#fb2c00}.hljs-title>.hljs-built_in{color:teal;font-weight:400}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#221a0f;color:#d3af86}.hljs-comment,.hljs-quote{color:#d6baad}.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#dc3958}.hljs-built_in,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f79a32}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#889b4a}.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#98676a}.hljs-attribute,.hljs-section,.hljs-title{color:#f06431}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fbebd4;color:#84613d}.hljs-comment,.hljs-quote{color:#a57a4c}.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#dc3958}.hljs-built_in,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f79a32}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#889b4a}.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#98676a}.hljs-attribute,.hljs-section,.hljs-title{color:#f06431}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#444;background:#fff}.hljs-name{color:#01a3a3}.hljs-meta,.hljs-tag{color:#789}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#4286f4}.hljs-section,.hljs-title{color:#4286f4;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#62bcbc}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#25c6c6}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#303030;color:#c5c8c6}.hljs-comment{color:#8d8d8d}.hljs-quote{color:#b3c7d8}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#c66}.hljs-built_in,.hljs-literal,.hljs-number,.hljs-subst .hljs-link,.hljs-type{color:#de935f}.hljs-attribute{color:#f0c674}.hljs-addition,.hljs-bullet,.hljs-params,.hljs-string{color:#b5bd68}.hljs-class,.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#be94bb}.hljs-meta,.hljs-section,.hljs-title{color:#81a2be}.hljs-symbol{color:#dbc4d9}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#f4f4f4;color:#000}.hljs-subst{color:#000}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-variable{color:#050}.hljs-comment,.hljs-quote{color:#777}.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp,.hljs-type{color:#800}.hljs-deletion,.hljs-meta{color:#00e}.hljs-built_in,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-tag,.hljs-title{font-weight:700;color:navy}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#eaeef3;color:#00193a}.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title{font-weight:700}.hljs-comment{color:#738191}.hljs-addition,.hljs-built_in,.hljs-literal,.hljs-name,.hljs-quote,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-tag,.hljs-title,.hljs-type{color:#0048ab}.hljs-attribute,.hljs-bullet,.hljs-deletion,.hljs-link,.hljs-meta,.hljs-regexp,.hljs-subst,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#4c81c9}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#23241f;color:#f8f8f2}.hljs-subst,.hljs-tag{color:#f8f8f2}.hljs-emphasis,.hljs-strong{color:#a8a8a2}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:#ae81ff}.hljs-code,.hljs-section,.hljs-selector-class,.hljs-title{color:#a6e22e}.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}.hljs-attr,.hljs-keyword,.hljs-name,.hljs-selector-tag{color:#f92672}.hljs-attribute,.hljs-symbol{color:#66d9ef}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_{color:#f8f8f2}.hljs-addition,.hljs-built_in,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-variable,.hljs-type,.hljs-variable{color:#e6db74}.hljs-comment,.hljs-deletion,.hljs-meta{color:#75715e}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#272822;color:#ddd}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-number,.hljs-selector-tag,.hljs-strong,.hljs-tag{color:#f92672}.hljs-code{color:#66d9ef}.hljs-attr,.hljs-attribute,.hljs-link,.hljs-regexp,.hljs-symbol{color:#bf79db}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-emphasis,.hljs-section,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string,.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#a6e22e}.hljs-class .hljs-title,.hljs-title.class_{color:#fff}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#75715e}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-section,.hljs-selector-id,.hljs-selector-tag,.hljs-title,.hljs-type{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#011627;color:#d6deeb}.hljs-keyword{color:#c792ea;font-style:italic}.hljs-built_in{color:#addb67;font-style:italic}.hljs-type{color:#82aaff}.hljs-literal{color:#ff5874}.hljs-number{color:#f78c6c}.hljs-regexp{color:#5ca7e4}.hljs-string{color:#ecc48d}.hljs-subst{color:#d3423e}.hljs-symbol{color:#82aaff}.hljs-class{color:#ffcb8b}.hljs-function{color:#82aaff}.hljs-title{color:#dcdcaa;font-style:italic}.hljs-params{color:#7fdbca}.hljs-comment{color:#637777;font-style:italic}.hljs-doctag{color:#7fdbca}.hljs-meta,.hljs-meta .hljs-keyword{color:#82aaff}.hljs-meta .hljs-string{color:#ecc48d}.hljs-section{color:#82b1ff}.hljs-attr,.hljs-name,.hljs-tag{color:#7fdbca}.hljs-attribute{color:#80cbc4}.hljs-variable{color:#addb67}.hljs-bullet{color:#d9f5dd}.hljs-code{color:#80cbc4}.hljs-emphasis{color:#c792ea;font-style:italic}.hljs-strong{color:#addb67;font-weight:700}.hljs-formula{color:#c792ea}.hljs-link{color:#ff869a}.hljs-quote{color:#697098;font-style:italic}.hljs-selector-tag{color:#ff6363}.hljs-selector-id{color:#fad430}.hljs-selector-class{color:#addb67;font-style:italic}.hljs-selector-attr,.hljs-selector-pseudo{color:#c792ea;font-style:italic}.hljs-template-tag{color:#c792ea}.hljs-template-variable{color:#addb67}.hljs-addition{color:#addb67ff;font-style:italic}.hljs-deletion{color:#ef535090;font-style:italic}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: nnfx dark
|
||||||
|
Description: a theme inspired by Netscape Navigator/Firefox
|
||||||
|
Author: (c) 2020-2021 Jim Mason <jmason@ibinx.com>
|
||||||
|
Maintainer: @RocketMan
|
||||||
|
License: https://creativecommons.org/licenses/by-sa/4.0 CC BY-SA 4.0
|
||||||
|
Updated: 2021-05-17
|
||||||
|
|
||||||
|
@version 1.1.0
|
||||||
|
*/.hljs{background:#333;color:#fff}.language-xml .hljs-meta,.language-xml .hljs-meta-string{font-weight:700;font-style:italic;color:#69f}.hljs-comment,.hljs-quote{font-style:italic;color:#9c6}.hljs-built_in,.hljs-keyword,.hljs-name{color:#a7a}.hljs-attr,.hljs-name{font-weight:700}.hljs-string{font-weight:400}.hljs-code,.hljs-link,.hljs-meta .hljs-string,.hljs-number,.hljs-regexp,.hljs-string{color:#bce}.hljs-bullet,.hljs-symbol,.hljs-template-variable,.hljs-title,.hljs-variable{color:#d40}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{font-weight:700;color:#96c}.hljs-attr,.hljs-function .hljs-title,.hljs-subst,.hljs-tag,.hljs-title.function_{color:#fff}.hljs-formula{background-color:#eee;font-style:italic}.hljs-addition{background-color:#797}.hljs-deletion{background-color:#c99}.hljs-meta{color:#69f}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{font-weight:700;color:#69f}.hljs-selector-pseudo{font-style:italic}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: nnfx light
|
||||||
|
Description: a theme inspired by Netscape Navigator/Firefox
|
||||||
|
Author: (c) 2020-2021 Jim Mason <jmason@ibinx.com>
|
||||||
|
Maintainer: @RocketMan
|
||||||
|
License: https://creativecommons.org/licenses/by-sa/4.0 CC BY-SA 4.0
|
||||||
|
Updated: 2021-05-17
|
||||||
|
|
||||||
|
@version 1.1.0
|
||||||
|
*/.hljs{background:#fff;color:#000}.language-xml .hljs-meta,.language-xml .hljs-meta-string{font-weight:700;font-style:italic;color:#48b}.hljs-comment,.hljs-quote{font-style:italic;color:#070}.hljs-built_in,.hljs-keyword,.hljs-name{color:#808}.hljs-attr,.hljs-name{font-weight:700}.hljs-string{font-weight:400}.hljs-code,.hljs-link,.hljs-meta .hljs-string,.hljs-number,.hljs-regexp,.hljs-string{color:#00f}.hljs-bullet,.hljs-symbol,.hljs-template-variable,.hljs-title,.hljs-variable{color:#f40}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{font-weight:700;color:#639}.hljs-attr,.hljs-function .hljs-title,.hljs-subst,.hljs-tag,.hljs-title.function_{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-addition{background-color:#beb}.hljs-deletion{background-color:#fbb}.hljs-meta{color:#269}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{font-weight:700;color:#48b}.hljs-selector-pseudo{font-style:italic}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#2e3440}.hljs,.hljs-subst{color:#d8dee9}.hljs-selector-tag{color:#81a1c1}.hljs-selector-id{color:#8fbcbb;font-weight:700}.hljs-selector-attr,.hljs-selector-class{color:#8fbcbb}.hljs-property,.hljs-selector-pseudo{color:#88c0d0}.hljs-addition{background-color:rgba(163,190,140,.5)}.hljs-deletion{background-color:rgba(191,97,106,.5)}.hljs-built_in,.hljs-class,.hljs-type{color:#8fbcbb}.hljs-function,.hljs-function>.hljs-title,.hljs-title.hljs-function{color:#88c0d0}.hljs-keyword,.hljs-literal,.hljs-symbol{color:#81a1c1}.hljs-number{color:#b48ead}.hljs-regexp{color:#ebcb8b}.hljs-string{color:#a3be8c}.hljs-title{color:#8fbcbb}.hljs-params{color:#d8dee9}.hljs-bullet{color:#81a1c1}.hljs-code{color:#8fbcbb}.hljs-emphasis{font-style:italic}.hljs-formula{color:#8fbcbb}.hljs-strong{font-weight:700}.hljs-link:hover{text-decoration:underline}.hljs-comment,.hljs-quote{color:#4c566a}.hljs-doctag{color:#8fbcbb}.hljs-meta,.hljs-meta .hljs-keyword{color:#5e81ac}.hljs-meta .hljs-string{color:#a3be8c}.hljs-attr{color:#8fbcbb}.hljs-attribute{color:#d8dee9}.hljs-name{color:#81a1c1}.hljs-section{color:#88c0d0}.hljs-tag{color:#81a1c1}.hljs-template-variable,.hljs-variable{color:#d8dee9}.hljs-template-tag{color:#5e81ac}.language-abnf .hljs-attribute{color:#88c0d0}.language-abnf .hljs-symbol{color:#ebcb8b}.language-apache .hljs-attribute{color:#88c0d0}.language-apache .hljs-section{color:#81a1c1}.language-arduino .hljs-built_in{color:#88c0d0}.language-aspectj .hljs-meta{color:#d08770}.language-aspectj>.hljs-title{color:#88c0d0}.language-bnf .hljs-attribute{color:#8fbcbb}.language-clojure .hljs-name{color:#88c0d0}.language-clojure .hljs-symbol{color:#ebcb8b}.language-coq .hljs-built_in{color:#88c0d0}.language-cpp .hljs-meta .hljs-string{color:#8fbcbb}.language-css .hljs-built_in{color:#88c0d0}.language-css .hljs-keyword{color:#d08770}.language-diff .hljs-meta,.language-ebnf .hljs-attribute{color:#8fbcbb}.language-glsl .hljs-built_in{color:#88c0d0}.language-groovy .hljs-meta:not(:first-child),.language-haxe .hljs-meta,.language-java .hljs-meta{color:#d08770}.language-ldif .hljs-attribute{color:#8fbcbb}.language-lisp .hljs-name,.language-lua .hljs-built_in,.language-moonscript .hljs-built_in,.language-nginx .hljs-attribute{color:#88c0d0}.language-nginx .hljs-section{color:#5e81ac}.language-pf .hljs-built_in,.language-processing .hljs-built_in{color:#88c0d0}.language-scss .hljs-keyword,.language-stylus .hljs-keyword{color:#81a1c1}.language-swift .hljs-meta{color:#d08770}.language-vim .hljs-built_in{color:#88c0d0;font-style:italic}.language-yaml .hljs-meta{color:#d08770}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e0e2e4;background:#282b2e}.hljs-keyword,.hljs-literal,.hljs-selector-id,.hljs-selector-tag{color:#93c763}.hljs-number{color:#ffcd22}.hljs-attribute{color:#668bb0}.hljs-link,.hljs-regexp{color:#d39745}.hljs-meta{color:#557182}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-emphasis,.hljs-name,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-subst,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable{color:#8cbbad}.hljs-string,.hljs-symbol{color:#ec7600}.hljs-comment,.hljs-deletion,.hljs-quote{color:#818e96}.hljs-selector-class{color:#a082bd}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-class .hljs-title,.hljs-code,.hljs-section,.hljs-title.class_{color:#fff}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e6e6e6;background:#2a2c2d}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}.hljs-comment,.hljs-quote{color:#bbb;font-style:italic}.hljs-params{color:#bbb}.hljs-attr,.hljs-punctuation{color:#e6e6e6}.hljs-meta,.hljs-name,.hljs-selector-tag{color:#ff4b82}.hljs-char.escape_,.hljs-operator{color:#b084eb}.hljs-deletion,.hljs-keyword{color:#ff75b5}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-variable.language_{color:#ff9ac1}.hljs-code,.hljs-formula,.hljs-property,.hljs-section,.hljs-subst,.hljs-title.function_{color:#45a9f9}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-selector-class,.hljs-string,.hljs-symbol,.hljs-title.class_,.hljs-title.class_.inherited__{color:#19f9d8}.hljs-attribute,.hljs-built_in,.hljs-doctag,.hljs-link,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-number,.hljs-punctuation,.hljs-selector-id,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#ffb86c}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#2a2c2d;background:#e6e6e6}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}.hljs-comment,.hljs-quote{color:#676b79;font-style:italic}.hljs-params{color:#676b79}.hljs-attr,.hljs-punctuation{color:#2a2c2d}.hljs-char.escape_,.hljs-meta,.hljs-name,.hljs-operator,.hljs-selector-tag{color:#c56200}.hljs-deletion,.hljs-keyword{color:#d92792}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-variable.language_{color:#cc5e91}.hljs-code,.hljs-formula,.hljs-property,.hljs-section,.hljs-subst,.hljs-title.function_{color:#3787c7}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-selector-class,.hljs-string,.hljs-symbol,.hljs-title.class_,.hljs-title.class_.inherited__{color:#0d7d6c}.hljs-attribute,.hljs-built_in,.hljs-doctag,.hljs-link,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-number,.hljs-selector-id,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#7641bb}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#2f1e2e;color:#a39e9b}.hljs-comment,.hljs-quote{color:#8d8687}.hljs-link,.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ef6155}.hljs-built_in,.hljs-deletion,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f99b15}.hljs-attribute,.hljs-section,.hljs-title{color:#fec418}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#48b685}.hljs-keyword,.hljs-selector-tag{color:#815ba4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#e7e9db;color:#4f424c}.hljs-comment,.hljs-quote{color:#776e71}.hljs-link,.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ef6155}.hljs-built_in,.hljs-deletion,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f99b15}.hljs-attribute,.hljs-section,.hljs-title{color:#fec418}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#48b685}.hljs-keyword,.hljs-selector-tag{color:#815ba4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#dccf8f;background:url(./pojoaque.jpg) left top #181914}.hljs-comment,.hljs-quote{color:#586e75;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#b64926}.hljs-doctag,.hljs-number,.hljs-regexp,.hljs-string{color:#468966}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#ffb03b}.hljs-class .hljs-title,.hljs-tag,.hljs-template-variable,.hljs-title.class_,.hljs-type,.hljs-variable{color:#b58900}.hljs-attribute{color:#b89859}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#cb4b16}.hljs-deletion{color:#dc322f}.hljs-selector-class,.hljs-selector-id{color:#d3a60c}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#ffffdf}.hljs,.hljs-attr,.hljs-function,.hljs-name,.hljs-number,.hljs-params,.hljs-subst,.hljs-type{color:#000}.hljs-addition,.hljs-comment,.hljs-regexp,.hljs-section,.hljs-selector-pseudo{color:#0aa}.hljs-built_in,.hljs-class,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-selector-class{color:#066;font-weight:700}.hljs-code,.hljs-tag,.hljs-title,.hljs-variable{color:#066}.hljs-selector-attr,.hljs-string{color:#0080ff}.hljs-attribute,.hljs-deletion,.hljs-link,.hljs-symbol{color:#924b72}.hljs-literal,.hljs-meta,.hljs-selector-id{color:#924b72;font-weight:700}.hljs-name,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#aaa;background:#000}.hljs-emphasis,.hljs-strong{color:#a8a8a2}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:#f5f}.hljs-code .hljs-selector-class{color:#aaf}.hljs-emphasis,.hljs-stronge,.hljs-type{font-style:italic}.hljs-function,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-symbol{color:#ff5}.hljs-subst,.hljs-tag,.hljs-title{color:#aaa}.hljs-attribute{color:#f55}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_,.hljs-variable{color:#88f}.hljs-addition,.hljs-built_in,.hljs-link,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-type{color:#f5f}.hljs-comment,.hljs-deletion,.hljs-meta{color:#5ff}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-emphasis,.hljs-strong{color:#000}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:navy}.hljs-code .hljs-selector-class{color:purple}.hljs-emphasis,.hljs-stronge,.hljs-type{font-style:italic}.hljs-function,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-symbol{color:olive}.hljs-subst,.hljs-tag,.hljs-title{color:#000}.hljs-attribute{color:maroon}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_,.hljs-variable{color:#0055af}.hljs-addition,.hljs-built_in,.hljs-comment,.hljs-deletion,.hljs-link,.hljs-meta,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-type{color:green}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#444;background:#f0f0f0}.hljs-subst{color:#444}.hljs-comment{color:#888}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-attribute{color:#0e9a00}.hljs-function{color:#99069a}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#0c9a9a}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#3e5915;background:#f6f5b2}.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#059}.hljs-subst{color:#3e5915}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-link,.hljs-section,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#2c009f}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#e60415}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#2d2b57;color:#e3dfff;font-weight:400}.hljs-subst{color:#e3dfff}.hljs-title{color:#fad000;font-weight:400}.hljs-name{color:#a1feff}.hljs-tag{color:#fff}.hljs-attr{color:#f8d000;font-style:italic}.hljs-built_in,.hljs-keyword,.hljs-section,.hljs-selector-tag{color:#fb9e00}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-code,.hljs-deletion,.hljs-quote,.hljs-regexp,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-string,.hljs-symbol,.hljs-template-tag{color:#4cd213}.hljs-meta,.hljs-meta .hljs-string{color:#fb9e00}.hljs-comment{color:#ac65ff}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-selector-tag,.hljs-strong{font-weight:400}.hljs-literal,.hljs-number{color:#fa658d}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#1c1b19;color:#fce8c3}.hljs-literal,.hljs-quote,.hljs-subst{color:#fce8c3}.hljs-symbol,.hljs-type{color:#68a8e4}.hljs-deletion,.hljs-keyword{color:#ef2f27}.hljs-attribute,.hljs-function,.hljs-name,.hljs-section,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-title{color:#fbb829}.hljs-class,.hljs-code,.hljs-property,.hljs-template-variable,.hljs-variable{color:#0aaeb3}.hljs-addition,.hljs-bullet,.hljs-regexp,.hljs-string{color:#98bc37}.hljs-built_in,.hljs-params{color:#ff5c8f}.hljs-selector-tag,.hljs-template-tag{color:#2c78bf}.hljs-comment,.hljs-link,.hljs-meta,.hljs-number{color:#918175}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: StackOverflow Dark
|
||||||
|
Description: Dark theme as used on stackoverflow.com
|
||||||
|
Author: stackoverflow.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Website: https://github.com/StackExchange/Stacks
|
||||||
|
License: MIT
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Updated for @stackoverflow/stacks v0.64.0
|
||||||
|
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
|
||||||
|
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
|
||||||
|
*/.hljs{color:#fff;background:#1c1b1b}.hljs-subst{color:#fff}.hljs-comment{color:#999}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#88aece}.hljs-attribute{color:#c59bc1}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#f08d49}.hljs-selector-class{color:#88aece}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#b5bd68}.hljs-meta,.hljs-selector-pseudo{color:#88aece}.hljs-built_in,.hljs-literal,.hljs-title{color:#f08d49}.hljs-bullet,.hljs-code{color:#ccc}.hljs-meta .hljs-string{color:#b5bd68}.hljs-deletion{color:#de7176}.hljs-addition{color:#76c490}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: StackOverflow Light
|
||||||
|
Description: Light theme as used on stackoverflow.com
|
||||||
|
Author: stackoverflow.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Website: https://github.com/StackExchange/Stacks
|
||||||
|
License: MIT
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Updated for @stackoverflow/stacks v0.64.0
|
||||||
|
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
|
||||||
|
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
|
||||||
|
*/.hljs{color:#2f3337;background:#f6f6f6}.hljs-subst{color:#2f3337}.hljs-comment{color:#656e77}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#015692}.hljs-attribute{color:#803378}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#b75501}.hljs-selector-class{color:#015692}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#54790d}.hljs-meta,.hljs-selector-pseudo{color:#015692}.hljs-built_in,.hljs-literal,.hljs-title{color:#b75501}.hljs-bullet,.hljs-code{color:#535a60}.hljs-meta .hljs-string{color:#54790d}.hljs-deletion{color:#c02d2e}.hljs-addition{color:#2f6f44}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#000;color:#f8f8f8}.hljs-comment,.hljs-quote{color:#aeaeae;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#e28964}.hljs-string{color:#65b042}.hljs-subst{color:#daefa3}.hljs-link,.hljs-regexp{color:#e9c062}.hljs-name,.hljs-section,.hljs-tag,.hljs-title{color:#89bdff}.hljs-class .hljs-title,.hljs-doctag,.hljs-title.class_{text-decoration:underline}.hljs-bullet,.hljs-number,.hljs-symbol{color:#3387cc}.hljs-params,.hljs-template-variable,.hljs-variable{color:#3e87e3}.hljs-attribute{color:#cda869}.hljs-meta{color:#8996a8}.hljs-formula{background-color:#0e2231;color:#f8f8f8;font-style:italic}.hljs-addition{background-color:#253b22;color:#f8f8f8}.hljs-deletion{background-color:#420e09;color:#f8f8f8}.hljs-selector-class{color:#9b703f}.hljs-selector-id{color:#8b98ab}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: Tokyo-night-Dark
|
||||||
|
origin: https://github.com/enkia/tokyo-night-vscode-theme
|
||||||
|
Description: Original highlight.js style
|
||||||
|
Author: (c) Henri Vandersleyen <hvandersleyen@gmail.com>
|
||||||
|
License: see project LICENSE
|
||||||
|
Touched: 2022
|
||||||
|
*/.hljs-comment,.hljs-meta{color:#565f89}.hljs-deletion,.hljs-doctag,.hljs-regexp,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-tag,.hljs-template-tag,.hljs-variable.language_{color:#f7768e}.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-template-variable,.hljs-type,.hljs-variable{color:#ff9e64}.hljs-attribute,.hljs-built_in{color:#e0af68}.hljs-keyword,.hljs-property,.hljs-subst,.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#7dcfff}.hljs-selector-tag{color:#73daca}.hljs-addition,.hljs-bullet,.hljs-quote,.hljs-string,.hljs-symbol{color:#9ece6a}.hljs-code,.hljs-formula,.hljs-section{color:#7aa2f7}.hljs-attr,.hljs-char.escape_,.hljs-keyword,.hljs-name,.hljs-operator{color:#bb9af7}.hljs-punctuation{color:#c0caf5}.hljs{background:#1a1b26;color:#9aa5ce}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: Tokyo-night-light
|
||||||
|
origin: https://github.com/enkia/tokyo-night-vscode-theme
|
||||||
|
Description: Original highlight.js style
|
||||||
|
Author: (c) Henri Vandersleyen <hvandersleyen@gmail.com>
|
||||||
|
License: see project LICENSE
|
||||||
|
Touched: 2022
|
||||||
|
*/.hljs-comment,.hljs-meta{color:#9699a3}.hljs-deletion,.hljs-doctag,.hljs-regexp,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-tag,.hljs-template-tag,.hljs-variable.language_{color:#8c4351}.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-template-variable,.hljs-type,.hljs-variable{color:#965027}.hljs-attribute,.hljs-built_in{color:#8f5e15}.hljs-keyword,.hljs-property,.hljs-subst,.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#0f4b6e}.hljs-selector-tag{color:#33635c}.hljs-addition,.hljs-bullet,.hljs-quote,.hljs-string,.hljs-symbol{color:#485e30}.hljs-code,.hljs-formula,.hljs-section{color:#34548a}.hljs-attr,.hljs-char.escape_,.hljs-keyword,.hljs-name,.hljs-operator{color:#5a4a78}.hljs-punctuation{color:#343b58}.hljs{background:#d5d6db;color:#565a6e}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs-comment,.hljs-quote{color:#7285b7}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ff9da4}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#ffc58f}.hljs-attribute{color:#ffeead}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#d1f1a9}.hljs-section,.hljs-title{color:#bbdaff}.hljs-keyword,.hljs-selector-tag{color:#ebbbff}.hljs{background:#002451;color:#fff}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs-comment,.hljs-quote{color:#969896}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#d54e53}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#e78c45}.hljs-attribute{color:#e7c547}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#b9ca4a}.hljs-section,.hljs-title{color:#7aa6da}.hljs-keyword,.hljs-selector-tag{color:#c397d8}.hljs{background:#000;color:#eaeaea}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-comment,.hljs-quote,.hljs-variable{color:green}.hljs-built_in,.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#00f}.hljs-addition,.hljs-attribute,.hljs-literal,.hljs-section,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type{color:#a31515}.hljs-deletion,.hljs-meta,.hljs-selector-attr,.hljs-selector-pseudo{color:#2b91af}.hljs-doctag{color:grey}.hljs-attr{color:red}.hljs-bullet,.hljs-link,.hljs-symbol{color:#00b0e8}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#1e1e1e;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta .hljs-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.xml .hljs-meta{color:silver}.hljs-comment,.hljs-quote{color:#007400}.hljs-attribute,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#aa0d91}.hljs-template-variable,.hljs-variable{color:#3f6e74}.hljs-code,.hljs-meta .hljs-string,.hljs-string{color:#c41a16}.hljs-link,.hljs-regexp{color:#0e0eff}.hljs-bullet,.hljs-number,.hljs-symbol,.hljs-title{color:#1c00cf}.hljs-meta,.hljs-section{color:#643820}.hljs-built_in,.hljs-class .hljs-title,.hljs-params,.hljs-title.class_,.hljs-type{color:#5c2699}.hljs-attr{color:#836c28}.hljs-subst{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-selector-class,.hljs-selector-id{color:#9b703f}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#eaeaea;background:#000}.hljs-subst{color:#eaeaea}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-type{color:#eaeaea}.hljs-params{color:#da0000}.hljs-literal,.hljs-name,.hljs-number{color:red;font-weight:bolder}.hljs-comment{color:#969896}.hljs-quote,.hljs-selector-id{color:#0ff}.hljs-template-variable,.hljs-title,.hljs-variable{color:#0ff;font-weight:700}.hljs-keyword,.hljs-selector-class,.hljs-symbol{color:#fff000}.hljs-bullet,.hljs-string{color:#0f0}.hljs-section,.hljs-tag{color:#000fff}.hljs-selector-tag{color:#000fff;font-weight:700}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-regexp{color:#f0f}.hljs-meta{color:#fff;font-weight:bolder}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import type { StyleConfig } from "./types.js";
|
||||||
|
|
||||||
|
export const FONT_FAMILY_MAP: Record<string, string> = {
|
||||||
|
sans: `-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif`,
|
||||||
|
serif: `Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif`,
|
||||||
|
"serif-cjk": `"Source Han Serif SC", "Noto Serif CJK SC", "Source Han Serif CN", STSong, SimSun, serif`,
|
||||||
|
mono: `Menlo, Monaco, 'Courier New', monospace`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FONT_SIZE_OPTIONS = ["14px", "15px", "16px", "17px", "18px"];
|
||||||
|
|
||||||
|
export const COLOR_PRESETS: Record<string, string> = {
|
||||||
|
blue: "#0F4C81",
|
||||||
|
green: "#009874",
|
||||||
|
vermilion: "#FA5151",
|
||||||
|
yellow: "#FECE00",
|
||||||
|
purple: "#92617E",
|
||||||
|
sky: "#55C9EA",
|
||||||
|
rose: "#B76E79",
|
||||||
|
olive: "#556B2F",
|
||||||
|
black: "#333333",
|
||||||
|
gray: "#A9A9A9",
|
||||||
|
pink: "#FFB7C5",
|
||||||
|
red: "#A93226",
|
||||||
|
orange: "#D97757",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CODE_BLOCK_THEMES = [
|
||||||
|
"1c-light", "a11y-dark", "a11y-light", "agate", "an-old-hope",
|
||||||
|
"androidstudio", "arduino-light", "arta", "ascetic",
|
||||||
|
"atom-one-dark-reasonable", "atom-one-dark", "atom-one-light",
|
||||||
|
"brown-paper", "codepen-embed", "color-brewer", "dark", "default",
|
||||||
|
"devibeans", "docco", "far", "felipec", "foundation",
|
||||||
|
"github-dark-dimmed", "github-dark", "github", "gml", "googlecode",
|
||||||
|
"gradient-dark", "gradient-light", "grayscale", "hybrid", "idea",
|
||||||
|
"intellij-light", "ir-black", "isbl-editor-dark", "isbl-editor-light",
|
||||||
|
"kimbie-dark", "kimbie-light", "lightfair", "lioshi", "magula",
|
||||||
|
"mono-blue", "monokai-sublime", "monokai", "night-owl", "nnfx-dark",
|
||||||
|
"nnfx-light", "nord", "obsidian", "panda-syntax-dark",
|
||||||
|
"panda-syntax-light", "paraiso-dark", "paraiso-light", "pojoaque",
|
||||||
|
"purebasic", "qtcreator-dark", "qtcreator-light", "rainbow", "routeros",
|
||||||
|
"school-book", "shades-of-purple", "srcery", "stackoverflow-dark",
|
||||||
|
"stackoverflow-light", "sunburst", "tokyo-night-dark", "tokyo-night-light",
|
||||||
|
"tomorrow-night-blue", "tomorrow-night-bright", "vs", "vs2015", "xcode",
|
||||||
|
"xt256",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_STYLE: StyleConfig = {
|
||||||
|
primaryColor: "#0F4C81",
|
||||||
|
fontFamily: FONT_FAMILY_MAP.sans!,
|
||||||
|
fontSize: "16px",
|
||||||
|
foreground: "0 0% 3.9%",
|
||||||
|
blockquoteBackground: "#f7f7f7",
|
||||||
|
accentColor: "#6B7280",
|
||||||
|
containerBg: "transparent",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const THEME_STYLE_DEFAULTS: Record<string, Partial<StyleConfig>> = {
|
||||||
|
default: {
|
||||||
|
primaryColor: COLOR_PRESETS.blue,
|
||||||
|
},
|
||||||
|
grace: {
|
||||||
|
primaryColor: COLOR_PRESETS.purple,
|
||||||
|
},
|
||||||
|
simple: {
|
||||||
|
primaryColor: COLOR_PRESETS.green,
|
||||||
|
},
|
||||||
|
modern: {
|
||||||
|
primaryColor: COLOR_PRESETS.orange,
|
||||||
|
accentColor: "#E4B1A0",
|
||||||
|
containerBg: "rgba(250, 249, 245, 1)",
|
||||||
|
fontFamily: FONT_FAMILY_MAP.sans,
|
||||||
|
fontSize: "15px",
|
||||||
|
blockquoteBackground: "rgba(255, 255, 255, 0.6)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const macCodeSvg = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" width="45px" height="13px" viewBox="0 0 450 130">
|
||||||
|
<ellipse cx="50" cy="65" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2" fill="rgb(237,108,96)" />
|
||||||
|
<ellipse cx="225" cy="65" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2" fill="rgb(247,193,81)" />
|
||||||
|
<ellipse cx="400" cy="65" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2" fill="rgb(100,200,86)" />
|
||||||
|
</svg>
|
||||||
|
`.trim();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import type { ExtendConfig } from "./types.js";
|
||||||
|
|
||||||
|
function extractYamlFrontMatter(content: string): string | null {
|
||||||
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*$/m);
|
||||||
|
return match ? match[1]! : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseExtendYaml(yaml: string): Partial<ExtendConfig> {
|
||||||
|
const config: Partial<ExtendConfig> = {};
|
||||||
|
for (const line of yaml.split("\n")) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||||
|
const colonIdx = trimmed.indexOf(":");
|
||||||
|
if (colonIdx < 0) continue;
|
||||||
|
const key = trimmed.slice(0, colonIdx).trim();
|
||||||
|
let value = trimmed.slice(colonIdx + 1).trim().replace(/^['"]|['"]$/g, "");
|
||||||
|
if (value === "null" || value === "") continue;
|
||||||
|
|
||||||
|
if (key === "default_theme") config.default_theme = value;
|
||||||
|
else if (key === "default_color") config.default_color = value;
|
||||||
|
else if (key === "default_font_family") config.default_font_family = value;
|
||||||
|
else if (key === "default_font_size") config.default_font_size = value.endsWith("px") ? value : `${value}px`;
|
||||||
|
else if (key === "default_code_theme") config.default_code_theme = value;
|
||||||
|
else if (key === "mac_code_block") config.mac_code_block = value === "true";
|
||||||
|
else if (key === "show_line_number") config.show_line_number = value === "true";
|
||||||
|
else if (key === "cite") config.cite = value === "true";
|
||||||
|
else if (key === "count") config.count = value === "true";
|
||||||
|
else if (key === "legend") config.legend = value;
|
||||||
|
else if (key === "keep_title") config.keep_title = value === "true";
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadExtendConfig(): Partial<ExtendConfig> {
|
||||||
|
const paths = [
|
||||||
|
path.join(process.cwd(), ".baoyu-skills", "baoyu-markdown-to-html", "EXTEND.md"),
|
||||||
|
path.join(homedir(), ".baoyu-skills", "baoyu-markdown-to-html", "EXTEND.md"),
|
||||||
|
];
|
||||||
|
for (const p of paths) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(p, "utf-8");
|
||||||
|
const yaml = extractYamlFrontMatter(content);
|
||||||
|
if (!yaml) continue;
|
||||||
|
return parseExtendYaml(yaml);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import type { StyleConfig, HtmlDocumentMeta } from "./types.js";
|
||||||
|
import { DEFAULT_STYLE } from "./constants.js";
|
||||||
|
|
||||||
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const CODE_THEMES_DIR = path.resolve(SCRIPT_DIR, "code-themes");
|
||||||
|
|
||||||
|
export function buildCss(baseCss: string, themeCss: string, style: StyleConfig = DEFAULT_STYLE): string {
|
||||||
|
const variables = `
|
||||||
|
:root {
|
||||||
|
--md-primary-color: ${style.primaryColor};
|
||||||
|
--md-font-family: ${style.fontFamily};
|
||||||
|
--md-font-size: ${style.fontSize};
|
||||||
|
--foreground: ${style.foreground};
|
||||||
|
--blockquote-background: ${style.blockquoteBackground};
|
||||||
|
--md-accent-color: ${style.accentColor};
|
||||||
|
--md-container-bg: ${style.containerBg};
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
max-width: 860px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
return [variables, baseCss, themeCss].join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadCodeThemeCss(themeName: string): string {
|
||||||
|
const filePath = path.join(CODE_THEMES_DIR, `${themeName}.min.css`);
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, "utf-8");
|
||||||
|
} catch {
|
||||||
|
console.error(`Code theme CSS not found: ${filePath}`);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildHtmlDocument(meta: HtmlDocumentMeta, css: string, html: string, codeThemeCss?: string): string {
|
||||||
|
const lines = [
|
||||||
|
"<!doctype html>",
|
||||||
|
"<html>",
|
||||||
|
"<head>",
|
||||||
|
' <meta charset="utf-8" />',
|
||||||
|
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
||||||
|
` <title>${meta.title}</title>`,
|
||||||
|
];
|
||||||
|
if (meta.author) {
|
||||||
|
lines.push(` <meta name="author" content="${meta.author}" />`);
|
||||||
|
}
|
||||||
|
if (meta.description) {
|
||||||
|
lines.push(` <meta name="description" content="${meta.description}" />`);
|
||||||
|
}
|
||||||
|
lines.push(` <style>${css}</style>`);
|
||||||
|
if (codeThemeCss) {
|
||||||
|
lines.push(` <style>${codeThemeCss}</style>`);
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
|
"</head>",
|
||||||
|
"<body>",
|
||||||
|
' <div id="output">',
|
||||||
|
html,
|
||||||
|
" </div>",
|
||||||
|
"</body>",
|
||||||
|
"</html>"
|
||||||
|
);
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inlineCss(html: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const { default: juice } = await import("juice");
|
||||||
|
return juice(html, {
|
||||||
|
inlinePseudoElements: true,
|
||||||
|
preserveImportant: true,
|
||||||
|
resolveCSSVariables: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const detail = error instanceof Error ? error.message : String(error);
|
||||||
|
throw new Error(
|
||||||
|
`Missing dependency "juice" for CSS inlining. Install it first (e.g. "bun add juice" or "npm add juice"). Original error: ${detail}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCssText(cssText: string, style: StyleConfig = DEFAULT_STYLE): string {
|
||||||
|
return cssText
|
||||||
|
.replace(/var\(--md-primary-color\)/g, style.primaryColor)
|
||||||
|
.replace(/var\(--md-font-family\)/g, style.fontFamily)
|
||||||
|
.replace(/var\(--md-font-size\)/g, style.fontSize)
|
||||||
|
.replace(/var\(--blockquote-background\)/g, style.blockquoteBackground)
|
||||||
|
.replace(/var\(--md-accent-color\)/g, style.accentColor)
|
||||||
|
.replace(/var\(--md-container-bg\)/g, style.containerBg)
|
||||||
|
.replace(/hsl\(var\(--foreground\)\)/g, "#3f3f3f")
|
||||||
|
.replace(/--md-primary-color:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--md-font-family:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--md-font-size:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--blockquote-background:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--md-accent-color:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--md-container-bg:\s*[^;"']+;?/g, "")
|
||||||
|
.replace(/--foreground:\s*[^;"']+;?/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeInlineCss(html: string, style: StyleConfig = DEFAULT_STYLE): string {
|
||||||
|
let output = html;
|
||||||
|
output = output.replace(
|
||||||
|
/<style([^>]*)>([\s\S]*?)<\/style>/gi,
|
||||||
|
(_match, attrs: string, cssText: string) =>
|
||||||
|
`<style${attrs}>${normalizeCssText(cssText, style)}</style>`
|
||||||
|
);
|
||||||
|
output = output.replace(
|
||||||
|
/style="([^"]*)"/gi,
|
||||||
|
(_match, cssText: string) => `style="${normalizeCssText(cssText, style)}"`
|
||||||
|
);
|
||||||
|
output = output.replace(
|
||||||
|
/style='([^']*)'/gi,
|
||||||
|
(_match, cssText: string) => `style='${normalizeCssText(cssText, style)}'`
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modifyHtmlStructure(htmlString: string): string {
|
||||||
|
let output = htmlString;
|
||||||
|
const pattern =
|
||||||
|
/<li([^>]*)>([\s\S]*?)(<ul[\s\S]*?<\/ul>|<ol[\s\S]*?<\/ol>)<\/li>/i;
|
||||||
|
while (pattern.test(output)) {
|
||||||
|
output = output.replace(pattern, "<li$1>$2</li>$3");
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFirstHeading(html: string): string {
|
||||||
|
return html.replace(/<h[12][^>]*>[\s\S]*?<\/h[12]>/, "");
|
||||||
|
}
|
||||||
+1395
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"fflate": "^0.8.2",
|
||||||
|
"front-matter": "^4.0.2",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
|
"juice": "^11.0.1",
|
||||||
|
"marked": "^15.0.6",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
|
"remark-cjk-friendly": "^1.1.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-stringify": "^11.0.0",
|
||||||
|
"unified": "^11.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,585 +2,20 @@
|
|||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import type { StyleConfig, HtmlDocumentMeta } from "./types.js";
|
||||||
import frontMatter from "front-matter";
|
import { DEFAULT_STYLE, THEME_STYLE_DEFAULTS } from "./constants.js";
|
||||||
import hljs from "highlight.js/lib/core";
|
import { loadThemeCss, normalizeThemeCss } from "./themes.js";
|
||||||
import { marked, type RendererObject, type Tokens } from "marked";
|
import { parseArgs, printUsage } from "./cli.js";
|
||||||
import readingTime, { type ReadTimeResults } from "reading-time";
|
import { initRenderer, renderMarkdown, postProcessHtml } from "./renderer.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
markedAlert,
|
buildCss,
|
||||||
markedFootnotes,
|
loadCodeThemeCss,
|
||||||
markedInfographic,
|
buildHtmlDocument,
|
||||||
markedMarkup,
|
inlineCss,
|
||||||
markedPlantUML,
|
normalizeInlineCss,
|
||||||
markedRuby,
|
modifyHtmlStructure,
|
||||||
markedSlider,
|
removeFirstHeading,
|
||||||
markedToc,
|
} from "./html-builder.js";
|
||||||
MDKatex,
|
|
||||||
} from "./extensions/index.js";
|
|
||||||
import {
|
|
||||||
COMMON_LANGUAGES,
|
|
||||||
highlightAndFormatCode,
|
|
||||||
} from "./utils/languages.js";
|
|
||||||
|
|
||||||
type ThemeName = string;
|
|
||||||
|
|
||||||
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
const THEME_DIR = path.resolve(SCRIPT_DIR, "themes");
|
|
||||||
const EXTERNAL_THEME_CONFIG_PATH =
|
|
||||||
process.env.MD_THEME_CONFIG_PATH
|
|
||||||
|| "/Users/jimliu/GitHub/md/packages/shared/src/configs/theme.ts";
|
|
||||||
const EXTERNAL_THEME_DIR =
|
|
||||||
process.env.MD_THEME_DIR
|
|
||||||
|| path.resolve(path.dirname(EXTERNAL_THEME_CONFIG_PATH), "theme-css");
|
|
||||||
const FALLBACK_THEMES: ThemeName[] = ["default", "grace", "simple"];
|
|
||||||
|
|
||||||
const DEFAULT_STYLE = {
|
|
||||||
primaryColor: "#0F4C81",
|
|
||||||
fontFamily:
|
|
||||||
"-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif",
|
|
||||||
fontSize: "16px",
|
|
||||||
foreground: "0 0% 3.9%",
|
|
||||||
blockquoteBackground: "#f7f7f7",
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.entries(COMMON_LANGUAGES).forEach(([name, lang]) => {
|
|
||||||
hljs.registerLanguage(name, lang);
|
|
||||||
});
|
|
||||||
|
|
||||||
export { hljs };
|
|
||||||
|
|
||||||
function stripOutputScope(cssContent: string): string {
|
|
||||||
let css = cssContent;
|
|
||||||
css = css.replace(/#output\s*\{/g, "body {");
|
|
||||||
css = css.replace(/#output\s+/g, "");
|
|
||||||
css = css.replace(/^#output\s*/gm, "");
|
|
||||||
return css;
|
|
||||||
}
|
|
||||||
|
|
||||||
function discoverThemesFromDir(dir: string): string[] {
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return fs
|
|
||||||
.readdirSync(dir)
|
|
||||||
.filter((name) => name.endsWith(".css"))
|
|
||||||
.map((name) => name.replace(/\.css$/i, ""))
|
|
||||||
.filter((name) => name.toLowerCase() !== "base");
|
|
||||||
}
|
|
||||||
|
|
||||||
function readThemeNamesFromConfig(configPath: string): string[] {
|
|
||||||
if (!fs.existsSync(configPath)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const content = fs.readFileSync(configPath, "utf-8");
|
|
||||||
const match = content.match(/themeOptionsMap\s*=\s*\{([\s\S]*?)\n\}/);
|
|
||||||
if (!match) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return Array.from(match[1].matchAll(/^\s*([a-zA-Z0-9_-]+)\s*:/gm)).map(
|
|
||||||
(item) => item[1]!
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveThemeNames(): ThemeName[] {
|
|
||||||
const localThemes = discoverThemesFromDir(THEME_DIR);
|
|
||||||
const externalThemes = discoverThemesFromDir(EXTERNAL_THEME_DIR);
|
|
||||||
const configThemes = readThemeNamesFromConfig(EXTERNAL_THEME_CONFIG_PATH);
|
|
||||||
const combined = new Set<ThemeName>([
|
|
||||||
...localThemes,
|
|
||||||
...externalThemes,
|
|
||||||
...configThemes,
|
|
||||||
]);
|
|
||||||
const resolved = Array.from(combined).filter((name) =>
|
|
||||||
fs.existsSync(path.join(THEME_DIR, `${name}.css`))
|
|
||||||
|| fs.existsSync(path.join(EXTERNAL_THEME_DIR, `${name}.css`))
|
|
||||||
);
|
|
||||||
return resolved.length ? resolved : FALLBACK_THEMES;
|
|
||||||
}
|
|
||||||
|
|
||||||
const THEME_NAMES: ThemeName[] = resolveThemeNames();
|
|
||||||
|
|
||||||
marked.setOptions({
|
|
||||||
breaks: true,
|
|
||||||
});
|
|
||||||
marked.use(markedSlider());
|
|
||||||
|
|
||||||
interface IOpts {
|
|
||||||
legend?: string;
|
|
||||||
citeStatus?: boolean;
|
|
||||||
countStatus?: boolean;
|
|
||||||
isMacCodeBlock?: boolean;
|
|
||||||
isShowLineNumber?: boolean;
|
|
||||||
themeMode?: "light" | "dark";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RendererAPI {
|
|
||||||
reset: (newOpts: Partial<IOpts>) => void;
|
|
||||||
setOptions: (newOpts: Partial<IOpts>) => void;
|
|
||||||
getOpts: () => IOpts;
|
|
||||||
parseFrontMatterAndContent: (markdown: string) => {
|
|
||||||
yamlData: Record<string, any>;
|
|
||||||
markdownContent: string;
|
|
||||||
readingTime: ReadTimeResults;
|
|
||||||
};
|
|
||||||
buildReadingTime: (reading: ReadTimeResults) => string;
|
|
||||||
buildFootnotes: () => string;
|
|
||||||
buildAddition: () => string;
|
|
||||||
createContainer: (html: string) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParseResult {
|
|
||||||
yamlData: Record<string, any>;
|
|
||||||
markdownContent: string;
|
|
||||||
readingTime: ReadTimeResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(text: string): string {
|
|
||||||
return text
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'")
|
|
||||||
.replace(/`/g, "`");
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildAddition(): string {
|
|
||||||
return `
|
|
||||||
<style>
|
|
||||||
.preview-wrapper pre::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
color: #ccc;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 5px 10px 0;
|
|
||||||
line-height: 15px;
|
|
||||||
height: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFootnoteArray(footnotes: [number, string, string][]): string {
|
|
||||||
return footnotes
|
|
||||||
.map(([index, title, link]) =>
|
|
||||||
link === title
|
|
||||||
? `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code>: <i style="word-break: break-all">${title}</i><br/>`
|
|
||||||
: `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code> ${title}: <i style="word-break: break-all">${link}</i><br/>`
|
|
||||||
)
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function transform(legend: string, text: string | null, title: string | null): string {
|
|
||||||
const options = legend.split("-");
|
|
||||||
for (const option of options) {
|
|
||||||
if (option === "alt" && text) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
if (option === "title" && title) {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const macCodeSvg = `
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" width="45px" height="13px" viewBox="0 0 450 130">
|
|
||||||
<ellipse cx="50" cy="65" rx="50" ry="52" stroke="rgb(220,60,54)" stroke-width="2" fill="rgb(237,108,96)" />
|
|
||||||
<ellipse cx="225" cy="65" rx="50" ry="52" stroke="rgb(218,151,33)" stroke-width="2" fill="rgb(247,193,81)" />
|
|
||||||
<ellipse cx="400" cy="65" rx="50" ry="52" stroke="rgb(27,161,37)" stroke-width="2" fill="rgb(100,200,86)" />
|
|
||||||
</svg>
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
function parseFrontMatterAndContent(markdownText: string): ParseResult {
|
|
||||||
try {
|
|
||||||
const parsed = frontMatter(markdownText);
|
|
||||||
const yamlData = parsed.attributes;
|
|
||||||
const markdownContent = parsed.body;
|
|
||||||
|
|
||||||
const readingTimeResult = readingTime(markdownContent);
|
|
||||||
|
|
||||||
return {
|
|
||||||
yamlData: yamlData as Record<string, any>,
|
|
||||||
markdownContent,
|
|
||||||
readingTime: readingTimeResult,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error parsing front-matter:", error);
|
|
||||||
return {
|
|
||||||
yamlData: {},
|
|
||||||
markdownContent: markdownText,
|
|
||||||
readingTime: readingTime(markdownText),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initRenderer(opts: IOpts = {}): RendererAPI {
|
|
||||||
const footnotes: [number, string, string][] = [];
|
|
||||||
let footnoteIndex = 0;
|
|
||||||
let codeIndex = 0;
|
|
||||||
const listOrderedStack: boolean[] = [];
|
|
||||||
const listCounters: number[] = [];
|
|
||||||
const isBrowser = typeof window !== "undefined";
|
|
||||||
|
|
||||||
function getOpts(): IOpts {
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
|
||||||
const tag = tagName ?? styleLabel;
|
|
||||||
const className = `${styleLabel.replace(/_/g, "-")}`;
|
|
||||||
const headingAttr = /^h\d$/.test(tag) ? " data-heading=\"true\"" : "";
|
|
||||||
return `<${tag} class="${className}"${headingAttr}>${content}</${tag}>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFootnote(title: string, link: string): number {
|
|
||||||
const existingFootnote = footnotes.find(([, , existingLink]) => existingLink === link);
|
|
||||||
if (existingFootnote) {
|
|
||||||
return existingFootnote[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
footnotes.push([++footnoteIndex, title, link]);
|
|
||||||
return footnoteIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset(newOpts: Partial<IOpts>): void {
|
|
||||||
footnotes.length = 0;
|
|
||||||
footnoteIndex = 0;
|
|
||||||
setOptions(newOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setOptions(newOpts: Partial<IOpts>): void {
|
|
||||||
opts = { ...opts, ...newOpts };
|
|
||||||
marked.use(markedAlert());
|
|
||||||
if (isBrowser) {
|
|
||||||
marked.use(MDKatex({ nonStandard: true }, true));
|
|
||||||
}
|
|
||||||
marked.use(markedMarkup());
|
|
||||||
marked.use(markedInfographic({ themeMode: opts.themeMode }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildReadingTime(readingTimeResult: ReadTimeResults): string {
|
|
||||||
if (!opts.countStatus) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (!readingTimeResult.words) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return `
|
|
||||||
<blockquote class="md-blockquote">
|
|
||||||
<p class="md-blockquote-p">字数 ${readingTimeResult?.words},阅读大约需 ${Math.ceil(readingTimeResult?.minutes)} 分钟</p>
|
|
||||||
</blockquote>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildFootnotes = () => {
|
|
||||||
if (!footnotes.length) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
styledContent("h4", "引用链接")
|
|
||||||
+ styledContent("footnotes", buildFootnoteArray(footnotes), "p")
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderer: RendererObject = {
|
|
||||||
heading({ tokens, depth }: Tokens.Heading) {
|
|
||||||
const text = this.parser.parseInline(tokens);
|
|
||||||
const tag = `h${depth}`;
|
|
||||||
return styledContent(tag, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
paragraph({ tokens }: Tokens.Paragraph): string {
|
|
||||||
const text = this.parser.parseInline(tokens);
|
|
||||||
const isFigureImage = text.includes("<figure") && text.includes("<img");
|
|
||||||
const isEmpty = text.trim() === "";
|
|
||||||
if (isFigureImage || isEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
return styledContent("p", text);
|
|
||||||
},
|
|
||||||
|
|
||||||
blockquote({ tokens }: Tokens.Blockquote): string {
|
|
||||||
const text = this.parser.parse(tokens);
|
|
||||||
return styledContent("blockquote", text);
|
|
||||||
},
|
|
||||||
|
|
||||||
code({ text, lang = "" }: Tokens.Code): string {
|
|
||||||
if (lang.startsWith("mermaid")) {
|
|
||||||
if (isBrowser) {
|
|
||||||
clearTimeout(codeIndex as any);
|
|
||||||
codeIndex = setTimeout(async () => {
|
|
||||||
const windowRef = typeof window !== "undefined" ? (window as any) : undefined;
|
|
||||||
if (windowRef && windowRef.mermaid) {
|
|
||||||
const mermaid = windowRef.mermaid;
|
|
||||||
await mermaid.run();
|
|
||||||
} else {
|
|
||||||
const mermaid = await import("mermaid");
|
|
||||||
await mermaid.default.run();
|
|
||||||
}
|
|
||||||
}, 0) as any as number;
|
|
||||||
}
|
|
||||||
return `<pre class="mermaid">${text}</pre>`;
|
|
||||||
}
|
|
||||||
const langText = lang.split(" ")[0];
|
|
||||||
const isLanguageRegistered = hljs.getLanguage(langText);
|
|
||||||
const language = isLanguageRegistered ? langText : "plaintext";
|
|
||||||
|
|
||||||
const highlighted = highlightAndFormatCode(
|
|
||||||
text,
|
|
||||||
language,
|
|
||||||
hljs,
|
|
||||||
!!opts.isShowLineNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
const span = `<span class="mac-sign" style="padding: 10px 14px 0;">${macCodeSvg}</span>`;
|
|
||||||
let pendingAttr = "";
|
|
||||||
if (!isLanguageRegistered && langText !== "plaintext") {
|
|
||||||
const escapedText = text.replace(/"/g, """);
|
|
||||||
pendingAttr = ` data-language-pending="${langText}" data-raw-code="${escapedText}" data-show-line-number="${opts.isShowLineNumber}"`;
|
|
||||||
}
|
|
||||||
const code = `<code class="language-${lang}"${pendingAttr}>${highlighted}</code>`;
|
|
||||||
|
|
||||||
return `<pre class="hljs code__pre">${span}${code}</pre>`;
|
|
||||||
},
|
|
||||||
|
|
||||||
codespan({ text }: Tokens.Codespan): string {
|
|
||||||
const escapedText = escapeHtml(text);
|
|
||||||
return styledContent("codespan", escapedText, "code");
|
|
||||||
},
|
|
||||||
|
|
||||||
list({ ordered, items, start = 1 }: Tokens.List) {
|
|
||||||
listOrderedStack.push(ordered);
|
|
||||||
listCounters.push(Number(start));
|
|
||||||
|
|
||||||
const html = items.map((item) => this.listitem(item)).join("");
|
|
||||||
|
|
||||||
listOrderedStack.pop();
|
|
||||||
listCounters.pop();
|
|
||||||
|
|
||||||
return styledContent(ordered ? "ol" : "ul", html);
|
|
||||||
},
|
|
||||||
|
|
||||||
listitem(token: Tokens.ListItem) {
|
|
||||||
const ordered = listOrderedStack[listOrderedStack.length - 1];
|
|
||||||
const idx = listCounters[listCounters.length - 1]!;
|
|
||||||
|
|
||||||
listCounters[listCounters.length - 1] = idx + 1;
|
|
||||||
|
|
||||||
const prefix = ordered ? "" : "• ";
|
|
||||||
|
|
||||||
let content: string;
|
|
||||||
try {
|
|
||||||
content = this.parser.parseInline(token.tokens);
|
|
||||||
} catch {
|
|
||||||
content = this.parser
|
|
||||||
.parse(token.tokens)
|
|
||||||
.replace(/^<p(?:\s[^>]*)?>([\s\S]*?)<\/p>/, "$1");
|
|
||||||
}
|
|
||||||
|
|
||||||
return styledContent("listitem", `${prefix}${content}`, "li");
|
|
||||||
},
|
|
||||||
|
|
||||||
image({ href, title, text }: Tokens.Image): string {
|
|
||||||
const newText = opts.legend ? transform(opts.legend, text, title) : "";
|
|
||||||
const subText = newText ? styledContent("figcaption", newText) : "";
|
|
||||||
const titleAttr = title ? ` title="${title}"` : "";
|
|
||||||
return `<figure><img src="${href}"${titleAttr} alt="${text}"/>${subText}</figure>`;
|
|
||||||
},
|
|
||||||
|
|
||||||
link({ href, title, text, tokens }: Tokens.Link): string {
|
|
||||||
const parsedText = this.parser.parseInline(tokens);
|
|
||||||
if (/^https?:\/\/mp\.weixin\.qq\.com/.test(href)) {
|
|
||||||
return `<a href="${href}" title="${title || text}">${parsedText}</a>`;
|
|
||||||
}
|
|
||||||
if (href === text) {
|
|
||||||
return parsedText;
|
|
||||||
}
|
|
||||||
if (opts.citeStatus) {
|
|
||||||
const ref = addFootnote(title || text, href);
|
|
||||||
return `<a href="${href}" title="${title || text}">${parsedText}<sup>[${ref}]</sup></a>`;
|
|
||||||
}
|
|
||||||
return `<a href="${href}" title="${title || text}">${parsedText}</a>`;
|
|
||||||
},
|
|
||||||
|
|
||||||
strong({ tokens }: Tokens.Strong): string {
|
|
||||||
return styledContent("strong", this.parser.parseInline(tokens));
|
|
||||||
},
|
|
||||||
|
|
||||||
em({ tokens }: Tokens.Em): string {
|
|
||||||
return styledContent("em", this.parser.parseInline(tokens));
|
|
||||||
},
|
|
||||||
|
|
||||||
table({ header, rows }: Tokens.Table): string {
|
|
||||||
const headerRow = header
|
|
||||||
.map((cell) => {
|
|
||||||
const text = this.parser.parseInline(cell.tokens);
|
|
||||||
return styledContent("th", text);
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
const body = rows
|
|
||||||
.map((row) => {
|
|
||||||
const rowContent = row.map((cell) => this.tablecell(cell)).join("");
|
|
||||||
return styledContent("tr", rowContent);
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
return `
|
|
||||||
<section style="max-width: 100%; overflow: auto">
|
|
||||||
<table class="preview-table">
|
|
||||||
<thead>${headerRow}</thead>
|
|
||||||
<tbody>${body}</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
|
|
||||||
tablecell(token: Tokens.TableCell): string {
|
|
||||||
const text = this.parser.parseInline(token.tokens);
|
|
||||||
return styledContent("td", text);
|
|
||||||
},
|
|
||||||
|
|
||||||
hr(_: Tokens.Hr): string {
|
|
||||||
return styledContent("hr", "");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
marked.use({ renderer });
|
|
||||||
marked.use(markedMarkup());
|
|
||||||
marked.use(markedToc());
|
|
||||||
marked.use(markedSlider());
|
|
||||||
marked.use(markedAlert({}));
|
|
||||||
if (isBrowser) {
|
|
||||||
marked.use(MDKatex({ nonStandard: true }, true));
|
|
||||||
}
|
|
||||||
marked.use(markedFootnotes());
|
|
||||||
marked.use(
|
|
||||||
markedPlantUML({
|
|
||||||
inlineSvg: isBrowser,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
marked.use(markedInfographic());
|
|
||||||
marked.use(markedRuby());
|
|
||||||
|
|
||||||
return {
|
|
||||||
buildAddition,
|
|
||||||
buildFootnotes,
|
|
||||||
setOptions,
|
|
||||||
reset,
|
|
||||||
parseFrontMatterAndContent,
|
|
||||||
buildReadingTime,
|
|
||||||
createContainer(content: string) {
|
|
||||||
return styledContent("container", content, "section");
|
|
||||||
},
|
|
||||||
getOpts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function printUsage(): void {
|
|
||||||
console.error(
|
|
||||||
[
|
|
||||||
"Usage:",
|
|
||||||
" npx tsx src/md/render.ts <markdown_file> [--theme <name>]",
|
|
||||||
"",
|
|
||||||
"Options:",
|
|
||||||
` --theme Theme name (${THEME_NAMES.join(", ")})`,
|
|
||||||
].join("\n")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv: string[]): CliOptions | null {
|
|
||||||
let inputPath = "";
|
|
||||||
let theme: ThemeName = "default";
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i];
|
|
||||||
if (!arg.startsWith("--") && !inputPath) {
|
|
||||||
inputPath = arg;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg === "--theme") {
|
|
||||||
theme = (argv[i + 1] || "") as ThemeName;
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith("--theme=")) {
|
|
||||||
theme = arg.slice("--theme=".length) as ThemeName;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg === "--help" || arg === "-h") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`Unknown argument: ${arg}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inputPath) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!THEME_NAMES.includes(theme)) {
|
|
||||||
console.error(`Unknown theme: ${theme}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputPath,
|
|
||||||
theme,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CliOptions {
|
|
||||||
inputPath: string;
|
|
||||||
theme: ThemeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMarkdown(raw: string, renderer: RendererAPI): {
|
|
||||||
html: string;
|
|
||||||
readingTime: ReadTimeResults;
|
|
||||||
} {
|
|
||||||
const { markdownContent, readingTime: readingTimeResult } =
|
|
||||||
renderer.parseFrontMatterAndContent(raw);
|
|
||||||
|
|
||||||
const html = marked.parse(markdownContent) as string;
|
|
||||||
|
|
||||||
return { html, readingTime: readingTimeResult };
|
|
||||||
}
|
|
||||||
|
|
||||||
function postProcessHtml(
|
|
||||||
baseHtml: string,
|
|
||||||
reading: ReadTimeResults,
|
|
||||||
renderer: RendererAPI
|
|
||||||
): string {
|
|
||||||
let html = baseHtml;
|
|
||||||
html = renderer.buildReadingTime(reading) + html;
|
|
||||||
html += renderer.buildFootnotes();
|
|
||||||
html += renderer.buildAddition();
|
|
||||||
html += `
|
|
||||||
<style>
|
|
||||||
.hljs.code__pre > .mac-sign {
|
|
||||||
display: ${renderer.getOpts().isMacCodeBlock ? "flex" : "none"};
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
html += `
|
|
||||||
<style>
|
|
||||||
h2 strong {
|
|
||||||
color: inherit !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
return renderer.createContainer(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTimestamp(date = new Date()): string {
|
function formatTimestamp(date = new Date()): string {
|
||||||
const pad = (value: number) => String(value).padStart(2, "0");
|
const pad = (value: number) => String(value).padStart(2, "0");
|
||||||
@@ -589,155 +24,6 @@ function formatTimestamp(date = new Date()): string {
|
|||||||
)}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
)}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureMarkdownPath(inputPath: string): void {
|
|
||||||
if (!inputPath.toLowerCase().endsWith(".md")) {
|
|
||||||
throw new Error("Input file must end with .md");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadThemeCss(theme: ThemeName): {
|
|
||||||
baseCss: string;
|
|
||||||
themeCss: string;
|
|
||||||
} {
|
|
||||||
const basePathCandidates = [
|
|
||||||
path.join(THEME_DIR, "base.css"),
|
|
||||||
path.join(EXTERNAL_THEME_DIR, "base.css"),
|
|
||||||
];
|
|
||||||
const themePathCandidates = [
|
|
||||||
path.join(THEME_DIR, `${theme}.css`),
|
|
||||||
path.join(EXTERNAL_THEME_DIR, `${theme}.css`),
|
|
||||||
];
|
|
||||||
const basePath = basePathCandidates.find((candidate) =>
|
|
||||||
fs.existsSync(candidate)
|
|
||||||
);
|
|
||||||
const themePath = themePathCandidates.find((candidate) =>
|
|
||||||
fs.existsSync(candidate)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!basePath) {
|
|
||||||
throw new Error(
|
|
||||||
`Missing base CSS. Checked: ${basePathCandidates.join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!themePath) {
|
|
||||||
throw new Error(
|
|
||||||
`Missing theme CSS for "${theme}". Checked: ${themePathCandidates.join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
baseCss: fs.readFileSync(basePath, "utf-8"),
|
|
||||||
themeCss: fs.readFileSync(themePath, "utf-8"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCss(baseCss: string, themeCss: string): string {
|
|
||||||
const variables = `
|
|
||||||
:root {
|
|
||||||
--md-primary-color: ${DEFAULT_STYLE.primaryColor};
|
|
||||||
--md-font-family: ${DEFAULT_STYLE.fontFamily};
|
|
||||||
--md-font-size: ${DEFAULT_STYLE.fontSize};
|
|
||||||
--foreground: ${DEFAULT_STYLE.foreground};
|
|
||||||
--blockquote-background: ${DEFAULT_STYLE.blockquoteBackground};
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 24px;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#output {
|
|
||||||
max-width: 860px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
return [variables, baseCss, themeCss].join("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeThemeCss(css: string): string {
|
|
||||||
return stripOutputScope(css);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHtmlDocument(title: string, css: string, html: string): string {
|
|
||||||
return [
|
|
||||||
"<!doctype html>",
|
|
||||||
"<html>",
|
|
||||||
"<head>",
|
|
||||||
' <meta charset="utf-8" />',
|
|
||||||
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
||||||
` <title>${title}</title>`,
|
|
||||||
` <style>${css}</style>`,
|
|
||||||
"</head>",
|
|
||||||
"<body>",
|
|
||||||
' <div id="output">',
|
|
||||||
html,
|
|
||||||
" </div>",
|
|
||||||
"</body>",
|
|
||||||
"</html>",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function inlineCss(html: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const { default: juice } = await import("juice");
|
|
||||||
return juice(html, {
|
|
||||||
inlinePseudoElements: true,
|
|
||||||
preserveImportant: true,
|
|
||||||
resolveCSSVariables: false,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const detail = error instanceof Error ? error.message : String(error);
|
|
||||||
throw new Error(
|
|
||||||
`Missing dependency "juice" for CSS inlining. Install it first (e.g. "bun add juice" or "npm add juice"). Original error: ${detail}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeCssText(cssText: string): string {
|
|
||||||
return cssText
|
|
||||||
.replace(/var\(--md-primary-color\)/g, DEFAULT_STYLE.primaryColor)
|
|
||||||
.replace(/var\(--md-font-family\)/g, DEFAULT_STYLE.fontFamily)
|
|
||||||
.replace(/var\(--md-font-size\)/g, DEFAULT_STYLE.fontSize)
|
|
||||||
.replace(/var\(--blockquote-background\)/g, DEFAULT_STYLE.blockquoteBackground)
|
|
||||||
.replace(/hsl\(var\(--foreground\)\)/g, "#3f3f3f")
|
|
||||||
.replace(/--md-primary-color:\s*[^;"']+;?/g, "")
|
|
||||||
.replace(/--md-font-family:\s*[^;"']+;?/g, "")
|
|
||||||
.replace(/--md-font-size:\s*[^;"']+;?/g, "")
|
|
||||||
.replace(/--blockquote-background:\s*[^;"']+;?/g, "")
|
|
||||||
.replace(/--foreground:\s*[^;"']+;?/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeInlineCss(html: string): string {
|
|
||||||
let output = html;
|
|
||||||
output = output.replace(
|
|
||||||
/<style([^>]*)>([\s\S]*?)<\/style>/gi,
|
|
||||||
(_match, attrs: string, cssText: string) =>
|
|
||||||
`<style${attrs}>${normalizeCssText(cssText)}</style>`
|
|
||||||
);
|
|
||||||
output = output.replace(
|
|
||||||
/style="([^"]*)"/gi,
|
|
||||||
(_match, cssText: string) => `style="${normalizeCssText(cssText)}"`
|
|
||||||
);
|
|
||||||
output = output.replace(
|
|
||||||
/style='([^']*)'/gi,
|
|
||||||
(_match, cssText: string) => `style='${normalizeCssText(cssText)}'`
|
|
||||||
);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifyHtmlStructure(htmlString: string): string {
|
|
||||||
let output = htmlString;
|
|
||||||
const pattern =
|
|
||||||
/<li([^>]*)>([\s\S]*?)(<ul[\s\S]*?<\/ul>|<ol[\s\S]*?<\/ol>)<\/li>/i;
|
|
||||||
while (pattern.test(output)) {
|
|
||||||
output = output.replace(pattern, "<li$1>$2</li>$3");
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const options = parseArgs(process.argv.slice(2));
|
const options = parseArgs(process.argv.slice(2));
|
||||||
if (!options) {
|
if (!options) {
|
||||||
@@ -746,7 +32,10 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputPath = path.resolve(process.cwd(), options.inputPath);
|
const inputPath = path.resolve(process.cwd(), options.inputPath);
|
||||||
ensureMarkdownPath(inputPath);
|
if (!inputPath.toLowerCase().endsWith(".md")) {
|
||||||
|
console.error("Input file must end with .md");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(inputPath)) {
|
if (!fs.existsSync(inputPath)) {
|
||||||
console.error(`File not found: ${inputPath}`);
|
console.error(`File not found: ${inputPath}`);
|
||||||
@@ -758,20 +47,56 @@ async function main(): Promise<void> {
|
|||||||
options.inputPath.replace(/\.md$/i, ".html")
|
options.inputPath.replace(/\.md$/i, ".html")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const themeDefaults = THEME_STYLE_DEFAULTS[options.theme] ?? {};
|
||||||
|
const style: StyleConfig = {
|
||||||
|
...DEFAULT_STYLE,
|
||||||
|
...themeDefaults,
|
||||||
|
...(options.primaryColor !== undefined ? { primaryColor: options.primaryColor } : {}),
|
||||||
|
...(options.fontFamily !== undefined ? { fontFamily: options.fontFamily } : {}),
|
||||||
|
...(options.fontSize !== undefined ? { fontSize: options.fontSize } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
const { baseCss, themeCss } = loadThemeCss(options.theme);
|
const { baseCss, themeCss } = loadThemeCss(options.theme);
|
||||||
const css = normalizeThemeCss(buildCss(baseCss, themeCss));
|
const css = normalizeThemeCss(buildCss(baseCss, themeCss, style));
|
||||||
|
const codeThemeCss = loadCodeThemeCss(options.codeTheme);
|
||||||
|
|
||||||
const markdown = fs.readFileSync(inputPath, "utf-8");
|
const markdown = fs.readFileSync(inputPath, "utf-8");
|
||||||
|
|
||||||
const renderer = initRenderer({});
|
const renderer = initRenderer({
|
||||||
|
legend: options.legend,
|
||||||
|
citeStatus: options.citeStatus,
|
||||||
|
countStatus: options.countStatus,
|
||||||
|
isMacCodeBlock: options.isMacCodeBlock,
|
||||||
|
isShowLineNumber: options.isShowLineNumber,
|
||||||
|
});
|
||||||
|
const { yamlData } = renderer.parseFrontMatterAndContent(markdown);
|
||||||
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(
|
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(
|
||||||
markdown,
|
markdown,
|
||||||
renderer
|
renderer
|
||||||
);
|
);
|
||||||
const content = postProcessHtml(baseHtml, readingTimeResult, renderer);
|
let content = postProcessHtml(baseHtml, readingTimeResult, renderer);
|
||||||
|
if (!options.keepTitle) {
|
||||||
|
content = removeFirstHeading(content);
|
||||||
|
}
|
||||||
|
|
||||||
const title = path.basename(outputPath, ".html");
|
const stripQuotes = (s?: string): string | undefined => {
|
||||||
const html = buildHtmlDocument(title, css, content);
|
if (!s) return s;
|
||||||
const inlinedHtml = normalizeInlineCss(await inlineCss(html));
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
if ((s.startsWith('\u201c') && s.endsWith('\u201d')) || (s.startsWith('\u2018') && s.endsWith('\u2019'))) {
|
||||||
|
return s.slice(1, -1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: HtmlDocumentMeta = {
|
||||||
|
title: stripQuotes(yamlData.title) || path.basename(outputPath, ".html"),
|
||||||
|
author: stripQuotes(yamlData.author),
|
||||||
|
description: stripQuotes(yamlData.description) || stripQuotes(yamlData.summary),
|
||||||
|
};
|
||||||
|
const html = buildHtmlDocument(meta, css, content, codeThemeCss);
|
||||||
|
const inlinedHtml = normalizeInlineCss(await inlineCss(html), style);
|
||||||
const finalHtml = modifyHtmlStructure(inlinedHtml);
|
const finalHtml = modifyHtmlStructure(inlinedHtml);
|
||||||
|
|
||||||
let backupPath = "";
|
let backupPath = "";
|
||||||
|
|||||||
@@ -0,0 +1,434 @@
|
|||||||
|
import frontMatter from "front-matter";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import { marked, type RendererObject, type Tokens } from "marked";
|
||||||
|
import readingTime, { type ReadTimeResults } from "reading-time";
|
||||||
|
import { unified } from "unified";
|
||||||
|
import remarkParse from "remark-parse";
|
||||||
|
import remarkCjkFriendly from "remark-cjk-friendly";
|
||||||
|
import remarkStringify from "remark-stringify";
|
||||||
|
|
||||||
|
import {
|
||||||
|
markedAlert,
|
||||||
|
markedFootnotes,
|
||||||
|
markedInfographic,
|
||||||
|
markedMarkup,
|
||||||
|
markedPlantUML,
|
||||||
|
markedRuby,
|
||||||
|
markedSlider,
|
||||||
|
markedToc,
|
||||||
|
MDKatex,
|
||||||
|
} from "./extensions/index.js";
|
||||||
|
import {
|
||||||
|
COMMON_LANGUAGES,
|
||||||
|
highlightAndFormatCode,
|
||||||
|
} from "./utils/languages.js";
|
||||||
|
import { macCodeSvg } from "./constants.js";
|
||||||
|
import type { IOpts, ParseResult, RendererAPI } from "./types.js";
|
||||||
|
|
||||||
|
Object.entries(COMMON_LANGUAGES).forEach(([name, lang]) => {
|
||||||
|
hljs.registerLanguage(name, lang);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { hljs };
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
breaks: true,
|
||||||
|
});
|
||||||
|
marked.use(markedSlider());
|
||||||
|
|
||||||
|
function escapeHtml(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/`/g, "`");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAddition(): string {
|
||||||
|
return `
|
||||||
|
<style>
|
||||||
|
.preview-wrapper pre::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: #ccc;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 5px 10px 0;
|
||||||
|
line-height: 15px;
|
||||||
|
height: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFootnoteArray(footnotes: [number, string, string][]): string {
|
||||||
|
return footnotes
|
||||||
|
.map(([index, title, link]) =>
|
||||||
|
link === title
|
||||||
|
? `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code>: <i style="word-break: break-all">${title}</i><br/>`
|
||||||
|
: `<code style="font-size: 90%; opacity: 0.6;">[${index}]</code> ${title}: <i style="word-break: break-all">${link}</i><br/>`
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function transform(legend: string, text: string | null, title: string | null): string {
|
||||||
|
const options = legend.split("-");
|
||||||
|
for (const option of options) {
|
||||||
|
if (option === "alt" && text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
if (option === "title" && title) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFrontMatterAndContent(markdownText: string): ParseResult {
|
||||||
|
try {
|
||||||
|
const parsed = frontMatter(markdownText);
|
||||||
|
const yamlData = parsed.attributes;
|
||||||
|
const markdownContent = parsed.body;
|
||||||
|
const readingTimeResult = readingTime(markdownContent);
|
||||||
|
return {
|
||||||
|
yamlData: yamlData as Record<string, any>,
|
||||||
|
markdownContent,
|
||||||
|
readingTime: readingTimeResult,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing front-matter:", error);
|
||||||
|
return {
|
||||||
|
yamlData: {},
|
||||||
|
markdownContent: markdownText,
|
||||||
|
readingTime: readingTime(markdownText),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initRenderer(opts: IOpts = {}): RendererAPI {
|
||||||
|
const footnotes: [number, string, string][] = [];
|
||||||
|
let footnoteIndex = 0;
|
||||||
|
let codeIndex = 0;
|
||||||
|
const listOrderedStack: boolean[] = [];
|
||||||
|
const listCounters: number[] = [];
|
||||||
|
const isBrowser = typeof window !== "undefined";
|
||||||
|
|
||||||
|
function getOpts(): IOpts {
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
||||||
|
const tag = tagName ?? styleLabel;
|
||||||
|
const className = `${styleLabel.replace(/_/g, "-")}`;
|
||||||
|
const headingAttr = /^h\d$/.test(tag) ? " data-heading=\"true\"" : "";
|
||||||
|
return `<${tag} class="${className}"${headingAttr}>${content}</${tag}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFootnote(title: string, link: string): number {
|
||||||
|
const existingFootnote = footnotes.find(([, , existingLink]) => existingLink === link);
|
||||||
|
if (existingFootnote) {
|
||||||
|
return existingFootnote[0];
|
||||||
|
}
|
||||||
|
footnotes.push([++footnoteIndex, title, link]);
|
||||||
|
return footnoteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(newOpts: Partial<IOpts>): void {
|
||||||
|
footnotes.length = 0;
|
||||||
|
footnoteIndex = 0;
|
||||||
|
setOptions(newOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOptions(newOpts: Partial<IOpts>): void {
|
||||||
|
opts = { ...opts, ...newOpts };
|
||||||
|
marked.use(markedAlert());
|
||||||
|
if (isBrowser) {
|
||||||
|
marked.use(MDKatex({ nonStandard: true }, true));
|
||||||
|
}
|
||||||
|
marked.use(markedMarkup());
|
||||||
|
marked.use(markedInfographic({ themeMode: opts.themeMode }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildReadingTime(readingTimeResult: ReadTimeResults): string {
|
||||||
|
if (!opts.countStatus) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!readingTimeResult.words) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<blockquote class="md-blockquote">
|
||||||
|
<p class="md-blockquote-p">字数 ${readingTimeResult?.words},阅读大约需 ${Math.ceil(readingTimeResult?.minutes)} 分钟</p>
|
||||||
|
</blockquote>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildFootnotes = () => {
|
||||||
|
if (!footnotes.length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
styledContent("h4", "引用链接")
|
||||||
|
+ styledContent("footnotes", buildFootnoteArray(footnotes), "p")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderer: RendererObject = {
|
||||||
|
heading({ tokens, depth }: Tokens.Heading) {
|
||||||
|
const text = this.parser.parseInline(tokens);
|
||||||
|
const tag = `h${depth}`;
|
||||||
|
return styledContent(tag, text);
|
||||||
|
},
|
||||||
|
|
||||||
|
paragraph({ tokens }: Tokens.Paragraph): string {
|
||||||
|
const text = this.parser.parseInline(tokens);
|
||||||
|
const isFigureImage = text.includes("<figure") && text.includes("<img");
|
||||||
|
const isEmpty = text.trim() === "";
|
||||||
|
if (isFigureImage || isEmpty) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return styledContent("p", text);
|
||||||
|
},
|
||||||
|
|
||||||
|
blockquote({ tokens }: Tokens.Blockquote): string {
|
||||||
|
const text = this.parser.parse(tokens);
|
||||||
|
return styledContent("blockquote", text);
|
||||||
|
},
|
||||||
|
|
||||||
|
code({ text, lang = "" }: Tokens.Code): string {
|
||||||
|
if (lang.startsWith("mermaid")) {
|
||||||
|
if (isBrowser) {
|
||||||
|
clearTimeout(codeIndex as any);
|
||||||
|
codeIndex = setTimeout(async () => {
|
||||||
|
const windowRef = typeof window !== "undefined" ? (window as any) : undefined;
|
||||||
|
if (windowRef && windowRef.mermaid) {
|
||||||
|
const mermaid = windowRef.mermaid;
|
||||||
|
await mermaid.run();
|
||||||
|
} else {
|
||||||
|
const mermaid = await import("mermaid");
|
||||||
|
await mermaid.default.run();
|
||||||
|
}
|
||||||
|
}, 0) as any as number;
|
||||||
|
}
|
||||||
|
return `<pre class="mermaid">${text}</pre>`;
|
||||||
|
}
|
||||||
|
const langText = lang.split(" ")[0];
|
||||||
|
const isLanguageRegistered = hljs.getLanguage(langText);
|
||||||
|
const language = isLanguageRegistered ? langText : "plaintext";
|
||||||
|
|
||||||
|
const highlighted = highlightAndFormatCode(
|
||||||
|
text,
|
||||||
|
language,
|
||||||
|
hljs,
|
||||||
|
!!opts.isShowLineNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
const span = `<span class="mac-sign" style="padding: 10px 14px 0;">${macCodeSvg}</span>`;
|
||||||
|
let pendingAttr = "";
|
||||||
|
if (!isLanguageRegistered && langText !== "plaintext") {
|
||||||
|
const escapedText = text.replace(/"/g, """);
|
||||||
|
pendingAttr = ` data-language-pending="${langText}" data-raw-code="${escapedText}" data-show-line-number="${opts.isShowLineNumber}"`;
|
||||||
|
}
|
||||||
|
const code = `<code class="language-${lang}"${pendingAttr}>${highlighted}</code>`;
|
||||||
|
|
||||||
|
return `<pre class="hljs code__pre">${span}${code}</pre>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
codespan({ text }: Tokens.Codespan): string {
|
||||||
|
const escapedText = escapeHtml(text);
|
||||||
|
return styledContent("codespan", escapedText, "code");
|
||||||
|
},
|
||||||
|
|
||||||
|
list({ ordered, items, start = 1 }: Tokens.List) {
|
||||||
|
listOrderedStack.push(ordered);
|
||||||
|
listCounters.push(Number(start));
|
||||||
|
const html = items.map((item) => this.listitem(item)).join("");
|
||||||
|
listOrderedStack.pop();
|
||||||
|
listCounters.pop();
|
||||||
|
return styledContent(ordered ? "ol" : "ul", html);
|
||||||
|
},
|
||||||
|
|
||||||
|
listitem(token: Tokens.ListItem) {
|
||||||
|
const ordered = listOrderedStack[listOrderedStack.length - 1];
|
||||||
|
const idx = listCounters[listCounters.length - 1]!;
|
||||||
|
listCounters[listCounters.length - 1] = idx + 1;
|
||||||
|
const prefix = ordered ? `${idx}. ` : "• ";
|
||||||
|
let content: string;
|
||||||
|
try {
|
||||||
|
content = this.parser.parseInline(token.tokens);
|
||||||
|
} catch {
|
||||||
|
content = this.parser
|
||||||
|
.parse(token.tokens)
|
||||||
|
.replace(/^<p(?:\s[^>]*)?>([\s\S]*?)<\/p>/, "$1");
|
||||||
|
}
|
||||||
|
return styledContent("listitem", `${prefix}${content}`, "li");
|
||||||
|
},
|
||||||
|
|
||||||
|
image({ href, title, text }: Tokens.Image): string {
|
||||||
|
const newText = opts.legend ? transform(opts.legend, text, title) : "";
|
||||||
|
const subText = newText ? styledContent("figcaption", newText) : "";
|
||||||
|
const titleAttr = title ? ` title="${title}"` : "";
|
||||||
|
return `<figure><img src="${href}"${titleAttr} alt="${text}"/>${subText}</figure>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
link({ href, title, text, tokens }: Tokens.Link): string {
|
||||||
|
const parsedText = this.parser.parseInline(tokens);
|
||||||
|
if (/^https?:\/\/mp\.weixin\.qq\.com/.test(href)) {
|
||||||
|
return `<a href="${href}" title="${title || text}">${parsedText}</a>`;
|
||||||
|
}
|
||||||
|
if (href === text) {
|
||||||
|
return parsedText;
|
||||||
|
}
|
||||||
|
if (opts.citeStatus) {
|
||||||
|
const ref = addFootnote(title || text, href);
|
||||||
|
return `<a href="${href}" title="${title || text}">${parsedText}<sup>[${ref}]</sup></a>`;
|
||||||
|
}
|
||||||
|
return `<a href="${href}" title="${title || text}">${parsedText}</a>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
strong({ tokens }: Tokens.Strong): string {
|
||||||
|
return styledContent("strong", this.parser.parseInline(tokens));
|
||||||
|
},
|
||||||
|
|
||||||
|
em({ tokens }: Tokens.Em): string {
|
||||||
|
return styledContent("em", this.parser.parseInline(tokens));
|
||||||
|
},
|
||||||
|
|
||||||
|
table({ header, rows }: Tokens.Table): string {
|
||||||
|
const headerRow = header
|
||||||
|
.map((cell) => {
|
||||||
|
const text = this.parser.parseInline(cell.tokens);
|
||||||
|
return styledContent("th", text);
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
const body = rows
|
||||||
|
.map((row) => {
|
||||||
|
const rowContent = row.map((cell) => this.tablecell(cell)).join("");
|
||||||
|
return styledContent("tr", rowContent);
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
return `
|
||||||
|
<section style="max-width: 100%; overflow: auto">
|
||||||
|
<table class="preview-table">
|
||||||
|
<thead>${headerRow}</thead>
|
||||||
|
<tbody>${body}</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
tablecell(token: Tokens.TableCell): string {
|
||||||
|
const text = this.parser.parseInline(token.tokens);
|
||||||
|
return styledContent("td", text);
|
||||||
|
},
|
||||||
|
|
||||||
|
hr(_: Tokens.Hr): string {
|
||||||
|
return styledContent("hr", "");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.use({ renderer });
|
||||||
|
marked.use(markedMarkup());
|
||||||
|
marked.use(markedToc());
|
||||||
|
marked.use(markedSlider());
|
||||||
|
marked.use(markedAlert({}));
|
||||||
|
if (isBrowser) {
|
||||||
|
marked.use(MDKatex({ nonStandard: true }, true));
|
||||||
|
}
|
||||||
|
marked.use(markedFootnotes());
|
||||||
|
marked.use(
|
||||||
|
markedPlantUML({
|
||||||
|
inlineSvg: isBrowser,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
marked.use(markedInfographic());
|
||||||
|
marked.use(markedRuby());
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildAddition,
|
||||||
|
buildFootnotes,
|
||||||
|
setOptions,
|
||||||
|
reset,
|
||||||
|
parseFrontMatterAndContent,
|
||||||
|
buildReadingTime,
|
||||||
|
createContainer(content: string) {
|
||||||
|
return styledContent("container", content, "section");
|
||||||
|
},
|
||||||
|
getOpts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function preprocessCjkEmphasis(markdown: string): string {
|
||||||
|
const processor = unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkCjkFriendly);
|
||||||
|
const tree = processor.parse(markdown);
|
||||||
|
const extractText = (node: any): string => {
|
||||||
|
if (node.type === "text") return node.value;
|
||||||
|
if (node.children) return node.children.map(extractText).join("");
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
const visit = (node: any, parent?: any, index?: number) => {
|
||||||
|
if (node.children) {
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
visit(node.children[i], node, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.type === "strong" && parent && typeof index === "number") {
|
||||||
|
const text = extractText(node);
|
||||||
|
parent.children[index] = { type: "html", value: `<strong>${text}</strong>` };
|
||||||
|
}
|
||||||
|
if (node.type === "emphasis" && parent && typeof index === "number") {
|
||||||
|
const text = extractText(node);
|
||||||
|
parent.children[index] = { type: "html", value: `<em>${text}</em>` };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit(tree);
|
||||||
|
const stringify = unified().use(remarkStringify);
|
||||||
|
let result = stringify.stringify(tree);
|
||||||
|
result = result.replace(/&#x([0-9A-Fa-f]+);/g, (_, hex) =>
|
||||||
|
String.fromCodePoint(parseInt(hex, 16))
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderMarkdown(raw: string, renderer: RendererAPI): {
|
||||||
|
html: string;
|
||||||
|
readingTime: ReadTimeResults;
|
||||||
|
} {
|
||||||
|
const { markdownContent, readingTime: readingTimeResult } =
|
||||||
|
renderer.parseFrontMatterAndContent(raw);
|
||||||
|
const preprocessed = preprocessCjkEmphasis(markdownContent);
|
||||||
|
const html = marked.parse(preprocessed) as string;
|
||||||
|
return { html, readingTime: readingTimeResult };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postProcessHtml(
|
||||||
|
baseHtml: string,
|
||||||
|
reading: ReadTimeResults,
|
||||||
|
renderer: RendererAPI
|
||||||
|
): string {
|
||||||
|
let html = baseHtml;
|
||||||
|
html = renderer.buildReadingTime(reading) + html;
|
||||||
|
html += renderer.buildFootnotes();
|
||||||
|
html += renderer.buildAddition();
|
||||||
|
html += `
|
||||||
|
<style>
|
||||||
|
.hljs.code__pre > .mac-sign {
|
||||||
|
display: ${renderer.getOpts().isMacCodeBlock ? "flex" : "none"};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
html += `
|
||||||
|
<style>
|
||||||
|
h2 strong {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
return renderer.createContainer(html);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import type { ThemeName } from "./types.js";
|
||||||
|
|
||||||
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
export const THEME_DIR = path.resolve(SCRIPT_DIR, "themes");
|
||||||
|
const FALLBACK_THEMES: ThemeName[] = ["default", "grace", "simple"];
|
||||||
|
|
||||||
|
function stripOutputScope(cssContent: string): string {
|
||||||
|
let css = cssContent;
|
||||||
|
css = css.replace(/#output\s*\{/g, "body {");
|
||||||
|
css = css.replace(/#output\s+/g, "");
|
||||||
|
css = css.replace(/^#output\s*/gm, "");
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverThemesFromDir(dir: string): string[] {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
.readdirSync(dir)
|
||||||
|
.filter((name) => name.endsWith(".css"))
|
||||||
|
.map((name) => name.replace(/\.css$/i, ""))
|
||||||
|
.filter((name) => name.toLowerCase() !== "base");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveThemeNames(): ThemeName[] {
|
||||||
|
const localThemes = discoverThemesFromDir(THEME_DIR);
|
||||||
|
const resolved = localThemes.filter((name) =>
|
||||||
|
fs.existsSync(path.join(THEME_DIR, `${name}.css`))
|
||||||
|
);
|
||||||
|
return resolved.length ? resolved : FALLBACK_THEMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const THEME_NAMES: ThemeName[] = resolveThemeNames();
|
||||||
|
|
||||||
|
export function loadThemeCss(theme: ThemeName): {
|
||||||
|
baseCss: string;
|
||||||
|
themeCss: string;
|
||||||
|
} {
|
||||||
|
const basePath = path.join(THEME_DIR, "base.css");
|
||||||
|
const themePath = path.join(THEME_DIR, `${theme}.css`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(basePath)) {
|
||||||
|
throw new Error(`Missing base CSS: ${basePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(themePath)) {
|
||||||
|
throw new Error(`Missing theme CSS for "${theme}": ${themePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseCss: fs.readFileSync(basePath, "utf-8"),
|
||||||
|
themeCss: fs.readFileSync(themePath, "utf-8"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeThemeCss(css: string): string {
|
||||||
|
return stripOutputScope(css);
|
||||||
|
}
|
||||||
@@ -20,7 +20,20 @@ container {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== Global resets ==================== */
|
||||||
|
blockquote {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 去除第一个元素的 margin-top */
|
/* 去除第一个元素的 margin-top */
|
||||||
#output section > :first-child {
|
#output section > :first-child {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram .nodeLabel p {
|
||||||
|
color: unset !important;
|
||||||
|
letter-spacing: unset !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ pre.code__pre,
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 10px 8px;
|
margin: 10px 8px;
|
||||||
|
box-shadow: inset 0 0 10px rgba(0,0,0,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 图片 ==================== */
|
/* ==================== 图片 ==================== */
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
/**
|
||||||
|
* MD 现代主题 (modern)
|
||||||
|
* 大圆角、药丸形标题、宽松行距、现代感
|
||||||
|
* 如需使用主题色,请使用 var(--md-primary-color) 代替颜色值
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ==================== 容器样式覆盖 ==================== */
|
||||||
|
section,
|
||||||
|
container {
|
||||||
|
font-family: var(--md-font-family);
|
||||||
|
font-size: var(--md-font-size);
|
||||||
|
line-height: 2;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
font-weight: 400;
|
||||||
|
background-color: var(--md-container-bg);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.01);
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
font-family: var(--md-font-family);
|
||||||
|
font-size: var(--md-font-size);
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 一级标题 ==================== */
|
||||||
|
h1 {
|
||||||
|
display: table;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
margin: 20px auto;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
background: var(--md-primary-color);
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 二级标题 ==================== */
|
||||||
|
h2 {
|
||||||
|
display: block;
|
||||||
|
padding: 0.2em 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.578px;
|
||||||
|
line-height: 1.7;
|
||||||
|
border-bottom: 2px solid var(--md-accent-color);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 三级标题 ==================== */
|
||||||
|
h3 {
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid var(--md-primary-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 8px 10px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 四级标题 ==================== */
|
||||||
|
h4 {
|
||||||
|
margin: 0 8px 10px;
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 五级标题 ==================== */
|
||||||
|
h5 {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 8px 10px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
border: 1px solid rgb(189, 224, 254);
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 六级标题 ==================== */
|
||||||
|
h6 {
|
||||||
|
margin: 0 8px 10px;
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 段落 ==================== */
|
||||||
|
p {
|
||||||
|
margin: 20px 0;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 2;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 引用块 ==================== */
|
||||||
|
blockquote {
|
||||||
|
font-style: normal;
|
||||||
|
padding: 15px 0;
|
||||||
|
margin: 12px 0;
|
||||||
|
border-left: 7px solid var(--md-accent-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
background-color: var(--blockquote-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote > p {
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== GFM 警告块 ==================== */
|
||||||
|
.alert-title-note,
|
||||||
|
.alert-title-tip,
|
||||||
|
.alert-title-info,
|
||||||
|
.alert-title-important,
|
||||||
|
.alert-title-warning,
|
||||||
|
.alert-title-caution,
|
||||||
|
.alert-title-abstract,
|
||||||
|
.alert-title-summary,
|
||||||
|
.alert-title-tldr,
|
||||||
|
.alert-title-todo,
|
||||||
|
.alert-title-success,
|
||||||
|
.alert-title-done,
|
||||||
|
.alert-title-question,
|
||||||
|
.alert-title-help,
|
||||||
|
.alert-title-faq,
|
||||||
|
.alert-title-failure,
|
||||||
|
.alert-title-fail,
|
||||||
|
.alert-title-missing,
|
||||||
|
.alert-title-danger,
|
||||||
|
.alert-title-error,
|
||||||
|
.alert-title-bug,
|
||||||
|
.alert-title-example,
|
||||||
|
.alert-title-quote,
|
||||||
|
.alert-title-cite {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-note {
|
||||||
|
color: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-tip {
|
||||||
|
color: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-info {
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-important {
|
||||||
|
color: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-warning {
|
||||||
|
color: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-caution {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-abstract,
|
||||||
|
.alert-title-summary,
|
||||||
|
.alert-title-tldr {
|
||||||
|
color: #00bfff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-todo {
|
||||||
|
color: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-success,
|
||||||
|
.alert-title-done {
|
||||||
|
color: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-question,
|
||||||
|
.alert-title-help,
|
||||||
|
.alert-title-faq {
|
||||||
|
color: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-failure,
|
||||||
|
.alert-title-fail,
|
||||||
|
.alert-title-missing {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-danger,
|
||||||
|
.alert-title-error {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-bug {
|
||||||
|
color: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-example {
|
||||||
|
color: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-title-quote,
|
||||||
|
.alert-title-cite {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GFM Alert SVG 图标颜色 */
|
||||||
|
.alert-icon-note {
|
||||||
|
fill: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-tip {
|
||||||
|
fill: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-info {
|
||||||
|
fill: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-important {
|
||||||
|
fill: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-warning {
|
||||||
|
fill: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-caution {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-abstract,
|
||||||
|
.alert-icon-summary,
|
||||||
|
.alert-icon-tldr {
|
||||||
|
fill: #00bfff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-todo {
|
||||||
|
fill: #478be6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-success,
|
||||||
|
.alert-icon-done {
|
||||||
|
fill: #57ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-question,
|
||||||
|
.alert-icon-help,
|
||||||
|
.alert-icon-faq {
|
||||||
|
fill: #c69026;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-failure,
|
||||||
|
.alert-icon-fail,
|
||||||
|
.alert-icon-missing {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-danger,
|
||||||
|
.alert-icon-error {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-bug {
|
||||||
|
fill: #e5534b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-example {
|
||||||
|
fill: #986ee2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-icon-quote,
|
||||||
|
.alert-icon-cite {
|
||||||
|
fill: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 代码块 ==================== */
|
||||||
|
pre.code__pre,
|
||||||
|
.hljs.code__pre {
|
||||||
|
font-size: 90%;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 10px 8px;
|
||||||
|
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 图片 ==================== */
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0.1em auto 0.5em;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 列表 ==================== */
|
||||||
|
ol {
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: circle;
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: block;
|
||||||
|
margin: 0.2em 8px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 脚注 ==================== */
|
||||||
|
p.footnotes {
|
||||||
|
margin: 0.5em 8px;
|
||||||
|
font-size: 80%;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 图表 ==================== */
|
||||||
|
figure {
|
||||||
|
margin: 1.5em 8px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption,
|
||||||
|
.md-figcaption {
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 分隔线 ==================== */
|
||||||
|
hr {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px 0 0;
|
||||||
|
border-color: var(--md-accent-color);
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 行内代码 ==================== */
|
||||||
|
code {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #d14;
|
||||||
|
background: rgba(27, 31, 35, 0.05);
|
||||||
|
padding: 3px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码块内的 code 标签需要特殊处理(覆盖行内 code 样式) */
|
||||||
|
pre.code__pre > code,
|
||||||
|
.hljs.code__pre > code {
|
||||||
|
display: -webkit-box;
|
||||||
|
padding: 0.5em 1em 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
text-indent: 0;
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 强调 ==================== */
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 链接 ==================== */
|
||||||
|
a {
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 粗体 ==================== */
|
||||||
|
strong {
|
||||||
|
color: var(--md-primary-color);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 表格 ==================== */
|
||||||
|
table {
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
font-weight: bold;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
border: 1px solid #dfdfdf;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
word-break: keep-all;
|
||||||
|
background: color-mix(in srgb, var(--md-primary-color) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid #dfdfdf;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== KaTeX 公式 ==================== */
|
||||||
|
.katex-inline {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.katex-block {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 标记高亮 ==================== */
|
||||||
|
.markup-highlight {
|
||||||
|
background-color: var(--md-primary-color);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup-underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: var(--md-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup-wavyline {
|
||||||
|
text-decoration: underline wavy;
|
||||||
|
text-decoration-color: var(--md-primary-color);
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import type { ReadTimeResults } from "reading-time";
|
||||||
|
|
||||||
|
export type ThemeName = string;
|
||||||
|
|
||||||
|
export interface StyleConfig {
|
||||||
|
primaryColor: string;
|
||||||
|
fontFamily: string;
|
||||||
|
fontSize: string;
|
||||||
|
foreground: string;
|
||||||
|
blockquoteBackground: string;
|
||||||
|
accentColor: string;
|
||||||
|
containerBg: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpts {
|
||||||
|
legend?: string;
|
||||||
|
citeStatus?: boolean;
|
||||||
|
countStatus?: boolean;
|
||||||
|
isMacCodeBlock?: boolean;
|
||||||
|
isShowLineNumber?: boolean;
|
||||||
|
themeMode?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RendererAPI {
|
||||||
|
reset: (newOpts: Partial<IOpts>) => void;
|
||||||
|
setOptions: (newOpts: Partial<IOpts>) => void;
|
||||||
|
getOpts: () => IOpts;
|
||||||
|
parseFrontMatterAndContent: (markdown: string) => {
|
||||||
|
yamlData: Record<string, any>;
|
||||||
|
markdownContent: string;
|
||||||
|
readingTime: ReadTimeResults;
|
||||||
|
};
|
||||||
|
buildReadingTime: (reading: ReadTimeResults) => string;
|
||||||
|
buildFootnotes: () => string;
|
||||||
|
buildAddition: () => string;
|
||||||
|
createContainer: (html: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParseResult {
|
||||||
|
yamlData: Record<string, any>;
|
||||||
|
markdownContent: string;
|
||||||
|
readingTime: ReadTimeResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CliOptions {
|
||||||
|
inputPath: string;
|
||||||
|
theme: ThemeName;
|
||||||
|
keepTitle: boolean;
|
||||||
|
primaryColor?: string;
|
||||||
|
fontFamily?: string;
|
||||||
|
fontSize?: string;
|
||||||
|
codeTheme: string;
|
||||||
|
isMacCodeBlock: boolean;
|
||||||
|
isShowLineNumber: boolean;
|
||||||
|
citeStatus: boolean;
|
||||||
|
countStatus: boolean;
|
||||||
|
legend: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendConfig {
|
||||||
|
default_theme: string | null;
|
||||||
|
default_color: string | null;
|
||||||
|
default_font_family: string | null;
|
||||||
|
default_font_size: string | null;
|
||||||
|
default_code_theme: string | null;
|
||||||
|
mac_code_block: boolean | null;
|
||||||
|
show_line_number: boolean | null;
|
||||||
|
cite: boolean | null;
|
||||||
|
count: boolean | null;
|
||||||
|
legend: string | null;
|
||||||
|
keep_title: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HtmlDocumentMeta {
|
||||||
|
title: string;
|
||||||
|
author?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
@@ -307,14 +307,17 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|||||||
return { frontmatter, body: match[2]! };
|
return { frontmatter, body: match[2]! };
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMarkdownToHtml(markdownPath: string, theme: string = "default"): string {
|
function renderMarkdownToHtml(markdownPath: string, theme: string = "default", color?: string): string {
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const renderScript = path.join(__dirname, "md", "render.ts");
|
const renderScript = path.join(__dirname, "md", "render.ts");
|
||||||
const baseDir = path.dirname(markdownPath);
|
const baseDir = path.dirname(markdownPath);
|
||||||
|
|
||||||
console.error(`[wechat-api] Rendering markdown with theme: ${theme}`);
|
const renderArgs = ["-y", "bun", renderScript, markdownPath, "--theme", theme];
|
||||||
const result = spawnSync("npx", ["-y", "bun", renderScript, markdownPath, "--theme", theme], {
|
if (color) renderArgs.push("--color", color);
|
||||||
|
|
||||||
|
console.error(`[wechat-api] Rendering markdown with theme: ${theme}${color ? `, color: ${color}` : ""}`);
|
||||||
|
const result = spawnSync("npx", renderArgs, {
|
||||||
stdio: ["inherit", "pipe", "pipe"],
|
stdio: ["inherit", "pipe", "pipe"],
|
||||||
cwd: baseDir,
|
cwd: baseDir,
|
||||||
});
|
});
|
||||||
@@ -356,7 +359,8 @@ Options:
|
|||||||
--title <title> Override title
|
--title <title> Override title
|
||||||
--author <name> Author name (max 16 chars)
|
--author <name> Author name (max 16 chars)
|
||||||
--summary <text> Article summary/digest (max 128 chars)
|
--summary <text> Article summary/digest (max 128 chars)
|
||||||
--theme <name> Theme name for markdown (default, grace, simple). Default: default
|
--theme <name> Theme name for markdown (default, grace, simple, modern). Default: default
|
||||||
|
--color <name|hex> Primary color (blue, green, vermilion, etc. or hex)
|
||||||
--cover <path> Cover image path (local or URL)
|
--cover <path> Cover image path (local or URL)
|
||||||
--dry-run Parse and render only, don't publish
|
--dry-run Parse and render only, don't publish
|
||||||
--help Show this help
|
--help Show this help
|
||||||
@@ -398,6 +402,7 @@ interface CliArgs {
|
|||||||
author?: string;
|
author?: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
color?: string;
|
||||||
cover?: string;
|
cover?: string;
|
||||||
dryRun: boolean;
|
dryRun: boolean;
|
||||||
}
|
}
|
||||||
@@ -430,6 +435,8 @@ function parseArgs(argv: string[]): CliArgs {
|
|||||||
args.summary = argv[++i];
|
args.summary = argv[++i];
|
||||||
} else if (arg === "--theme" && argv[i + 1]) {
|
} else if (arg === "--theme" && argv[i + 1]) {
|
||||||
args.theme = argv[++i]!;
|
args.theme = argv[++i]!;
|
||||||
|
} else if (arg === "--color" && argv[i + 1]) {
|
||||||
|
args.color = argv[++i];
|
||||||
} else if (arg === "--cover" && argv[i + 1]) {
|
} else if (arg === "--cover" && argv[i + 1]) {
|
||||||
args.cover = argv[++i];
|
args.cover = argv[++i];
|
||||||
} else if (arg === "--dry-run") {
|
} else if (arg === "--dry-run") {
|
||||||
@@ -506,8 +513,8 @@ async function main(): Promise<void> {
|
|||||||
if (!author) author = frontmatter.author || "";
|
if (!author) author = frontmatter.author || "";
|
||||||
if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
|
if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
|
||||||
|
|
||||||
console.error(`[wechat-api] Theme: ${args.theme}`);
|
console.error(`[wechat-api] Theme: ${args.theme}${args.color ? `, color: ${args.color}` : ""}`);
|
||||||
htmlPath = renderMarkdownToHtml(filePath, args.theme);
|
htmlPath = renderMarkdownToHtml(filePath, args.theme, args.color);
|
||||||
console.error(`[wechat-api] HTML generated: ${htmlPath}`);
|
console.error(`[wechat-api] HTML generated: ${htmlPath}`);
|
||||||
htmlContent = extractHtmlContent(htmlPath);
|
htmlContent = extractHtmlContent(htmlPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface ArticleOptions {
|
|||||||
htmlFile?: string;
|
htmlFile?: string;
|
||||||
markdownFile?: string;
|
markdownFile?: string;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
color?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
images?: string[];
|
images?: string[];
|
||||||
@@ -181,12 +182,13 @@ async function pasteFromClipboardInEditor(session: ChromeSession): Promise<void>
|
|||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: string): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> {
|
async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: string, color?: string): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> {
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const mdToWechatScript = path.join(__dirname, 'md-to-wechat.ts');
|
const mdToWechatScript = path.join(__dirname, 'md-to-wechat.ts');
|
||||||
const args = ['-y', 'bun', mdToWechatScript, markdownPath];
|
const args = ['-y', 'bun', mdToWechatScript, markdownPath];
|
||||||
if (theme) args.push('--theme', theme);
|
if (theme) args.push('--theme', theme);
|
||||||
|
if (color) args.push('--color', color);
|
||||||
|
|
||||||
const result = spawnSync('npx', args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
const result = spawnSync('npx', args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
@@ -381,7 +383,7 @@ async function removeExtraEmptyLineAfterImage(session: ChromeSession): Promise<b
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function postArticle(options: ArticleOptions): Promise<void> {
|
export async function postArticle(options: ArticleOptions): Promise<void> {
|
||||||
const { title, content, htmlFile, markdownFile, theme, author, summary, images = [], submit = false, profileDir, cdpPort } = options;
|
const { title, content, htmlFile, markdownFile, theme, color, author, summary, images = [], submit = false, profileDir, cdpPort } = options;
|
||||||
let { contentImages = [] } = options;
|
let { contentImages = [] } = options;
|
||||||
let effectiveTitle = title || '';
|
let effectiveTitle = title || '';
|
||||||
let effectiveAuthor = author || '';
|
let effectiveAuthor = author || '';
|
||||||
@@ -390,7 +392,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
|
|||||||
|
|
||||||
if (markdownFile) {
|
if (markdownFile) {
|
||||||
console.log(`[wechat] Parsing markdown: ${markdownFile}`);
|
console.log(`[wechat] Parsing markdown: ${markdownFile}`);
|
||||||
const parsed = await parseMarkdownWithPlaceholders(markdownFile, theme);
|
const parsed = await parseMarkdownWithPlaceholders(markdownFile, theme, color);
|
||||||
effectiveTitle = effectiveTitle || parsed.title;
|
effectiveTitle = effectiveTitle || parsed.title;
|
||||||
effectiveAuthor = effectiveAuthor || parsed.author;
|
effectiveAuthor = effectiveAuthor || parsed.author;
|
||||||
effectiveSummary = effectiveSummary || parsed.summary;
|
effectiveSummary = effectiveSummary || parsed.summary;
|
||||||
@@ -674,6 +676,7 @@ Options:
|
|||||||
--html <path> HTML file to paste (alternative to --content)
|
--html <path> HTML file to paste (alternative to --content)
|
||||||
--markdown <path> Markdown file to convert and post (recommended)
|
--markdown <path> Markdown file to convert and post (recommended)
|
||||||
--theme <name> Theme for markdown (default, grace, simple, modern)
|
--theme <name> Theme for markdown (default, grace, simple, modern)
|
||||||
|
--color <name|hex> Primary color (blue, green, vermilion, etc. or hex)
|
||||||
--author <name> Author name
|
--author <name> Author name
|
||||||
--summary <text> Article summary
|
--summary <text> Article summary
|
||||||
--image <path> Content image, can repeat (only with --content)
|
--image <path> Content image, can repeat (only with --content)
|
||||||
@@ -705,6 +708,7 @@ async function main(): Promise<void> {
|
|||||||
let htmlFile: string | undefined;
|
let htmlFile: string | undefined;
|
||||||
let markdownFile: string | undefined;
|
let markdownFile: string | undefined;
|
||||||
let theme: string | undefined;
|
let theme: string | undefined;
|
||||||
|
let color: string | undefined;
|
||||||
let author: string | undefined;
|
let author: string | undefined;
|
||||||
let summary: string | undefined;
|
let summary: string | undefined;
|
||||||
let submit = false;
|
let submit = false;
|
||||||
@@ -718,6 +722,7 @@ async function main(): Promise<void> {
|
|||||||
else if (arg === '--html' && args[i + 1]) htmlFile = args[++i];
|
else if (arg === '--html' && args[i + 1]) htmlFile = args[++i];
|
||||||
else if (arg === '--markdown' && args[i + 1]) markdownFile = args[++i];
|
else if (arg === '--markdown' && args[i + 1]) markdownFile = args[++i];
|
||||||
else if (arg === '--theme' && args[i + 1]) theme = args[++i];
|
else if (arg === '--theme' && args[i + 1]) theme = args[++i];
|
||||||
|
else if (arg === '--color' && args[i + 1]) color = args[++i];
|
||||||
else if (arg === '--author' && args[i + 1]) author = args[++i];
|
else if (arg === '--author' && args[i + 1]) author = args[++i];
|
||||||
else if (arg === '--summary' && args[i + 1]) summary = args[++i];
|
else if (arg === '--summary' && args[i + 1]) summary = args[++i];
|
||||||
else if (arg === '--image' && args[i + 1]) images.push(args[++i]!);
|
else if (arg === '--image' && args[i + 1]) images.push(args[++i]!);
|
||||||
@@ -729,7 +734,7 @@ async function main(): Promise<void> {
|
|||||||
if (!markdownFile && !htmlFile && !title) { console.error('Error: --title is required (or use --markdown/--html)'); process.exit(1); }
|
if (!markdownFile && !htmlFile && !title) { console.error('Error: --title is required (or use --markdown/--html)'); process.exit(1); }
|
||||||
if (!markdownFile && !htmlFile && !content) { console.error('Error: --content, --html, or --markdown is required'); process.exit(1); }
|
if (!markdownFile && !htmlFile && !content) { console.error('Error: --content, --html, or --markdown is required'); process.exit(1); }
|
||||||
|
|
||||||
await postArticle({ title: title || '', content, htmlFile, markdownFile, theme, author, summary, images, submit, profileDir, cdpPort });
|
await postArticle({ title: title || '', content, htmlFile, markdownFile, theme, color, author, summary, images, submit, profileDir, cdpPort });
|
||||||
}
|
}
|
||||||
|
|
||||||
await main().then(() => {
|
await main().then(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user