update at 2025-10-21 21:47:02
This commit is contained in:
@@ -38,7 +38,7 @@ export class XiaohongshuPreview {
|
||||
fontSizeInput!: HTMLInputElement;
|
||||
previewWidthSelect!: HTMLSelectElement;
|
||||
themeSelect!: HTMLSelectElement;
|
||||
highlightSelect!: HTMLSelectElement;
|
||||
platformSelect: HTMLSelectElement | null = null;
|
||||
|
||||
pageContainer!: HTMLDivElement;
|
||||
pageNumberInput!: HTMLInputElement;
|
||||
@@ -69,7 +69,8 @@ export class XiaohongshuPreview {
|
||||
*/
|
||||
build(): void {
|
||||
this.container.empty();
|
||||
this.container.addClass('xhs-preview-container');
|
||||
this.container.addClass('note2any-platform-container');
|
||||
|
||||
// 准备样式挂载节点
|
||||
if (!this.styleEl) {
|
||||
this.styleEl = document.createElement('style');
|
||||
@@ -79,152 +80,299 @@ export class XiaohongshuPreview {
|
||||
this.container.appendChild(this.styleEl);
|
||||
}
|
||||
|
||||
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 header = this.container.createDiv({ cls: 'note2any-platform-header' });
|
||||
|
||||
// 平台选择器区域
|
||||
const platformSelector = header.createDiv({ cls: 'note2any-platform-selector' });
|
||||
const platformLabel = platformSelector.createDiv({
|
||||
cls: 'note2any-platform-label',
|
||||
text: '发布平台'
|
||||
});
|
||||
|
||||
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');
|
||||
option.value = String(value);
|
||||
option.text = `${value}px`;
|
||||
});
|
||||
if (!XHS_PREVIEW_WIDTH_OPTIONS.includes(currentPreviewWidth)) {
|
||||
const customOption = this.previewWidthSelect.createEl('option');
|
||||
customOption.value = String(currentPreviewWidth);
|
||||
customOption.text = `${currentPreviewWidth}px`;
|
||||
}
|
||||
this.previewWidthSelect.value = String(currentPreviewWidth);
|
||||
this.previewWidthSelect.onchange = async () => {
|
||||
const value = parseInt(this.previewWidthSelect.value, 10);
|
||||
if (Number.isFinite(value) && value > 0) {
|
||||
await this.onPreviewWidthChanged(value);
|
||||
} else {
|
||||
this.previewWidthSelect.value = String(this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH);
|
||||
|
||||
// 使用真实的 select 元素,直接应用样式
|
||||
this.platformSelect = platformSelector.createEl('select', { cls: 'note2any-select note2any-platform-select' }) as HTMLSelectElement;
|
||||
|
||||
const wechatOption = this.platformSelect.createEl('option');
|
||||
wechatOption.value = 'wechat';
|
||||
wechatOption.text = '📱 公众号';
|
||||
|
||||
const xhsOption = this.platformSelect.createEl('option');
|
||||
xhsOption.value = 'xiaohongshu';
|
||||
xhsOption.text = '📔 小红书';
|
||||
|
||||
// 设置默认选中为小红书
|
||||
this.platformSelect.value = 'xiaohongshu';
|
||||
|
||||
this.platformSelect.onchange = () => {
|
||||
if (this.platformSelect && this.platformSelect.value === 'wechat' && this.onPlatformChangeCallback) {
|
||||
this.onPlatformChangeCallback('wechat');
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
this.fontSizeInput = fontSizeGroup.createEl('input', {
|
||||
cls: 'font-size-input',
|
||||
attr: {
|
||||
type: 'number',
|
||||
min: String(XHS_FONT_SIZE_MIN),
|
||||
max: String(XHS_FONT_SIZE_MAX),
|
||||
value: String(XHS_FONT_SIZE_DEFAULT)
|
||||
}
|
||||
// 按钮组
|
||||
const buttonGroup = header.createDiv({ cls: 'note2any-button-group' });
|
||||
const refreshBtn = buttonGroup.createEl('button', {
|
||||
text: '刷新',
|
||||
cls: 'note2any-button'
|
||||
});
|
||||
this.fontSizeInput.onchange = () => this.onFontSizeInputChanged();
|
||||
refreshBtn.onclick = () => this.refresh();
|
||||
|
||||
const increaseBtn = fontSizeGroup.createEl('button', { text: '+', cls: 'font-size-btn' });
|
||||
increaseBtn.onclick = () => this.changeFontSize(1);
|
||||
|
||||
// 主题选择器
|
||||
const themeCard = this.createGridCard(board, 'xhs-area-theme');
|
||||
const themeLabel = themeCard.createDiv({ cls: 'xhs-label', text: '主题' });
|
||||
this.themeSelect = themeCard.createEl('select', { cls: 'xhs-select' });
|
||||
const publishBtn = buttonGroup.createEl('button', {
|
||||
text: '发布',
|
||||
cls: 'note2any-button'
|
||||
});
|
||||
publishBtn.onclick = () => this.publish();
|
||||
|
||||
const accessBtn = buttonGroup.createEl('button', {
|
||||
text: '访问',
|
||||
cls: 'note2any-button'
|
||||
});
|
||||
accessBtn.onclick = () => {
|
||||
// TODO: 实现小红书访问逻辑
|
||||
new Notice('小红书访问功能待实现');
|
||||
};
|
||||
|
||||
// 控制栏 (账号、主题、宽度)
|
||||
const controlsRow = this.container.createDiv({ cls: 'note2any-controls-row' });
|
||||
|
||||
// 账号字段
|
||||
const accountField = controlsRow.createDiv({ cls: 'note2any-field note2any-field-account' });
|
||||
accountField.createDiv({ cls: 'note2any-field-label', text: '账号' });
|
||||
|
||||
const accountSelect = accountField.createEl('select', { cls: 'note2any-select' });
|
||||
const accountOption = accountSelect.createEl('option');
|
||||
accountOption.value = 'default';
|
||||
accountOption.text = 'Value';
|
||||
|
||||
// 主题字段
|
||||
const themeField = controlsRow.createDiv({ cls: 'note2any-field note2any-field-theme' });
|
||||
themeField.createDiv({ cls: 'note2any-field-label', text: '主题' });
|
||||
|
||||
this.themeSelect = themeField.createEl('select', { cls: 'note2any-select' }) as HTMLSelectElement;
|
||||
this.themeSelect.onchange = async () => {
|
||||
// 更新全局设置
|
||||
this.settings.defaultStyle = this.themeSelect.value;
|
||||
this.applyThemeCSS();
|
||||
await this.repaginateAndRender();
|
||||
// 保存设置
|
||||
const plugin = (this.app as any)?.plugins?.getPlugin?.('note2any');
|
||||
if (plugin?.saveSettings) {
|
||||
await plugin.saveSettings();
|
||||
}
|
||||
};
|
||||
|
||||
// 填充主题选项
|
||||
|
||||
for (let theme of this.assetsManager.themes) {
|
||||
const option = this.themeSelect.createEl('option');
|
||||
option.value = theme.className;
|
||||
option.text = theme.name;
|
||||
option.selected = theme.className === this.settings.defaultStyle;
|
||||
}
|
||||
|
||||
// 高亮选择器
|
||||
const highlightCard = this.createGridCard(board, 'xhs-area-highlight');
|
||||
const highlightLabel = highlightCard.createDiv({ cls: 'xhs-label', text: '代码高亮' });
|
||||
this.highlightSelect = highlightCard.createEl('select', { cls: 'xhs-select' });
|
||||
this.highlightSelect.onchange = async () => {
|
||||
// 更新全局设置
|
||||
this.settings.defaultHighlight = this.highlightSelect.value;
|
||||
this.applyThemeCSS();
|
||||
|
||||
// 宽度字段
|
||||
const widthField = controlsRow.createDiv({ cls: 'note2any-field note2any-field-width' });
|
||||
widthField.createDiv({ cls: 'note2any-field-label', text: '宽度' });
|
||||
|
||||
const currentPreviewWidth = this.settings.xhsPreviewWidth || XHS_PREVIEW_DEFAULT_WIDTH;
|
||||
this.previewWidthSelect = widthField.createEl('select', {
|
||||
cls: 'note2any-select'
|
||||
}) as HTMLSelectElement;
|
||||
|
||||
// 添加宽度选项
|
||||
const widthOptions = [360, 540, 720];
|
||||
for (let width of widthOptions) {
|
||||
const option = this.previewWidthSelect.createEl('option');
|
||||
option.value = String(width);
|
||||
option.text = `${width}px`;
|
||||
option.selected = width === currentPreviewWidth;
|
||||
}
|
||||
|
||||
this.previewWidthSelect.onchange = async () => {
|
||||
const newWidth = parseInt(this.previewWidthSelect.value);
|
||||
this.settings.xhsPreviewWidth = newWidth;
|
||||
await this.repaginateAndRender();
|
||||
// 保存设置
|
||||
const plugin = (this.app as any)?.plugins?.getPlugin?.('note2any');
|
||||
if (plugin?.saveSettings) {
|
||||
await plugin.saveSettings();
|
||||
}
|
||||
};
|
||||
|
||||
// 填充高亮选项
|
||||
for (let highlight of this.assetsManager.highlights) {
|
||||
const option = this.highlightSelect.createEl('option');
|
||||
option.value = highlight.url;
|
||||
option.text = highlight.name;
|
||||
option.selected = highlight.url === this.settings.defaultHighlight;
|
||||
}
|
||||
|
||||
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 = 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': '当前页码' }
|
||||
}) as HTMLInputElement;
|
||||
this.pageNumberInput.onfocus = () => this.pageNumberInput.select();
|
||||
this.pageNumberInput.onkeydown = (evt: KeyboardEvent) => {
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
this.handlePageNumberInput();
|
||||
}
|
||||
};
|
||||
this.pageNumberInput.oninput = () => {
|
||||
const sanitized = this.pageNumberInput.value.replace(/\D/g, '');
|
||||
if (sanitized !== this.pageNumberInput.value) {
|
||||
this.pageNumberInput.value = sanitized;
|
||||
}
|
||||
};
|
||||
this.pageNumberInput.onblur = () => this.handlePageNumberInput();
|
||||
|
||||
this.pageTotalLabel = indicator.createEl('span', { cls: 'xhs-page-number-total', text: '/1' });
|
||||
|
||||
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' });
|
||||
|
||||
// 内容区域
|
||||
this.pageContainer = this.container.createDiv({ cls: 'note2any-content-area' });
|
||||
|
||||
// 底部工具栏
|
||||
const bottomToolbar = this.container.createDiv({ cls: 'note2any-bottom-toolbar' });
|
||||
|
||||
// 当前图按钮
|
||||
const sliceCurrentBtn = bottomToolbar.createEl('button', {
|
||||
text: '当前图',
|
||||
cls: 'note2any-slice-button'
|
||||
});
|
||||
sliceCurrentBtn.onclick = () => this.sliceCurrentPage();
|
||||
|
||||
const sliceAllBtn = sliceCard.createEl('button', { text: '全部页切图', cls: 'xhs-slice-btn secondary' });
|
||||
// 字体大小控制
|
||||
const fontSizeControl = bottomToolbar.createDiv({ cls: 'note2any-fontsize-control' });
|
||||
|
||||
// 字体大小下拉选择器
|
||||
const fontSizeSelectWrapper = fontSizeControl.createDiv({ cls: 'note2any-fontsize-select-wrapper' });
|
||||
const fontSizeSelect = fontSizeSelectWrapper.createEl('select', {
|
||||
cls: 'note2any-fontsize-select'
|
||||
}) as HTMLSelectElement;
|
||||
|
||||
// 添加字体大小选项 (30-40)
|
||||
for (let size = XHS_FONT_SIZE_MIN; size <= XHS_FONT_SIZE_MAX; size++) {
|
||||
const option = fontSizeSelect.createEl('option');
|
||||
option.value = String(size);
|
||||
option.text = String(size);
|
||||
option.selected = size === XHS_FONT_SIZE_DEFAULT;
|
||||
}
|
||||
|
||||
fontSizeSelect.onchange = async () => {
|
||||
this.currentFontSize = parseInt(fontSizeSelect.value);
|
||||
this.fontSizeInput.value = String(this.currentFontSize);
|
||||
await this.repaginateAndRender();
|
||||
};
|
||||
|
||||
const stepper = fontSizeControl.createDiv({ cls: 'note2any-stepper' });
|
||||
const decreaseBtn = stepper.createEl('button', {
|
||||
text: '−',
|
||||
cls: 'note2any-stepper-button'
|
||||
});
|
||||
decreaseBtn.onclick = async () => {
|
||||
if (this.currentFontSize > XHS_FONT_SIZE_MIN) {
|
||||
this.currentFontSize--;
|
||||
fontSizeSelect.value = String(this.currentFontSize);
|
||||
this.fontSizeInput.value = String(this.currentFontSize);
|
||||
await this.repaginateAndRender();
|
||||
}
|
||||
};
|
||||
|
||||
stepper.createDiv({ cls: 'note2any-stepper-separator' });
|
||||
|
||||
const increaseBtn = stepper.createEl('button', {
|
||||
text: '+',
|
||||
cls: 'note2any-stepper-button'
|
||||
});
|
||||
increaseBtn.onclick = async () => {
|
||||
if (this.currentFontSize < XHS_FONT_SIZE_MAX) {
|
||||
this.currentFontSize++;
|
||||
fontSizeSelect.value = String(this.currentFontSize);
|
||||
this.fontSizeInput.value = String(this.currentFontSize);
|
||||
await this.repaginateAndRender();
|
||||
}
|
||||
};
|
||||
|
||||
// 隐藏的字体输入框 (保留以兼容现有逻辑)
|
||||
this.fontSizeInput = fontSizeControl.createEl('input', {
|
||||
attr: {
|
||||
type: 'number',
|
||||
min: String(XHS_FONT_SIZE_MIN),
|
||||
max: String(XHS_FONT_SIZE_MAX),
|
||||
value: String(XHS_FONT_SIZE_DEFAULT),
|
||||
style: 'display: none;'
|
||||
}
|
||||
});
|
||||
this.fontSizeInput.onchange = () => {
|
||||
this.currentFontSize = parseInt(this.fontSizeInput.value);
|
||||
fontSizeSelect.value = String(this.currentFontSize);
|
||||
};
|
||||
|
||||
// 分页控制
|
||||
const pagination = bottomToolbar.createDiv({ cls: 'note2any-pagination' });
|
||||
|
||||
pagination.createDiv({ cls: 'note2any-pagination-separator' });
|
||||
|
||||
const prevBtn = pagination.createEl('button', {
|
||||
cls: 'note2any-pagination-button',
|
||||
attr: { 'aria-label': '上一页' }
|
||||
});
|
||||
prevBtn.innerHTML = '<svg width="32" height="32" viewBox="0 0 32 32"><path d="M20 8 L12 16 L20 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
prevBtn.onclick = () => this.previousPage();
|
||||
|
||||
const pageCurrent = pagination.createEl('input', {
|
||||
cls: 'note2any-pagination-current',
|
||||
type: 'text',
|
||||
value: '1',
|
||||
attr: {
|
||||
'inputmode': 'numeric',
|
||||
'pattern': '[0-9]*'
|
||||
}
|
||||
});
|
||||
|
||||
// 处理页码输入 - 回车跳转
|
||||
pageCurrent.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
const targetPage = parseInt(pageCurrent.value);
|
||||
if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= this.pages.length) {
|
||||
this.currentPageIndex = targetPage - 1;
|
||||
this.renderCurrentPage();
|
||||
} else {
|
||||
// 恢复当前页码
|
||||
pageCurrent.value = String(this.currentPageIndex + 1);
|
||||
}
|
||||
pageCurrent.blur();
|
||||
}
|
||||
});
|
||||
|
||||
// 失焦时跳转
|
||||
pageCurrent.addEventListener('blur', () => {
|
||||
const targetPage = parseInt(pageCurrent.value);
|
||||
if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= this.pages.length) {
|
||||
this.currentPageIndex = targetPage - 1;
|
||||
this.renderCurrentPage();
|
||||
} else {
|
||||
// 恢复当前页码
|
||||
pageCurrent.value = String(this.currentPageIndex + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// 聚焦时全选文本,方便输入
|
||||
pageCurrent.addEventListener('focus', () => {
|
||||
pageCurrent.select();
|
||||
});
|
||||
|
||||
// 存储引用以便在其他地方更新显示
|
||||
(this as any).pageCurrentDisplay = pageCurrent;
|
||||
|
||||
pagination.createDiv({
|
||||
cls: 'note2any-pagination-separator-text',
|
||||
text: '/'
|
||||
});
|
||||
|
||||
this.pageTotalLabel = pagination.createEl('span', {
|
||||
cls: 'note2any-pagination-total',
|
||||
text: '68'
|
||||
});
|
||||
|
||||
const nextBtn = pagination.createEl('button', {
|
||||
cls: 'note2any-pagination-button',
|
||||
attr: { 'aria-label': '下一页' }
|
||||
});
|
||||
nextBtn.innerHTML = '<svg width="32" height="32" viewBox="0 0 32 32"><path d="M12 8 L20 16 L12 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
nextBtn.onclick = () => this.nextPage();
|
||||
|
||||
// 将可见的页码输入框设为主输入框
|
||||
this.pageNumberInput = pageCurrent as HTMLInputElement;
|
||||
|
||||
// 存储显示元素引用以便更新
|
||||
(this as any).pageCurrentDisplay = pageCurrent;
|
||||
|
||||
// 全部图按钮
|
||||
const sliceAllBtn = bottomToolbar.createEl('button', {
|
||||
text: '全部图',
|
||||
cls: 'note2any-slice-button'
|
||||
});
|
||||
sliceAllBtn.onclick = () => this.sliceAllPages();
|
||||
|
||||
// 模板选择器 (隐藏,保留以兼容)
|
||||
this.templateSelect = this.container.createEl('select', {
|
||||
attr: { style: 'display: none;' }
|
||||
});
|
||||
['默认模板', '简约模板', '杂志模板'].forEach(name => {
|
||||
const option = this.templateSelect.createEl('option');
|
||||
option.value = name;
|
||||
option.text = name;
|
||||
});
|
||||
}
|
||||
|
||||
private createGridCard(parent: HTMLElement, areaClass: string): HTMLDivElement {
|
||||
return parent.createDiv({ cls: `xhs-card ${areaClass}` });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染文章内容并分页
|
||||
@@ -291,14 +439,18 @@ export class XiaohongshuPreview {
|
||||
private updatePageNumberDisplay(): void {
|
||||
if (!this.pageNumberInput || !this.pageTotalLabel) return;
|
||||
const total = this.pages.length;
|
||||
const pageCurrentDisplay = (this as any).pageCurrentDisplay;
|
||||
|
||||
if (total === 0) {
|
||||
this.pageNumberInput.value = '0';
|
||||
this.pageTotalLabel.innerText = '/0';
|
||||
this.pageTotalLabel.innerText = '0';
|
||||
if (pageCurrentDisplay) pageCurrentDisplay.textContent = '0';
|
||||
return;
|
||||
}
|
||||
const current = Math.min(this.currentPageIndex + 1, total);
|
||||
this.pageNumberInput.value = String(current);
|
||||
this.pageTotalLabel.innerText = `/${total}`;
|
||||
this.pageTotalLabel.innerText = String(total);
|
||||
if (pageCurrentDisplay) pageCurrentDisplay.textContent = String(current);
|
||||
}
|
||||
|
||||
private handlePageNumberInput(): void {
|
||||
@@ -347,10 +499,11 @@ 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.transformOrigin = 'center center';
|
||||
pageElement.style.position = 'absolute';
|
||||
pageElement.style.top = '0';
|
||||
pageElement.style.left = '0';
|
||||
pageElement.style.top = '50%';
|
||||
pageElement.style.left = '50%';
|
||||
pageElement.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
|
||||
private async onPreviewWidthChanged(newWidth: number): Promise<void> {
|
||||
@@ -526,6 +679,10 @@ export class XiaohongshuPreview {
|
||||
if (this.container) {
|
||||
this.container.style.display = 'flex';
|
||||
}
|
||||
// 确保平台选择器显示正确的选项
|
||||
if (this.platformSelect) {
|
||||
this.platformSelect.value = 'xiaohongshu';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,9 +762,7 @@ export class XiaohongshuPreview {
|
||||
if (this.themeSelect) {
|
||||
this.themeSelect.value = theme;
|
||||
}
|
||||
if (this.highlightSelect) {
|
||||
this.highlightSelect.value = highlight;
|
||||
}
|
||||
// 高亮已移除,保留此方法以兼容外部调用
|
||||
this.applyThemeCSS();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user