diff --git a/src/article-render.ts b/src/article-render.ts index 1b19f85..c66408c 100644 --- a/src/article-render.ts +++ b/src/article-render.ts @@ -9,7 +9,7 @@ import { UploadImageToWx } from './imagelib'; import { NMPSettings } from './settings'; import AssetsManager from './assets'; import InlineCSS from './inline-css'; -import { wxGetToken, wxAddDraft, wxBatchGetMaterial, DraftArticle, DraftImageMediaId, DraftImages, wxAddDraftImages } from './weixin-api'; +import { wxGetToken, wxAddDraft, wxBatchGetMaterial, DraftArticle, DraftImageMediaId, DraftImages, wxAddDraftImages } from './wechat/weixin-api'; import { MDRendererCallback } from './markdown/extension'; import { MarkedParser } from './markdown/parser'; import { LocalImageManager, LocalFile } from './markdown/local-file'; @@ -855,4 +855,4 @@ export class ArticleRender implements MDRendererCallback { const key = category + ':' + id; this.cachedElements.set(key, data); } -} \ No newline at end of file +} diff --git a/src/imagelib.ts b/src/imagelib.ts index 1ca55fc..9452d6b 100644 --- a/src/imagelib.ts +++ b/src/imagelib.ts @@ -4,10 +4,10 @@ */ import { getBlobArrayBuffer } from "obsidian"; -import { wxUploadImage } from "./weixin-api"; +import { wxUploadImage } from "./wechat/weixin-api"; import { NMPSettings } from "./settings"; import { IsWasmReady, LoadWasm } from "./wasm/wasm"; -import AssetsManager from "./assets"; +import AssetsManager from "./assets"; declare function GoWebpToJPG(data: Uint8Array): Uint8Array; // wasm 返回 Uint8Array declare function GoWebpToPNG(data: Uint8Array): Uint8Array; @@ -51,4 +51,4 @@ export async function UploadImageToWx(data: Blob, filename: string, token: strin data = new Blob([bufferPart], { type: data.type }); } return await wxUploadImage(data, filename, token, type); -} \ No newline at end of file +} diff --git a/src/markdown/callouts.ts b/src/markdown/callouts.ts index 4785b75..06e9bb4 100644 --- a/src/markdown/callouts.ts +++ b/src/markdown/callouts.ts @@ -3,7 +3,7 @@ import { Tokens, MarkedExtension} from "marked"; import { Extension } from "./extension"; import AssetsManager from "src/assets"; -import { wxWidget } from "src/weixin-api"; +import { wxWidget } from 'src/wechat/weixin-api'; const icon_note = `` const icon_abstract = `` diff --git a/src/markdown/code.ts b/src/markdown/code.ts index e3f49c4..cf9add4 100644 --- a/src/markdown/code.ts +++ b/src/markdown/code.ts @@ -7,7 +7,7 @@ import { MathRendererQueue } from "./math"; import { Extension } from "./extension"; import { UploadImageToWx } from "../imagelib"; import AssetsManager from "src/assets"; -import { wxWidget } from "src/weixin-api"; +import { wxWidget } from 'src/wechat/weixin-api'; export class CardDataManager { private cardData: Map; diff --git a/src/markdown/heading.ts b/src/markdown/heading.ts index 0c303fa..5b91dc4 100644 --- a/src/markdown/heading.ts +++ b/src/markdown/heading.ts @@ -4,7 +4,7 @@ import { Tokens, MarkedExtension } from "marked"; import { Extension } from "./extension"; import AssetsManager from "src/assets"; import { ExpertSettings } from "src/expert-settings"; -import { wxWidget } from "src/weixin-api"; +import { wxWidget } from 'src/wechat/weixin-api'; export class HeadingRenderer extends Extension { index = [0, 0, 0, 0]; diff --git a/src/markdown/widget-box.ts b/src/markdown/widget-box.ts index 44b4f84..a71c86b 100644 --- a/src/markdown/widget-box.ts +++ b/src/markdown/widget-box.ts @@ -4,7 +4,7 @@ import { Tokens, MarkedExtension } from "marked"; import { Extension } from "./extension"; import { NMPSettings } from "src/settings"; import { uevent } from "src/utils"; -import { wxWidget } from "src/weixin-api"; +import { wxWidget } from 'src/wechat/weixin-api'; const widgetCache = new Map(); diff --git a/src/setting-tab.ts b/src/setting-tab.ts index afb76c6..f4cbd00 100644 --- a/src/setting-tab.ts +++ b/src/setting-tab.ts @@ -5,7 +5,7 @@ import { App, TextAreaComponent, PluginSettingTab, Setting, Notice, sanitizeHTMLToDom } from 'obsidian'; import NoteToMpPlugin from './main'; -import { wxGetToken,wxEncrypt } from './weixin-api'; +import { wxGetToken, wxEncrypt } from './wechat/weixin-api'; import { cleanMathCache } from './markdown/math'; import { NMPSettings } from './settings'; import { DocModal } from './doc-modal'; diff --git a/src/settings.ts b/src/settings.ts index ae1c2ca..705ab82 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -9,7 +9,7 @@ * - 批量发布预设 / 图片处理 / 样式控制等选项 */ -import { wxKeyInfo } from './weixin-api'; +import { wxKeyInfo } from './wechat/weixin-api'; export class NMPSettings { defaultStyle: string; diff --git a/src/utils.ts b/src/utils.ts index e99a17d..12ef73d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ */ import { App, sanitizeHTMLToDom, requestUrl, Platform } from "obsidian"; -import * as postcss from "./postcss/postcss"; +import * as postcss from "./postcss/postcss"; // 内置 PostCSS runtime,解析主题 CSS 用于内联样式 let PluginVersion = "0.0.0"; let PlugPlatform = "obsidian"; diff --git a/src/weixin-api.ts b/src/wechat/weixin-api.ts similarity index 100% rename from src/weixin-api.ts rename to src/wechat/weixin-api.ts diff --git a/src/xiaohongshu/xhs-preview.ts b/src/xiaohongshu/xhs-preview.ts index fe54290..12f1ac8 100644 --- a/src/xiaohongshu/xhs-preview.ts +++ b/src/xiaohongshu/xhs-preview.ts @@ -17,6 +17,8 @@ import { sliceCurrentPage, sliceAllPages } from './slice'; const XHS_PREVIEW_DEFAULT_WIDTH = 540; const XHS_PREVIEW_WIDTH_OPTIONS = [1080, 720, 540, 360]; + +// 字号控制常量:一处修改即可同步 UI 显示、输入校验和渲染逻辑 const XHS_FONT_SIZE_MIN = 18; const XHS_FONT_SIZE_MAX = 45; const XHS_FONT_SIZE_DEFAULT = 36; @@ -32,14 +34,11 @@ export class XiaohongshuPreview { currentFile: TFile | null = null; // UI 元素 - topToolbar!: HTMLDivElement; templateSelect!: HTMLSelectElement; fontSizeInput!: HTMLInputElement; previewWidthSelect!: HTMLSelectElement; pageContainer!: HTMLDivElement; - bottomToolbar!: HTMLDivElement; - pageNavigation!: HTMLDivElement; pageNumberInput!: HTMLInputElement; pageTotalLabel!: HTMLSpanElement; styleEl: HTMLStyleElement | null = null; // 主题样式注入节点 @@ -78,49 +77,28 @@ export class XiaohongshuPreview { this.container.appendChild(this.styleEl); } - // 顶部工具栏 - this.buildTopToolbar(); - - // 页面容器 - this.pageContainer = this.container.createDiv({ cls: 'xhs-page-container' }); - - // 分页导航 - this.buildPageNavigation(); - - // 底部操作栏 - this.buildBottomToolbar(); - } - - /** - * 构建顶部工具栏 - */ - private buildTopToolbar(): void { - this.topToolbar = this.container.createDiv({ cls: 'xhs-top-toolbar' }); - - // 刷新按钮 - const refreshBtn = this.topToolbar.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' }); - refreshBtn.onclick = () => this.onRefresh(); - - // 发布按钮 - const publishBtn = this.topToolbar.createEl('button', { text: '📤 发布', cls: 'toolbar-button' }); - publishBtn.onclick = () => this.onPublish(); - - // 分隔线 - const separator2 = this.topToolbar.createDiv({ cls: 'toolbar-separator' }); - - // 模板选择 - const templateLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' }); - templateLabel.innerText = '模板'; - this.templateSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' }); + const board = this.container.createDiv({ cls: 'xhs-board' }); + + const templateCard = this.createGridCard(board, 'xhs-area-template'); + const templateLabel = templateCard.createDiv({ cls: 'xhs-label', text: '模板' }); + this.templateSelect = templateCard.createEl('select', { cls: 'xhs-select' }); ['默认模板', '简约模板', '杂志模板'].forEach(name => { const option = this.templateSelect.createEl('option'); option.value = name; option.text = name; }); - - const previewWidthLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' }); - previewWidthLabel.innerText = '预览宽度'; - this.previewWidthSelect = this.topToolbar.createEl('select', { cls: 'xhs-select' }); + + const refreshCard = this.createGridCard(board, 'xhs-area-refresh'); + const refreshBtn = refreshCard.createEl('button', { text: '🔄 刷新', cls: 'toolbar-button purple-gradient' }); + refreshBtn.onclick = () => this.onRefresh(); + + const publishCard = this.createGridCard(board, 'xhs-area-publish'); + const publishBtn = publishCard.createEl('button', { text: '📤 发布', cls: 'toolbar-button' }); + publishBtn.onclick = () => this.onPublish(); + + const previewCard = this.createGridCard(board, 'xhs-area-preview'); + const previewLabel = previewCard.createDiv({ cls: 'xhs-label', text: '预览宽度' }); + this.previewWidthSelect = previewCard.createEl('select', { cls: 'xhs-select' }); const currentPreviewWidth = this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH; XHS_PREVIEW_WIDTH_OPTIONS.forEach(value => { const option = this.previewWidthSelect.createEl('option'); @@ -141,12 +119,11 @@ export class XiaohongshuPreview { this.previewWidthSelect.value = String(this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH); } }; - - // 字号控制(可直接编辑) - const fontSizeLabel = this.topToolbar.createDiv({ cls: 'toolbar-label' }); - fontSizeLabel.innerText = '字号'; - const fontSizeGroup = this.topToolbar.createDiv({ cls: 'font-size-group' }); - + + const fontCard = this.createGridCard(board, 'xhs-area-font'); + fontCard.createDiv({ cls: 'xhs-label', text: '字号' }); + const fontSizeGroup = fontCard.createDiv({ cls: 'font-size-group' }); + const decreaseBtn = fontSizeGroup.createEl('button', { text: '−', cls: 'font-size-btn' }); decreaseBtn.onclick = () => this.changeFontSize(-1); @@ -159,24 +136,19 @@ export class XiaohongshuPreview { value: String(XHS_FONT_SIZE_DEFAULT) } }); - this.fontSizeInput.style.width = '50px'; - this.fontSizeInput.style.textAlign = 'center'; this.fontSizeInput.onchange = () => this.onFontSizeInputChanged(); const increaseBtn = fontSizeGroup.createEl('button', { text: '+', cls: 'font-size-btn' }); increaseBtn.onclick = () => this.changeFontSize(1); - } - - /** - * 构建分页导航 - */ - private buildPageNavigation(): void { - this.pageNavigation = this.container.createDiv({ cls: 'xhs-page-navigation' }); - - const prevBtn = this.pageNavigation.createEl('button', { text: '‹', cls: 'xhs-nav-btn' }); + + const contentWrapper = board.createDiv({ cls: 'xhs-area-content' }); + this.pageContainer = contentWrapper.createDiv({ cls: 'xhs-page-container' }); + + const paginationCard = this.createGridCard(board, 'xhs-area-pagination xhs-pagination'); + const prevBtn = paginationCard.createEl('button', { text: '‹', cls: 'xhs-nav-btn' }); prevBtn.onclick = () => this.previousPage(); - - const indicator = this.pageNavigation.createDiv({ cls: 'xhs-page-indicator' }); + + const indicator = paginationCard.createDiv({ cls: 'xhs-page-indicator' }); this.pageNumberInput = indicator.createEl('input', { cls: 'xhs-page-number-input', attr: { type: 'text', value: '1', inputmode: 'numeric', 'aria-label': '当前页码' } @@ -197,22 +169,20 @@ export class XiaohongshuPreview { this.pageNumberInput.onblur = () => this.handlePageNumberInput(); this.pageTotalLabel = indicator.createEl('span', { cls: 'xhs-page-number-total', text: '/1' }); - - const nextBtn = this.pageNavigation.createEl('button', { text: '›', cls: 'xhs-nav-btn' }); + + const nextBtn = paginationCard.createEl('button', { text: '›', cls: 'xhs-nav-btn' }); nextBtn.onclick = () => this.nextPage(); + + const sliceCard = this.createGridCard(board, 'xhs-area-slice'); + const sliceCurrentBtn = sliceCard.createEl('button', { text: '⬇ 当前页切图', cls: 'xhs-slice-btn' }); + sliceCurrentBtn.onclick = () => this.sliceCurrentPage(); + + const sliceAllBtn = sliceCard.createEl('button', { text: '⇓ 全部页切图', cls: 'xhs-slice-btn secondary' }); + sliceAllBtn.onclick = () => this.sliceAllPages(); } - /** - * 构建底部操作栏 - */ - private buildBottomToolbar(): void { - this.bottomToolbar = this.container.createDiv({ cls: 'xhs-bottom-toolbar' }); - - const currentPageBtn = this.bottomToolbar.createEl('button', { text: '⬇ 当前页切图', cls: 'xhs-slice-btn' }); - currentPageBtn.onclick = () => this.sliceCurrentPage(); - - const allPagesBtn = this.bottomToolbar.createEl('button', { text: '⇓ 全部页切图', cls: 'xhs-slice-btn secondary' }); - allPagesBtn.onclick = () => this.sliceAllPages(); + private createGridCard(parent: HTMLElement, areaClass: string): HTMLDivElement { + return parent.createDiv({ cls: `xhs-card ${areaClass}` }); } /** @@ -336,6 +306,7 @@ export class XiaohongshuPreview { pageElement.style.width = `${actualWidth}px`; pageElement.style.height = `${actualHeight}px`; pageElement.style.transform = `scale(${scale})`; + pageElement.style.transformOrigin = 'top left'; pageElement.style.position = 'absolute'; pageElement.style.top = '0'; pageElement.style.left = '0'; @@ -521,13 +492,10 @@ export class XiaohongshuPreview { * 清理资源 */ destroy(): void { - this.topToolbar = null as any; this.templateSelect = null as any; this.previewWidthSelect = null as any; this.fontSizeInput = null as any; this.pageContainer = null as any; - this.bottomToolbar = null as any; - this.pageNavigation = null as any; this.pageNumberInput = null as any; this.pageTotalLabel = null as any; this.pages = []; diff --git a/styles.css b/styles.css index 03e909a..da97839 100644 --- a/styles.css +++ b/styles.css @@ -460,38 +460,142 @@ label:hover { color: var(--c-primary); } .xiaohongshu-preview-container { width: 100%; height: 100%; + display: flex; + padding: 12px; + box-sizing: border-box; } -.xhs-preview-container:not([style*="display: none"]) { - display: grid !important; - grid-template-rows: auto 1fr auto; - height: 100%; +.xhs-board { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-rows: auto auto auto 1fr auto; + grid-template-areas: + "platform platform platform refresh publish" + "template template preview preview font" + "content content content content content" + "content content content content content" + "pagination pagination slice slice slice"; + gap: 12px; + width: 100%; background: var(--grad-xhs-bg); + border-radius: 12px; + padding: 16px; + box-shadow: var(--shadow-sm); min-height: 0; } -.xhs-page-container { - overflow-y: auto; - overflow-x: hidden; - display: grid; - align-content: start; - justify-content: center; - padding: 0px; - background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%); - min-height: 0; /* 允许子项正确收缩和滚动 */ +.xhs-card { + display: flex; + align-items: center; + gap: 10px; + background: white; + border-radius: 10px; + padding: 10px 14px; + box-shadow: var(--shadow-sm); +} + +.xhs-label { + font-size: 13px; + font-weight: 600; + color: var(--c-text-muted); + white-space: nowrap; +} + +.xhs-select { + flex: 1 1 auto; + padding: 6px 10px; + border: 1px solid var(--c-border); + border-radius: 6px; + background: white; + font-size: 13px; + cursor: pointer; + transition: border-color 0.2s ease; +} + +.xhs-select:hover { border-color: var(--c-primary); } + +.xhs-select:focus { + outline: none; + border-color: var(--c-primary); + box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1); +} + +.font-size-group { + display: grid; + grid-auto-flow: column; + align-items: center; + gap: 6px; + padding: 2px; + border: 1px solid var(--c-border); + border-radius: 6px; + background: #f7f9fb; +} + +.font-size-btn { + width: 26px; + height: 26px; + border: none; + background: transparent; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + color: #5f6368; + transition: background 0.2s ease; +} + +.font-size-btn:hover { background: #eaf1fe; } + +.font-size-input { + width: 60px; + border: none; + background: transparent; + text-align: center; + font: inherit; + color: inherit; +} + +.font-size-input:focus { outline: none; } + +.xhs-area-platform, +.xhs-board .platform-chooser-container, +.xhs-board .platform-selector-line { + grid-area: platform; +} + +.xhs-area-template { grid-area: template; } +.xhs-area-preview { grid-area: preview; } +.xhs-area-refresh { grid-area: refresh; justify-content: center; } +.xhs-area-publish { grid-area: publish; justify-content: center; } +.xhs-area-font { grid-area: font; flex-wrap: wrap; } +.xhs-area-pagination { grid-area: pagination; justify-content: center; gap: 16px; } +.xhs-area-slice { grid-area: slice; justify-content: center; gap: 16px; } + +.xhs-area-content { + grid-area: content; + background: white; + border-radius: 12px; + padding: 0; + box-shadow: var(--shadow-sm); + display: flex; +} + +.xhs-page-container { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%); + border-radius: 12px; + min-height: 0; + position: relative; } -/* 小红书单页包裹器:为缩放后的页面预留正确的布局空间 */ .xhs-page-wrapper { - margin: 0px auto; + margin: 0 auto; position: relative; overflow: hidden; } -/* 小红书单页样式:实际尺寸 1080×1440,通过 scale 缩放到 540×720 */ .xhs-page { - /* 实际尺寸与缩放由代码在运行时设置 */ - transform-origin: top left; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); border-radius: 8px; @@ -502,87 +606,10 @@ label:hover { color: var(--c-primary); } height: auto; } -.xhs-top-toolbar { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, max-content)); - align-items: center; - gap: 12px; - padding: 8px 12px; - background: var(--grad-toolbar); - border-bottom: 1px solid #e8eaed; - box-shadow: var(--shadow-overlay); -} - -.toolbar-label { - font-size: 11px; - color: var(--c-text-muted); - font-weight: 500; - white-space: nowrap; -} - -.xhs-select { - padding: 4px 8px; - border: 1px solid var(--c-border); - border-radius: 4px; - background: white; - font-size: 11px; - cursor: pointer; - transition: border-color 0.2s ease; -} - -.xhs-select:hover { - border-color: var(--c-primary); -} - -.xhs-select:focus { - outline: none; - border-color: var(--c-primary); -} - -.font-size-group { +.xhs-pagination { display: grid; grid-auto-flow: column; align-items: center; - gap: 6px; - background: white; - border: 1px solid var(--c-border); - border-radius: 4px; - padding: 2px; -} - -.font-size-btn { - width: 24px; - height: 24px; - border: none; - background: transparent; - border-radius: 3px; - cursor: pointer; - font-size: 16px; - color: #5f6368; - transition: background 0.2s ease; -} - -.font-size-btn:hover { - background: #f1f3f4; -} - -.font-size-display { - min-width: 24px; - text-align: center; - font-size: 12px; - color: #202124; - font-weight: 500; -} - -.xhs-page-navigation { - display: grid; - grid-auto-flow: column; - justify-content: center; - align-items: center; - gap: 16px; - padding: 12px; - background: white; - border-bottom: 1px solid #e8eaed; } .xhs-nav-btn { @@ -638,17 +665,6 @@ label:hover { color: var(--c-primary); } user-select: none; } -.xhs-bottom-toolbar { - display: grid; - grid-auto-flow: column; - justify-content: center; - gap: 12px; - padding: 12px 16px; - background: var(--grad-toolbar-bottom); - border-top: 1px solid #e8eaed; - box-shadow: 0 -2px 4px rgba(0,0,0,0.04); -} - .xhs-slice-btn { padding: 8px 20px; background: var(--grad-primary); diff --git a/styles.css.bk b/styles.css.bk new file mode 100644 index 0000000..2a29bdd --- /dev/null +++ b/styles.css.bk @@ -0,0 +1,754 @@ +/* styles.css — 全局样式表,用于渲染及导出样式。 */ + +/* =========================================================== */ +/* UI 样式 */ +/* 共用样式与去重 */ +/* =========================================================== */ + +/* 主题变量统一常用色值/阴影/渐变 */ +:root { + --c-bg: #ffffff; + --c-border: #dadce0; + --c-text-muted: #5f6368; + --c-primary: #1e88e5; + --c-primary-dark: #1565c0; + --c-purple: #667eea; + --c-purple-dark: #764ba2; + --c-blue-2: #42a5f5; + + --shadow-sm: 0 1px 3px rgba(0,0,0,0.08); + --shadow-overlay: 0 2px 4px rgba(0,0,0,0.04); + --shadow-primary-2: 0 2px 6px rgba(30, 136, 229, 0.3); + --shadow-primary-4: 0 4px 8px rgba(30, 136, 229, 0.4); + --shadow-purple-2: 0 2px 6px rgba(102, 126, 234, 0.3); + --shadow-purple-4: 0 4px 8px rgba(102, 126, 234, 0.4); + + --grad-primary: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%); + --grad-purple: linear-gradient(135deg, var(--c-purple) 0%, var(--c-purple-dark) 100%); + --grad-blue: linear-gradient(135deg, var(--c-blue-2) 0%, var(--c-primary) 100%); + --grad-toolbar: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + --grad-toolbar-bottom: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + --grad-xhs-bg: linear-gradient(135deg, #f5f7fa 0%, #e8eaf6 100%); +} + +/* 通用按钮外观(不含背景与尺寸) */ +.copy-button, +.refresh-button, +.toolbar-button, +.msg-ok-btn, +.xhs-slice-btn { + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; + transition: all 0.2s ease; +} + +/* 通用按钮 hover 的位移效果(各自保留独立阴影) */ +.copy-button:hover, +.refresh-button:hover, +.toolbar-button:hover, +.msg-ok-btn:hover { + transform: translateY(-1px); +} + +/* 下拉选择的通用外观(各自保留尺寸差异) */ +.platform-select, +.wechat-select, +.style-select { + border: 1px solid var(--c-border); + border-radius: 6px; + background: white; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +/* 平台与公众号选择的相同 hover/focus 效果(style-select 单独增强) */ +.platform-select:hover, +.wechat-select:hover { border-color: var(--c-primary); } +.platform-select:focus, +.wechat-select:focus { outline: none; border-color: var(--c-primary); } + +.note-preview { + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: 1fr; + min-height: 100%; + width: 100%; + height: 100%; + background-color: var(--c-bg); +} + +/* 预览内部平台容器需要可伸缩: */ +.wechat-preview-container:not([style*="display: none"]), +.xiaohongshu-preview-container:not([style*="display: none"]) { + flex: 1; + display: grid !important; + grid-template-rows: auto 1fr; + min-height: 0; /* 允许内部滚动区域正确计算高度 */ +} + +.render-div { + overflow-y: auto; + padding: 10px; + -webkit-user-select: text; + user-select: text; + min-height: 0; +} + +/* 文章包裹:模拟公众号编辑器阅读宽度 */ +.wechat-article-wrapper { + max-width: 720px; + margin: 0 auto; + padding: 12px 18px 80px 18px; /* 底部留白方便滚动到底部操作 */ + box-sizing: border-box; +} + +/* 若内部 section.note-to-mp 主题没有撑开,确保文本可见基色 */ +.wechat-article-wrapper .note-to-mp { + background: transparent; +} + +.preview-toolbar { + position: relative; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, max-content)); + gap: 12px; + align-items: center; + min-height: auto; + padding: 8px 12px; + border-bottom: 1px solid #e8eaed; + background: var(--grad-toolbar); + box-shadow: var(--shadow-overlay); +} + +.copy-button { + margin-right: 10px; + padding: 6px 14px; + background: var(--grad-primary); + color: white; + font-size: 13px; + box-shadow: var(--shadow-primary-2); +} + +.copy-button:hover { box-shadow: var(--shadow-primary-4); } + +.refresh-button { + margin-right: 10px; + padding: 6px 14px; + background: var(--grad-purple); + color: white; + font-size: 13px; + box-shadow: var(--shadow-purple-2); +} + +.refresh-button:hover { box-shadow: var(--shadow-purple-4); } + +.upload-input { + margin-left: 10px; + padding: 6px 10px; + border: 1px solid var(--c-border); + border-radius: 6px; + font-size: 13px; + transition: all 0.2s ease; +} + +.upload-input[type="file"] { + cursor: pointer; +} + +.upload-input:focus, +.style-select:focus { + outline: none; + border-color: var(--c-primary); + box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1); +} + +/* 单选按钮样式 */ +.input-style[type="radio"] { + width: 16px; + height: 16px; + margin: 0 6px 0 0; + cursor: pointer; + accent-color: var(--c-primary); +} + +/* Label 标签样式 */ +label { + font-size: 13px; + color: var(--c-text-muted); + cursor: pointer; + user-select: none; + transition: color 0.2s ease; +} + +label:hover { color: var(--c-primary); } + +.style-label { + margin-right: 10px; + font-size: 13px; + color: var(--c-text-muted); + font-weight: 500; + white-space: nowrap; +} + +.style-select { + margin-right: 10px; + width: 120px; + padding: 6px 10px; +} + +.style-select:hover { + border-color: var(--c-primary); + box-shadow: 0 2px 6px rgba(30, 136, 229, 0.2); +} + +/* focus 规则见与 .upload-input:focus 的组合声明 */ + +.msg-view { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--background-primary); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 18px; + z-index: 9999; + display: none; +} + +.msg-title { + margin-bottom: 20px; + max-width: 90%; +} + +.msg-ok-btn { + padding: 10px 24px; + margin: 0 8px; + background: var(--grad-primary); + color: white; + font-size: 14px; + box-shadow: var(--shadow-primary-2); + min-width: 80px; +} + +.msg-ok-btn:hover { box-shadow: var(--shadow-primary-4); } + +.msg-ok-btn:active { + transform: translateY(0); +} + +.note-mpcard-wrapper { + margin: 20px 20px; + background-color: rgb(250, 250, 250); + padding: 10px 20px; + border-radius: 10px; +} +.note-mpcard-content { + display: grid; + grid-auto-flow: column; + align-items: center; +} +.note-mpcard-headimg { + border: none !important; + border-radius: 27px !important; + box-shadow: none !important; + width: 54px !important; + height: 54px !important; + margin: 0 !important; +} +.note-mpcard-info { + margin-left: 10px; +} +.note-mpcard-nickname { + font-size: 17px; + font-weight: 500; + color: rgba(0, 0, 0, 0.9); +} + +.note-mpcard-signature { + font-size: 14px; + color: rgba(0, 0, 0, 0.55); +} +.note-mpcard-foot { + margin-top: 20px; + padding-top: 10px; + border-top: 1px solid #ececec; + font-size: 14px; + color: rgba(0, 0, 0, 0.3); +} + +.loading-wrapper { + display: grid; + width: 100%; + height: 100%; + place-items: center; +} + +.loading-spinner { + width: 50px; /* 可调整大小 */ + height: 50px; + border: 4px solid #fcd6ff; /* 底色,浅灰 */ + border-top: 4px solid #bb0cdf; /* 主色,蓝色顶部产生旋转感 */ + border-radius: 50%; /* 圆形 */ + animation: spin 1s linear infinite; /* 旋转动画 */ +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* =========================================================== */ +/* Toolbar 行样式 */ +/* =========================================================== */ + +.toolbar-line { + display: grid; + grid-auto-flow: column; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: white; + border-radius: 6px; + margin: 8px 10px; + box-shadow: var(--shadow-sm); +} + +.toolbar-line.flex-wrap { + grid-auto-flow: row; + grid-template-columns: repeat(auto-fit, minmax(160px, max-content)); +} + +.platform-selector-line { + background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%) !important; + border-left: 4px solid var(--c-primary); +} + +/* 平台选择容器:单层 Grid 排列 */ +.platform-chooser-container.platform-chooser-grid { + display: grid; + grid-auto-flow: column; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: white; /* 被 .platform-selector-line 的背景覆写 */ + border-radius: 6px; + margin: 8px 10px; + box-shadow: var(--shadow-sm); +} + +/* =========================================================== */ +/* 平台选择器样式 */ +/* =========================================================== */ + +.platform-select { + padding: 6px 12px; + min-width: 150px; + font-weight: 500; +} + +/* =========================================================== */ +/* 微信公众号选择器样式 */ +/* =========================================================== */ + +.wechat-select { + padding: 6px 12px; + min-width: 200px; +} + +/* =========================================================== */ +/* 按钮样式 */ +/* =========================================================== */ + +.toolbar-button { + padding: 6px 14px; + background: var(--grad-primary); + color: white; + font-size: 13px; + box-shadow: var(--shadow-primary-2); +} + +.toolbar-button:hover { box-shadow: var(--shadow-primary-4); } + +.toolbar-button.purple-gradient { + background: var(--grad-purple); + box-shadow: var(--shadow-purple-2); +} + +.toolbar-button.purple-gradient:hover { box-shadow: var(--shadow-purple-4); } + +/* =========================================================== */ +/* 分隔线样式 */ +/* =========================================================== */ + +.toolbar-separator { + width: 1px; + height: 24px; + background: var(--c-border); + margin: 0 4px; +} + +/* =========================================================== */ +/* Doc Modal 样式 */ +/* =========================================================== */ + +.doc-modal { + width: 640px; + height: 720px; +} + +.doc-modal-content { + display: grid; + grid-template-rows: auto auto 1fr; + row-gap: 8px; + min-height: 0; +} + +.doc-modal-title { + margin-top: 0.5em; +} + +.doc-modal-desc { + margin-bottom: 1em; + -webkit-user-select: text; + user-select: text; +} + +.doc-modal-iframe { + min-height: 0; +} + +/* =========================================================== */ +/* Setting Tab 帮助文档样式 */ +/* =========================================================== */ + +.setting-help-section { + display: grid; + grid-auto-flow: column; + align-items: center; + column-gap: 10px; +} + +.setting-help-title { + margin-right: 10px; +} + +/* =========================================================== */ +/* Xiaohongshu WebView 样式 */ +/* =========================================================== */ + +.xhs-webview { + display: none; + width: 1200px; + height: 800px; +} + +/* =========================================================== */ +/* Xiaohongshu Preview View 样式 */ +/* =========================================================== */ + +.xiaohongshu-preview-container { + width: 100%; + height: 100%; +} + +.xhs-preview-container:not([style*="display: none"]) { + display: grid !important; + grid-template-rows: auto 1fr auto; + height: 100%; + background: var(--grad-xhs-bg); + min-height: 0; +} + +.xhs-page-container { + overflow-y: auto; + overflow-x: hidden; + display: grid; + align-content: start; + justify-content: center; + padding: 0px; + background: radial-gradient(ellipse at top, rgba(255,255,255,0.1) 0%, transparent 70%); + min-height: 0; /* 允许子项正确收缩和滚动 */ +} + +/* 小红书单页包裹器:为缩放后的页面预留正确的布局空间 */ +.xhs-page-wrapper { + margin: 0px auto; + position: relative; + overflow: hidden; +} + +/* 小红书单页样式:实际尺寸 1080×1440,通过 scale 缩放到 540×720 */ +.xhs-page { + /* 实际尺寸与缩放由代码在运行时设置 */ + transform-origin: top left; + background: white; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + border-radius: 8px; +} + +.xhs-page img { + max-width: 100%; + height: auto; +} + +.xhs-top-toolbar { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, max-content)); + align-items: center; + gap: 12px; + padding: 8px 12px; + background: var(--grad-toolbar); + border-bottom: 1px solid #e8eaed; + box-shadow: var(--shadow-overlay); +} + +.toolbar-label { + font-size: 11px; + color: var(--c-text-muted); + font-weight: 500; + white-space: nowrap; +} + +.xhs-select { + padding: 4px 8px; + border: 1px solid var(--c-border); + border-radius: 4px; + background: white; + font-size: 11px; + cursor: pointer; + transition: border-color 0.2s ease; +} + +.xhs-select:hover { + border-color: var(--c-primary); +} + +.xhs-select:focus { + outline: none; + border-color: var(--c-primary); +} + +.font-size-group { + display: grid; + grid-auto-flow: column; + align-items: center; + gap: 6px; + background: white; + border: 1px solid var(--c-border); + border-radius: 4px; + padding: 2px; +} + +.font-size-btn { + width: 24px; + height: 24px; + border: none; + background: transparent; + border-radius: 3px; + cursor: pointer; + font-size: 16px; + color: #5f6368; + transition: background 0.2s ease; +} + +.font-size-btn:hover { + background: #f1f3f4; +} + +.font-size-display { + min-width: 24px; + text-align: center; + font-size: 12px; + color: #202124; + font-weight: 500; +} + +.xhs-page-navigation { + display: grid; + grid-auto-flow: column; + justify-content: center; + align-items: center; + gap: 16px; + padding: 12px; + background: white; + border-bottom: 1px solid #e8eaed; +} + +.xhs-nav-btn { + width: 36px; + height: 36px; + border: 1px solid var(--c-border); + border-radius: 50%; + cursor: pointer; + font-size: 20px; + background: white; + color: #5f6368; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.xhs-nav-btn:hover { + background: var(--grad-primary); + color: white; + border-color: var(--c-primary); +} + +/* 页码指示器:输入框 + 总页数标记 */ +.xhs-page-indicator { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 500; + color: #202124; +} + +/* 可编辑页码输入框 */ +.xhs-page-number-input { + width: 56px; + padding: 4px 6px; + text-align: center; + border: 1px solid var(--c-border); + border-radius: 6px; + background: white; + color: inherit; + font: inherit; + box-shadow: inset 0 1px 2px rgba(0,0,0,0.08); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.xhs-page-number-input:focus { + outline: none; + border-color: var(--c-primary); + box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.15); +} + +/* 总页数显示(只读) */ +.xhs-page-number-total { + font-size: 14px; + color: #5f6368; + user-select: none; +} + +.xhs-bottom-toolbar { + display: grid; + grid-auto-flow: column; + justify-content: center; + gap: 12px; + padding: 12px 16px; + background: var(--grad-toolbar-bottom); + border-top: 1px solid #e8eaed; + box-shadow: 0 -2px 4px rgba(0,0,0,0.04); +} + +.xhs-slice-btn { + padding: 8px 20px; + background: var(--grad-primary); + color: white; + font-size: 13px; + box-shadow: var(--shadow-primary-2); +} + +.xhs-slice-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(30, 136, 229, 0.4); +} + +.xhs-slice-btn.secondary { + background: var(--grad-blue); + box-shadow: 0 2px 6px rgba(66, 165, 245, 0.3); +} + +.xhs-slice-btn.secondary:hover { + box-shadow: 0 4px 12px rgba(66, 165, 245, 0.4); +} + +/* =========================================================== */ +/* Xiaohongshu Login Modal 样式 */ +/* =========================================================== */ + +.xiaohongshu-login-modal { + width: 400px; + padding: 20px; +} + +.xhs-login-title { + text-align: center; + margin-bottom: 20px; + color: #ff4757; +} + +.xhs-login-desc { + text-align: center; + color: #666; + margin-bottom: 30px; +} + +.xhs-code-container { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 10px; + margin-bottom: 20px; +} + +.xhs-code-label { + min-width: 80px; +} + +.xhs-code-input-wrapper { + flex: 1; +} + +.xhs-input-full { + width: 100%; + font-size: 16px; +} + +.xhs-send-code-btn { + min-width: 120px; + margin-left: 10px; +} + +.xhs-status-message { + min-height: 30px; + margin-bottom: 20px; + text-align: center; + font-size: 14px; +} + +.xhs-status-message.success { + color: #27ae60; +} + +.xhs-status-message.error { + color: #e74c3c; +} + +.xhs-status-message.info { + color: #3498db; +} + +.xhs-button-container { + display: grid; + grid-auto-flow: column; + justify-content: center; + gap: 15px; + margin-top: 20px; +} + +.xhs-login-btn { + min-width: 100px; +} diff --git a/t2.html b/t2.html new file mode 100644 index 0000000..857f471 --- /dev/null +++ b/t2.html @@ -0,0 +1,170 @@ + + + + + + CSS Grid 5列布局示例 + + + +
+

Grid 5列布局 Demo

+ +
+
+ 发布平台 + +
+
+
+ +
+ 模版 + +
+
+ 预览宽度 + +
+
调节文字大小组件
+ +
小红书预览区域
+ +
页面选择组件
+
+
+
+
+ + diff --git a/todolist.md b/todolist.md index 79f057e..e0b494c 100644 --- a/todolist.md +++ b/todolist.md @@ -1,19 +1,19 @@ -# todo list +# todo list & recording ## 功能 1. 实现markdown预览页面切图功能,预览页面是以完成渲染的页面,生成一整张长图。再按文章顺序裁剪为图片(png格式)。(v1.3.1) - 长图宽度为1080,可配置。切图图片横竖比例3:4,图片宽度保持与长图相同。 - ✅ +✅ - 横竖比例和图片宽像素可配置。 - ✅ +✅ - 标题取frontmatter的title属性。 - ✅ +✅ - 图片保存路径可配置,默认为/Users/gavin/note2mp/images/xhs。 - ✅ +✅ - 图片名取frontmatter的slug属性,如: slug: mmm,文章长图命名为mmm.png,如切为3张图片,则切图图片名按顺序依次为mmm_1.png,mmm_2.png,mmm_3.png - ✅ +✅ - 文章预览中增加“切图”按钮,点击执行预览文章的切图操作。 - ✅ +✅ 2. 说明/修改/增加: - 发布平台选"小红书"时,保留“刷新”,“发布到小红书”,去掉“切图”按钮,用"当前页切图"和"全部页切图"替代切图按钮和功能。 @@ -59,11 +59,15 @@ ## 问题 1. "发布平台"首次选“小红书”时,预览页面没有加载当前文章。 ✅ + 2. 顶部按钮适应窗口宽度,超出窗口,折行显示。 ✅ -3. 小红书模式,html分页预览不是从顶部开始显示,显示不完整。 - 小红书模式,预览窗口似乎只显示了一部分?上面部分被挡住了吗? - 参考微信公众号模式下的预览窗口,不同点在于小红书模式下,每页的宽高比按配置要求。 + +3. 小红书模式问题: + - html分页预览不是从顶部开始显示,显示不完整。 + - 预览窗口似乎只显示了一部分?上面部分被挡住了吗? + - 参考微信公众号模式下的预览窗口,不同点在于小红书模式下,每页的宽高比按配置要求。 +✅ 4. 修改: - 公共部分独立出来,如“发布平台”,放在新建platform-choose.ts中,“发布平台”选择切换平台逻辑放在该模块中,便于以后其他平台扩展。 @@ -103,17 +107,22 @@ SOLVE:obsidian控制台打印信息,定位在哪里阻塞,AI修复。 问题: - 字变大时,一页的内容放不下,重新分页应该会增加页数。但现在重新分页当前页放不下的内容只是被剪掉了。 - 表格显示不完整。 +✅ 9. styles.css中有很多冗余。改为grid布局。部分完成,这部分需要后面**手动调整**重构。🧶 ♻️ ❇️ 问题:小红书预览布局有问题❓ + 小红书布局改为grid,但平台选择器部分没有完成修改。 + 保持微信和小红书页面一致性,都用grid重构,复用css样式代码。 + 10. 新建docs文件夹,把除了README和todolist以外的markdown文件放到docs中。 ✅ - ## 经验 -1. 在不确定AI是否理解,或者需求是否准确的情况下,先用chat模式提问,看回答确定AI理解是否准确。 - 尤其对于较大规模的重构需求,这点很重要‼️ 。 - +1. 在不确定AI是否理解,或者需求是否准确的情况下,先用codex chat模式提问,看回答确定AI理解是否准确。 + 尤其对于较大规模的重构需求,这点很重要 ‼️ 。 +2. 复杂页面,codex生成的css可能无比复杂,不便于维护修改。 + 自己写布局demo原型,让codex参考布局修改(原来元素美化的css可保留)。 + demo原型可以手绘后,拍照让chatgpt生成,在此基础上自己修改。