mirror of
https://github.com/yeban8090/note-to-red.git
synced 2026-07-22 05:42:34 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a68d36341 | ||
|
|
e8651dd050 | ||
|
|
5400e81d85 | ||
|
|
b900096ad5 | ||
|
|
66073354a1 | ||
|
|
1b6e0a1bb0 | ||
|
|
aa1e14d586 | ||
|
|
8b339e833f | ||
|
|
bcf6def0e8 | ||
|
|
f2075f421e |
13 changed files with 156 additions and 74 deletions
|
|
@ -12,7 +12,7 @@
|
|||
</p>
|
||||
|
||||
## 功能特点
|
||||
- 📝 使用二级标题分割内容,每个标题自动生成一张配图
|
||||
- 📝 可设置使用一级或二级标题分割内容,每个标题自动生成一张配图
|
||||
- 🎨 提供多种精美模板,支持自定义字体和字号
|
||||
- 🎯 支持自定义主题,可调整颜色、字体、间距等样式
|
||||
- 👤 可自定义用户头像、昵称和页脚文案
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
- 🔒 锁定功能避免预览刷新打断书写
|
||||
|
||||
## 使用方法
|
||||
1. 核心用法:用二级标题(##)分割内容,每个标题生成一张小红书配图
|
||||
1. 核心用法:在设置中选择标题级别(一级#或二级##),用对应标题分割内容,每个标题生成一张小红书配图
|
||||
2. 首图制作:单独调整首节字号至20-24px,使用【下载当前页】导出
|
||||
3. 长文优化:内容较多的章节可调小字号至14-16px后单独导出
|
||||
4. 批量操作:保持统一字号时,用【导出全部页】批量生成
|
||||
|
|
@ -44,7 +44,8 @@
|
|||
4. 在设置中启用插件
|
||||
|
||||
## 使用技巧
|
||||
- 使用二级标题来分割不同的图片内容
|
||||
- 在设置中选择适合的标题级别(一级#或二级##)来分割不同的图片内容
|
||||
- 一级标题适合大章节分割,二级标题适合小章节分割
|
||||
- 调整字号大小以适应不同长度的内容
|
||||
- 可以自定义头像和用户信息
|
||||
- 支持实时预览和编辑
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "note-to-red",
|
||||
"name": "Note to RED",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.19",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Convert Markdown notes to RED (Xiaohongshu) style images",
|
||||
"author": "Yeban",
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 845 KiB After Width: | Height: | Size: 705 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 987 KiB After Width: | Height: | Size: 676 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 879 KiB After Width: | Height: | Size: 688 KiB |
|
|
@ -1,27 +1,34 @@
|
|||
import { App } from 'obsidian';
|
||||
import RedPlugin from './main';
|
||||
|
||||
export class RedConverter {
|
||||
private static app: App;
|
||||
private static plugin: RedPlugin;
|
||||
|
||||
static initialize(app: App) {
|
||||
static initialize(app: App, plugin: RedPlugin) {
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
static hasValidContent(element: HTMLElement): boolean {
|
||||
const headers = element.querySelectorAll('h2');
|
||||
const settings = this.plugin?.settingsManager?.getSettings();
|
||||
const headingLevel = settings?.headingLevel || 'h1';
|
||||
const headers = element.querySelectorAll(headingLevel);
|
||||
return headers.length > 0;
|
||||
}
|
||||
|
||||
static formatContent(element: HTMLElement): void {
|
||||
const headers = Array.from(element.querySelectorAll('h2'));
|
||||
const settings = this.plugin?.settingsManager?.getSettings();
|
||||
const headingLevel = settings?.headingLevel || 'h1';
|
||||
const headers = Array.from(element.querySelectorAll(headingLevel));
|
||||
|
||||
if (headers.length === 0) {
|
||||
element.empty();
|
||||
const tip = element.createEl('div', {
|
||||
cls: 'red-empty-message',
|
||||
text: `⚠️ 温馨提示
|
||||
请使用二级标题(##)来分割内容
|
||||
每个二级标题将生成一张独立的图片
|
||||
请使用${headingLevel === 'h1' ? '一级标题(#)' : '二级标题(##)'}来分割内容
|
||||
每个${headingLevel === 'h1' ? '一级标题' : '二级标题'}将生成一张独立的图片
|
||||
现在编辑文档,实时预览效果`
|
||||
});
|
||||
// 触发自定义事件
|
||||
|
|
@ -98,11 +105,14 @@ export class RedConverter {
|
|||
}
|
||||
|
||||
private static createContentSection(header: Element, index: number): HTMLElement | null {
|
||||
const settings = this.plugin?.settingsManager?.getSettings();
|
||||
const headingLevel = settings?.headingLevel || 'h1';
|
||||
|
||||
// 获取当前标题到下一个标题之间的所有内容
|
||||
let content: Element[] = [];
|
||||
let current = header.nextElementSibling;
|
||||
|
||||
while (current && current.tagName !== 'H2') {
|
||||
while (current && current.tagName !== headingLevel.toUpperCase()) {
|
||||
content.push(current.cloneNode(true) as Element);
|
||||
current = current.nextElementSibling;
|
||||
}
|
||||
|
|
|
|||
118
src/main.ts
118
src/main.ts
|
|
@ -7,71 +7,79 @@ import { DonateManager } from './donateManager';
|
|||
import { RedSettingTab } from './settings/SettingTab';
|
||||
|
||||
export default class RedPlugin extends Plugin {
|
||||
settingsManager: SettingsManager;
|
||||
themeManager: ThemeManager;
|
||||
settingsManager: SettingsManager;
|
||||
themeManager: ThemeManager;
|
||||
|
||||
async onload() {
|
||||
// 初始化设置管理器
|
||||
this.settingsManager = new SettingsManager(this);
|
||||
await this.settingsManager.loadSettings();
|
||||
async onload() {
|
||||
// 初始化设置管理器
|
||||
this.settingsManager = new SettingsManager(this);
|
||||
await this.settingsManager.loadSettings();
|
||||
|
||||
// 初始化主题管理器
|
||||
this.themeManager = new ThemeManager(this.app, this.settingsManager);
|
||||
// 初始化主题管理器
|
||||
this.themeManager = new ThemeManager(this.app, this.settingsManager);
|
||||
|
||||
// 初始化转换器
|
||||
RedConverter.initialize(this.app);
|
||||
// 初始化转换器
|
||||
RedConverter.initialize(this.app, this);
|
||||
|
||||
DonateManager.initialize(this.app, this);
|
||||
DonateManager.initialize(this.app, this);
|
||||
|
||||
// 注册视图
|
||||
this.registerView(
|
||||
VIEW_TYPE_RED,
|
||||
(leaf) => new RedView(leaf, this.themeManager, this.settingsManager)
|
||||
);
|
||||
// 注册视图
|
||||
this.registerView(
|
||||
VIEW_TYPE_RED,
|
||||
(leaf) => new RedView(leaf, this.themeManager, 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.app.workspace.onLayoutReady(() => {
|
||||
// this.app.workspace.on("layout-change", () => {
|
||||
// const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_RED);
|
||||
// if (leaves.length === 0) {
|
||||
// const rightLeaf = this.app.workspace.getRightLeaf(false);
|
||||
// if (rightLeaf) {
|
||||
// rightLeaf.setViewState({
|
||||
// type: VIEW_TYPE_RED,
|
||||
// active: false,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// 添加命令到命令面板
|
||||
this.addCommand({
|
||||
// 添加命令到命令面板
|
||||
this.addCommand({
|
||||
id: 'open-mp-preview',
|
||||
name: '打开小红书图片预览',
|
||||
callback: async () => {
|
||||
await this.activateView();
|
||||
}
|
||||
});
|
||||
callback: async () => {
|
||||
await this.activateView();
|
||||
},
|
||||
});
|
||||
|
||||
// 在插件的 onload 方法中添加:
|
||||
this.addSettingTab(new RedSettingTab(this.app, this));
|
||||
}
|
||||
// 添加一个功能按钮用于打开所有面板
|
||||
this.addRibbonIcon("image", "打开小红书图片预览", () => {
|
||||
this.activateView();
|
||||
});
|
||||
|
||||
async activateView() {
|
||||
// 如果视图已经存在,激活它
|
||||
// 在插件的 onload 方法中添加:
|
||||
this.addSettingTab(new RedSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
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('无法创建视图面板');
|
||||
}
|
||||
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('无法创建视图面板');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -566,6 +566,34 @@ export class CreateThemeModal extends Modal {
|
|||
.replace(/border:\s*1px solid\s*#[a-fA-F0-9]+80/, `border: 1px solid ${value}80`);
|
||||
});
|
||||
});
|
||||
|
||||
// 添加头像圆角设置
|
||||
new Setting(headerSection)
|
||||
.setName('头像圆角')
|
||||
.setDesc('设置头像的圆角大小(单位:px)。设置为-1表示圆形,大于1则有圆角')
|
||||
.addText(text => {
|
||||
// 获取当前圆角值
|
||||
const currentRadius = styles.avatar.container.match(/border-radius:\s*([\d-]+)px/)?.[1] || '12';
|
||||
text.setValue(currentRadius)
|
||||
.setPlaceholder('输入圆角大小')
|
||||
.onChange(value => {
|
||||
// 解析输入值
|
||||
let radius: string;
|
||||
if (value === '-1') {
|
||||
// 设置为圆形(50%)
|
||||
radius = '50%';
|
||||
} else {
|
||||
// 设置为具体的像素值
|
||||
const pixelValue = parseInt(value) || 12;
|
||||
radius = `${pixelValue}px`;
|
||||
}
|
||||
|
||||
// 更新样式
|
||||
styles.avatar.container = styles.avatar.container
|
||||
.replace(/border-radius:\s*[^;]+/, `border-radius: ${radius}`);
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(headerSection)
|
||||
.setName('认证图标颜色')
|
||||
.setDesc('设置认证图标的颜色')
|
||||
|
|
|
|||
|
|
@ -58,6 +58,39 @@ export class RedSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private renderBasicSettings(containerEl: HTMLElement): void {
|
||||
// 排版管理区域
|
||||
const typographySection = containerEl.createDiv('red-settings-subsection');
|
||||
const typographyHeader = typographySection.createDiv('red-settings-subsection-header');
|
||||
const typographyToggle = typographyHeader.createSpan('red-settings-subsection-toggle');
|
||||
setIcon(typographyToggle, 'chevron-right');
|
||||
|
||||
typographyHeader.createEl('h3', { text: '排版管理' });
|
||||
|
||||
const typographyContent = typographySection.createDiv('red-settings-subsection-content');
|
||||
|
||||
// 折叠/展开逻辑
|
||||
typographyHeader.addEventListener('click', () => {
|
||||
const isExpanded = !typographySection.hasClass('is-expanded');
|
||||
typographySection.toggleClass('is-expanded', isExpanded);
|
||||
setIcon(typographyToggle, isExpanded ? 'chevron-down' : 'chevron-right');
|
||||
});
|
||||
|
||||
// 内容分割标题级别设置
|
||||
new Setting(typographyContent)
|
||||
.setName('内容分割标题级别')
|
||||
.setDesc('选择用于分割内容生成图片的标题级别:')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('h1', '一级标题(#) - 按大章节分割')
|
||||
.addOption('h2', '二级标题(##) - 按小章节分割')
|
||||
.setValue(this.plugin.settingsManager.getSettings().headingLevel)
|
||||
.onChange(async (value: 'h1' | 'h2') => {
|
||||
await this.plugin.settingsManager.updateSettings({
|
||||
headingLevel: value
|
||||
});
|
||||
new Notice('标题级别设置已更新,请重启 Obsidian 或重新加载以使更改生效');
|
||||
})
|
||||
);
|
||||
|
||||
// 字体管理区域
|
||||
const fontSection = containerEl.createDiv('red-settings-subsection');
|
||||
const fontHeader = fontSection.createDiv('red-settings-subsection-header');
|
||||
|
|
@ -353,4 +386,4 @@ export class RedSettingTab extends PluginSettingTab {
|
|||
).open();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface RedSettings {
|
|||
showFooter?: boolean;
|
||||
footerLeftText: string;
|
||||
footerRightText: string;
|
||||
headingLevel: 'h1' | 'h2'; // 标题级别选项
|
||||
customFonts: { value: string; label: string; isPreset?: boolean }[]; // 添加自定义字体配置
|
||||
backgroundSettings: {
|
||||
imageUrl: string;
|
||||
|
|
@ -44,6 +45,7 @@ export const DEFAULT_SETTINGS: RedSettings = {
|
|||
userId: '@Yeban',
|
||||
showTime: true,
|
||||
timeFormat: 'zh-CN',
|
||||
headingLevel: 'h2', // 默认使用二级标题
|
||||
footerLeftText: '夜半过后,光明便启程',
|
||||
footerRightText: '欢迎关注公众号:夜半',
|
||||
customFonts: [
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@
|
|||
|
||||
/* 设置子区域折叠样式 */
|
||||
.red-settings .red-settings-subsection {
|
||||
margin: 0 12px;
|
||||
margin: 0 12px 12px 12px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
.red-preview-container {
|
||||
position: relative;
|
||||
min-width: 490px;
|
||||
height: 100%;
|
||||
aspect-ratio: 3/4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
/* 导航容器 */
|
||||
.red-nav-container {
|
||||
position: fixed;
|
||||
bottom: 120px; /* 调整位置避免与底部工具栏重叠 */
|
||||
bottom: 110px; /* 调整位置避免与底部工具栏重叠 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
|
|
|
|||
10
src/view.ts
10
src/view.ts
|
|
@ -316,12 +316,12 @@ export class RedView extends ItemView {
|
|||
attr: { 'aria-label': '使用指南' }
|
||||
});
|
||||
setIcon(helpButton, 'help');
|
||||
|
||||
const headingLevel = this.settingsManager.getSettings().headingLevel || 'h1';
|
||||
parent.createEl('div', {
|
||||
cls: 'red-help-tooltip',
|
||||
text: `使用指南:
|
||||
1. 核心用法:用二级标题(##)分割内容,每个标题生成一张小红书配图
|
||||
2. 内容分页:在二级标题下使用 --- 可将内容分割为多页,每页都会带上标题
|
||||
1. 核心用法:用${headingLevel === 'h1' ? '一级标题(#)' : '二级标题(##)'}来分割内容,每个标题生成一张小红书配图
|
||||
2. 内容分页:在${headingLevel === 'h1' ? '一级标题(#)' : '二级标题(##)'}下使用 --- 可将内容分割为多页,每页都会带上标题
|
||||
3. 首图制作:单独调整首节字号至20-24px,使用【下载当前页】导出
|
||||
4. 长文优化:内容较多的章节可调小字号至14-16px后单独导出
|
||||
5. 批量操作:保持统一字号时,用【导出全部页】批量生成
|
||||
|
|
@ -553,7 +553,7 @@ export class RedView extends ItemView {
|
|||
|
||||
this.updateControlsState(hasValidContent);
|
||||
if (!hasValidContent) {
|
||||
this.copyButton.setAttribute('title', '请先添加二级标题内容');
|
||||
this.copyButton.setAttribute('title', '请先添加一级标题内容');
|
||||
} else {
|
||||
this.copyButton.removeAttribute('title');
|
||||
}
|
||||
|
|
@ -737,4 +737,4 @@ export class RedView extends ItemView {
|
|||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue