tinymce-plugin-multipreview 1.0.0 → 1.0.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.
@@ -0,0 +1,550 @@
1
+ const m = "multipreview", n = {
2
+ mobile: {
3
+ label: "手机",
4
+ icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
5
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
6
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
7
+ </svg>`,
8
+ width: 375,
9
+ height: 812,
10
+ scale: 0.75
11
+ },
12
+ tablet: {
13
+ label: "平板",
14
+ icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
15
+ <rect x="4" y="2" width="16" height="20" rx="2" ry="2"/>
16
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
17
+ </svg>`,
18
+ width: 768,
19
+ height: 1024,
20
+ scale: 0.65
21
+ },
22
+ desktop: {
23
+ label: "电脑",
24
+ icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
26
+ <line x1="8" y1="21" x2="16" y2="21"/>
27
+ <line x1="12" y1="17" x2="12" y2="21"/>
28
+ </svg>`,
29
+ width: 1280,
30
+ height: 800,
31
+ scale: 0.55
32
+ }
33
+ };
34
+ function l(i, e) {
35
+ const t = i.getContent();
36
+ return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
38
+ <title>预览</title>${`
39
+ <style>
40
+ * { box-sizing: border-box; }
41
+ body {
42
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
43
+ font-size: 16px;
44
+ line-height: 1.7;
45
+ color: #333;
46
+ margin: 0;
47
+ padding: 20px;
48
+ word-wrap: break-word;
49
+ overflow-wrap: break-word;
50
+ }
51
+ img { max-width: 100%; height: auto; }
52
+ table { border-collapse: collapse; width: 100%; }
53
+ td, th { border: 1px solid #ddd; padding: 8px; }
54
+ pre { overflow-x: auto; background: #f5f5f5; padding: 12px; border-radius: 4px; }
55
+ code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-size: 0.9em; }
56
+ blockquote {
57
+ border-left: 4px solid #ddd;
58
+ margin: 0;
59
+ padding-left: 16px;
60
+ color: #666;
61
+ }
62
+ h1,h2,h3,h4,h5,h6 { line-height: 1.3; margin-top: 1.5em; margin-bottom: 0.5em; }
63
+ p { margin: 0.8em 0; }
64
+ a { color: #0066cc; }
65
+ ul, ol { padding-left: 1.5em; }
66
+ ${e || ""}
67
+ </style>
68
+ `}</head><body>${t}</body></html>`;
69
+ }
70
+ function h() {
71
+ return `
72
+ <div class="tpm-overlay" id="tpm-overlay">
73
+ <div class="tpm-modal">
74
+ <!-- 顶部工具栏 -->
75
+ <div class="tpm-toolbar">
76
+ <div class="tpm-toolbar-left">
77
+ <div class="tpm-logo">
78
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
79
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
80
+ <circle cx="12" cy="12" r="3"/>
81
+ </svg>
82
+ <span>内容预览</span>
83
+ </div>
84
+ </div>
85
+ <div class="tpm-device-tabs">
86
+ ${Object.entries(n).map(
87
+ ([e, t]) => `
88
+ <button class="tpm-device-btn ${e === "mobile" ? "active" : ""}" data-device="${e}" title="${t.label}">
89
+ ${t.icon}
90
+ <span>${t.label}</span>
91
+ </button>
92
+ `
93
+ ).join("")}
94
+ </div>
95
+ <div class="tpm-toolbar-right">
96
+ <button class="tpm-btn tpm-btn-rotate" id="tpm-rotate" title="旋转屏幕">
97
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
98
+ <polyline points="23 4 23 10 17 10"/>
99
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
100
+ </svg>
101
+ </button>
102
+ <button class="tpm-btn tpm-btn-close" id="tpm-close" title="关闭">
103
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
104
+ <line x1="18" y1="6" x2="6" y2="18"/>
105
+ <line x1="6" y1="6" x2="18" y2="18"/>
106
+ </svg>
107
+ </button>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- 预览区域 -->
112
+ <div class="tpm-stage" id="tpm-stage">
113
+ <div class="tpm-device-wrapper" id="tpm-device-wrapper">
114
+ <div class="tpm-device-frame" id="tpm-device-frame">
115
+ <div class="tpm-device-notch" id="tpm-device-notch">
116
+ <div class="tpm-notch-inner"></div>
117
+ </div>
118
+ <div class="tpm-device-screen">
119
+ <iframe class="tpm-iframe" id="tpm-iframe" sandbox="allow-same-origin allow-scripts"></iframe>
120
+ </div>
121
+ <div class="tpm-device-home" id="tpm-device-home">
122
+ <div class="tpm-home-bar"></div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- 底部信息栏 -->
129
+ <div class="tpm-footer">
130
+ <span class="tpm-device-info" id="tpm-device-info">375 × 812</span>
131
+ <span class="tpm-separator">·</span>
132
+ <span class="tpm-device-label" id="tpm-device-label">手机预览</span>
133
+ <span class="tpm-hint">横屏模式下旋转设备以获得更好体验</span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ `;
138
+ }
139
+ const g = `
140
+ .tpm-overlay {
141
+ position: fixed;
142
+ inset: 0;
143
+ background: rgba(8, 8, 20, 0.85);
144
+ backdrop-filter: blur(12px);
145
+ -webkit-backdrop-filter: blur(12px);
146
+ z-index: 999999;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ animation: tpm-fade-in 0.25s ease;
151
+ }
152
+ @keyframes tpm-fade-in {
153
+ from { opacity: 0; }
154
+ to { opacity: 1; }
155
+ }
156
+ .tpm-modal {
157
+ width: 100%;
158
+ height: 100%;
159
+ display: flex;
160
+ flex-direction: column;
161
+ overflow: hidden;
162
+ }
163
+
164
+ /* 工具栏 */
165
+ .tpm-toolbar {
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: space-between;
169
+ padding: 0 24px;
170
+ height: 60px;
171
+ background: rgba(255,255,255,0.04);
172
+ border-bottom: 1px solid rgba(255,255,255,0.08);
173
+ flex-shrink: 0;
174
+ gap: 16px;
175
+ }
176
+ .tpm-toolbar-left, .tpm-toolbar-right {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 8px;
180
+ min-width: 120px;
181
+ }
182
+ .tpm-toolbar-right { justify-content: flex-end; }
183
+ .tpm-logo {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 8px;
187
+ color: rgba(255,255,255,0.9);
188
+ font-size: 15px;
189
+ font-weight: 600;
190
+ letter-spacing: 0.02em;
191
+ }
192
+ .tpm-logo svg {
193
+ width: 20px;
194
+ height: 20px;
195
+ color: #6ee7f7;
196
+ }
197
+ .tpm-device-tabs {
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 4px;
201
+ background: rgba(255,255,255,0.06);
202
+ border-radius: 10px;
203
+ padding: 4px;
204
+ }
205
+ .tpm-device-btn {
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 6px;
209
+ padding: 6px 14px;
210
+ border: none;
211
+ background: transparent;
212
+ color: rgba(255,255,255,0.5);
213
+ border-radius: 7px;
214
+ cursor: pointer;
215
+ font-size: 13px;
216
+ font-weight: 500;
217
+ transition: all 0.2s ease;
218
+ white-space: nowrap;
219
+ }
220
+ .tpm-device-btn svg {
221
+ width: 16px;
222
+ height: 16px;
223
+ flex-shrink: 0;
224
+ }
225
+ .tpm-device-btn:hover {
226
+ color: rgba(255,255,255,0.8);
227
+ background: rgba(255,255,255,0.08);
228
+ }
229
+ .tpm-device-btn.active {
230
+ background: rgba(110, 231, 247, 0.15);
231
+ color: #6ee7f7;
232
+ }
233
+ .tpm-btn {
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ width: 36px;
238
+ height: 36px;
239
+ border: none;
240
+ background: rgba(255,255,255,0.06);
241
+ color: rgba(255,255,255,0.6);
242
+ border-radius: 8px;
243
+ cursor: pointer;
244
+ transition: all 0.2s ease;
245
+ }
246
+ .tpm-btn svg { width: 18px; height: 18px; }
247
+ .tpm-btn:hover {
248
+ background: rgba(255,255,255,0.12);
249
+ color: rgba(255,255,255,0.9);
250
+ }
251
+ .tpm-btn-close:hover { background: rgba(255, 80, 80, 0.2); color: #ff6b6b; }
252
+
253
+ /* 预览舞台 */
254
+ .tpm-stage {
255
+ flex: 1;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ overflow: hidden;
260
+ padding: 30px 20px;
261
+ position: relative;
262
+ }
263
+ .tpm-stage::before {
264
+ content: '';
265
+ position: absolute;
266
+ inset: 0;
267
+ background: radial-gradient(ellipse at 50% 50%, rgba(110, 231, 247, 0.03) 0%, transparent 70%);
268
+ pointer-events: none;
269
+ }
270
+ .tpm-device-wrapper {
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
275
+ transform-origin: center center;
276
+ }
277
+
278
+ /* 设备外壳 */
279
+ .tpm-device-frame {
280
+ position: relative;
281
+ border-radius: 40px;
282
+ background: linear-gradient(160deg, #2a2a3e 0%, #1a1a2e 50%, #12121f 100%);
283
+ box-shadow:
284
+ 0 0 0 1px rgba(255,255,255,0.12),
285
+ 0 0 0 2px rgba(0,0,0,0.8),
286
+ 0 30px 80px rgba(0,0,0,0.6),
287
+ inset 0 1px 0 rgba(255,255,255,0.1),
288
+ 0 0 60px rgba(110, 231, 247, 0.06);
289
+ transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
290
+ overflow: hidden;
291
+ }
292
+ .tpm-device-frame::before {
293
+ content: '';
294
+ position: absolute;
295
+ inset: 0;
296
+ border-radius: inherit;
297
+ background: linear-gradient(135deg, rgba(255,255,255,0.06) 0%, transparent 50%);
298
+ pointer-events: none;
299
+ z-index: 2;
300
+ }
301
+
302
+ /* 刘海 */
303
+ .tpm-device-notch {
304
+ position: absolute;
305
+ top: 0;
306
+ left: 50%;
307
+ transform: translateX(-50%);
308
+ width: 120px;
309
+ height: 30px;
310
+ background: #0a0a14;
311
+ border-radius: 0 0 20px 20px;
312
+ z-index: 10;
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: center;
316
+ transition: all 0.3s ease;
317
+ }
318
+ .tpm-notch-inner {
319
+ width: 60px;
320
+ height: 6px;
321
+ background: #1a1a2e;
322
+ border-radius: 3px;
323
+ }
324
+
325
+ /* 屏幕 */
326
+ .tpm-device-screen {
327
+ position: absolute;
328
+ inset: 12px;
329
+ border-radius: 28px;
330
+ overflow: hidden;
331
+ background: #fff;
332
+ }
333
+ .tpm-iframe {
334
+ width: 100%;
335
+ height: 100%;
336
+ border: none;
337
+ display: block;
338
+ }
339
+
340
+ /* Home 条 */
341
+ .tpm-device-home {
342
+ position: absolute;
343
+ bottom: 8px;
344
+ left: 50%;
345
+ transform: translateX(-50%);
346
+ z-index: 10;
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ transition: all 0.3s ease;
351
+ }
352
+ .tpm-home-bar {
353
+ width: 100px;
354
+ height: 4px;
355
+ background: rgba(255,255,255,0.35);
356
+ border-radius: 2px;
357
+ }
358
+
359
+ /* 平板样式 */
360
+ .tpm-device-frame.tablet {
361
+ border-radius: 24px;
362
+ }
363
+ .tpm-device-frame.tablet .tpm-device-notch {
364
+ width: 80px;
365
+ height: 20px;
366
+ border-radius: 0 0 12px 12px;
367
+ }
368
+ .tpm-device-frame.tablet .tpm-notch-inner {
369
+ width: 40px;
370
+ height: 5px;
371
+ }
372
+
373
+ /* PC 样式 */
374
+ .tpm-device-frame.desktop {
375
+ border-radius: 12px 12px 0 0;
376
+ }
377
+ .tpm-device-frame.desktop .tpm-device-notch { display: none; }
378
+ .tpm-device-frame.desktop .tpm-device-home { display: none; }
379
+ .tpm-device-frame.desktop .tpm-device-screen { inset: 32px 8px 8px; border-radius: 4px; }
380
+ .tpm-device-frame.desktop::after {
381
+ content: '';
382
+ position: absolute;
383
+ top: 0;
384
+ left: 0;
385
+ right: 0;
386
+ height: 32px;
387
+ background: linear-gradient(to bottom, #252538, #1e1e30);
388
+ display: flex;
389
+ align-items: center;
390
+ padding-left: 16px;
391
+ }
392
+ .tpm-desktop-bar {
393
+ position: absolute;
394
+ top: 0;
395
+ left: 0;
396
+ right: 0;
397
+ height: 32px;
398
+ background: linear-gradient(to bottom, #2a2a42, #1e1e32);
399
+ border-radius: 12px 12px 0 0;
400
+ display: flex;
401
+ align-items: center;
402
+ padding: 0 12px;
403
+ gap: 6px;
404
+ z-index: 5;
405
+ }
406
+ .tpm-desktop-dot {
407
+ width: 10px;
408
+ height: 10px;
409
+ border-radius: 50%;
410
+ }
411
+
412
+ /* 底部信息 */
413
+ .tpm-footer {
414
+ display: flex;
415
+ align-items: center;
416
+ justify-content: center;
417
+ gap: 8px;
418
+ height: 40px;
419
+ background: rgba(255,255,255,0.03);
420
+ border-top: 1px solid rgba(255,255,255,0.06);
421
+ flex-shrink: 0;
422
+ font-size: 12px;
423
+ color: rgba(255,255,255,0.3);
424
+ }
425
+ .tpm-device-info { color: rgba(110, 231, 247, 0.7); font-weight: 600; letter-spacing: 0.05em; }
426
+ .tpm-separator { color: rgba(255,255,255,0.15); }
427
+ .tpm-device-label { color: rgba(255,255,255,0.5); }
428
+ .tpm-hint { margin-left: 16px; font-size: 11px; opacity: 0.6; }
429
+
430
+ /* 旋转动画 */
431
+ .tpm-device-wrapper.rotating .tpm-device-frame {
432
+ animation: tpm-rotate-pulse 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
433
+ }
434
+ @keyframes tpm-rotate-pulse {
435
+ 0% { transform: scale(1); }
436
+ 50% { transform: scale(0.9) rotate(5deg); }
437
+ 100% { transform: scale(1); }
438
+ }
439
+
440
+ /* 设备切换动画 */
441
+ @keyframes tpm-device-in {
442
+ from { opacity: 0; transform: scale(0.85) translateY(20px); }
443
+ to { opacity: 1; transform: scale(1) translateY(0); }
444
+ }
445
+ .tpm-device-frame.entering {
446
+ animation: tpm-device-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
447
+ }
448
+ `;
449
+ function b() {
450
+ if (document.getElementById("tpm-styles")) return;
451
+ const i = document.createElement("style");
452
+ i.id = "tpm-styles", i.textContent = g, document.head.appendChild(i);
453
+ }
454
+ class p {
455
+ constructor(e, t = {}) {
456
+ this.editor = e, this.options = t, this.currentDevice = "mobile", this.isLandscape = !1, this.modal = null;
457
+ }
458
+ open() {
459
+ b();
460
+ const e = document.createElement("div");
461
+ e.innerHTML = h(), this.modal = e.firstElementChild, document.body.appendChild(this.modal), this._setupEvents(), this._switchDevice("mobile", !1), this._loadContent();
462
+ }
463
+ close() {
464
+ this.modal && (this.modal.style.animation = "tpm-fade-in 0.2s ease reverse", setTimeout(() => {
465
+ var e;
466
+ (e = this.modal) == null || e.remove(), this.modal = null;
467
+ }, 200));
468
+ }
469
+ _loadContent() {
470
+ const e = this.modal.querySelector("#tpm-iframe"), t = l(this.editor, this.options.customStyles), r = new Blob([t], { type: "text/html;charset=utf-8" }), o = URL.createObjectURL(r);
471
+ e.src = o, e.onload = () => URL.revokeObjectURL(o);
472
+ }
473
+ _switchDevice(e, t = !0) {
474
+ var c;
475
+ this.currentDevice = e, this.isLandscape = !1;
476
+ const r = n[e], o = this.modal.querySelector("#tpm-device-frame");
477
+ if (this.modal.querySelector("#tpm-device-wrapper"), o.className = `tpm-device-frame ${e}`, t && (o.classList.add("entering"), setTimeout(() => o.classList.remove("entering"), 400)), e === "desktop") {
478
+ if (!o.querySelector(".tpm-desktop-bar")) {
479
+ const a = document.createElement("div");
480
+ a.className = "tpm-desktop-bar", a.innerHTML = `
481
+ <div class="tpm-desktop-dot" style="background:#ff5f57"></div>
482
+ <div class="tpm-desktop-dot" style="background:#febc2e"></div>
483
+ <div class="tpm-desktop-dot" style="background:#28c840"></div>
484
+ `, o.prepend(a);
485
+ }
486
+ } else
487
+ (c = o.querySelector(".tpm-desktop-bar")) == null || c.remove();
488
+ this._applyDimensions(r.width, r.height, r.scale), this._updateInfo(r), this.modal.querySelectorAll(".tpm-device-btn").forEach((a) => {
489
+ a.classList.toggle("active", a.dataset.device === e);
490
+ });
491
+ const s = this.modal.querySelector("#tpm-rotate");
492
+ s.style.display = e === "desktop" ? "none" : "flex";
493
+ }
494
+ _applyDimensions(e, t, r) {
495
+ const o = this.modal.querySelector("#tpm-device-frame"), s = this.modal.querySelector("#tpm-device-wrapper");
496
+ o.style.width = e + "px", o.style.height = t + "px", s.style.transform = `scale(${r})`;
497
+ }
498
+ _rotate() {
499
+ const e = n[this.currentDevice];
500
+ this.isLandscape = !this.isLandscape;
501
+ const t = this.modal.querySelector("#tpm-device-wrapper");
502
+ t.classList.add("rotating"), setTimeout(() => t.classList.remove("rotating"), 500), this.isLandscape ? (this._applyDimensions(e.height, e.width, e.scale * 0.85), this._updateInfo({ ...e, width: e.height, height: e.width, label: e.label + " (横屏)" })) : (this._applyDimensions(e.width, e.height, e.scale), this._updateInfo(e));
503
+ }
504
+ _updateInfo(e) {
505
+ const t = this.modal.querySelector("#tpm-device-frame"), r = parseInt(t.style.width), o = parseInt(t.style.height);
506
+ this.modal.querySelector("#tpm-device-info").textContent = `${r} × ${o}`, this.modal.querySelector("#tpm-device-label").textContent = e.label + " 预览";
507
+ }
508
+ _setupEvents() {
509
+ this.modal.querySelector("#tpm-close").addEventListener("click", () => this.close()), this.modal.addEventListener("click", (e) => {
510
+ e.target === this.modal && this.close();
511
+ }), this._keyHandler = (e) => {
512
+ e.key === "Escape" && this.close();
513
+ }, document.addEventListener("keydown", this._keyHandler), this.modal.querySelectorAll(".tpm-device-btn").forEach((e) => {
514
+ e.addEventListener("click", () => this._switchDevice(e.dataset.device));
515
+ }), this.modal.querySelector("#tpm-rotate").addEventListener("click", () => this._rotate());
516
+ }
517
+ destroy() {
518
+ document.removeEventListener("keydown", this._keyHandler), this.close();
519
+ }
520
+ }
521
+ function d(i, e = {}) {
522
+ return i.addCommand("mceMultiPreview", function() {
523
+ new p(i, e).open();
524
+ }), i.ui.registry.addButton("multipreview", {
525
+ tooltip: "多端预览",
526
+ icon: "preview",
527
+ onAction: () => i.execCommand("mceMultiPreview")
528
+ }), i.ui.registry.addMenuItem("multipreview", {
529
+ text: "多端预览",
530
+ icon: "preview",
531
+ onAction: () => i.execCommand("mceMultiPreview")
532
+ }), {
533
+ getMetadata: () => ({
534
+ name: "Multi-Device Preview Plugin",
535
+ url: "https://github.com/your-org/tinymce-plugin-multipreview"
536
+ })
537
+ };
538
+ }
539
+ typeof window < "u" && (typeof window.tinymce < "u" && window.tinymce.PluginManager.add(m, function(i) {
540
+ const e = i.getParam("multipreview", {});
541
+ return d(i, e);
542
+ }), window.TinyMCEPreviewPlugin = { PreviewController: p, DEVICES: n, getFullHtml: l, initPlugin: d });
543
+ typeof module < "u" && module.exports && (module.exports = { PreviewController: p, DEVICES: n, getFullHtml: l, initPlugin: d, PLUGIN_NAME: m });
544
+ export {
545
+ n as DEVICES,
546
+ m as PLUGIN_NAME,
547
+ p as PreviewController,
548
+ l as getFullHtml,
549
+ d as initPlugin
550
+ };
@@ -0,0 +1,420 @@
1
+ (function(n,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(n=typeof globalThis<"u"?globalThis:n||self,a(n.TinyMCEPreviewPlugin={}))})(this,function(n){"use strict";const a="multipreview",s={mobile:{label:"手机",icon:`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
3
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
4
+ </svg>`,width:375,height:812,scale:.75},tablet:{label:"平板",icon:`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
5
+ <rect x="4" y="2" width="16" height="20" rx="2" ry="2"/>
6
+ <line x1="12" y1="18" x2="12.01" y2="18"/>
7
+ </svg>`,width:768,height:1024,scale:.65},desktop:{label:"电脑",icon:`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
8
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
9
+ <line x1="8" y1="21" x2="16" y2="21"/>
10
+ <line x1="12" y1="17" x2="12" y2="21"/>
11
+ </svg>`,width:1280,height:800,scale:.55}};function l(i,e){const t=i.getContent();return`<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>预览</title>${`
14
+ <style>
15
+ * { box-sizing: border-box; }
16
+ body {
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
18
+ font-size: 16px;
19
+ line-height: 1.7;
20
+ color: #333;
21
+ margin: 0;
22
+ padding: 20px;
23
+ word-wrap: break-word;
24
+ overflow-wrap: break-word;
25
+ }
26
+ img { max-width: 100%; height: auto; }
27
+ table { border-collapse: collapse; width: 100%; }
28
+ td, th { border: 1px solid #ddd; padding: 8px; }
29
+ pre { overflow-x: auto; background: #f5f5f5; padding: 12px; border-radius: 4px; }
30
+ code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-size: 0.9em; }
31
+ blockquote {
32
+ border-left: 4px solid #ddd;
33
+ margin: 0;
34
+ padding-left: 16px;
35
+ color: #666;
36
+ }
37
+ h1,h2,h3,h4,h5,h6 { line-height: 1.3; margin-top: 1.5em; margin-bottom: 0.5em; }
38
+ p { margin: 0.8em 0; }
39
+ a { color: #0066cc; }
40
+ ul, ol { padding-left: 1.5em; }
41
+ ${e||""}
42
+ </style>
43
+ `}</head><body>${t}</body></html>`}function g(){return`
44
+ <div class="tpm-overlay" id="tpm-overlay">
45
+ <div class="tpm-modal">
46
+ <!-- 顶部工具栏 -->
47
+ <div class="tpm-toolbar">
48
+ <div class="tpm-toolbar-left">
49
+ <div class="tpm-logo">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
51
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
52
+ <circle cx="12" cy="12" r="3"/>
53
+ </svg>
54
+ <span>内容预览</span>
55
+ </div>
56
+ </div>
57
+ <div class="tpm-device-tabs">
58
+ ${Object.entries(s).map(([e,t])=>`
59
+ <button class="tpm-device-btn ${e==="mobile"?"active":""}" data-device="${e}" title="${t.label}">
60
+ ${t.icon}
61
+ <span>${t.label}</span>
62
+ </button>
63
+ `).join("")}
64
+ </div>
65
+ <div class="tpm-toolbar-right">
66
+ <button class="tpm-btn tpm-btn-rotate" id="tpm-rotate" title="旋转屏幕">
67
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
68
+ <polyline points="23 4 23 10 17 10"/>
69
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
70
+ </svg>
71
+ </button>
72
+ <button class="tpm-btn tpm-btn-close" id="tpm-close" title="关闭">
73
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
74
+ <line x1="18" y1="6" x2="6" y2="18"/>
75
+ <line x1="6" y1="6" x2="18" y2="18"/>
76
+ </svg>
77
+ </button>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- 预览区域 -->
82
+ <div class="tpm-stage" id="tpm-stage">
83
+ <div class="tpm-device-wrapper" id="tpm-device-wrapper">
84
+ <div class="tpm-device-frame" id="tpm-device-frame">
85
+ <div class="tpm-device-notch" id="tpm-device-notch">
86
+ <div class="tpm-notch-inner"></div>
87
+ </div>
88
+ <div class="tpm-device-screen">
89
+ <iframe class="tpm-iframe" id="tpm-iframe" sandbox="allow-same-origin allow-scripts"></iframe>
90
+ </div>
91
+ <div class="tpm-device-home" id="tpm-device-home">
92
+ <div class="tpm-home-bar"></div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- 底部信息栏 -->
99
+ <div class="tpm-footer">
100
+ <span class="tpm-device-info" id="tpm-device-info">375 × 812</span>
101
+ <span class="tpm-separator">·</span>
102
+ <span class="tpm-device-label" id="tpm-device-label">手机预览</span>
103
+ <span class="tpm-hint">横屏模式下旋转设备以获得更好体验</span>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ `}const b=`
108
+ .tpm-overlay {
109
+ position: fixed;
110
+ inset: 0;
111
+ background: rgba(8, 8, 20, 0.85);
112
+ backdrop-filter: blur(12px);
113
+ -webkit-backdrop-filter: blur(12px);
114
+ z-index: 999999;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ animation: tpm-fade-in 0.25s ease;
119
+ }
120
+ @keyframes tpm-fade-in {
121
+ from { opacity: 0; }
122
+ to { opacity: 1; }
123
+ }
124
+ .tpm-modal {
125
+ width: 100%;
126
+ height: 100%;
127
+ display: flex;
128
+ flex-direction: column;
129
+ overflow: hidden;
130
+ }
131
+
132
+ /* 工具栏 */
133
+ .tpm-toolbar {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: space-between;
137
+ padding: 0 24px;
138
+ height: 60px;
139
+ background: rgba(255,255,255,0.04);
140
+ border-bottom: 1px solid rgba(255,255,255,0.08);
141
+ flex-shrink: 0;
142
+ gap: 16px;
143
+ }
144
+ .tpm-toolbar-left, .tpm-toolbar-right {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 8px;
148
+ min-width: 120px;
149
+ }
150
+ .tpm-toolbar-right { justify-content: flex-end; }
151
+ .tpm-logo {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ color: rgba(255,255,255,0.9);
156
+ font-size: 15px;
157
+ font-weight: 600;
158
+ letter-spacing: 0.02em;
159
+ }
160
+ .tpm-logo svg {
161
+ width: 20px;
162
+ height: 20px;
163
+ color: #6ee7f7;
164
+ }
165
+ .tpm-device-tabs {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 4px;
169
+ background: rgba(255,255,255,0.06);
170
+ border-radius: 10px;
171
+ padding: 4px;
172
+ }
173
+ .tpm-device-btn {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 6px;
177
+ padding: 6px 14px;
178
+ border: none;
179
+ background: transparent;
180
+ color: rgba(255,255,255,0.5);
181
+ border-radius: 7px;
182
+ cursor: pointer;
183
+ font-size: 13px;
184
+ font-weight: 500;
185
+ transition: all 0.2s ease;
186
+ white-space: nowrap;
187
+ }
188
+ .tpm-device-btn svg {
189
+ width: 16px;
190
+ height: 16px;
191
+ flex-shrink: 0;
192
+ }
193
+ .tpm-device-btn:hover {
194
+ color: rgba(255,255,255,0.8);
195
+ background: rgba(255,255,255,0.08);
196
+ }
197
+ .tpm-device-btn.active {
198
+ background: rgba(110, 231, 247, 0.15);
199
+ color: #6ee7f7;
200
+ }
201
+ .tpm-btn {
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ width: 36px;
206
+ height: 36px;
207
+ border: none;
208
+ background: rgba(255,255,255,0.06);
209
+ color: rgba(255,255,255,0.6);
210
+ border-radius: 8px;
211
+ cursor: pointer;
212
+ transition: all 0.2s ease;
213
+ }
214
+ .tpm-btn svg { width: 18px; height: 18px; }
215
+ .tpm-btn:hover {
216
+ background: rgba(255,255,255,0.12);
217
+ color: rgba(255,255,255,0.9);
218
+ }
219
+ .tpm-btn-close:hover { background: rgba(255, 80, 80, 0.2); color: #ff6b6b; }
220
+
221
+ /* 预览舞台 */
222
+ .tpm-stage {
223
+ flex: 1;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ overflow: hidden;
228
+ padding: 30px 20px;
229
+ position: relative;
230
+ }
231
+ .tpm-stage::before {
232
+ content: '';
233
+ position: absolute;
234
+ inset: 0;
235
+ background: radial-gradient(ellipse at 50% 50%, rgba(110, 231, 247, 0.03) 0%, transparent 70%);
236
+ pointer-events: none;
237
+ }
238
+ .tpm-device-wrapper {
239
+ display: flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
243
+ transform-origin: center center;
244
+ }
245
+
246
+ /* 设备外壳 */
247
+ .tpm-device-frame {
248
+ position: relative;
249
+ border-radius: 40px;
250
+ background: linear-gradient(160deg, #2a2a3e 0%, #1a1a2e 50%, #12121f 100%);
251
+ box-shadow:
252
+ 0 0 0 1px rgba(255,255,255,0.12),
253
+ 0 0 0 2px rgba(0,0,0,0.8),
254
+ 0 30px 80px rgba(0,0,0,0.6),
255
+ inset 0 1px 0 rgba(255,255,255,0.1),
256
+ 0 0 60px rgba(110, 231, 247, 0.06);
257
+ transition: all 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
258
+ overflow: hidden;
259
+ }
260
+ .tpm-device-frame::before {
261
+ content: '';
262
+ position: absolute;
263
+ inset: 0;
264
+ border-radius: inherit;
265
+ background: linear-gradient(135deg, rgba(255,255,255,0.06) 0%, transparent 50%);
266
+ pointer-events: none;
267
+ z-index: 2;
268
+ }
269
+
270
+ /* 刘海 */
271
+ .tpm-device-notch {
272
+ position: absolute;
273
+ top: 0;
274
+ left: 50%;
275
+ transform: translateX(-50%);
276
+ width: 120px;
277
+ height: 30px;
278
+ background: #0a0a14;
279
+ border-radius: 0 0 20px 20px;
280
+ z-index: 10;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ transition: all 0.3s ease;
285
+ }
286
+ .tpm-notch-inner {
287
+ width: 60px;
288
+ height: 6px;
289
+ background: #1a1a2e;
290
+ border-radius: 3px;
291
+ }
292
+
293
+ /* 屏幕 */
294
+ .tpm-device-screen {
295
+ position: absolute;
296
+ inset: 12px;
297
+ border-radius: 28px;
298
+ overflow: hidden;
299
+ background: #fff;
300
+ }
301
+ .tpm-iframe {
302
+ width: 100%;
303
+ height: 100%;
304
+ border: none;
305
+ display: block;
306
+ }
307
+
308
+ /* Home 条 */
309
+ .tpm-device-home {
310
+ position: absolute;
311
+ bottom: 8px;
312
+ left: 50%;
313
+ transform: translateX(-50%);
314
+ z-index: 10;
315
+ display: flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ transition: all 0.3s ease;
319
+ }
320
+ .tpm-home-bar {
321
+ width: 100px;
322
+ height: 4px;
323
+ background: rgba(255,255,255,0.35);
324
+ border-radius: 2px;
325
+ }
326
+
327
+ /* 平板样式 */
328
+ .tpm-device-frame.tablet {
329
+ border-radius: 24px;
330
+ }
331
+ .tpm-device-frame.tablet .tpm-device-notch {
332
+ width: 80px;
333
+ height: 20px;
334
+ border-radius: 0 0 12px 12px;
335
+ }
336
+ .tpm-device-frame.tablet .tpm-notch-inner {
337
+ width: 40px;
338
+ height: 5px;
339
+ }
340
+
341
+ /* PC 样式 */
342
+ .tpm-device-frame.desktop {
343
+ border-radius: 12px 12px 0 0;
344
+ }
345
+ .tpm-device-frame.desktop .tpm-device-notch { display: none; }
346
+ .tpm-device-frame.desktop .tpm-device-home { display: none; }
347
+ .tpm-device-frame.desktop .tpm-device-screen { inset: 32px 8px 8px; border-radius: 4px; }
348
+ .tpm-device-frame.desktop::after {
349
+ content: '';
350
+ position: absolute;
351
+ top: 0;
352
+ left: 0;
353
+ right: 0;
354
+ height: 32px;
355
+ background: linear-gradient(to bottom, #252538, #1e1e30);
356
+ display: flex;
357
+ align-items: center;
358
+ padding-left: 16px;
359
+ }
360
+ .tpm-desktop-bar {
361
+ position: absolute;
362
+ top: 0;
363
+ left: 0;
364
+ right: 0;
365
+ height: 32px;
366
+ background: linear-gradient(to bottom, #2a2a42, #1e1e32);
367
+ border-radius: 12px 12px 0 0;
368
+ display: flex;
369
+ align-items: center;
370
+ padding: 0 12px;
371
+ gap: 6px;
372
+ z-index: 5;
373
+ }
374
+ .tpm-desktop-dot {
375
+ width: 10px;
376
+ height: 10px;
377
+ border-radius: 50%;
378
+ }
379
+
380
+ /* 底部信息 */
381
+ .tpm-footer {
382
+ display: flex;
383
+ align-items: center;
384
+ justify-content: center;
385
+ gap: 8px;
386
+ height: 40px;
387
+ background: rgba(255,255,255,0.03);
388
+ border-top: 1px solid rgba(255,255,255,0.06);
389
+ flex-shrink: 0;
390
+ font-size: 12px;
391
+ color: rgba(255,255,255,0.3);
392
+ }
393
+ .tpm-device-info { color: rgba(110, 231, 247, 0.7); font-weight: 600; letter-spacing: 0.05em; }
394
+ .tpm-separator { color: rgba(255,255,255,0.15); }
395
+ .tpm-device-label { color: rgba(255,255,255,0.5); }
396
+ .tpm-hint { margin-left: 16px; font-size: 11px; opacity: 0.6; }
397
+
398
+ /* 旋转动画 */
399
+ .tpm-device-wrapper.rotating .tpm-device-frame {
400
+ animation: tpm-rotate-pulse 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
401
+ }
402
+ @keyframes tpm-rotate-pulse {
403
+ 0% { transform: scale(1); }
404
+ 50% { transform: scale(0.9) rotate(5deg); }
405
+ 100% { transform: scale(1); }
406
+ }
407
+
408
+ /* 设备切换动画 */
409
+ @keyframes tpm-device-in {
410
+ from { opacity: 0; transform: scale(0.85) translateY(20px); }
411
+ to { opacity: 1; transform: scale(1) translateY(0); }
412
+ }
413
+ .tpm-device-frame.entering {
414
+ animation: tpm-device-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
415
+ }
416
+ `;function f(){if(document.getElementById("tpm-styles"))return;const i=document.createElement("style");i.id="tpm-styles",i.textContent=b,document.head.appendChild(i)}class p{constructor(e,t={}){this.editor=e,this.options=t,this.currentDevice="mobile",this.isLandscape=!1,this.modal=null}open(){f();const e=document.createElement("div");e.innerHTML=g(),this.modal=e.firstElementChild,document.body.appendChild(this.modal),this._setupEvents(),this._switchDevice("mobile",!1),this._loadContent()}close(){this.modal&&(this.modal.style.animation="tpm-fade-in 0.2s ease reverse",setTimeout(()=>{var e;(e=this.modal)==null||e.remove(),this.modal=null},200))}_loadContent(){const e=this.modal.querySelector("#tpm-iframe"),t=l(this.editor,this.options.customStyles),r=new Blob([t],{type:"text/html;charset=utf-8"}),o=URL.createObjectURL(r);e.src=o,e.onload=()=>URL.revokeObjectURL(o)}_switchDevice(e,t=!0){var h;this.currentDevice=e,this.isLandscape=!1;const r=s[e],o=this.modal.querySelector("#tpm-device-frame");if(this.modal.querySelector("#tpm-device-wrapper"),o.className=`tpm-device-frame ${e}`,t&&(o.classList.add("entering"),setTimeout(()=>o.classList.remove("entering"),400)),e==="desktop"){if(!o.querySelector(".tpm-desktop-bar")){const d=document.createElement("div");d.className="tpm-desktop-bar",d.innerHTML=`
417
+ <div class="tpm-desktop-dot" style="background:#ff5f57"></div>
418
+ <div class="tpm-desktop-dot" style="background:#febc2e"></div>
419
+ <div class="tpm-desktop-dot" style="background:#28c840"></div>
420
+ `,o.prepend(d)}}else(h=o.querySelector(".tpm-desktop-bar"))==null||h.remove();this._applyDimensions(r.width,r.height,r.scale),this._updateInfo(r),this.modal.querySelectorAll(".tpm-device-btn").forEach(d=>{d.classList.toggle("active",d.dataset.device===e)});const m=this.modal.querySelector("#tpm-rotate");m.style.display=e==="desktop"?"none":"flex"}_applyDimensions(e,t,r){const o=this.modal.querySelector("#tpm-device-frame"),m=this.modal.querySelector("#tpm-device-wrapper");o.style.width=e+"px",o.style.height=t+"px",m.style.transform=`scale(${r})`}_rotate(){const e=s[this.currentDevice];this.isLandscape=!this.isLandscape;const t=this.modal.querySelector("#tpm-device-wrapper");t.classList.add("rotating"),setTimeout(()=>t.classList.remove("rotating"),500),this.isLandscape?(this._applyDimensions(e.height,e.width,e.scale*.85),this._updateInfo({...e,width:e.height,height:e.width,label:e.label+" (横屏)"})):(this._applyDimensions(e.width,e.height,e.scale),this._updateInfo(e))}_updateInfo(e){const t=this.modal.querySelector("#tpm-device-frame"),r=parseInt(t.style.width),o=parseInt(t.style.height);this.modal.querySelector("#tpm-device-info").textContent=`${r} × ${o}`,this.modal.querySelector("#tpm-device-label").textContent=e.label+" 预览"}_setupEvents(){this.modal.querySelector("#tpm-close").addEventListener("click",()=>this.close()),this.modal.addEventListener("click",e=>{e.target===this.modal&&this.close()}),this._keyHandler=e=>{e.key==="Escape"&&this.close()},document.addEventListener("keydown",this._keyHandler),this.modal.querySelectorAll(".tpm-device-btn").forEach(e=>{e.addEventListener("click",()=>this._switchDevice(e.dataset.device))}),this.modal.querySelector("#tpm-rotate").addEventListener("click",()=>this._rotate())}destroy(){document.removeEventListener("keydown",this._keyHandler),this.close()}}function c(i,e={}){return i.addCommand("mceMultiPreview",function(){new p(i,e).open()}),i.ui.registry.addButton("multipreview",{tooltip:"多端预览",icon:"preview",onAction:()=>i.execCommand("mceMultiPreview")}),i.ui.registry.addMenuItem("multipreview",{text:"多端预览",icon:"preview",onAction:()=>i.execCommand("mceMultiPreview")}),{getMetadata:()=>({name:"Multi-Device Preview Plugin",url:"https://github.com/your-org/tinymce-plugin-multipreview"})}}typeof window<"u"&&(typeof window.tinymce<"u"&&window.tinymce.PluginManager.add(a,function(i){const e=i.getParam("multipreview",{});return c(i,e)}),window.TinyMCEPreviewPlugin={PreviewController:p,DEVICES:s,getFullHtml:l,initPlugin:c}),typeof module<"u"&&module.exports&&(module.exports={PreviewController:p,DEVICES:s,getFullHtml:l,initPlugin:c,PLUGIN_NAME:a}),n.DEVICES=s,n.PLUGIN_NAME=a,n.PreviewController=p,n.getFullHtml=l,n.initPlugin=c,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinymce-plugin-multipreview",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "TinyMCE 多端预览插件,支持手机、平板、PC 三种视口预览",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/plugin.esm.js",