mirror of
https://github.com/yeban8090/note-to-red.git
synced 2026-07-22 05:42:34 +00:00
Initial commit: Note to RED plugin for Obsidian
This commit is contained in:
commit
eeff220b7c
34 changed files with 6108 additions and 0 deletions
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Node
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
*.log
|
||||
|
||||
# Build
|
||||
main.js
|
||||
*.js.map
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Obsidian
|
||||
.obsidian/
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 夜半Yeban
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
58
README.md
Normal file
58
README.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Note to RED
|
||||
|
||||
一键将 Obsidian 笔记转换为小红书图片格式的插件。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 📝 使用二级标题分割内容,每个标题自动生成一张配图
|
||||
- 🎨 提供多种精美模板,支持自定义字体和字号
|
||||
- 👤 可自定义用户头像、昵称和页脚文案
|
||||
- 🔄 实时预览编辑效果
|
||||
- 📥 支持单页导出和批量导出
|
||||
- 🔒 锁定功能避免预览刷新打断书写
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 核心用法:用二级标题(##)分割内容,每个标题生成一张小红书配图
|
||||
2. 首图制作:单独调整首节字号至20-24px,使用【下载当前页】导出
|
||||
3. 长文优化:内容较多的章节可调小字号至14-16px后单独导出
|
||||
4. 批量操作:保持统一字号时,用【导出全部页】批量生成
|
||||
5. 模板切换:顶部选择器可切换不同视觉风格
|
||||
6. 实时编辑:解锁状态(🔓)下编辑文档即时预览效果
|
||||
|
||||
## 安装方法
|
||||
|
||||
### 从 Obsidian 社区插件安装(推荐)
|
||||
|
||||
1. 打开 Obsidian 设置
|
||||
2. 转到第三方插件设置
|
||||
3. 关闭安全模式
|
||||
4. 点击浏览社区插件
|
||||
5. 搜索 "Note to RED"
|
||||
6. 点击安装并启用插件
|
||||
|
||||
### 手动安装
|
||||
|
||||
1. 下载最新版本的 release 文件
|
||||
2. 解压后将文件夹复制到 Obsidian 插件目录:`{vault}/.obsidian/plugins/`
|
||||
3. 重启 Obsidian
|
||||
4. 在设置中启用插件
|
||||
|
||||
## 使用技巧
|
||||
|
||||
- 使用二级标题来分割不同的图片内容
|
||||
- 调整字号大小以适应不同长度的内容
|
||||
- 可以自定义头像和用户信息
|
||||
- 支持实时预览和编辑
|
||||
- 提供多种模板以适应不同场景
|
||||
|
||||
## 问题反馈
|
||||
|
||||
如果您在使用过程中遇到任何问题,或有任何功能建议,欢迎:
|
||||
|
||||
1. 提交 [Issue](https://github.com/YeBan/note-to-red/issues)
|
||||
2. 发送邮件至:[yuliu8090@gmail.com]
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License。查看 [LICENSE](LICENSE) 获取更多信息。
|
||||
BIN
assets/donate.png
Normal file
BIN
assets/donate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
BIN
assets/qrcode.png
Normal file
BIN
assets/qrcode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
36
build.sh
Executable file
36
build.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 获取版本号
|
||||
version=$(grep '"version"' manifest.json | cut -d '"' -f 4)
|
||||
zip_name="note-to-red-${version}.zip"
|
||||
|
||||
# 检查目标文件是否存在
|
||||
if [ -f "../${zip_name}" ]; then
|
||||
read -p "文件 ${zip_name} 已存在,是否覆盖?(y/n) " answer
|
||||
if [ "$answer" != "y" ]; then
|
||||
echo "打包已取消"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建临时目录
|
||||
mkdir -p ../temp/note-to-red
|
||||
|
||||
# 复制必要文件
|
||||
cp main.js manifest.json styles.css ../temp/note-to-red/
|
||||
cp -r assets ../temp/note-to-red/
|
||||
|
||||
# 切换到临时目录的上级目录
|
||||
cd ../temp
|
||||
|
||||
# 创建 zip 文件
|
||||
zip -r "${zip_name}" note-to-red
|
||||
|
||||
# 移动 zip 文件到上级目录
|
||||
mv "${zip_name}" ../
|
||||
|
||||
# 清理临时目录
|
||||
cd ..
|
||||
rm -rf temp
|
||||
|
||||
echo "打包完成:${zip_name}"
|
||||
13
data.json
Normal file
13
data.json
Normal file
File diff suppressed because one or more lines are too long
38
esbuild.config.mjs
Normal file
38
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
...builtins
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2016",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
12
manifest.json
Normal file
12
manifest.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"id": "note-to-red",
|
||||
"name": "Note to RED",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Convert Obsidian notes to RED (Xiaohongshu) style images",
|
||||
"author": "Yeban",
|
||||
"isDesktopOnly": true,
|
||||
"assets": [
|
||||
"assets/*"
|
||||
]
|
||||
}
|
||||
2567
package-lock.json
generated
Normal file
2567
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
35
package.json
Normal file
35
package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "note-to-red",
|
||||
"version": "1.0.0",
|
||||
"description": "一键将 Obsidian 笔记转换为小红书图片格式",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [
|
||||
"obsidian",
|
||||
"plugin",
|
||||
"xiaohongshu"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/jszip": "^3.4.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"dom-to-image": "^2.6.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jszip": "^3.10.1"
|
||||
}
|
||||
}
|
||||
43
src/backgroundManager.ts
Normal file
43
src/backgroundManager.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { backgrounds } from './backgrounds';
|
||||
|
||||
export interface Background {
|
||||
id: string;
|
||||
name: string;
|
||||
style: string;
|
||||
}
|
||||
|
||||
export class BackgroundManager {
|
||||
private backgrounds: Background[];
|
||||
private currentBackground: Background | null = null;
|
||||
|
||||
constructor() {
|
||||
this.backgrounds = backgrounds.backgrounds;
|
||||
}
|
||||
|
||||
public getAllBackgrounds(): Background[] {
|
||||
return this.backgrounds;
|
||||
}
|
||||
|
||||
public setBackground(id: string | null) {
|
||||
if (!id) {
|
||||
this.currentBackground = null;
|
||||
return;
|
||||
}
|
||||
const background = this.backgrounds.find(bg => bg.id === id);
|
||||
if (background) {
|
||||
this.currentBackground = background;
|
||||
}
|
||||
}
|
||||
|
||||
public applyBackground(element: HTMLElement) {
|
||||
// 修改选择器为目标预览容器
|
||||
const previewContainer = element.querySelector('.red-image-preview');
|
||||
if (!previewContainer) return;
|
||||
|
||||
if (!this.currentBackground) {
|
||||
previewContainer.setAttribute('style', ''); // 清除样式
|
||||
return;
|
||||
}
|
||||
previewContainer.setAttribute('style', this.currentBackground.style);
|
||||
}
|
||||
}
|
||||
39
src/backgrounds/index.ts
Normal file
39
src/backgrounds/index.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
export const backgrounds = {
|
||||
backgrounds: [
|
||||
{
|
||||
id: "grid",
|
||||
name: "网格",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.03) 2%, rgba(0, 0, 0, 0) 2%), linear-gradient(360deg, rgba(50, 0, 0, 0.03) 2%, rgba(0, 0, 0, 0) 2%); background-size: 20px 20px; background-position: center center;"
|
||||
},
|
||||
{
|
||||
id: "crosshatch",
|
||||
name: "交叉",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: repeating-linear-gradient(45deg, rgba(50, 0, 0, 0.02) 0, rgba(50, 0, 0, 0.02) 1px, transparent 1px, transparent 50%), repeating-linear-gradient(-45deg, rgba(50, 0, 0, 0.02) 0, rgba(50, 0, 0, 0.02) 1px, transparent 1px, transparent 50%); background-size: 20px 20px;"
|
||||
},
|
||||
{
|
||||
id: "dots",
|
||||
name: "圆点",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: radial-gradient(rgba(50, 0, 0, 0.03) 1px, transparent 1px); background-size: 20px 20px; background-position: center center;"
|
||||
},
|
||||
{
|
||||
id: "dash",
|
||||
name: "虚线",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.03) 50%, transparent 50%); background-size: 8px 1px; background-position: center center;"
|
||||
},
|
||||
{
|
||||
id: "wave",
|
||||
name: "波浪",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: linear-gradient(45deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%), linear-gradient(135deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%), linear-gradient(45deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%), linear-gradient(135deg, rgba(50, 0, 0, 0.04) 12%, transparent 12%, transparent 88%, rgba(50, 0, 0, 0.04) 88%); background-size: 30px 30px; background-position: 0 0, 0 0, 15px 15px, 15px 15px;"
|
||||
},
|
||||
{
|
||||
id: "checkerboard",
|
||||
name: "棋盘",
|
||||
style: "box-sizing: border-box; background-color: white; background-image: linear-gradient(45deg, rgba(50, 0, 0, 0.04) 25%, transparent 25%), linear-gradient(-45deg, rgba(50, 0, 0, 0.04) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(50, 0, 0, 0.04) 75%), linear-gradient(-45deg, transparent 75%, rgba(50, 0, 0, 0.04) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px;"
|
||||
},
|
||||
{
|
||||
id: "dark",
|
||||
name: "暗黑",
|
||||
style: "box-sizing: border-box; background-color: #1a1a1a; background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.03) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 255, 255, 0.03) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.03) 75%), linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.03) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px;"
|
||||
}
|
||||
]
|
||||
};
|
||||
244
src/converter.ts
Normal file
244
src/converter.ts
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import { App } from 'obsidian';
|
||||
|
||||
export class RedConverter {
|
||||
private static app: App;
|
||||
|
||||
static initialize(app: App) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
static hasValidContent(element: HTMLElement): boolean {
|
||||
const headers = element.querySelectorAll('h2');
|
||||
return headers.length > 0;
|
||||
}
|
||||
|
||||
static formatContent(element: HTMLElement): void {
|
||||
const headers = Array.from(element.querySelectorAll('h2'));
|
||||
|
||||
if (headers.length === 0) {
|
||||
element.empty();
|
||||
const tip = element.createEl('div', {
|
||||
cls: 'red-empty-message',
|
||||
text: `⚠️ 温馨提示
|
||||
请使用二级标题(##)来分割内容
|
||||
每个二级标题将生成一张独立的图片
|
||||
现在编辑文档,实时预览效果`
|
||||
});
|
||||
// 触发自定义事件
|
||||
element.dispatchEvent(new CustomEvent('content-validation-change', {
|
||||
detail: { isValid: false },
|
||||
bubbles: true
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 触发自定义事件表示内容有效
|
||||
element.dispatchEvent(new CustomEvent('content-validation-change', {
|
||||
detail: { isValid: true },
|
||||
bubbles: true
|
||||
}));
|
||||
|
||||
// 创建预览容器
|
||||
const previewContainer = document.createElement('div');
|
||||
previewContainer.className = 'red-preview-container';
|
||||
|
||||
// 创建图片预览区域
|
||||
const imagePreview = document.createElement('div');
|
||||
imagePreview.className = 'red-image-preview';
|
||||
|
||||
// 创建三个主要区域
|
||||
const headerArea = document.createElement('div');
|
||||
headerArea.className = 'red-preview-header';
|
||||
|
||||
const contentArea = document.createElement('div');
|
||||
contentArea.className = 'red-preview-content';
|
||||
|
||||
const footerArea = document.createElement('div');
|
||||
footerArea.className = 'red-preview-footer';
|
||||
|
||||
// 创建内容容器
|
||||
const contentContainer = document.createElement('div');
|
||||
contentContainer.className = 'red-content-container';
|
||||
|
||||
// 处理每个二级标题及其内容
|
||||
headers.forEach((header, index) => {
|
||||
const section = this.createContentSection(header, index);
|
||||
if (section) {
|
||||
contentContainer.appendChild(section);
|
||||
}
|
||||
});
|
||||
|
||||
// 组装结构
|
||||
contentArea.appendChild(contentContainer);
|
||||
imagePreview.appendChild(headerArea);
|
||||
imagePreview.appendChild(contentArea);
|
||||
imagePreview.appendChild(footerArea);
|
||||
previewContainer.appendChild(imagePreview);
|
||||
|
||||
// 处理完成后再清空原容器并添加新内容
|
||||
element.empty();
|
||||
element.appendChild(previewContainer);
|
||||
}
|
||||
|
||||
private static createContentSection(header: Element, index: number): HTMLElement | null {
|
||||
// 获取当前标题到下一个标题之间的所有内容
|
||||
let content = [];
|
||||
let current = header.nextElementSibling;
|
||||
|
||||
while (current && current.tagName !== 'H2') {
|
||||
content.push(current.cloneNode(true));
|
||||
current = current.nextElementSibling;
|
||||
}
|
||||
|
||||
// 创建内容区域
|
||||
const section = document.createElement('section');
|
||||
section.className = 'red-content-section';
|
||||
section.setAttribute('data-index', index.toString());
|
||||
|
||||
// 添加标题
|
||||
section.appendChild(header.cloneNode(true));
|
||||
|
||||
// 添加内容
|
||||
content.forEach(el => section.appendChild(el));
|
||||
|
||||
// 处理样式和格式
|
||||
this.processElements(section);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
private static processElements(container: HTMLElement | null): void {
|
||||
if (!container) return;
|
||||
|
||||
// 处理强调文本
|
||||
container.querySelectorAll('strong, em').forEach(el => {
|
||||
(el as HTMLElement).style.display = 'inline';
|
||||
});
|
||||
|
||||
// 处理链接
|
||||
container.querySelectorAll('a').forEach(el => {
|
||||
(el as HTMLElement).style.color = '#576b95';
|
||||
(el as HTMLElement).style.textDecoration = 'none';
|
||||
});
|
||||
|
||||
// 处理表格
|
||||
container.querySelectorAll('table').forEach(el => {
|
||||
if (el === container.closest('table')) return;
|
||||
(el as HTMLTableElement).style.borderCollapse = 'collapse';
|
||||
(el as HTMLTableElement).style.margin = '1em 0';
|
||||
(el as HTMLTableElement).style.width = '100%';
|
||||
});
|
||||
|
||||
// 处理表格单元格
|
||||
container.querySelectorAll('th, td').forEach(el => {
|
||||
if (el === container.closest('td')) return;
|
||||
(el as HTMLTableCellElement).style.border = '1px solid #dfdfdf';
|
||||
(el as HTMLTableCellElement).style.padding = '8px';
|
||||
});
|
||||
|
||||
// 处理分割线
|
||||
container.querySelectorAll('hr').forEach(el => {
|
||||
(el as HTMLElement).style.border = 'none';
|
||||
(el as HTMLElement).style.borderTop = '1px solid #dfdfdf';
|
||||
(el as HTMLElement).style.margin = '20px 0';
|
||||
});
|
||||
|
||||
// 处理删除线
|
||||
container.querySelectorAll('del').forEach(el => {
|
||||
(el as HTMLElement).style.textDecoration = 'line-through';
|
||||
});
|
||||
|
||||
// 处理任务列表
|
||||
container.querySelectorAll('.task-list-item').forEach(el => {
|
||||
(el as HTMLElement).style.listStyle = 'none';
|
||||
const checkbox = el.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) {
|
||||
(checkbox as HTMLElement).style.marginRight = '6px';
|
||||
}
|
||||
});
|
||||
|
||||
// 处理脚注
|
||||
container.querySelectorAll('.footnote-ref, .footnote-backref').forEach(el => {
|
||||
(el as HTMLElement).style.color = '#576b95';
|
||||
(el as HTMLElement).style.textDecoration = 'none';
|
||||
});
|
||||
|
||||
// 处理代码块
|
||||
container.querySelectorAll('pre code').forEach(el => {
|
||||
const pre = el.parentElement;
|
||||
if (pre) {
|
||||
// 添加 macOS 风格的窗口按钮
|
||||
const dots = document.createElement('div');
|
||||
dots.style.cssText = `
|
||||
margin: 8px 0 12px 0;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
const colors = ['#ff5f56', '#ffbd2e', '#27c93f'];
|
||||
colors.forEach(color => {
|
||||
const dot = document.createElement('span');
|
||||
dot.style.cssText = `
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: ${color};
|
||||
`;
|
||||
dots.appendChild(dot);
|
||||
});
|
||||
|
||||
// 将红绿灯插入到代码块的最前面
|
||||
pre.insertBefore(dots, pre.firstChild);
|
||||
pre.style.paddingTop = '32px';
|
||||
|
||||
// 移除原有的复制按钮
|
||||
const copyButton = pre.querySelector('.copy-code-button');
|
||||
if (copyButton) {
|
||||
copyButton.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 处理图片
|
||||
container.querySelectorAll('span.internal-embed[alt][src]').forEach(async el => {
|
||||
const originalSpan = el as HTMLElement;
|
||||
const src = originalSpan.getAttribute('src');
|
||||
const alt = originalSpan.getAttribute('alt');
|
||||
|
||||
if (!src) return;
|
||||
|
||||
try {
|
||||
// 获取文件的元数据
|
||||
const linktext = src.split('|')[0]; // 处理可能带有别名的链接
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(linktext, '');
|
||||
if (file) {
|
||||
const absolutePath = this.app.vault.adapter.getResourcePath(file.path);
|
||||
const newImg = document.createElement('img');
|
||||
newImg.src = absolutePath;
|
||||
if (alt) newImg.alt = alt;
|
||||
|
||||
// 应用样式
|
||||
newImg.style.maxWidth = '100%';
|
||||
newImg.style.height = 'auto';
|
||||
newImg.style.display = 'block';
|
||||
newImg.style.margin = '1em auto';
|
||||
|
||||
// 直接替换 span 元素,保留段落中的其他内容
|
||||
originalSpan.parentNode?.replaceChild(newImg, originalSpan);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('图片处理失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 处理引用块
|
||||
container.querySelectorAll('blockquote').forEach(el => {
|
||||
// 清理引用块内部段落的样式
|
||||
el.querySelectorAll('p').forEach(p => {
|
||||
p.style.margin = '0';
|
||||
p.style.padding = '0';
|
||||
p.style.lineHeight = 'inherit';
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
129
src/donateManager.ts
Normal file
129
src/donateManager.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
import { App, Plugin } from 'obsidian';
|
||||
|
||||
export class DonateManager {
|
||||
private static overlay: HTMLElement;
|
||||
private static modal: HTMLElement;
|
||||
private static app: App;
|
||||
private static plugin: Plugin;
|
||||
|
||||
public static initialize(app: App, plugin: Plugin) {
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static showDonateModal(container: HTMLElement) {
|
||||
this.overlay = container.createEl('div', {
|
||||
cls: 'mp-donate-overlay'
|
||||
});
|
||||
|
||||
this.modal = this.overlay.createEl('div', {
|
||||
cls: 'mp-about-modal'
|
||||
});
|
||||
|
||||
// 添加关闭按钮
|
||||
const closeButton = this.modal.createEl('button', {
|
||||
cls: 'mp-donate-close',
|
||||
text: '×'
|
||||
});
|
||||
|
||||
// 添加作者信息区域
|
||||
const authorSection = this.modal.createEl('div', {
|
||||
cls: 'mp-about-section mp-about-intro-section'
|
||||
});
|
||||
|
||||
authorSection.createEl('h4', {
|
||||
text: '关于作者',
|
||||
cls: 'mp-about-title'
|
||||
});
|
||||
|
||||
const introEl = authorSection.createEl('p', {
|
||||
cls: 'mp-about-intro'
|
||||
});
|
||||
introEl.innerHTML = '你好,我是<span class="mp-about-name">【夜半】</span>,一名<span class="mp-about-identity">全职写作与独立开发者</span>。';
|
||||
|
||||
const roleList = authorSection.createEl('div', {
|
||||
cls: 'mp-about-roles'
|
||||
});
|
||||
|
||||
const roleEl = roleList.createEl('p', {
|
||||
cls: 'mp-about-role'
|
||||
});
|
||||
roleEl.innerHTML = `这款插件是我为了在 Obsidian 写作后,<br>
|
||||
无需繁琐排版一键即可发布到小红书而开发的工具,<br>
|
||||
希望能让你的<span class="mp-about-highlight">排版更轻松</span>,
|
||||
让你的<span class="mp-about-value">创作更高效</span>。`;
|
||||
|
||||
// 添加插件介绍
|
||||
const descEl = authorSection.createEl('p', {
|
||||
cls: 'mp-about-desc'
|
||||
});
|
||||
descEl.innerHTML = `如果这款插件对你有帮助,<br>或者你愿意支持我的独立开发与写作,欢迎请我喝咖啡☕️。<br>
|
||||
你的支持对我来说意义重大,它能让我更专注地开发、写作。`;
|
||||
|
||||
// 添加打赏区域
|
||||
const donateSection = this.modal.createEl('div', {
|
||||
cls: 'mp-about-section mp-about-donate-section'
|
||||
});
|
||||
|
||||
donateSection.createEl('h4', {
|
||||
text: '请我喝咖啡',
|
||||
cls: 'mp-about-subtitle'
|
||||
});
|
||||
|
||||
const donateQR = donateSection.createEl('div', {
|
||||
cls: 'mp-about-qr'
|
||||
});
|
||||
donateQR.createEl('img', {
|
||||
attr: {
|
||||
src: this.app.vault.adapter.getResourcePath(`${this.plugin.manifest.dir}/assets/donate.png`),
|
||||
alt: '打赏二维码'
|
||||
}
|
||||
});
|
||||
|
||||
// 添加公众号区域
|
||||
const mpSection = this.modal.createEl('div', {
|
||||
cls: 'mp-about-section mp-about-mp-section'
|
||||
});
|
||||
|
||||
const mpDescEl = mpSection.createEl('p', {
|
||||
cls: 'mp-about-desc'
|
||||
});
|
||||
mpDescEl.innerHTML = `如果你想了解更多关于创作、效率工具的小技巧,<br>
|
||||
或者关注我未来的写作动态,欢迎关注我的微信公众号。`;
|
||||
|
||||
mpSection.createEl('h4', {
|
||||
text: '微信公众号',
|
||||
cls: 'mp-about-subtitle'
|
||||
});
|
||||
|
||||
const mpQR = mpSection.createEl('div', {
|
||||
cls: 'mp-about-qr'
|
||||
});
|
||||
mpQR.createEl('img', {
|
||||
attr: {
|
||||
src: this.app.vault.adapter.getResourcePath(`${this.plugin.manifest.dir}/assets/qrcode.png`),
|
||||
alt: '公众号二维码'
|
||||
}
|
||||
});
|
||||
|
||||
const footerEl = mpSection.createEl('p', {
|
||||
cls: 'mp-about-footer'
|
||||
});
|
||||
footerEl.innerHTML = '期待与你一起,在创作的世界里<strong>找到属于自己的意义</strong>。';
|
||||
|
||||
// 添加关闭事件
|
||||
closeButton.addEventListener('click', () => this.closeDonateModal());
|
||||
this.overlay.addEventListener('click', (e) => {
|
||||
if (e.target === this.overlay) {
|
||||
this.closeDonateModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static closeDonateModal() {
|
||||
if (this.overlay) {
|
||||
this.overlay.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/downloadManager.ts
Normal file
89
src/downloadManager.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import domtoimage from 'dom-to-image'; // 替换为 dom-to-image
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export class DownloadManager {
|
||||
// 添加共用的导出配置方法
|
||||
private static getExportConfig(imageElement: HTMLElement) {
|
||||
return {
|
||||
quality: 1,
|
||||
width: imageElement.offsetWidth * 4,
|
||||
height: imageElement.offsetHeight * 4,
|
||||
style: {
|
||||
transform: 'scale(4)',
|
||||
'transform-origin': 'top left',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static async downloadAllImages(element: HTMLElement): Promise<void> {
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
const previewContainer = element.querySelector('.red-preview-container');
|
||||
if (!previewContainer) {
|
||||
throw new Error('找不到预览容器');
|
||||
}
|
||||
|
||||
const sections = previewContainer.querySelectorAll('.red-content-section');
|
||||
const totalSections = sections.length;
|
||||
const originalDisplayStates = Array.from(sections).map(
|
||||
s => (s as HTMLElement).style.display
|
||||
);
|
||||
|
||||
for (let i = 0; i < totalSections; i++) {
|
||||
sections.forEach(s => (s as HTMLElement).style.display = 'none');
|
||||
(sections[i] as HTMLElement).style.display = 'block';
|
||||
|
||||
const imageElement = element.querySelector('.red-image-preview') as HTMLElement;
|
||||
const blob = await domtoimage.toBlob(imageElement, this.getExportConfig(imageElement));
|
||||
zip.file(`小红书笔记_第${i + 1}页.png`, blob);
|
||||
}
|
||||
|
||||
// 恢复所有 sections 的原始显示状态
|
||||
sections.forEach((s, index) => {
|
||||
(s as HTMLElement).style.display = originalDisplayStates[index];
|
||||
});
|
||||
|
||||
// 生成 zip 文件并下载
|
||||
const content = await zip.generateAsync({ type: "blob" });
|
||||
const url = URL.createObjectURL(content);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `小红书笔记_${new Date().getTime()}.zip`;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出图片失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async downloadSingleImage(element: HTMLElement): Promise<void> {
|
||||
try {
|
||||
const imageElement = element.querySelector('.red-image-preview') as HTMLElement;
|
||||
if (!imageElement) {
|
||||
throw new Error('找不到预览区域');
|
||||
}
|
||||
|
||||
const blob = await domtoimage.toBlob(imageElement, this.getExportConfig(imageElement));
|
||||
|
||||
// 创建下载链接并触发下载
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `小红书笔记_${new Date().getTime()}.png`;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出图片失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/main.ts
Normal file
77
src/main.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Plugin, Notice } from 'obsidian';
|
||||
import { RedView, VIEW_TYPE_RED } from './view'; // 暂时改回原来的导入
|
||||
import { TemplateManager } from './templateManager';
|
||||
import { SettingsManager } from './settings';
|
||||
import { RedConverter } from './converter'; // 暂时使用原来的转换器
|
||||
import { DonateManager } from './donateManager';
|
||||
|
||||
export default class RedPlugin extends Plugin {
|
||||
private settingsManager: SettingsManager;
|
||||
|
||||
async onload() {
|
||||
// 初始化设置管理器
|
||||
this.settingsManager = new SettingsManager(this);
|
||||
await this.settingsManager.loadSettings();
|
||||
|
||||
// 初始化模板管理器
|
||||
const templateManager = new TemplateManager(this.app);
|
||||
|
||||
// 初始化转换器
|
||||
RedConverter.initialize(this.app);
|
||||
|
||||
DonateManager.initialize(this.app, this);
|
||||
|
||||
// 注册视图
|
||||
this.registerView(
|
||||
VIEW_TYPE_RED,
|
||||
(leaf) => new RedView(leaf, templateManager, this.settingsManager)
|
||||
);
|
||||
|
||||
// 添加首次加载自动打开视图的逻辑
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
if (this.app.workspace.getLeavesOfType(VIEW_TYPE_RED).length === 0) {
|
||||
const leaf = this.app.workspace.getRightLeaf(false);
|
||||
if (leaf) {
|
||||
leaf.setViewState({
|
||||
type: VIEW_TYPE_RED,
|
||||
active: false // 设置为 false 表示不聚焦
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加命令到命令面板
|
||||
this.addCommand({
|
||||
id: 'open-mp-preview',
|
||||
name: '打开小红书图片预览',
|
||||
callback: async () => {
|
||||
await this.activateView();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
// 清理视图
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_RED); // 使用原来的视图类型
|
||||
}
|
||||
|
||||
async activateView() {
|
||||
// 如果视图已经存在,激活它
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_RED); // 使用原来的视图类型
|
||||
if (leaves.length > 0) {
|
||||
this.app.workspace.revealLeaf(leaves[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新视图
|
||||
const rightLeaf = this.app.workspace.getRightLeaf(false);
|
||||
if (rightLeaf) {
|
||||
await rightLeaf.setViewState({
|
||||
type: VIEW_TYPE_RED,
|
||||
active: true,
|
||||
});
|
||||
} else {
|
||||
new Notice('无法创建视图面板');
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/previewManager.ts
Normal file
156
src/previewManager.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import type { SettingsManager } from './settings';
|
||||
|
||||
export class PreviewManager {
|
||||
constructor(private settingsManager: SettingsManager) {}
|
||||
|
||||
// 创建页头内容
|
||||
createHeaderContent(headerArea: HTMLElement, handleAvatarClick: () => void,
|
||||
handleUserNameEdit: (el: HTMLElement) => void,
|
||||
handleUserIdEdit: (el: HTMLElement) => void) {
|
||||
headerArea.empty();
|
||||
const settings = this.settingsManager.getSettings();
|
||||
const userInfo = headerArea.createEl('div', { cls: 'red-user-info' });
|
||||
|
||||
// 左侧用户信息区
|
||||
const userLeft = userInfo.createEl('div', { cls: 'red-user-left' });
|
||||
|
||||
// 用户头像
|
||||
const avatar = userLeft.createEl('div', {
|
||||
cls: 'red-user-avatar',
|
||||
attr: { 'title': '点击上传头像' }
|
||||
});
|
||||
|
||||
if (settings.userAvatar) {
|
||||
avatar.createEl('img', {
|
||||
attr: {
|
||||
src: settings.userAvatar,
|
||||
alt: '用户头像'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const placeholder = avatar.createEl('div', {
|
||||
cls: 'red-avatar-placeholder'
|
||||
});
|
||||
placeholder.createEl('span', {
|
||||
cls: 'red-avatar-upload-icon',
|
||||
text: '📷'
|
||||
});
|
||||
}
|
||||
|
||||
avatar.addEventListener('click', handleAvatarClick);
|
||||
|
||||
// 用户信息区
|
||||
const userMeta = userLeft.createEl('div', { cls: 'red-user-meta' });
|
||||
|
||||
const userNameContainer = userMeta.createEl('div', {
|
||||
cls: 'red-user-name-container'
|
||||
});
|
||||
|
||||
const userName = userNameContainer.createEl('div', {
|
||||
cls: 'red-user-name',
|
||||
text: settings.userName,
|
||||
attr: { 'title': '点击编辑用户名' }
|
||||
});
|
||||
|
||||
userNameContainer.createEl('div', {
|
||||
cls: 'red-verified-icon',
|
||||
text: '✓'
|
||||
});
|
||||
|
||||
const userId = userMeta.createEl('div', {
|
||||
cls: 'red-user-id',
|
||||
text: settings.userId,
|
||||
attr: { 'title': '点击编辑用户ID' }
|
||||
});
|
||||
|
||||
userName.addEventListener('click', () => handleUserNameEdit(userName));
|
||||
userId.addEventListener('click', () => handleUserIdEdit(userId));
|
||||
|
||||
if (settings.showTime) {
|
||||
const userRight = userInfo.createEl('div', { cls: 'red-user-right' });
|
||||
userRight.createEl('div', {
|
||||
cls: 'red-post-time',
|
||||
text: new Date().toLocaleDateString(settings.timeFormat)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建页脚内容
|
||||
createFooterContent(footerArea: HTMLElement, handleFooterTextEdit: (el: HTMLElement, position: 'left' | 'right') => void) {
|
||||
footerArea.empty();
|
||||
const settings = this.settingsManager.getSettings();
|
||||
|
||||
// 直接使用 footerArea,移除多余的容器创建
|
||||
// 创建左侧文本
|
||||
const leftText = footerArea.createEl('div', {
|
||||
cls: 'red-footer-text',
|
||||
text: settings.footerLeftText,
|
||||
attr: { 'title': '点击编辑文本' }
|
||||
});
|
||||
|
||||
// 创建分隔符
|
||||
footerArea.createEl('div', {
|
||||
cls: 'red-footer-separator',
|
||||
text: '|'
|
||||
});
|
||||
|
||||
// 创建右侧文本
|
||||
const rightText = footerArea.createEl('div', {
|
||||
cls: 'red-footer-text',
|
||||
text: settings.footerRightText,
|
||||
attr: { 'title': '点击编辑文本' }
|
||||
});
|
||||
|
||||
// 添加点击编辑事件
|
||||
leftText.addEventListener('click', () => handleFooterTextEdit(leftText, 'left'));
|
||||
rightText.addEventListener('click', () => handleFooterTextEdit(rightText, 'right'));
|
||||
}
|
||||
|
||||
// 创建导航按钮
|
||||
createNavigationButtons(container: HTMLElement, totalImages: number, onNavigate: (direction: 'prev' | 'next') => void) {
|
||||
const previewContainer = container.querySelector('.red-preview-container');
|
||||
if (!previewContainer) return;
|
||||
|
||||
const navContainer = previewContainer.createEl('div', {
|
||||
cls: 'red-nav-container'
|
||||
});
|
||||
|
||||
const prevButton = navContainer.createEl('button', {
|
||||
cls: 'red-nav-button',
|
||||
text: '←'
|
||||
});
|
||||
|
||||
const indicator = navContainer.createEl('span', {
|
||||
cls: 'red-page-indicator',
|
||||
text: `1/${totalImages}`
|
||||
});
|
||||
|
||||
const nextButton = navContainer.createEl('button', {
|
||||
cls: 'red-nav-button',
|
||||
text: '→'
|
||||
});
|
||||
|
||||
// 添加导航事件
|
||||
prevButton.addEventListener('click', () => onNavigate('prev'));
|
||||
nextButton.addEventListener('click', () => onNavigate('next'));
|
||||
|
||||
return { prev: prevButton, next: nextButton, indicator };
|
||||
}
|
||||
|
||||
// 显示指定索引的图片
|
||||
showImage(index: number, sections: NodeListOf<Element>, navigationButtons?: {
|
||||
prev: HTMLButtonElement;
|
||||
next: HTMLButtonElement;
|
||||
indicator: HTMLElement;
|
||||
}) {
|
||||
sections.forEach((section, i) => {
|
||||
(section as HTMLElement).style.display = i === index ? 'block' : 'none';
|
||||
});
|
||||
|
||||
if (navigationButtons) {
|
||||
navigationButtons.prev.style.visibility = index === 0 ? 'hidden' : 'visible';
|
||||
navigationButtons.next.style.visibility = index === sections.length - 1 ? 'hidden' : 'visible';
|
||||
navigationButtons.indicator.textContent = `${index + 1}/${sections.length}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/settings.ts
Normal file
57
src/settings.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
interface RedSettings {
|
||||
templateId: string;
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
backgroundId: string;
|
||||
// 添加用户信息设置
|
||||
userAvatar: string;
|
||||
userName: string;
|
||||
userId: string;
|
||||
showTime: boolean;
|
||||
timeFormat: string;
|
||||
footerLeftText: string;
|
||||
footerRightText: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: RedSettings = {
|
||||
templateId: 'default',
|
||||
fontFamily: 'Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC"',
|
||||
fontSize: 16,
|
||||
backgroundId: '',
|
||||
// 修改默认用户信息
|
||||
userAvatar: '', // 默认为空,提示用户上传
|
||||
userName: '夜半',
|
||||
userId: '@Yeban',
|
||||
showTime: true,
|
||||
timeFormat: 'zh-CN',
|
||||
footerLeftText: '夜半过后,光明便启程',
|
||||
footerRightText: '欢迎关注公众号:夜半'
|
||||
|
||||
}
|
||||
|
||||
export class SettingsManager {
|
||||
private plugin: any;
|
||||
private settings: RedSettings;
|
||||
|
||||
constructor(plugin: any) {
|
||||
this.plugin = plugin;
|
||||
this.settings = DEFAULT_SETTINGS;
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.plugin.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.plugin.saveData(this.settings);
|
||||
}
|
||||
|
||||
getSettings(): RedSettings {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
async updateSettings(settings: Partial<RedSettings>) {
|
||||
this.settings = { ...this.settings, ...settings };
|
||||
await this.saveSettings();
|
||||
}
|
||||
}
|
||||
319
src/templateManager.ts
Normal file
319
src/templateManager.ts
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
import { App } from 'obsidian';
|
||||
import { templates } from './templates';
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
name: string;
|
||||
styles: {
|
||||
// 容器基础样式
|
||||
imagePreview: string;
|
||||
// 页眉样式组
|
||||
header: {
|
||||
avatar: {
|
||||
container: string;
|
||||
placeholder: string;
|
||||
image: string;
|
||||
};
|
||||
nameContainer: string;
|
||||
userName: string;
|
||||
userId: string;
|
||||
postTime: string;
|
||||
verifiedIcon: string;
|
||||
};
|
||||
// 页脚样式组
|
||||
footer: {
|
||||
container: string;
|
||||
text: string;
|
||||
separator: string;
|
||||
};
|
||||
title: {
|
||||
h1: {
|
||||
base: string;
|
||||
content: string;
|
||||
after: string;
|
||||
};
|
||||
h2: {
|
||||
base: string;
|
||||
content: string;
|
||||
after: string;
|
||||
};
|
||||
h3: {
|
||||
base: string;
|
||||
content: string;
|
||||
after: string;
|
||||
};
|
||||
base: {
|
||||
base: string;
|
||||
content: string;
|
||||
after: string;
|
||||
};
|
||||
};
|
||||
paragraph: string;
|
||||
list: {
|
||||
container: string;
|
||||
item: string;
|
||||
taskList: string;
|
||||
};
|
||||
quote: string;
|
||||
code: {
|
||||
block: string;
|
||||
inline: string;
|
||||
};
|
||||
image: string;
|
||||
link: string;
|
||||
emphasis: {
|
||||
strong: string;
|
||||
em: string;
|
||||
del: string;
|
||||
};
|
||||
table: {
|
||||
container: string;
|
||||
header: string;
|
||||
cell: string;
|
||||
};
|
||||
hr: string;
|
||||
footnote: {
|
||||
ref: string;
|
||||
backref: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class TemplateManager {
|
||||
private templates: Map<string, Template> = new Map();
|
||||
private currentTemplate: Template;
|
||||
private currentFont: string = '-apple-system';
|
||||
private currentFontSize: number = 16;
|
||||
private app: App;
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
this.loadTemplates(); // 加载模板
|
||||
}
|
||||
|
||||
public async loadTemplates() {
|
||||
try {
|
||||
// 直接从内置模板加载
|
||||
Object.values(templates).forEach(template => {
|
||||
this.templates.set(template.id, template);
|
||||
if (template.id === 'default') {
|
||||
this.currentTemplate = template;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载模板失败:', error);
|
||||
throw new Error('无法加载模板文件');
|
||||
}
|
||||
}
|
||||
|
||||
public getTemplate(id: string): Template | undefined {
|
||||
return this.templates.get(id);
|
||||
}
|
||||
|
||||
public getCurrentTemplate(): Template {
|
||||
return this.currentTemplate;
|
||||
}
|
||||
|
||||
public setCurrentTemplate(id: string): boolean {
|
||||
const template = this.templates.get(id);
|
||||
if (template) {
|
||||
this.currentTemplate = template;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getAllTemplates(): Template[] {
|
||||
return Array.from(this.templates.values());
|
||||
}
|
||||
|
||||
public setFont(fontFamily: string) {
|
||||
this.currentFont = fontFamily;
|
||||
}
|
||||
|
||||
public setFontSize(size: number) {
|
||||
this.currentFontSize = size;
|
||||
}
|
||||
|
||||
// 修改 applyTemplate 方法
|
||||
public applyTemplate(element: HTMLElement): void {
|
||||
const styles = this.currentTemplate.styles;
|
||||
|
||||
// 修改应用基础样式的方式
|
||||
const imagePreview = element.querySelector('.red-image-preview');
|
||||
if (imagePreview) {
|
||||
imagePreview.setAttribute('style', styles.imagePreview);
|
||||
}
|
||||
|
||||
// 应用页眉样式
|
||||
const header = element.querySelector('.red-preview-header');
|
||||
if (header) {
|
||||
// 用户头像
|
||||
header.querySelectorAll('.red-user-avatar').forEach(el => {
|
||||
el.setAttribute('style', styles.header.avatar.container);
|
||||
});
|
||||
header.querySelectorAll('.red-avatar-placeholder').forEach(el => {
|
||||
el.setAttribute('style', styles.header.avatar.placeholder);
|
||||
});
|
||||
header.querySelectorAll('.red-user-avatar img').forEach(el => {
|
||||
el.setAttribute('style', styles.header.avatar.image);
|
||||
});
|
||||
|
||||
// 用户名容器
|
||||
header.querySelectorAll('.red-user-name-container').forEach(el => {
|
||||
el.setAttribute('style', styles.header.nameContainer);
|
||||
});
|
||||
|
||||
// 用户名
|
||||
header.querySelectorAll('.red-user-name').forEach(el => {
|
||||
el.setAttribute('style', styles.header.userName);
|
||||
});
|
||||
|
||||
// 用户ID
|
||||
header.querySelectorAll('.red-user-id').forEach(el => {
|
||||
el.setAttribute('style', styles.header.userId);
|
||||
});
|
||||
|
||||
// 发布时间
|
||||
header.querySelectorAll('.red-post-time').forEach(el => {
|
||||
el.setAttribute('style', styles.header.postTime);
|
||||
});
|
||||
|
||||
// 认证图标
|
||||
header.querySelectorAll('.red-verified-icon').forEach(el => {
|
||||
el.setAttribute('style', styles.header.verifiedIcon);
|
||||
});
|
||||
}
|
||||
|
||||
// 应用页脚样式
|
||||
const footer = element.querySelector('.red-preview-footer');
|
||||
if (footer) {
|
||||
// 页脚容器
|
||||
footer.setAttribute('style', styles.footer.container);
|
||||
|
||||
// 页脚文本
|
||||
footer.querySelectorAll('.red-footer-text').forEach(el => {
|
||||
el.setAttribute('style', styles.footer.text);
|
||||
});
|
||||
|
||||
// 分隔符
|
||||
footer.querySelectorAll('.red-footer-separator').forEach(el => {
|
||||
el.setAttribute('style', styles.footer.separator);
|
||||
});
|
||||
}
|
||||
|
||||
// 应用标题样式
|
||||
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
|
||||
element.querySelectorAll(tag).forEach(el => {
|
||||
// 检查是否已经处理过
|
||||
if (!el.querySelector('.content')) {
|
||||
const content = document.createElement('span');
|
||||
content.className = 'content';
|
||||
content.innerHTML = el.innerHTML;
|
||||
el.innerHTML = '';
|
||||
el.appendChild(content);
|
||||
|
||||
const after = document.createElement('span');
|
||||
after.className = 'after';
|
||||
el.appendChild(after);
|
||||
}
|
||||
|
||||
// 根据标签选择对应的样式
|
||||
const styleKey = (tag === 'h4' || tag === 'h5' || tag === 'h6' ? 'base' : tag) as keyof typeof styles.title;
|
||||
const titleStyle = styles.title[styleKey];
|
||||
|
||||
// 应用样式
|
||||
el.setAttribute('style', `${titleStyle.base}; font-family: ${this.currentFont};`);
|
||||
el.querySelector('.content')?.setAttribute('style', titleStyle.content);
|
||||
el.querySelector('.after')?.setAttribute('style', titleStyle.after);
|
||||
});
|
||||
});
|
||||
|
||||
// 应用段落样式
|
||||
element.querySelectorAll('p').forEach(el => {
|
||||
if (!el.parentElement?.closest('p') && !el.parentElement?.closest('blockquote')) {
|
||||
el.setAttribute('style', `${styles.paragraph}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
}
|
||||
});
|
||||
|
||||
// 应用列表样式
|
||||
element.querySelectorAll('ul, ol').forEach(el => {
|
||||
el.setAttribute('style', styles.list.container);
|
||||
});
|
||||
element.querySelectorAll('li').forEach(el => {
|
||||
el.setAttribute('style', `${styles.list.item}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
element.querySelectorAll('.task-list-item').forEach(el => {
|
||||
el.setAttribute('style', `${styles.list.taskList}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
|
||||
// 应用引用样式
|
||||
element.querySelectorAll('blockquote').forEach(el => {
|
||||
el.setAttribute('style', `${styles.quote}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
|
||||
// 应用代码样式
|
||||
element.querySelectorAll('pre').forEach(el => {
|
||||
el.setAttribute('style', `${styles.code.block}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
element.querySelectorAll('code:not(pre code)').forEach(el => {
|
||||
el.setAttribute('style', `${styles.code.inline}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
|
||||
// 应用链接样式
|
||||
element.querySelectorAll('a').forEach(el => {
|
||||
el.setAttribute('style', styles.link);
|
||||
});
|
||||
|
||||
// 应用强调样式
|
||||
element.querySelectorAll('strong').forEach(el => {
|
||||
el.setAttribute('style', styles.emphasis.strong);
|
||||
});
|
||||
element.querySelectorAll('em').forEach(el => {
|
||||
el.setAttribute('style', styles.emphasis.em);
|
||||
});
|
||||
element.querySelectorAll('del').forEach(el => {
|
||||
el.setAttribute('style', styles.emphasis.del);
|
||||
});
|
||||
|
||||
// 应用表格样式(内容表格,非包裹表格)
|
||||
element.querySelectorAll('table').forEach(el => {
|
||||
el.setAttribute('style', styles.table.container);
|
||||
});
|
||||
element.querySelectorAll('th').forEach(el => {
|
||||
el.setAttribute('style', `${styles.table.header}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
element.querySelectorAll('td').forEach(el => {
|
||||
el.setAttribute('style', `${styles.table.cell}; font-family: ${this.currentFont}; font-size: ${this.currentFontSize}px;`);
|
||||
});
|
||||
|
||||
// 应用分割线样式
|
||||
element.querySelectorAll('hr').forEach(el => {
|
||||
el.setAttribute('style', styles.hr);
|
||||
});
|
||||
|
||||
// 应用脚注样式
|
||||
element.querySelectorAll('.footnote-ref').forEach(el => {
|
||||
el.setAttribute('style', styles.footnote.ref);
|
||||
});
|
||||
element.querySelectorAll('.footnote-backref').forEach(el => {
|
||||
el.setAttribute('style', styles.footnote.backref);
|
||||
});
|
||||
|
||||
// 应用图片样式
|
||||
element.querySelectorAll('img').forEach(el => {
|
||||
const img = el as HTMLImageElement;
|
||||
el.setAttribute('style', `${styles.image}; font-family: ${this.currentFont};`);
|
||||
|
||||
const parent = img.parentElement;
|
||||
if (parent && parent.tagName.toLowerCase() === 'p') {
|
||||
if (parent.childNodes.length === 1) {
|
||||
parent.style.textAlign = 'center';
|
||||
parent.style.margin = '1em 0';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const templateManager = (app: App) => new TemplateManager(app);
|
||||
69
src/templates/cyber.json
Normal file
69
src/templates/cyber.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "cyber",
|
||||
"name": "赛博朋克",
|
||||
"styles": {
|
||||
"imagePreview": "background: #0a0a16; padding: 32px 28px;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 2px; overflow: hidden; border: 1px solid #00ffaa80; box-shadow: 0 0 12px #00ffaa40;",
|
||||
"placeholder": "background: #1a1a2e; transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: saturate(1.4);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 700; color: #00ffaa; text-shadow: 0 0 12px #00ffaa80;",
|
||||
"userId": "font-size: 14px; color: #4dffbe; font-family: 'JetBrains Mono', monospace;",
|
||||
"postTime": "font-size: 13px; color: #2f8f98; font-family: 'JetBrains Mono', monospace;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #000000; background-color: #00ffaa; border-radius: 2px; font-size: 10px; flex-shrink: 0; border: 1px solid #00ffaa80; box-shadow: 0 0 12px #00ffaa40;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #4dffbe; font-size: 13px; border-top: 1px solid #00ffaa80; background: #0a0a16; font-family: 'JetBrains Mono', monospace;",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #2f8f98;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: 0.02em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #00ffaa; text-shadow: 0 0 16px #00ffaa80;",
|
||||
"after": "content: ' //'; color: #2f8f98;"
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 26px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #00ffaa;",
|
||||
"after": "content: ' //';"
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 22px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #00ffaa;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.75; margin-bottom: 1.1em; font-size: 15px; color: #e0e0e0;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #00ffaa; text-shadow: 0 0 12px #00ffaa80;",
|
||||
"em": "font-style: normal; color: #4dffbe; background: rgba(77,255,190,0.15); padding: 0 4px;",
|
||||
"del": "text-decoration: line-through; color: #2f8f98; opacity: 0.9;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.1em; color: #7feceb;",
|
||||
"item": "margin-bottom: 0.7em; font-size: 15px; color: #7feceb; line-height: 1.75;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #7feceb; line-height: 1.75;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #1a1a2e; padding: 1.2em; border-radius: 4px; font-size: 14px; font-family: 'JetBrains Mono', monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #00ffaa; margin: 1.3em 0; border: 1px solid #00ffaa80; box-shadow: 0 0 24px #00ffaa20;",
|
||||
"inline": "background: #1a1a2e; padding: 4px 10px; border-radius: 4px; color: #00ffaa; font-size: 14px; font-family: 'JetBrains Mono', monospace; border: 1px solid #00ffaa80;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #00ffaa; padding: 0 0 0 20px; margin: 1.5em 0; color: #4dffbe; font-style: normal; font-size: 15px; line-height: 1.75; text-shadow: 0 0 12px #00ffaa40;",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border: 2px solid #00ffaa80; box-shadow: 0 0 32px #00ffaa30; filter: saturate(1.2);",
|
||||
"link": "color: #00ffaa; text-decoration: none; border-bottom: 2px solid #00ffaa80; transition: all 0.2s ease; text-shadow: 0 0 12px #00ffaa40;",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: collapse; border: 2px solid #00ffaa80; box-shadow: 0 0 24px #00ffaa20;",
|
||||
"header": "background: #1a1a2e; font-weight: 700; color: #00ffaa; padding: 14px; border: 2px solid #00ffaa80; text-shadow: 0 0 12px #00ffaa40;",
|
||||
"cell": "padding: 14px; color: #7feceb; border: 2px solid #00ffaa80; font-family: 'JetBrains Mono', monospace;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #00ffaa80; margin: 32px 0; box-shadow: 0 0 24px #00ffaa30;",
|
||||
"footnote": {
|
||||
"ref": "color: #4dffbe; text-decoration: none; font-size: 0.9em; font-family: 'JetBrains Mono', monospace;",
|
||||
"backref": "color: #4dffbe; text-decoration: none; font-size: 0.9em; font-family: 'JetBrains Mono', monospace;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/default.json
Normal file
69
src/templates/default.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "default",
|
||||
"name": "默认主题",
|
||||
"styles": {
|
||||
"imagePreview": "background: #1c1c1e; padding: 28px 24px;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 12px; overflow: hidden;",
|
||||
"placeholder": "background: #2c2c2e; transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease;"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 8px;",
|
||||
"userName": "font-size: 16px; font-weight: 600; color: #ffffff;",
|
||||
"userId": "font-size: 14px; color: #98989d;",
|
||||
"postTime": "font-size: 13px; color: #636366;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #ffffff; background-color: #0A84FF; border-radius: 50%; font-size: 10px; flex-shrink: 0;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #98989d; font-size: 13px; border-top: 1px solid #2c2c2e; background: #1c1c1e;",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #636366;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #f2f2f7;",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 26px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #f2f2f7;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 22px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #f2f2f7;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.1em; font-size: 15px; color: #f2f2f7;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #0A84FF;",
|
||||
"em": "font-style: normal; color: #98989d; background: rgba(152,152,157,0.1); padding: 0 4px;",
|
||||
"del": "text-decoration: line-through; color: #636366;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.1em; color: #d1d1d6;",
|
||||
"item": "margin-bottom: 0.7em; font-size: 15px; color: #d1d1d6; line-height: 1.75;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #d1d1d6; line-height: 1.75;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #2c2c2e; padding: 1em 1.2em; border-radius: 12px; font-size: 14px; font-family: SF Mono, Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #ffffff; margin: 1.3em 0;",
|
||||
"inline": "background: #2c2c2e; padding: 3px 8px; border-radius: 6px; color: #ffffff; font-size: 14px; font-family: SF Mono, Menlo, monospace;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #0A84FF; padding: 0 0 0 18px; margin: 1.2em 0; color: #f2f2f7; font-style: normal; font-size: 15px; line-height: 1.75;",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.3em auto; border-radius: 12px;",
|
||||
"link": "color: #0A84FF; text-decoration: underline; text-underline-offset: 2px; transition: opacity 0.2s ease;",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.3em 0; border-collapse: separate; border-spacing: 0; border-radius: 12px; overflow: hidden; border: 1px solid #2c2c2e;",
|
||||
"header": "background: #2c2c2e; font-weight: 600; color: #f2f2f7; padding: 12px;",
|
||||
"cell": "padding: 12px; color: #d1d1d6; border-top: 1px solid #2c2c2e;"
|
||||
},
|
||||
"hr": "border: none; border-top: 1px solid #2c2c2e; margin: 26px 0;",
|
||||
"footnote": {
|
||||
"ref": "color: #98989d; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #98989d; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/elegant.json
Normal file
69
src/templates/elegant.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "elegant",
|
||||
"name": "优雅暗色",
|
||||
"styles": {
|
||||
"imagePreview": "background: #1a1721; padding: 32px 28px;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 14px; overflow: hidden; box-shadow: 0 2px 8px rgba(180,144,255,0.15);",
|
||||
"placeholder": "background: #2a2433; transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease;"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 8px;",
|
||||
"userName": "font-size: 16px; font-weight: 600; color: #e2d9f3; letter-spacing: 0.02em;",
|
||||
"userId": "font-size: 14px; color: #a393c9;",
|
||||
"postTime": "font-size: 13px; color: #6d617f;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #1a1721; background-color: #b490ff; border-radius: 6px; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(180,144,255,0.2);"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #a393c9; font-size: 13px; border-top: 1px solid #2a2433; background: rgba(26,23,33,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #6d617f;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #e2d9f3; text-shadow: 0 2px 4px rgba(0,0,0,0.1);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #e2d9f3;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #e2d9f3;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.75; margin-bottom: 1.1em; font-size: 15px; color: #c4b8dd;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 600; color: #b490ff; text-shadow: 0 1px 2px rgba(180,144,255,0.2);",
|
||||
"em": "font-style: normal; color: #a393c9; background: rgba(163,147,201,0.1); padding: 0 4px; border-radius: 2px;",
|
||||
"del": "text-decoration: line-through; color: #6d617f; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.2em; color: #c4b8dd;",
|
||||
"item": "margin-bottom: 0.7em; font-size: 15px; color: #c4b8dd; line-height: 1.75;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #c4b8dd; line-height: 1.75;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #2a2433; padding: 1.2em; border-radius: 14px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #c4b8dd; margin: 1.3em 0; box-shadow: inset 0 4px 12px rgba(0,0,0,0.1); border: 1px solid #3a3443;",
|
||||
"inline": "background: #2a2433; padding: 4px 10px; border-radius: 6px; color: #c4b8dd; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #3a3443;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #b490ff; padding: 0 0 0 18px; margin: 1.5em 0; color: #a393c9; font-style: normal; font-size: 15px; line-height: 1.75; background: rgba(180,144,255,0.05); border-radius: 0 4px 4px 0;",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 14px; box-shadow: 0 8px 24px rgba(0,0,0,0.2);",
|
||||
"link": "color: #b490ff; text-decoration: none; transition: all 0.2s ease; border-bottom: 1px solid rgba(180,144,255,0.3);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 14px; overflow: hidden; border: 1px solid #3a3443; box-shadow: 0 4px 12px rgba(0,0,0,0.1);",
|
||||
"header": "background: #2a2433; font-weight: 600; color: #e2d9f3; padding: 14px;",
|
||||
"cell": "padding: 14px; color: #c4b8dd; border-top: 1px solid #3a3443;"
|
||||
},
|
||||
"hr": "border: none; border-top: 1px solid #3a3443; margin: 32px 0;",
|
||||
"footnote": {
|
||||
"ref": "color: #a393c9; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #a393c9; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/forest.json
Normal file
69
src/templates/forest.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "forest",
|
||||
"name": "森林清晨",
|
||||
"styles": {
|
||||
"imagePreview": "background: #1a2420; padding: 32px 28px; border: 1px solid #2ecc7180;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 14px; overflow: hidden; box-shadow: 0 4px 16px rgba(46,204,113,0.15); border: 1px solid #2ecc7180;",
|
||||
"placeholder": "background: #243830; transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: brightness(1.05);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 600; color: #2ecc71; text-shadow: 0 2px 4px rgba(46,204,113,0.1);",
|
||||
"userId": "font-size: 14px; color: #27ae60; font-family: 'SF Pro Text';",
|
||||
"postTime": "font-size: 13px; color: #16a085; letter-spacing: 0.02em;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #1a2420; background-color: #2ecc71; border-radius: 6px; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(46,204,113,0.3); border: 1px solid #2ecc7180;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #27ae60; font-size: 13px; border-top: 1px solid #2ecc7140; background: rgba(26,36,32,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #16a085;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #2ecc71; text-shadow: 0 2px 4px rgba(46,204,113,0.1);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #2ecc71;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #2ecc71;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.1em; font-size: 15px; color: #e8f5e9;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #2ecc71; text-shadow: 0 2px 4px rgba(46,204,113,0.2);",
|
||||
"em": "font-style: normal; color: #27ae60; background: rgba(39,174,96,0.1); padding: 0 4px; border-radius: 2px;",
|
||||
"del": "text-decoration: line-through; color: #16a085; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.2em; color: #a8e6cf;",
|
||||
"item": "margin-bottom: 0.7em; font-size: 15px; color: #a8e6cf; line-height: 1.75;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #a8e6cf; line-height: 1.75;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #243830; padding: 1.2em; border-radius: 14px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #a8e6cf; margin: 1.5em 0; box-shadow: inset 0 4px 12px rgba(0,0,0,0.1); border: 1px solid #2ecc7180;",
|
||||
"inline": "background: #243830; padding: 4px 10px; border-radius: 6px; color: #a8e6cf; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #2ecc7180;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #2ecc71; padding: 0 0 0 20px; margin: 1.5em 0; color: #27ae60; font-style: normal; font-size: 15px; line-height: 1.75; background: rgba(46,204,113,0.05);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 14px; box-shadow: 0 8px 24px rgba(46,204,113,0.1); border: 1px solid #2ecc7180;",
|
||||
"link": "color: #2ecc71; text-decoration: none; border-bottom: 1px solid #2ecc7180; transition: all 0.2s ease; text-shadow: 0 2px 4px rgba(46,204,113,0.1);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 14px; overflow: hidden; border: 1px solid #2ecc7180; box-shadow: 0 4px 12px rgba(46,204,113,0.1);",
|
||||
"header": "background: #243830; font-weight: 600; color: #2ecc71; padding: 14px; border-bottom: 1px solid #2ecc7180;",
|
||||
"cell": "padding: 14px; color: #a8e6cf; border-top: 1px solid #2ecc7180;"
|
||||
},
|
||||
"hr": "border: none; border-top: 1px solid #2ecc7180; margin: 32px 0; box-shadow: 0 2px 8px rgba(46,204,113,0.1);",
|
||||
"footnote": {
|
||||
"ref": "color: #27ae60; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #27ae60; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/templates/index.ts
Normal file
24
src/templates/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// 使用 require 导入 JSON 文件以避免 TypeScript 的 JSON 模块解析问题
|
||||
const defaultTemplate = require('./default.json');
|
||||
const minimalTemplate = require('./minimal.json');
|
||||
const elegantTemplate = require('./elegant.json');
|
||||
const cyberTemplate = require('./cyber.json');
|
||||
const warmTemplate = require('./warm.json');
|
||||
const forestTemplate = require('./forest.json');
|
||||
const oceanTemplate = require('./ocean.json');
|
||||
const sakuraTemplate = require('./sakura.json');
|
||||
const starryTemplate = require('./starry.json');
|
||||
const metalTemplate = require('./metal.json');
|
||||
|
||||
export const templates = {
|
||||
default: defaultTemplate,
|
||||
minimal: minimalTemplate,
|
||||
elegant: elegantTemplate,
|
||||
cyber: cyberTemplate,
|
||||
warm: warmTemplate,
|
||||
forest: forestTemplate,
|
||||
ocean: oceanTemplate,
|
||||
sakura: sakuraTemplate,
|
||||
starry: starryTemplate,
|
||||
metal: metalTemplate
|
||||
};
|
||||
69
src/templates/metal.json
Normal file
69
src/templates/metal.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "metal",
|
||||
"name": "金属科技",
|
||||
"styles": {
|
||||
"imagePreview": "background: #1e2228; padding: 32px 28px; border: 1px solid #4d4d4d80;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 4px; overflow: hidden; box-shadow: 0 4px 16px rgba(192,192,192,0.3); border: 1px solid #4d4d4d80;",
|
||||
"placeholder": "background: #282c34; transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: contrast(1.1) saturate(1.2);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 700; color: #f0f0f0; text-shadow: 0 2px 4px rgba(0,0,0,0.3);",
|
||||
"userId": "font-size: 14px; color: #a0a0a0; font-family: 'SF Pro Display';",
|
||||
"postTime": "font-size: 13px; color: #808080; letter-spacing: 0.03em;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #1e2228; background: linear-gradient(135deg, #d0d0d0, #ffffff); border-radius: 4px; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.3); border: 1px solid #4d4d4d80;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #a0a0a0; font-size: 13px; border-top: 1px solid #4d4d4d80; background: rgba(30,34,40,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #808080;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #f0f0f0; text-shadow: 0 2px 4px rgba(0,0,0,0.3);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #f0f0f0;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #f0f0f0;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.1em; font-size: 15px; color: #e0e0e0;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #4d8ee7; text-shadow: 0 2px 4px rgba(77,142,231,0.2);",
|
||||
"em": "font-style: normal; color: #a0a0a0; background: rgba(160,160,160,0.1); padding: 0 4px; border-left: 2px solid #4d4d4d80;",
|
||||
"del": "text-decoration: line-through; color: #808080; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.2em; color: #c0c0c0;",
|
||||
"item": "margin-bottom: 0.7em; font-size: 15px; color: #c0c0c0; line-height: 1.75;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #c0c0c0; line-height: 1.75;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #282c34; padding: 1.2em; border-radius: 4px; font-size: 14px; font-family: 'JetBrains Mono', monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #e0e0e0; margin: 1.5em 0; border: 1px solid #4d4d4d80; box-shadow: inset 0 4px 12px rgba(0,0,0,0.2);",
|
||||
"inline": "background: #282c34; padding: 4px 10px; border-radius: 2px; color: #e0e0e0; font-size: 14px; font-family: 'JetBrains Mono', monospace; border: 1px solid #4d4d4d80;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #4d8ee7; padding: 0 0 0 20px; margin: 1.5em 0; color: #a0a0a0; font-style: normal; font-size: 15px; line-height: 1.75; background: linear-gradient(to right, rgba(77,142,231,0.1), transparent);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 4px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); border: 1px solid #4d4d4d80;",
|
||||
"link": "color: #4d8ee7; text-decoration: none; border-bottom: 1px solid #4d8ee780; transition: all 0.2s ease; text-shadow: 0 2px 4px rgba(77,142,231,0.1);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 4px; overflow: hidden; border: 1px solid #4d4d4d80; box-shadow: 0 4px 12px rgba(0,0,0,0.2);",
|
||||
"header": "background: linear-gradient(135deg, #282c34, #1e2228); font-weight: 700; color: #4d8ee7; padding: 14px; border-bottom: 2px solid #4d4d4d80;",
|
||||
"cell": "padding: 14px; color: #c0c0c0; border-top: 1px solid #4d4d4d80;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #4d4d4d80; margin: 32px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.3);",
|
||||
"footnote": {
|
||||
"ref": "color: #a0a0a0; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #a0a0a0; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/minimal.json
Normal file
69
src/templates/minimal.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "minimal",
|
||||
"name": "极简主题",
|
||||
"styles": {
|
||||
"imagePreview": "background: #ffffff; padding: 36px 32px; border: 1px solid #eeeeee;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05);",
|
||||
"placeholder": "background: #f5f5f5; transition: all 0.2s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease;"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 8px;",
|
||||
"userName": "font-size: 16px; font-weight: 600; color: #333333; letter-spacing: 0.02em;",
|
||||
"userId": "font-size: 14px; color: #666666;",
|
||||
"postTime": "font-size: 13px; color: #999999;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #ffffff; background-color: #6c9eb8; border-radius: 4px; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 4px rgba(108,158,184,0.1);"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 16px; padding: 16px; color: #999999; font-size: 13px; border-top: 1px solid #eeeeee; background: rgba(250,250,250,0.95); backdrop-filter: blur(8px);",
|
||||
"text": "color: inherit; transition: opacity 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #dddddd;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #333333;",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 32px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #333333;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #333333;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.2em; font-size: 15px; color: #444444;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #4a4a4a; background: rgba(74,74,74,0.05); padding: 0 2px;",
|
||||
"em": "font-style: normal; color: #6c9eb8; background: rgba(108,158,184,0.05); padding: 0 2px;",
|
||||
"del": "text-decoration: line-through; color: #999999; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 28px; margin-bottom: 1.2em; color: #666666;",
|
||||
"item": "margin-bottom: 0.8em; font-size: 15px; color: #666666; line-height: 1.8;",
|
||||
"taskList": "list-style: none; margin-left: -20px; font-size: 15px; color: #666666; line-height: 1.8;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #f5f5f5; padding: 1.2em; border-radius: 8px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #555555; margin: 1.5em 0; border: 1px solid #eeeeee;",
|
||||
"inline": "background: #f5f5f5; padding: 4px 8px; border-radius: 4px; color: #555555; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #eeeeee;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #eeeeee; padding: 0 0 0 20px; margin: 1.5em 0; color: #666666; font-style: normal; font-size: 15px; line-height: 1.8;",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05);",
|
||||
"link": "color: #6c9eb8; text-decoration: none; border-bottom: 1px solid rgba(108,158,184,0.3); transition: all 0.2s ease;",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: collapse;",
|
||||
"header": "background: #f5f5f5; font-weight: 600; color: #333333; border-bottom: 2px solid #eeeeee; padding: 14px;",
|
||||
"cell": "padding: 14px; color: #666666; border-bottom: 1px solid #eeeeee;"
|
||||
},
|
||||
"hr": "border: none; border-top: 1px solid #eeeeee; margin: 32px 0;",
|
||||
"footnote": {
|
||||
"ref": "color: #999999; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #999999; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/ocean.json
Normal file
69
src/templates/ocean.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "ocean",
|
||||
"name": "深海之境",
|
||||
"styles": {
|
||||
"imagePreview": "background: #0a192f; padding: 32px 28px; border: 1px solid #112a4580;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 14px; overflow: hidden; box-shadow: 0 4px 16px rgba(64,169,255,0.25); border: 1px solid #1d3552;",
|
||||
"placeholder": "background: linear-gradient(135deg, #112a45, #1d3552); transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: brightness(1.1) contrast(1.05);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 700; color: #40a9ff; text-shadow: 0 2px 8px rgba(64,169,255,0.2);",
|
||||
"userId": "font-size: 14px; color: #69c0ff; font-family: 'SF Pro Text';",
|
||||
"postTime": "font-size: 13px; color: #91d5ff; letter-spacing: 0.03em;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #0a192f; background: linear-gradient(135deg, #40a9ff, #69c0ff); border-radius: 6px; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 12px rgba(64,169,255,0.3); border: 1px solid #69c0ff80;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #69c0ff; font-size: 13px; border-top: 1px solid #1d3552; background: rgba(10,25,47,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #91d5ff;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #40a9ff; text-shadow: 0 2px 8px rgba(64,169,255,0.15);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #40a9ff;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #40a9ff;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.2em; font-size: 15px; color: #e6f7ff;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #40a9ff; text-shadow: 0 2px 8px rgba(64,169,255,0.2);",
|
||||
"em": "font-style: normal; color: #69c0ff; background: rgba(105,192,255,0.1); padding: 0 4px; border-left: 2px solid #69c0ff80;",
|
||||
"del": "text-decoration: line-through; color: #91d5ff; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 28px; margin-bottom: 1.2em; color: #bae7ff;",
|
||||
"item": "margin-bottom: 0.8em; font-size: 15px; color: #bae7ff; line-height: 1.8;",
|
||||
"taskList": "list-style: none; margin-left: -24px; font-size: 15px; color: #bae7ff; line-height: 1.8;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #112a45; padding: 1.2em; border-radius: 14px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #bae7ff; margin: 1.5em 0; box-shadow: inset 0 4px 12px rgba(0,0,0,0.15), 0 2px 8px rgba(64,169,255,0.1); border: 1px solid #1d3552;",
|
||||
"inline": "background: #112a45; padding: 4px 10px; border-radius: 6px; color: #bae7ff; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #1d3552;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #40a9ff; padding: 0 0 0 20px; margin: 1.5em 0; color: #69c0ff; font-style: normal; font-size: 15px; line-height: 1.8; background: linear-gradient(to right, rgba(64,169,255,0.05), transparent);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 14px; box-shadow: 0 8px 24px rgba(64,169,255,0.25); border: 1px solid #69c0ff80;",
|
||||
"link": "color: #40a9ff; text-decoration: none; border-bottom: 1px solid rgba(64,169,255,0.3); transition: all 0.2s ease; text-shadow: 0 2px 4px rgba(64,169,255,0.1);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 14px; overflow: hidden; border: 1px solid #1d3552; box-shadow: 0 4px 12px rgba(64,169,255,0.15);",
|
||||
"header": "background: linear-gradient(135deg, #112a45, #0a192f); font-weight: 700; color: #40a9ff; padding: 14px; border-bottom: 2px solid #1d3552;",
|
||||
"cell": "padding: 14px; color: #bae7ff; border-top: 1px solid #1d3552;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #1d3552; margin: 32px 0; box-shadow: 0 2px 8px rgba(64,169,255,0.15);",
|
||||
"footnote": {
|
||||
"ref": "color: #69c0ff; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #69c0ff; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/sakura.json
Normal file
69
src/templates/sakura.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "sakura",
|
||||
"name": "樱花飞舞",
|
||||
"styles": {
|
||||
"imagePreview": "background: #1f1a1d; padding: 32px 28px; border: 1px solid #ffb6c180;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 14px; overflow: hidden; box-shadow: 0 4px 16px rgba(255,182,193,0.25); border: 1px solid #ffb6c180;",
|
||||
"placeholder": "background: linear-gradient(135deg, #2d2428, #3a2d33); transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: brightness(1.05) contrast(1.02);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 700; color: #ffb6c1; text-shadow: 0 2px 8px rgba(255,182,193,0.2);",
|
||||
"userId": "font-size: 14px; color: #ff8da1; font-family: 'SF Pro Text';",
|
||||
"postTime": "font-size: 13px; color: #ff6b88; letter-spacing: 0.03em;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #1f1a1d; background: linear-gradient(135deg, #ffb6c1, #ff8da1); border-radius: 50%; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 12px rgba(255,182,193,0.3); border: 1px solid #ffb6c180;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #ff8da1; font-size: 13px; border-top: 1px solid #ffb6c140; background: rgba(31,26,29,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #ff6b88;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #ffb6c1; text-shadow: 0 2px 8px rgba(255,182,193,0.15);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #ffb6c1;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #ffb6c1;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.2em; font-size: 15px; color: #fff5f7;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #ff69b4; text-shadow: 0 2px 8px rgba(255,105,180,0.2);",
|
||||
"em": "font-style: normal; color: #ff8da1; background: rgba(255,141,161,0.1); padding: 0 4px; border-left: 2px solid #ff8da180;",
|
||||
"del": "text-decoration: line-through; color: #ff6b88; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 28px; margin-bottom: 1.2em; color: #ffd1d9;",
|
||||
"item": "margin-bottom: 0.8em; font-size: 15px; color: #ffd1d9; line-height: 1.8;",
|
||||
"taskList": "list-style: none; margin-left: -24px; font-size: 15px; color: #ffd1d9; line-height: 1.8;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #2d2428; padding: 1.2em; border-radius: 14px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #ffd1d9; margin: 1.5em 0; box-shadow: inset 0 4px 12px rgba(0,0,0,0.15), 0 2px 8px rgba(255,182,193,0.1); border: 1px solid #ffb6c180;",
|
||||
"inline": "background: #2d2428; padding: 4px 10px; border-radius: 6px; color: #ffd1d9; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #ffb6c180;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #ff8da1; padding: 0 0 0 20px; margin: 1.5em 0; color: #ff8da1; font-style: normal; font-size: 15px; line-height: 1.8; background: linear-gradient(to right, rgba(255,141,161,0.05), transparent);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 14px; box-shadow: 0 8px 24px rgba(255,182,193,0.25); border: 1px solid #ffb6c180;",
|
||||
"link": "color: #ffb6c1; text-decoration: none; border-bottom: 1px solid rgba(255,182,193,0.3); transition: all 0.2s ease; text-shadow: 0 2px 4px rgba(255,182,193,0.1);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 14px; overflow: hidden; border: 1px solid #ffb6c180; box-shadow: 0 4px 12px rgba(255,182,193,0.15);",
|
||||
"header": "background: linear-gradient(135deg, #2d2428, #1f1a1d); font-weight: 700; color: #ffb6c1; padding: 14px; border-bottom: 2px solid #ffb6c180;",
|
||||
"cell": "padding: 14px; color: #ffd1d9; border-top: 1px solid #ffb6c180;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #ffb6c180; margin: 32px 0; box-shadow: 0 2px 8px rgba(255,182,193,0.15);",
|
||||
"footnote": {
|
||||
"ref": "color: #ff8da1; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #ff8da1; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/starry.json
Normal file
69
src/templates/starry.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "starry",
|
||||
"name": "星空梦境",
|
||||
"styles": {
|
||||
"imagePreview": "background: #0d0f1a; padding: 32px 28px; border: 1px solid #9370db80;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 40px; height: 40px; border-radius: 14px; overflow: hidden; box-shadow: 0 4px 24px rgba(147,112,219,0.3); border: 1px solid #9370db80;",
|
||||
"placeholder": "background: linear-gradient(135deg, #252640, #1a1b2e); transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: brightness(1.1) contrast(1.05);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 10px;",
|
||||
"userName": "font-size: 16px; font-weight: 700; color: #9370db; text-shadow: 0 2px 8px rgba(147,112,219,0.2);",
|
||||
"userId": "font-size: 14px; color: #b19cd9; font-family: 'SF Pro Text';",
|
||||
"postTime": "font-size: 13px; color: #8a7bb5; letter-spacing: 0.03em;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #0d0f1a; background: linear-gradient(135deg, #9370db, #b19cd9); border-radius: 50%; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 12px rgba(147,112,219,0.3); border: 1px solid #9370db80;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #b19cd9; font-size: 13px; border-top: 1px solid #252640; background: rgba(13,15,26,0.95); backdrop-filter: blur(12px);",
|
||||
"text": "color: inherit; transition: color 0.2s ease; white-space: nowrap;",
|
||||
"separator": "color: #8a7bb5;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.5em; letter-spacing: -0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #9370db; text-shadow: 0 2px 8px rgba(147,112,219,0.15);",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.25em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #9370db;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.1em; line-height: 1.5;",
|
||||
"content": "font-weight: 700; color: #9370db;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.8; margin-bottom: 1.2em; font-size: 15px; color: #f0e6ff;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 700; color: #9b59b6; text-shadow: 0 2px 8px rgba(155,89,182,0.2);",
|
||||
"em": "font-style: normal; color: #b19cd9; background: rgba(177,156,217,0.1); padding: 0 4px; border-left: 2px solid #b19cd980;",
|
||||
"del": "text-decoration: line-through; color: #8a7bb5; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 28px; margin-bottom: 1.2em; color: #d8cef6;",
|
||||
"item": "margin-bottom: 0.8em; font-size: 15px; color: #d8cef6; line-height: 1.8;",
|
||||
"taskList": "list-style: none; margin-left: -24px; font-size: 15px; color: #d8cef6; line-height: 1.8;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #252640; padding: 1.2em; border-radius: 14px; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #d8cef6; margin: 1.5em 0; box-shadow: inset 0 4px 12px rgba(0,0,0,0.15), 0 2px 8px rgba(147,112,219,0.1); border: 1px solid #9370db80;",
|
||||
"inline": "background: #252640; padding: 4px 10px; border-radius: 6px; color: #d8cef6; font-size: 14px; font-family: 'SF Mono', Menlo, monospace; border: 1px solid #9370db80;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #b19cd9; padding: 0 0 0 20px; margin: 1.5em 0; color: #b19cd9; font-style: normal; font-size: 15px; line-height: 1.8; background: linear-gradient(to right, rgba(177,156,217,0.05), transparent);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 14px; box-shadow: 0 8px 24px rgba(147,112,219,0.25); border: 1px solid #9370db80;",
|
||||
"link": "color: #9370db; text-decoration: none; background-image: linear-gradient(to right, #9370db80, #b19cd980); background-size: 0% 1px; background-repeat: no-repeat; background-position: 0 100%; transition: all 0.3s ease; text-shadow: 0 2px 4px rgba(147,112,219,0.1);",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.5em 0; border-collapse: separate; border-spacing: 0; border-radius: 14px; overflow: hidden; border: 1px solid #9370db80; box-shadow: 0 4px 12px rgba(147,112,219,0.15);",
|
||||
"header": "background: linear-gradient(135deg, #252640, #0d0f1a); font-weight: 700; color: #9370db; padding: 14px; border-bottom: 2px solid #9370db80;",
|
||||
"cell": "padding: 14px; color: #d8cef6; border-top: 1px solid #9370db80;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #9370db80; margin: 32px 0; box-shadow: 0 2px 8px rgba(147,112,219,0.15);",
|
||||
"footnote": {
|
||||
"ref": "color: #b19cd9; text-decoration: none; font-size: 0.9em;",
|
||||
"backref": "color: #b19cd9; text-decoration: none; font-size: 0.9em;"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/templates/warm.json
Normal file
69
src/templates/warm.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"id": "warm",
|
||||
"name": "暖阳文艺",
|
||||
"styles": {
|
||||
"imagePreview": "background: #fffaf5; padding: 32px 28px; border: 1px solid #b8733380;",
|
||||
"header": {
|
||||
"avatar": {
|
||||
"container": "width: 42px; height: 42px; border-radius: 14px; overflow: hidden; box-shadow: 0 4px 16px rgba(184,115,51,0.1); border: 1px solid #b8733380;",
|
||||
"placeholder": "background: linear-gradient(135deg, #fff0e6, #ffe4c4); transition: all 0.3s ease;",
|
||||
"image": "object-fit: cover; transition: transform 0.3s ease; filter: brightness(1.05) contrast(1.02);"
|
||||
},
|
||||
"nameContainer": "display: flex; align-items: center; gap: 8px;",
|
||||
"userName": "font-size: 17px; font-weight: 600; color: #8b4513; font-family: 'Noto Serif SC', serif; text-shadow: 0 2px 4px rgba(139,69,19,0.1);",
|
||||
"userId": "font-size: 14px; color: #b87333; font-family: 'Noto Serif SC', serif;",
|
||||
"postTime": "font-size: 13px; color: #d2691e; font-style: italic;",
|
||||
"verifiedIcon": "width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: #fffaf5; background: linear-gradient(135deg, #d2691e, #b87333); border-radius: 50%; font-size: 10px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(210,105,30,0.2); border: 1px solid #b8733380;"
|
||||
},
|
||||
"footer": {
|
||||
"container": "position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px; padding: 16px; color: #b87333; font-size: 13px; border-top: 1px solid #b8733380; background: rgba(255,250,245,0.95); backdrop-filter: blur(8px); font-family: 'Noto Serif SC', serif;",
|
||||
"text": "color: inherit; transition: color 0.2s ease; font-style: italic; white-space: nowrap;",
|
||||
"separator": "color: #deb887;"
|
||||
},
|
||||
"title": {
|
||||
"h2": {
|
||||
"base": "margin: 0; font-size: 1.6em; letter-spacing: 0.01em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #8b4513; font-family: 'Noto Serif SC', serif;",
|
||||
"after": ""
|
||||
},
|
||||
"h3": {
|
||||
"base": "margin: 28px 0 0; font-size: 1.3em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #8b4513; font-family: 'Noto Serif SC', serif;",
|
||||
"after": ""
|
||||
},
|
||||
"base": {
|
||||
"base": "margin: 24px 0 0; font-size: 1.15em; line-height: 1.5;",
|
||||
"content": "font-weight: 600; color: #8b4513; font-family: 'Noto Serif SC', serif;",
|
||||
"after": ""
|
||||
}
|
||||
},
|
||||
"paragraph": "line-height: 1.75; margin-bottom: 1.1em; font-size: 15px; color: #5a4a42;",
|
||||
"emphasis": {
|
||||
"strong": "font-weight: 600; color: #d2691e; text-shadow: 0 2px 4px rgba(210,105,30,0.1);",
|
||||
"em": "font-style: normal; color: #b87333; background: rgba(184,115,51,0.1); padding: 0 4px; border-left: 2px solid #b8733380;",
|
||||
"del": "text-decoration: line-through; color: #8b4513; opacity: 0.8;"
|
||||
},
|
||||
"list": {
|
||||
"container": "padding-left: 26px; margin-bottom: 1.2em; color: #6b4423;",
|
||||
"item": "margin-bottom: 0.8em; font-size: 15px; color: #6b4423; line-height: 1.85; font-family: 'Noto Serif SC', serif;",
|
||||
"taskList": "list-style: none; margin-left: -22px; font-size: 15px; color: #6b4423; line-height: 1.85; font-family: 'Noto Serif SC', serif;"
|
||||
},
|
||||
"code": {
|
||||
"block": "background: #fff6e9; padding: 1.2em; border-radius: 8px; font-size: 14px; font-family: 'Fira Code', monospace; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; color: #8b4513; margin: 1.3em 0; border: 1px solid #b8733380; box-shadow: inset 0 4px 12px rgba(184,115,51,0.05), 0 2px 8px rgba(184,115,51,0.05);",
|
||||
"inline": "background: #fff6e9; padding: 4px 10px; border-radius: 4px; color: #8b4513; font-size: 14px; font-family: 'Fira Code', monospace; border: 1px solid #b8733380;"
|
||||
},
|
||||
"quote": "border-left: 3px solid #deb887; padding: 0 0 0 20px; margin: 1.3em 0; color: #b87333; font-style: italic; font-size: 15px; line-height: 1.85; font-family: 'Noto Serif SC', serif; background: linear-gradient(to right, rgba(222,184,135,0.1), transparent);",
|
||||
"image": "max-width: 100%; height: auto; margin: 1.5em auto; border-radius: 12px; box-shadow: 0 4px 16px rgba(184,115,51,0.1); border: 1px solid #b8733380;",
|
||||
"link": "color: #d2691e; text-decoration: none; background-image: linear-gradient(to right, #d2691e80, #b8733380); background-size: 0% 1px; background-repeat: no-repeat; background-position: 0 100%; transition: all 0.3s ease; font-family: 'Noto Serif SC', serif;",
|
||||
"table": {
|
||||
"container": "width: 100%; margin: 1.4em 0; border-collapse: separate; border-spacing: 0; border: 1px solid #b8733380; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 12px rgba(184,115,51,0.05);",
|
||||
"header": "background: linear-gradient(135deg, #fff6e9, #fffaf5); font-weight: 600; color: #8b4513; padding: 14px; font-family: 'Noto Serif SC', serif; border-bottom: 2px solid #b8733380;",
|
||||
"cell": "padding: 14px; color: #6b4423; border-top: 1px solid #b8733380; font-family: 'Noto Serif SC', serif;"
|
||||
},
|
||||
"hr": "border: none; border-top: 2px solid #b8733380; margin: 28px 0; box-shadow: 0 2px 8px rgba(184,115,51,0.05);",
|
||||
"footnote": {
|
||||
"ref": "color: #b87333; text-decoration: none; font-size: 0.9em; font-style: italic;",
|
||||
"backref": "color: #b87333; text-decoration: none; font-size: 0.9em; font-style: italic;"
|
||||
}
|
||||
}
|
||||
}
|
||||
680
src/view.ts
Normal file
680
src/view.ts
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
import { ItemView, WorkspaceLeaf, MarkdownRenderer, TFile } from 'obsidian';
|
||||
import { RedConverter } from './converter';
|
||||
import { DownloadManager } from './downloadManager';
|
||||
import type { TemplateManager } from './templateManager';
|
||||
import { DonateManager } from './donateManager';
|
||||
import type { SettingsManager } from './settings';
|
||||
import { PreviewManager } from './previewManager';
|
||||
export const VIEW_TYPE_RED = 'note-to-red';
|
||||
|
||||
export class RedView extends ItemView {
|
||||
private previewEl: HTMLElement;
|
||||
private currentFile: TFile | null = null;
|
||||
private updateTimer: NodeJS.Timeout | null = null;
|
||||
private isPreviewLocked: boolean = false;
|
||||
private lockButton: HTMLButtonElement;
|
||||
private copyButton: HTMLButtonElement;
|
||||
private templateManager: TemplateManager;
|
||||
private settingsManager: SettingsManager;
|
||||
private customTemplateSelect: HTMLElement;
|
||||
private customFontSelect: HTMLElement;
|
||||
private fontSizeSelect: HTMLInputElement;
|
||||
private customBackgroundSelect: HTMLElement;
|
||||
private currentImageIndex: number = 0;
|
||||
private navigationButtons: {
|
||||
prev: HTMLButtonElement;
|
||||
next: HTMLButtonElement;
|
||||
indicator: HTMLElement;
|
||||
} | undefined;
|
||||
private previewManager: PreviewManager;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
templateManager: TemplateManager,
|
||||
settingsManager: SettingsManager
|
||||
) {
|
||||
super(leaf);
|
||||
this.templateManager = templateManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.previewManager = new PreviewManager(settingsManager);
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE_RED;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return '小红书预览';
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
|
||||
const toolbar = container.createEl('div', { cls: 'red-toolbar' });
|
||||
|
||||
// 锁定按钮
|
||||
this.lockButton = toolbar.createEl('button', {
|
||||
cls: 'red-lock-button',
|
||||
attr: { 'aria-label': '关闭实时预览状态' }
|
||||
});
|
||||
this.lockButton.innerHTML = '🔓';
|
||||
this.lockButton.addEventListener('click', () => this.togglePreviewLock());
|
||||
|
||||
// 创建中间控件容器
|
||||
const controlsGroup = toolbar.createEl('div', { cls: 'red-controls-group' });
|
||||
|
||||
// 创建自定义下拉选择器
|
||||
this.customTemplateSelect = this.createCustomSelect(
|
||||
controlsGroup,
|
||||
'red-template-select',
|
||||
await this.getTemplateOptions()
|
||||
);
|
||||
this.customTemplateSelect.id = 'template-select';
|
||||
|
||||
// 添加模板选择器的 change 事件监听
|
||||
this.customTemplateSelect.querySelector('.red-select')?.addEventListener('change', async (e: any) => {
|
||||
const value = e.detail.value;
|
||||
this.templateManager.setCurrentTemplate(value);
|
||||
await this.settingsManager.updateSettings({
|
||||
templateId: value
|
||||
});
|
||||
this.templateManager.applyTemplate(this.previewEl);
|
||||
});
|
||||
|
||||
this.customFontSelect = this.createCustomSelect(
|
||||
controlsGroup,
|
||||
'red-font-select',
|
||||
this.getFontOptions()
|
||||
);
|
||||
|
||||
// 添加字体选择器的 change 事件监听
|
||||
this.customFontSelect.querySelector('.red-select')?.addEventListener('change', async (e: any) => {
|
||||
const value = e.detail.value;
|
||||
this.templateManager.setFont(value);
|
||||
await this.settingsManager.updateSettings({
|
||||
fontFamily: value
|
||||
});
|
||||
this.templateManager.applyTemplate(this.previewEl);
|
||||
});
|
||||
this.customFontSelect.id = 'font-select';
|
||||
|
||||
// 字号调整
|
||||
const fontSizeGroup = controlsGroup.createEl('div', { cls: 'red-font-size-group' });
|
||||
const decreaseButton = fontSizeGroup.createEl('button', {
|
||||
cls: 'red-font-size-btn',
|
||||
text: '-'
|
||||
});
|
||||
this.fontSizeSelect = fontSizeGroup.createEl('input', {
|
||||
cls: 'red-font-size-input',
|
||||
type: 'text',
|
||||
value: '16',
|
||||
attr: {
|
||||
style: 'border: none; outline: none; background: transparent;'
|
||||
}
|
||||
});
|
||||
const increaseButton = fontSizeGroup.createEl('button', {
|
||||
cls: 'red-font-size-btn',
|
||||
text: '+'
|
||||
});
|
||||
|
||||
// 从设置中恢复上次的选择
|
||||
const settings = this.settingsManager.getSettings();
|
||||
|
||||
// 恢复设置
|
||||
if (settings.templateId) {
|
||||
const templateSelect = this.customTemplateSelect.querySelector('.red-select-text');
|
||||
const templateDropdown = this.customTemplateSelect.querySelector('.red-select-dropdown');
|
||||
if (templateSelect && templateDropdown) {
|
||||
const option = await this.getTemplateOptions();
|
||||
const selected = option.find(o => o.value === settings.templateId);
|
||||
if (selected) {
|
||||
// 更新选中文本和值
|
||||
templateSelect.textContent = selected.label;
|
||||
this.customTemplateSelect.querySelector('.red-select')?.setAttribute('data-value', selected.value);
|
||||
// 更新下拉列表中的选中状态
|
||||
templateDropdown.querySelectorAll('.red-select-item').forEach(el => {
|
||||
if (el.getAttribute('data-value') === selected.value) {
|
||||
el.classList.add('red-selected');
|
||||
} else {
|
||||
el.classList.remove('red-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.templateManager.setCurrentTemplate(settings.templateId);
|
||||
}
|
||||
|
||||
if (settings.fontFamily) {
|
||||
const fontSelect = this.customFontSelect.querySelector('.red-select-text');
|
||||
const fontDropdown = this.customFontSelect.querySelector('.red-select-dropdown');
|
||||
if (fontSelect && fontDropdown) {
|
||||
const option = this.getFontOptions();
|
||||
const selected = option.find(o => o.value === settings.fontFamily);
|
||||
if (selected) {
|
||||
// 更新选中文本和值
|
||||
fontSelect.textContent = selected.label;
|
||||
this.customFontSelect.querySelector('.red-select')?.setAttribute('data-value', selected.value);
|
||||
// 更新下拉列表中的选中状态
|
||||
fontDropdown.querySelectorAll('.red-select-item').forEach(el => {
|
||||
if (el.getAttribute('data-value') === selected.value) {
|
||||
el.classList.add('red-selected');
|
||||
} else {
|
||||
el.classList.remove('red-selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.templateManager.setFont(settings.fontFamily);
|
||||
}
|
||||
|
||||
if (settings.fontSize) {
|
||||
this.fontSizeSelect.value = settings.fontSize.toString();
|
||||
this.templateManager.setFontSize(settings.fontSize);
|
||||
}
|
||||
|
||||
// 更新字号调整事件
|
||||
const updateFontSize = async () => {
|
||||
const size = parseInt(this.fontSizeSelect.value);
|
||||
this.templateManager.setFontSize(size);
|
||||
await this.settingsManager.updateSettings({
|
||||
fontSize: size
|
||||
});
|
||||
this.templateManager.applyTemplate(this.previewEl);
|
||||
};
|
||||
|
||||
// 字号调整按钮事件
|
||||
decreaseButton.addEventListener('click', () => {
|
||||
const currentSize = parseInt(this.fontSizeSelect.value);
|
||||
if (currentSize > 12) {
|
||||
this.fontSizeSelect.value = (currentSize - 1).toString();
|
||||
updateFontSize();
|
||||
}
|
||||
});
|
||||
|
||||
increaseButton.addEventListener('click', () => {
|
||||
const currentSize = parseInt(this.fontSizeSelect.value);
|
||||
if (currentSize < 30) {
|
||||
this.fontSizeSelect.value = (currentSize + 1).toString();
|
||||
updateFontSize();
|
||||
}
|
||||
});
|
||||
|
||||
this.fontSizeSelect.addEventListener('change', updateFontSize);
|
||||
|
||||
// 预览区域
|
||||
this.previewEl = container.createEl('div', { cls: 'red-preview-wrapper' });
|
||||
|
||||
// 底部工具栏
|
||||
const bottomBar = container.createEl('div', { cls: 'red-bottom-bar' });
|
||||
|
||||
// 添加使用说明按钮
|
||||
const helpButton = bottomBar.createEl('button', {
|
||||
cls: 'red-help-button',
|
||||
attr: { 'aria-label': '使用指南' }
|
||||
});
|
||||
helpButton.innerHTML = '❓';
|
||||
|
||||
// 更新帮助文本
|
||||
const tooltip = bottomBar.createEl('div', {
|
||||
cls: 'red-help-tooltip',
|
||||
text: `使用指南:
|
||||
1. 核心用法:用二级标题(##)分割内容,每个标题生成一张小红书配图
|
||||
2. 首图制作:单独调整首节字号至20-24px,使用【下载当前页】导出
|
||||
3. 长文优化:内容较多的章节可调小字号至14-16px后单独导出
|
||||
4. 批量操作:保持统一字号时,用【导出全部页】批量生成
|
||||
5. 模板切换:顶部选择器可切换不同视觉风格
|
||||
6. 实时编辑:解锁状态(🔓)下编辑文档即时预览效果
|
||||
7. 支持创作:点击❤️关于作者可进行打赏支持`
|
||||
});
|
||||
|
||||
// 创建中间控件容器
|
||||
const bottomControlsGroup = bottomBar.createEl('div', { cls: 'red-bottom-controls-group' });
|
||||
|
||||
// 请作者喝咖啡按钮
|
||||
const likeButton = bottomControlsGroup.createEl('button', {
|
||||
cls: 'red-like-button'
|
||||
});
|
||||
likeButton.innerHTML = '<span style="margin-right: 4px">❤️</span>关于作者';
|
||||
likeButton.addEventListener('click', () => {
|
||||
DonateManager.showDonateModal(this.containerEl);
|
||||
});
|
||||
|
||||
// 单张下载按钮
|
||||
const singleDownloadButton = bottomControlsGroup.createEl('button', {
|
||||
text: '下载当前页',
|
||||
cls: 'red-export-button'
|
||||
});
|
||||
|
||||
singleDownloadButton.addEventListener('click', async () => {
|
||||
if (this.previewEl) {
|
||||
singleDownloadButton.disabled = true;
|
||||
singleDownloadButton.setText('导出中...');
|
||||
|
||||
try {
|
||||
await DownloadManager.downloadSingleImage(this.previewEl);
|
||||
singleDownloadButton.setText('导出成功');
|
||||
|
||||
setTimeout(() => {
|
||||
singleDownloadButton.disabled = false;
|
||||
singleDownloadButton.setText('下载当前页');
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
singleDownloadButton.setText('导出失败');
|
||||
setTimeout(() => {
|
||||
singleDownloadButton.disabled = false;
|
||||
singleDownloadButton.setText('下载当前页');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 更新按钮文本和类名
|
||||
this.copyButton = bottomControlsGroup.createEl('button', {
|
||||
text: '导出全部页',
|
||||
cls: 'red-export-button'
|
||||
});
|
||||
|
||||
// 添加导出按钮点击事件
|
||||
this.copyButton.addEventListener('click', async () => {
|
||||
if (this.previewEl) {
|
||||
this.copyButton.disabled = true;
|
||||
this.copyButton.setText('导出中...');
|
||||
|
||||
try {
|
||||
await DownloadManager.downloadAllImages(this.previewEl);
|
||||
this.copyButton.setText('导出成功');
|
||||
|
||||
setTimeout(() => {
|
||||
this.copyButton.disabled = false;
|
||||
this.copyButton.setText('导出全部页');
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
this.copyButton.setText('导出失败');
|
||||
setTimeout(() => {
|
||||
this.copyButton.disabled = false;
|
||||
this.copyButton.setText('导出全部页');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const newButton = bottomControlsGroup.createEl('button', {
|
||||
text: '敬请期待',
|
||||
cls: 'red-feature-button'
|
||||
});
|
||||
|
||||
// 监听文档变化
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-open', this.onFileOpen.bind(this))
|
||||
);
|
||||
|
||||
// 监听文档内容变化
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', this.onFileModify.bind(this))
|
||||
);
|
||||
|
||||
// 检查当前打开的文件
|
||||
const currentFile = this.app.workspace.getActiveFile();
|
||||
await this.onFileOpen(currentFile);
|
||||
}
|
||||
|
||||
private updateControlsState(enabled: boolean) {
|
||||
this.lockButton.disabled = !enabled;
|
||||
|
||||
// 更新自定义选择器的禁用状态
|
||||
const templateSelect = this.customTemplateSelect.querySelector('.red-select');
|
||||
const fontSelect = this.customFontSelect.querySelector('.red-select');
|
||||
if (templateSelect) {
|
||||
templateSelect.classList.toggle('disabled', !enabled);
|
||||
templateSelect.setAttribute('style', `pointer-events: ${enabled ? 'auto' : 'none'}`);
|
||||
}
|
||||
if (fontSelect) {
|
||||
fontSelect.classList.toggle('disabled', !enabled);
|
||||
fontSelect.setAttribute('style', `pointer-events: ${enabled ? 'auto' : 'none'}`);
|
||||
}
|
||||
|
||||
// 字号相关控件
|
||||
this.fontSizeSelect.disabled = !enabled;
|
||||
const fontSizeButtons = this.containerEl.querySelectorAll('.red-font-size-btn');
|
||||
fontSizeButtons.forEach(button => {
|
||||
(button as HTMLButtonElement).disabled = !enabled;
|
||||
});
|
||||
|
||||
// 导出按钮
|
||||
this.copyButton.disabled = !enabled;
|
||||
const singleDownloadButton = this.containerEl.querySelector('.red-export-button');
|
||||
if (singleDownloadButton) {
|
||||
(singleDownloadButton as HTMLButtonElement).disabled = !enabled;
|
||||
}
|
||||
}
|
||||
|
||||
async onFileOpen(file: TFile | null) {
|
||||
this.currentFile = file;
|
||||
this.currentImageIndex = 0; // 重置图片索引
|
||||
|
||||
if (!file || file.extension !== 'md') {
|
||||
this.previewEl.empty();
|
||||
this.previewEl.createEl('div', {
|
||||
text: '只能预览 Markdown 文本文档',
|
||||
cls: 'red-empty-state'
|
||||
});
|
||||
this.updateControlsState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateControlsState(true);
|
||||
this.isPreviewLocked = false;
|
||||
this.lockButton.innerHTML = '🔓';
|
||||
await this.updatePreview();
|
||||
}
|
||||
private async togglePreviewLock() {
|
||||
this.isPreviewLocked = !this.isPreviewLocked;
|
||||
const lockIcon = this.isPreviewLocked ? '🔒' : '🔓';
|
||||
const lockStatus = this.isPreviewLocked ? '开启实时预览状态' : '关闭实时预览状态';
|
||||
this.lockButton.innerHTML = lockIcon;
|
||||
this.lockButton.setAttribute('aria-label', lockStatus);
|
||||
|
||||
if (!this.isPreviewLocked) {
|
||||
await this.updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
async onFileModify(file: TFile) {
|
||||
if (file === this.currentFile && !this.isPreviewLocked) {
|
||||
if (this.updateTimer) {
|
||||
clearTimeout(this.updateTimer);
|
||||
}
|
||||
|
||||
this.updateTimer = setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
async updatePreview() {
|
||||
if (!this.currentFile) return;
|
||||
this.previewEl.empty();
|
||||
const content = await this.app.vault.read(this.currentFile);
|
||||
|
||||
// 渲染 Markdown 内容
|
||||
await MarkdownRenderer.renderMarkdown(
|
||||
content,
|
||||
this.previewEl,
|
||||
this.currentFile.path,
|
||||
this
|
||||
);
|
||||
|
||||
// 转换内容并检查有效性
|
||||
RedConverter.formatContent(this.previewEl);
|
||||
const hasValidContent = RedConverter.hasValidContent(this.previewEl);
|
||||
|
||||
// 更新所有控件状态
|
||||
this.updateControlsState(hasValidContent);
|
||||
if (!hasValidContent) {
|
||||
this.copyButton.setAttribute('title', '请先添加二级标题内容');
|
||||
} else {
|
||||
this.copyButton.removeAttribute('title');
|
||||
}
|
||||
|
||||
// 添加用户信息到页头
|
||||
const header = this.previewEl.querySelector('.red-preview-header');
|
||||
if (header) {
|
||||
this.previewManager.createHeaderContent(
|
||||
header as HTMLElement,
|
||||
() => this.handleAvatarClick(),
|
||||
(el) => this.handleUserNameEdit(el),
|
||||
(el) => this.handleUserIdEdit(el)
|
||||
);
|
||||
}
|
||||
|
||||
// 添加页脚内容
|
||||
const footer = this.previewEl.querySelector('.red-preview-footer');
|
||||
if (footer) {
|
||||
this.previewManager.createFooterContent(
|
||||
footer as HTMLElement,
|
||||
(el, position) => this.handleFooterTextEdit(el, position)
|
||||
);
|
||||
}
|
||||
|
||||
// 应用模板和背景
|
||||
this.templateManager.applyTemplate(this.previewEl);
|
||||
|
||||
// 获取所有内容区块
|
||||
const sections = this.previewEl.querySelectorAll('.red-content-section');
|
||||
if (sections.length === 0) return;
|
||||
|
||||
// 创建导航按钮
|
||||
const navButtons = this.previewManager.createNavigationButtons(
|
||||
this.previewEl,
|
||||
sections.length,
|
||||
(direction) => this.navigateImages(direction)
|
||||
);
|
||||
this.navigationButtons = navButtons;
|
||||
|
||||
// 显示当前图片
|
||||
this.previewManager.showImage(this.currentImageIndex, sections, this.navigationButtons);
|
||||
}
|
||||
|
||||
// 处理头像点击
|
||||
private async handleAvatarClick() {
|
||||
// 创建文件选择器
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
|
||||
input.addEventListener('change', async () => {
|
||||
const file = input.files?.[0];
|
||||
if (file) {
|
||||
try {
|
||||
// 转换为 base64
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const base64 = e.target?.result as string;
|
||||
// 更新设置
|
||||
await this.settingsManager.updateSettings({
|
||||
userAvatar: base64
|
||||
});
|
||||
// 刷新预览
|
||||
await this.updatePreview();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} catch (error) {
|
||||
console.error('头像更新失败:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
input.click();
|
||||
}
|
||||
|
||||
// 处理用户名编辑
|
||||
private async handleUserNameEdit(element: HTMLElement) {
|
||||
const input = document.createElement('input');
|
||||
input.value = element.textContent || '';
|
||||
input.className = 'red-user-edit-input';
|
||||
input.placeholder = '请输入用户名';
|
||||
element.replaceWith(input);
|
||||
input.focus();
|
||||
|
||||
const handleBlur = async () => {
|
||||
const newName = input.value.trim();
|
||||
await this.settingsManager.updateSettings({
|
||||
userName: newName || '夜半' // 设置默认值
|
||||
});
|
||||
await this.updatePreview();
|
||||
input.remove();
|
||||
};
|
||||
|
||||
input.addEventListener('blur', handleBlur);
|
||||
input.addEventListener('keypress', async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
await handleBlur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理用户ID编辑
|
||||
private async handleUserIdEdit(element: HTMLElement) {
|
||||
const input = document.createElement('input');
|
||||
input.value = element.textContent || '';
|
||||
input.className = 'red-user-edit-input';
|
||||
input.placeholder = '请输入用户ID';
|
||||
element.replaceWith(input);
|
||||
input.focus();
|
||||
|
||||
const handleBlur = async () => {
|
||||
let newId = input.value.trim();
|
||||
if (!newId) {
|
||||
newId = '@Yeban'; // 设置默认值
|
||||
} else if (!newId.startsWith('@')) {
|
||||
newId = '@' + newId;
|
||||
}
|
||||
await this.settingsManager.updateSettings({
|
||||
userId: newId
|
||||
});
|
||||
await this.updatePreview();
|
||||
input.remove();
|
||||
};
|
||||
|
||||
input.addEventListener('blur', handleBlur);
|
||||
input.addEventListener('keypress', async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
await handleBlur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ... existing code ...
|
||||
|
||||
private async handleFooterTextEdit(element: HTMLElement, position: 'left' | 'right') {
|
||||
const input = document.createElement('input');
|
||||
input.value = element.textContent || '';
|
||||
input.className = 'red-user-edit-input';
|
||||
input.placeholder = '请输入页脚文本';
|
||||
element.replaceWith(input);
|
||||
input.focus();
|
||||
|
||||
const handleBlur = async () => {
|
||||
const newText = input.value.trim();
|
||||
await this.settingsManager.updateSettings({
|
||||
[`footer${position === 'left' ? 'Left' : 'Right'}Text`]: newText ||
|
||||
(position === 'left' ? '夜半过后,光明便启程' : '欢迎关注公众号:夜半')
|
||||
});
|
||||
await this.updatePreview();
|
||||
input.remove();
|
||||
};
|
||||
|
||||
input.addEventListener('blur', handleBlur);
|
||||
input.addEventListener('keypress', async (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
await handleBlur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改 navigateImages 方法
|
||||
private navigateImages(direction: 'prev' | 'next') {
|
||||
const sections = this.previewEl.querySelectorAll('.red-content-section');
|
||||
if (direction === 'prev' && this.currentImageIndex > 0) {
|
||||
this.currentImageIndex--;
|
||||
} else if (direction === 'next' && this.currentImageIndex < sections.length - 1) {
|
||||
this.currentImageIndex++;
|
||||
}
|
||||
this.previewManager.showImage(this.currentImageIndex, sections, this.navigationButtons);
|
||||
}
|
||||
|
||||
// 添加自定义下拉选择器创建方法
|
||||
private createCustomSelect(
|
||||
parent: HTMLElement,
|
||||
className: string,
|
||||
options: { value: string; label: string }[]
|
||||
) {
|
||||
const container = parent.createEl('div', { cls: `red-select-container ${className}` });
|
||||
const select = container.createEl('div', { cls: 'red-select' });
|
||||
const selectedText = select.createEl('span', { cls: 'red-select-text' });
|
||||
const arrow = select.createEl('span', { cls: 'red-select-arrow', text: '▾' });
|
||||
|
||||
const dropdown = container.createEl('div', { cls: 'red-select-dropdown' });
|
||||
|
||||
options.forEach(option => {
|
||||
const item = dropdown.createEl('div', {
|
||||
cls: 'red-select-item',
|
||||
text: option.label
|
||||
});
|
||||
|
||||
item.dataset.value = option.value;
|
||||
item.addEventListener('click', () => {
|
||||
dropdown.querySelectorAll('.red-select-item').forEach(el =>
|
||||
el.classList.remove('red-selected'));
|
||||
item.classList.add('red-selected');
|
||||
selectedText.textContent = option.label;
|
||||
select.dataset.value = option.value;
|
||||
dropdown.classList.remove('red-show');
|
||||
select.dispatchEvent(new CustomEvent('change', {
|
||||
detail: { value: option.value }
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// 设置默认值和选中状态
|
||||
if (options.length > 0) {
|
||||
selectedText.textContent = options[0].label;
|
||||
select.dataset.value = options[0].value;
|
||||
dropdown.querySelector('.red-select-item')?.classList.add('red-selected');
|
||||
}
|
||||
|
||||
// 点击显示/隐藏下拉列表
|
||||
select.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdown.classList.toggle('red-show');
|
||||
});
|
||||
|
||||
// 点击其他地方关闭下拉列表
|
||||
document.addEventListener('click', () => {
|
||||
dropdown.classList.remove('red-show');
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
// 获取模板选项
|
||||
private async getTemplateOptions() {
|
||||
await this.templateManager.loadTemplates();
|
||||
const templates = this.templateManager.getAllTemplates();
|
||||
|
||||
return templates.length > 0
|
||||
? templates.map(t => ({ value: t.id, label: t.name }))
|
||||
: [{ value: 'default', label: '默认模板' }];
|
||||
}
|
||||
|
||||
// 获取字体选项
|
||||
private getFontOptions() {
|
||||
return [
|
||||
{
|
||||
value: 'Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif',
|
||||
label: '默认字体'
|
||||
},
|
||||
{
|
||||
value: 'SimSun, "宋体", serif',
|
||||
label: '宋体'
|
||||
},
|
||||
{
|
||||
value: 'SimHei, "黑体", sans-serif',
|
||||
label: '黑体'
|
||||
},
|
||||
{
|
||||
value: 'KaiTi, "楷体", serif',
|
||||
label: '楷体'
|
||||
},
|
||||
{
|
||||
value: '"Microsoft YaHei", "微软雅黑", sans-serif',
|
||||
label: '雅黑'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
736
styles.css
Normal file
736
styles.css
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
/* 状态栏样式覆盖 */
|
||||
.workspace-leaf-content .view-content {
|
||||
padding: 10px 10px 13px 10px;
|
||||
}
|
||||
|
||||
/* 工具栏基础样式 */
|
||||
.red-toolbar, .red-bottom-bar {
|
||||
padding: 20px 32px;
|
||||
position: sticky;
|
||||
border-radius: 6px;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 680px;
|
||||
}
|
||||
|
||||
.red-toolbar {
|
||||
top: 0;
|
||||
background: rgba(82, 144, 220, 0.03);
|
||||
border-bottom: 1px solid rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-bottom-bar {
|
||||
bottom: 0;
|
||||
background: rgba(82, 144, 220, 0.02);
|
||||
border-top: 1px solid rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
/* 控件组布局 */
|
||||
.red-controls-group, .red-bottom-controls-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
min-width: fit-content;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
/* 锁定按钮 */
|
||||
.red-lock-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 1px 3px rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-lock-button:hover {
|
||||
background: #f5f8fc;
|
||||
border-color: #5C5F77;
|
||||
color: #5C5F77;
|
||||
box-shadow: 0 2px 6px rgba(82, 144, 220, 0.15);
|
||||
}
|
||||
|
||||
/* 下拉选择器 */
|
||||
.red-select-container {
|
||||
position: relative;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.red-select {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #5C5F77;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.2s ease;
|
||||
color: #5C5F77;
|
||||
box-shadow: 0 1px 3px rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-select:hover {
|
||||
background: #f5f8fc;
|
||||
box-shadow: 0 2px 6px rgba(82, 144, 220, 0.15);
|
||||
}
|
||||
|
||||
.red-select.disabled {
|
||||
opacity: 0.5;
|
||||
background: #f5f5f5 !important;
|
||||
color: #999 !important;
|
||||
border-color: #e8e8e8 !important;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.red-select-arrow {
|
||||
color: #5C5F77;
|
||||
font-size: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.red-select-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: white;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(82, 144, 220, 0.15);
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.red-select-dropdown.red-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.red-select-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.red-select-item:hover {
|
||||
background: #f5f8fc;
|
||||
}
|
||||
|
||||
.red-select-item.red-selected {
|
||||
background-color: #f5f8fc;
|
||||
color: #5C5F77;
|
||||
}
|
||||
|
||||
/* 字号调整组件 */
|
||||
.red-font-size-group {
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 32px;
|
||||
background: white;
|
||||
border: 1px solid #5C5F77;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-font-size-btn {
|
||||
width: 55px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: white;
|
||||
color: #5C5F77;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.red-font-size-btn:hover {
|
||||
background: rgba(82, 144, 220, 0.05);
|
||||
}
|
||||
|
||||
.red-font-size-input {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #5C5F77;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 预览区域 */
|
||||
.red-preview-wrapper {
|
||||
padding: 10px 20px 20px 20px;
|
||||
margin: 10px;
|
||||
height: calc(100% - 180px);
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 帮助按钮 */
|
||||
.red-help-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 1px 3px rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-help-button:hover {
|
||||
background: #f5f8fc;
|
||||
border-color: #5C5F77;
|
||||
color: #5C5F77;
|
||||
box-shadow: 0 2px 6px rgba(82, 144, 220, 0.15);
|
||||
}
|
||||
|
||||
/* 提示框位置调整 */
|
||||
.red-help-tooltip {
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
bottom: 48px;
|
||||
width: 450px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 2px solid rgba(82, 144, 220, 0.1);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #5290DC;
|
||||
display: none;
|
||||
white-space: pre-line;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.red-help-button:hover + .red-help-tooltip {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.red-bottom-controls-group button {
|
||||
padding: 8px 24px;
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #5C5F77;
|
||||
background: white;
|
||||
color: #5C5F77;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
box-shadow: 0 1px 3px rgba(82, 144, 220, 0.1);
|
||||
}
|
||||
|
||||
.red-bottom-controls-group button:hover {
|
||||
background: rgba(82, 144, 220, 0.05);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(82, 144, 220, 0.2);
|
||||
}
|
||||
|
||||
.red-export-button:not(:disabled) {
|
||||
background: #5290DC !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 2px 6px rgba(82, 144, 220, 0.2) !important;
|
||||
}
|
||||
|
||||
.red-export-button:not(:disabled):hover {
|
||||
background: #4a83c8 !important;
|
||||
box-shadow: 0 4px 12px rgba(82, 144, 220, 0.3) !important;
|
||||
}
|
||||
|
||||
.red-feature-button {
|
||||
opacity: 0.5;
|
||||
border-color: #5C5F77 !important;
|
||||
background: white !important;
|
||||
color: #5C5F77 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.red-bottom-controls-group button:disabled {
|
||||
opacity: 0.5;
|
||||
background: white !important;
|
||||
color: #5C5F77 !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none;
|
||||
border-color: #5C5F77 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 关于作者的框 */
|
||||
.red-about-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* 弹框主体 */
|
||||
.red-about-modal {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px 20px;
|
||||
width: 520px;
|
||||
max-height: 90vh;
|
||||
position: relative;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
.red-about-modal::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.red-about-modal::-webkit-scrollbar-track {
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.red-about-modal::-webkit-scrollbar-thumb {
|
||||
background: #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.red-about-modal::-webkit-scrollbar-thumb:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
/* 关闭按钮 */
|
||||
.red-about-close {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 17px;
|
||||
color: #bfbfbf;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* macOS 系统下的样式 */
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 0) and (min-color-index:0) {
|
||||
.red-about-close {
|
||||
right: auto;
|
||||
left: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.red-about-close:hover {
|
||||
background: #f5f5f5;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
/* 内容区块 */
|
||||
.red-about-section {
|
||||
padding: 10px;
|
||||
margin: 8px 20px 0 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.red-about-intro-section {
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.red-about-donate-section {
|
||||
background: #fff9f5;
|
||||
border: 1px solid #ffe4d9;
|
||||
}
|
||||
|
||||
.red-about-mp-section {
|
||||
background: #f5fff7;
|
||||
border: 1px solid #e6f7e9;
|
||||
}
|
||||
|
||||
/* 文字样式 */
|
||||
.red-about-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 0 0 1px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.red-about-name {
|
||||
color: #5290DC;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.red-about-identity {
|
||||
color: #34495e;
|
||||
font-weight: 500;
|
||||
background: rgba(8, 98, 209, 0.08);
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.red-about-highlight {
|
||||
color: #e67e22;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.red-about-value {
|
||||
color: #16a085;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.red-about-subtitle {
|
||||
font-size: 18px;
|
||||
color: #d35400;
|
||||
margin: 8px 0 12px;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.red-about-intro {
|
||||
font-size: 15px;
|
||||
color: #2c3e50;
|
||||
margin: 8px 0;
|
||||
line-height: 1.8;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.red-about-role,
|
||||
.red-about-desc {
|
||||
font-size: 14.5px;
|
||||
color: #34495e;
|
||||
margin: 6px 0;
|
||||
line-height: 1.8;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.red-about-emphasis {
|
||||
font-weight: 500;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.red-about-footer {
|
||||
font-size: 15px;
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin: 1px 0 0;
|
||||
padding: 1px 1px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.red-about-footer strong {
|
||||
color: #8e44ad;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 二维码容器 */
|
||||
.red-about-qr {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
margin: 5px 0;
|
||||
padding: 5px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.red-about-qr img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 小红书预览容器 */
|
||||
.red-preview-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 图片预览区域 */
|
||||
.red-image-preview {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
aspect-ratio: 3/4;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 内容区域容器 */
|
||||
.red-content-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 导航按钮 */
|
||||
.red-nav-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.red-nav-button:hover {
|
||||
background: white;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 页码指示器 */
|
||||
.red-page-indicator {
|
||||
padding: 6px 12px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 导航容器 */
|
||||
.red-nav-container {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 内容区块样式 */
|
||||
.red-content-section {
|
||||
display: none;
|
||||
margin: 0 13px;
|
||||
}
|
||||
|
||||
.red-content-section[data-index="0"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 空状态提示 */
|
||||
.red-empty-message {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #ff4757;
|
||||
font-size: 15px;
|
||||
background: rgba(255, 71, 87, 0.03);
|
||||
border: 1px dashed rgba(255, 71, 87, 0.2);
|
||||
border-radius: 12px;
|
||||
margin: 40px auto;
|
||||
max-width: 320px;
|
||||
line-height: 1.8;
|
||||
white-space: pre-line;
|
||||
box-shadow: 0 2px 8px rgba(255, 71, 87, 0.05);
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 用户信息区域样式 */
|
||||
.red-user-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px 3px 12px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.red-user-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.red-user-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.red-user-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.red-avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.red-avatar-upload-icon {
|
||||
font-size: 20px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.red-avatar-placeholder:hover {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.red-avatar-placeholder:hover .red-avatar-upload-icon {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.red-user-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.red-user-name-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.red-user-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.red-user-id {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.red-user-right {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 编辑输入框样式 */
|
||||
.red-user-edit-input {
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
outline: none;
|
||||
padding: 2px 4px;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 悬停效果 */
|
||||
.red-user-avatar:hover,
|
||||
.red-user-name:hover,
|
||||
.red-user-id:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
, "templates/index.ts" ]
|
||||
}
|
||||
Loading…
Reference in a new issue