@lemonppt/renderer 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client-render.js +65 -5401
- package/dist/client/editor-script.js +3355 -0
- package/dist/client/theme-echarts.js +65 -25
- package/dist/client-render.d.ts +1 -7
- package/dist/client-render.d.ts.map +1 -1
- package/dist/client-render.js +1 -11
- package/dist/client-render.js.map +1 -1
- package/dist/editor-script.d.ts +1 -1
- package/dist/editor-script.d.ts.map +1 -1
- package/dist/editor-script.js +80 -77
- package/dist/editor-script.js.map +1 -1
- package/dist/export-pdf.js +1 -1
- package/dist/export-pdf.js.map +1 -1
- package/dist/export-pptx.d.ts +1132 -2
- package/dist/export-pptx.d.ts.map +1 -1
- package/dist/export-pptx.js +1208 -19525
- package/dist/export-pptx.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/render-editor-data.d.ts +21 -0
- package/dist/render-editor-data.d.ts.map +1 -0
- package/dist/render-editor-data.js +1068 -0
- package/dist/render-editor-data.js.map +1 -0
- package/dist/render.d.ts +0 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +7 -323
- package/dist/render.js.map +1 -1
- package/dist/theme01-pptx.d.ts +12 -0
- package/dist/theme01-pptx.d.ts.map +1 -0
- package/dist/theme01-pptx.js +2954 -0
- package/dist/theme01-pptx.js.map +1 -0
- package/dist/theme02-pptx.d.ts +5 -0
- package/dist/theme02-pptx.d.ts.map +1 -0
- package/dist/theme02-pptx.js +1063 -0
- package/dist/theme02-pptx.js.map +1 -0
- package/dist/theme03-pptx.d.ts +5 -0
- package/dist/theme03-pptx.d.ts.map +1 -0
- package/dist/theme03-pptx.js +3279 -0
- package/dist/theme03-pptx.js.map +1 -0
- package/dist/theme04-pptx.d.ts +6 -0
- package/dist/theme04-pptx.d.ts.map +1 -0
- package/dist/theme04-pptx.js +5496 -0
- package/dist/theme04-pptx.js.map +1 -0
- package/dist/theme05-pptx.d.ts +5 -0
- package/dist/theme05-pptx.d.ts.map +1 -0
- package/dist/theme05-pptx.js +3152 -0
- package/dist/theme05-pptx.js.map +1 -0
- package/dist/theme06-pptx.d.ts +124 -0
- package/dist/theme06-pptx.d.ts.map +1 -0
- package/dist/theme06-pptx.js +8154 -0
- package/dist/theme06-pptx.js.map +1 -0
- package/dist/theme07-pptx.d.ts +49 -0
- package/dist/theme07-pptx.d.ts.map +1 -0
- package/dist/theme07-pptx.js +2579 -0
- package/dist/theme07-pptx.js.map +1 -0
- package/dist/theme08-pptx.d.ts +19 -0
- package/dist/theme08-pptx.d.ts.map +1 -0
- package/dist/theme08-pptx.js +1411 -0
- package/dist/theme08-pptx.js.map +1 -0
- package/dist/theme09-pptx.d.ts +18 -0
- package/dist/theme09-pptx.d.ts.map +1 -0
- package/dist/theme09-pptx.js +1513 -0
- package/dist/theme09-pptx.js.map +1 -0
- package/dist/theme10-pptx.d.ts +18 -0
- package/dist/theme10-pptx.d.ts.map +1 -0
- package/dist/theme10-pptx.js +2889 -0
- package/dist/theme10-pptx.js.map +1 -0
- package/package.json +7 -6
- package/templates/editor.html +580 -0
- package/templates/editor.js +222 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const params = new URLSearchParams(window.location.search);
|
|
3
|
+
const initialTheme = params.get('theme') || window.__lemonPPT_editorData?.theme || window.__lemonPPT_goal?.theme || 'theme01';
|
|
4
|
+
const isStatic = !!window.__lemonPPT_editorData || !!window.__lemonPPT_goal;
|
|
5
|
+
const assetsBase = window.__lemonPPT_assetsBase || '/deck/assets/';
|
|
6
|
+
const apiBase = window.__lemonPPT_apiBase || '';
|
|
7
|
+
let editorScriptLoaded = false;
|
|
8
|
+
|
|
9
|
+
function loadScript(src) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const script = document.createElement('script');
|
|
12
|
+
script.src = src;
|
|
13
|
+
script.onload = resolve;
|
|
14
|
+
script.onerror = reject;
|
|
15
|
+
document.body.appendChild(script);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function escapeHtml(text) {
|
|
20
|
+
return String(text)
|
|
21
|
+
.replace(/&/g, '&')
|
|
22
|
+
.replace(/</g, '<')
|
|
23
|
+
.replace(/>/g, '>')
|
|
24
|
+
.replace(/"/g, '"')
|
|
25
|
+
.replace(/'/g, ''');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assetUrl(name) {
|
|
29
|
+
const base = assetsBase.endsWith('/') ? assetsBase : assetsBase + '/';
|
|
30
|
+
return base + name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildEditorBar(goal, currentTheme, staticMode) {
|
|
34
|
+
const appearance = goal.appearance || 'primary';
|
|
35
|
+
const appearanceButtons = [
|
|
36
|
+
{ value: 'primary', label: '亮', icon: '☀' },
|
|
37
|
+
{ value: 'dark', label: '暗', icon: '☾' },
|
|
38
|
+
{ value: 'contrast', label: '彩', icon: '◐' },
|
|
39
|
+
]
|
|
40
|
+
.map(btn => `<button type="button" class="lp-appearance-btn ${btn.value === appearance ? 'lp-appearance-active' : ''}" data-appearance="${btn.value}"><span>${btn.icon}</span><span>${btn.label}</span></button>`)
|
|
41
|
+
.join('');
|
|
42
|
+
|
|
43
|
+
const themeSelect = staticMode
|
|
44
|
+
? `<select id="lp-theme-select" class="lp-editor-select" title="当前主题" disabled><option>${escapeHtml(currentTheme)}</option></select>`
|
|
45
|
+
: `<select id="lp-theme-select" class="lp-editor-select" title="切换主题">
|
|
46
|
+
${[1,2,3,4,5,6,7,8,9,10].map(i => { const id = `theme${String(i).padStart(2, '0')}`; return `<option value="${id}" ${currentTheme === id ? 'selected' : ''}>Theme ${String(i).padStart(2, '0')}</option>`; }).join('')}
|
|
47
|
+
</select>`;
|
|
48
|
+
|
|
49
|
+
return `
|
|
50
|
+
<div class="lp-editor-title">${escapeHtml(goal.title || 'Untitled')}</div>
|
|
51
|
+
<div class="lp-appearance-switcher">${appearanceButtons}</div>
|
|
52
|
+
${themeSelect}
|
|
53
|
+
<div class="lp-editor-btn-group">
|
|
54
|
+
<button id="lp-undo" class="lp-editor-btn" type="button" title="撤销 (Ctrl+Z)" disabled><span>↶</span> 撤销</button>
|
|
55
|
+
<button id="lp-redo" class="lp-editor-btn" type="button" title="重做 (Ctrl+Y)" disabled><span>↷</span> 重做</button>
|
|
56
|
+
</div>
|
|
57
|
+
<button id="lp-add-slide" class="lp-editor-btn" type="button"><span>+</span> 添加页面</button>
|
|
58
|
+
<button id="lp-save-deck" class="lp-editor-btn lp-editor-btn-primary" type="button">保存</button>
|
|
59
|
+
<div id="lp-editor-export" class="lp-editor-export">
|
|
60
|
+
<button id="lp-export-toggle" class="lp-editor-btn lp-editor-export-toggle" type="button" aria-expanded="false"><span>⬇</span> 导出 <span class="lp-editor-caret">▼</span></button>
|
|
61
|
+
<div id="lp-export-menu" class="lp-editor-export-menu" hidden>
|
|
62
|
+
<button id="lp-export-html" class="lp-editor-export-item" data-format="html"><span class="lp-editor-export-icon">🌐</span><span class="lp-editor-export-label">HTML 演示</span><span class="lp-editor-export-tag">在线播放</span></button>
|
|
63
|
+
<button id="lp-export-pptx" class="lp-editor-export-item" data-format="pptx"><span class="lp-editor-export-icon">📊</span><span class="lp-editor-export-label">PowerPoint</span><span class="lp-editor-export-tag">.pptx</span></button>
|
|
64
|
+
<button id="lp-export-pdf" class="lp-editor-export-item" data-format="pdf"><span class="lp-editor-export-icon">📄</span><span class="lp-editor-export-label">PDF</span><span class="lp-editor-export-tag">打印/分享</span></button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildLeftPanel(goal) {
|
|
70
|
+
const slideCount = goal.slides.length;
|
|
71
|
+
const thumbnails = goal.slides.map((slide, index) => {
|
|
72
|
+
const activeClass = index === 0 ? ' active' : '';
|
|
73
|
+
const title = slide.props && slide.props.title ? String(slide.props.title) : `Slide ${index + 1}`;
|
|
74
|
+
return `<div class="lp-thumbnail${activeClass}" tabindex="0" draggable="true" data-slide-index="${index}" data-layout="${slide.layout}">
|
|
75
|
+
<div class="lp-thumbnail-drag-handle" aria-hidden="true">⋮⋮</div>
|
|
76
|
+
<div class="lp-thumbnail-index">${index + 1} / ${slideCount}</div>
|
|
77
|
+
<div class="lp-thumbnail-title">${escapeHtml(title)}</div>
|
|
78
|
+
<div class="lp-thumbnail-layout">${slide.layout}</div>
|
|
79
|
+
<div class="lp-thumbnail-drop-indicator" data-drop-position="before"></div>
|
|
80
|
+
<div class="lp-thumbnail-drop-indicator" data-drop-position="after"></div>
|
|
81
|
+
</div>`;
|
|
82
|
+
}).join('\n');
|
|
83
|
+
return `<div class="lp-editor-thumbnails" id="lp-thumbnails">${thumbnails}</div>`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildRightPanel() {
|
|
87
|
+
return `<div class="lp-property-header">属性面板</div><div class="lp-property-empty">点击左侧缩略图选择幻灯片,然后编辑内容。</div>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildAddSlideModal(goal) {
|
|
91
|
+
const layouts = goal.slides.length > 0 ? Array.from(new Set(goal.slides.map(s => s.layout))) : ['title', 'content', 'section', 'closing'];
|
|
92
|
+
const layoutItems = layouts.map(layout =>
|
|
93
|
+
`<div class="lp-add-slide-item" data-layout="${layout}"><span class="lp-add-slide-item-icon">⊞</span><span class="lp-add-slide-item-label">${layout}</span></div>`
|
|
94
|
+
).join('\n');
|
|
95
|
+
return `<div id="lp-add-slide-modal" class="lp-add-slide-modal-overlay">
|
|
96
|
+
<div class="lp-add-slide-modal">
|
|
97
|
+
<div class="lp-add-slide-modal-header"><span class="lp-add-slide-modal-title">添加幻灯片</span><button id="lp-add-slide-close" class="lp-add-slide-modal-close" type="button">×</button></div>
|
|
98
|
+
<div class="lp-add-slide-modal-body">
|
|
99
|
+
<div class="lp-add-slide-section-title">选择版式</div>
|
|
100
|
+
<div class="lp-add-slide-grid">${layoutItems}</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function applyData(data, { isInitial = false, staticMode = false } = {}) {
|
|
107
|
+
const goal = data.goal;
|
|
108
|
+
const theme = data.theme;
|
|
109
|
+
|
|
110
|
+
document.documentElement.setAttribute('data-theme', data.colorScheme);
|
|
111
|
+
if (data.appearance) {
|
|
112
|
+
document.documentElement.setAttribute('data-appearance', data.appearance);
|
|
113
|
+
} else {
|
|
114
|
+
document.documentElement.removeAttribute('data-appearance');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 更新主题 CSS 变量
|
|
118
|
+
let varStyle = document.getElementById('lp-theme-vars');
|
|
119
|
+
if (!varStyle) {
|
|
120
|
+
varStyle = document.createElement('style');
|
|
121
|
+
varStyle.id = 'lp-theme-vars';
|
|
122
|
+
document.head.appendChild(varStyle);
|
|
123
|
+
}
|
|
124
|
+
varStyle.textContent = data.themeCssVars;
|
|
125
|
+
|
|
126
|
+
// 更新主题样式表
|
|
127
|
+
let themeLink = document.getElementById('lp-theme-css');
|
|
128
|
+
const themeCssUrl = assetUrl(`${theme}.css`);
|
|
129
|
+
if (themeLink) {
|
|
130
|
+
themeLink.href = themeCssUrl;
|
|
131
|
+
} else {
|
|
132
|
+
themeLink = document.createElement('link');
|
|
133
|
+
themeLink.id = 'lp-theme-css';
|
|
134
|
+
themeLink.rel = 'stylesheet';
|
|
135
|
+
themeLink.href = themeCssUrl;
|
|
136
|
+
document.head.appendChild(themeLink);
|
|
137
|
+
}
|
|
138
|
+
// 等待样式表加载完成(首次加载需要,后续 href 变更浏览器会异步加载)
|
|
139
|
+
await new Promise((resolve) => {
|
|
140
|
+
if (themeLink.sheet) return resolve();
|
|
141
|
+
themeLink.onload = resolve;
|
|
142
|
+
setTimeout(resolve, 100);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 填充 UI
|
|
146
|
+
document.getElementById('lp-editor-bar').innerHTML = buildEditorBar(goal, theme, staticMode);
|
|
147
|
+
document.getElementById('lp-left-panel').innerHTML = buildLeftPanel(goal);
|
|
148
|
+
document.getElementById('lp-right-panel').innerHTML = buildRightPanel();
|
|
149
|
+
document.getElementById('lp-modal').innerHTML = buildAddSlideModal(goal);
|
|
150
|
+
document.getElementById('lp-slides').innerHTML = data.slidesMarkup;
|
|
151
|
+
document.getElementById('lp-total').textContent = String(goal.slides.length);
|
|
152
|
+
|
|
153
|
+
// 暴露 goal 给 editor-script
|
|
154
|
+
window.__lemonPPT_goal = goal;
|
|
155
|
+
|
|
156
|
+
// 显示编辑器
|
|
157
|
+
document.getElementById('lp-loading').classList.add('hidden');
|
|
158
|
+
document.getElementById('lp-editor-root').style.display = '';
|
|
159
|
+
|
|
160
|
+
// 首次加载需要注入 editor-script,后续直接复用已初始化的实例
|
|
161
|
+
if (!editorScriptLoaded) {
|
|
162
|
+
await loadScript(assetUrl('editor-script.js'));
|
|
163
|
+
editorScriptLoaded = true;
|
|
164
|
+
} else if (typeof window.__lemonPPT_applyTheme === 'function') {
|
|
165
|
+
window.__lemonPPT_applyTheme(goal, data.slidesMarkup);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 同步 URL(不刷新)
|
|
169
|
+
if (!staticMode) {
|
|
170
|
+
const url = new URL(window.location.href);
|
|
171
|
+
url.searchParams.set('theme', theme);
|
|
172
|
+
window.history.replaceState({}, '', url.toString());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 主题切换事件
|
|
176
|
+
const themeSelect = document.getElementById('lp-theme-select');
|
|
177
|
+
if (themeSelect && !staticMode) {
|
|
178
|
+
themeSelect.addEventListener('change', (e) => {
|
|
179
|
+
if (document.activeElement) document.activeElement.blur();
|
|
180
|
+
fetchTheme(e.target.value);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function fetchTheme(theme) {
|
|
186
|
+
try {
|
|
187
|
+
const res = await fetch(`${apiBase}/api/render-editor?theme=${encodeURIComponent(theme)}`);
|
|
188
|
+
const json = await res.json();
|
|
189
|
+
if (!json.success) throw new Error(json.error || '加载失败');
|
|
190
|
+
await applyData(json.data, { staticMode: false });
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error(err);
|
|
193
|
+
document.getElementById('lp-loading').classList.remove('hidden');
|
|
194
|
+
document.getElementById('lp-loading').innerHTML = `<div>加载失败:${escapeHtml(err.message)}</div>`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function init() {
|
|
199
|
+
try {
|
|
200
|
+
if (window.__lemonPPT_editorData) {
|
|
201
|
+
await applyData(window.__lemonPPT_editorData, { isInitial: true, staticMode: true });
|
|
202
|
+
} else if (window.__lemonPPT_goal) {
|
|
203
|
+
// 兼容旧的内嵌方式:将 goal 转换为 editor data 结构
|
|
204
|
+
await applyData({
|
|
205
|
+
goal: window.__lemonPPT_goal,
|
|
206
|
+
theme: window.__lemonPPT_goal.theme,
|
|
207
|
+
colorScheme: window.__lemonPPT_goal.colorScheme || 'light',
|
|
208
|
+
appearance: window.__lemonPPT_goal.appearance,
|
|
209
|
+
slidesMarkup: document.getElementById('lp-slides')?.innerHTML || '',
|
|
210
|
+
themeCssVars: '',
|
|
211
|
+
}, { isInitial: true, staticMode: true });
|
|
212
|
+
} else {
|
|
213
|
+
await fetchTheme(initialTheme);
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(err);
|
|
217
|
+
document.getElementById('lp-loading').innerHTML = `<div>加载失败:${escapeHtml(err.message)}</div>`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
init();
|
|
222
|
+
})();
|