PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / markdown-preview / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/markdown-preview/frontend.js

378 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 decoration:'text-decoration', transform:'text-transform',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 Object.keys(_typoKeys).forEach(function (k) {
15 var v = obj[k];
16 if (v === undefined || v === '' || v === null) return;
17 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
18 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
19 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
20 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
21 el.style.setProperty(prefix + _typoKeys[k], String(v));
22 });
23 }
24
25 /* ─── Lightweight Markdown Parser ─── */
26 function parseMarkdown(md) {
27 var lines = md.split('\n');
28 var html = '';
29 var i = 0;
30
31 function escHtml(s) {
32 return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
33 }
34
35 function inlineFormat(s) {
36 s = escHtml(s);
37 s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
38 s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
39 s = s.replace(/`([^`]+)`/g, '<code>$1</code>');
40 s = s.replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>');
41 s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
42 s = s.replace(/\*([^*]+)\*/g, '<em>$1</em>');
43 s = s.replace(/___([^_]+)___/g, '<strong><em>$1</em></strong>');
44 s = s.replace(/__([^_]+)__/g, '<strong>$1</strong>');
45 s = s.replace(/_([^_]+)_/g, '<em>$1</em>');
46 s = s.replace(/~~([^~]+)~~/g, '<del>$1</del>');
47 s = s.replace(/ $/, '<br>');
48 return s;
49 }
50
51 while (i < lines.length) {
52 var line = lines[i];
53
54 /* Fenced code block */
55 if (/^```/.test(line)) {
56 var lang = line.slice(3).trim();
57 var code = '';
58 i++;
59 while (i < lines.length && !/^```/.test(lines[i])) {
60 code += escHtml(lines[i]) + '\n';
61 i++;
62 }
63 html += '<pre><code' + (lang ? ' class="language-' + escHtml(lang) + '"' : '') + '>' + code + '</code></pre>';
64 i++;
65 continue;
66 }
67
68 /* ATX Headings */
69 var hm = line.match(/^(#{1,6})\s+(.+)/);
70 if (hm) {
71 var level = hm[1].length;
72 html += '<h' + level + '>' + inlineFormat(hm[2].trim()) + '</h' + level + '>';
73 i++;
74 continue;
75 }
76
77 /* Horizontal rule */
78 if (/^([-*_]){3,}\s*$/.test(line)) {
79 html += '<hr>';
80 i++;
81 continue;
82 }
83
84 /* Blockquote */
85 if (/^>\s?/.test(line)) {
86 var bq = '';
87 while (i < lines.length && /^>\s?/.test(lines[i])) {
88 bq += lines[i].replace(/^>\s?/, '') + '\n';
89 i++;
90 }
91 html += '<blockquote>' + parseMarkdown(bq.trim()) + '</blockquote>';
92 continue;
93 }
94
95 /* Unordered list */
96 if (/^[-*+]\s/.test(line)) {
97 html += '<ul>';
98 while (i < lines.length && /^[-*+]\s/.test(lines[i])) {
99 html += '<li>' + inlineFormat(lines[i].replace(/^[-*+]\s/, '')) + '</li>';
100 i++;
101 }
102 html += '</ul>';
103 continue;
104 }
105
106 /* Ordered list */
107 if (/^\d+\.\s/.test(line)) {
108 html += '<ol>';
109 while (i < lines.length && /^\d+\.\s/.test(lines[i])) {
110 html += '<li>' + inlineFormat(lines[i].replace(/^\d+\.\s/, '')) + '</li>';
111 i++;
112 }
113 html += '</ol>';
114 continue;
115 }
116
117 /* Table (GFM) */
118 if (/\|/.test(line) && i + 1 < lines.length && /^\|?[-:| ]+\|/.test(lines[i + 1])) {
119 var headers = line.split('|').filter(function (c, idx, arr) { return idx > 0 || c.trim(); }).map(function (c) { return c.trim(); });
120 i += 2;
121 html += '<table><thead><tr>';
122 headers.forEach(function (h) { html += '<th>' + inlineFormat(h) + '</th>'; });
123 html += '</tr></thead><tbody>';
124 while (i < lines.length && /\|/.test(lines[i])) {
125 var cells = lines[i].split('|').filter(function (c, idx) { return idx > 0 || c.trim(); }).map(function (c) { return c.trim(); });
126 html += '<tr>';
127 cells.forEach(function (c) { html += '<td>' + inlineFormat(c) + '</td>'; });
128 html += '</tr>';
129 i++;
130 }
131 html += '</tbody></table>';
132 continue;
133 }
134
135 /* Empty line → paragraph separator */
136 if (/^\s*$/.test(line)) {
137 html += '';
138 i++;
139 continue;
140 }
141
142 /* Paragraph */
143 var para = '';
144 while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^#{1,6}\s/.test(lines[i]) && !/^[-*+]\s/.test(lines[i]) && !/^\d+\.\s/.test(lines[i]) && !/^>/.test(lines[i]) && !/^```/.test(lines[i]) && !/^([-*_]){3,}\s*$/.test(lines[i]) && !/\|/.test(lines[i])) {
145 if (para) para += ' ';
146 para += lines[i];
147 i++;
148 }
149 if (para) html += '<p>' + inlineFormat(para) + '</p>';
150 }
151
152 return html;
153 }
154
155
156 function initBlock(root) {
157 var opts;
158 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
159 var a = opts;
160
161 root.innerHTML = '';
162
163 var wrap = document.createElement('div');
164 wrap.className = 'bkbg-mdp-wrap';
165 wrap.style.cssText = 'max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
166 typoCssVarsForEl(wrap, a.titleTypo, '--bkbg-mdp-tt-');
167 root.appendChild(wrap);
168
169 if (a.showTitle) {
170 var h = document.createElement('div');
171 h.className = 'bkbg-mdp-title';
172 h.style.color = a.titleColor;
173 h.textContent = a.title;
174 wrap.appendChild(h);
175 }
176
177 var container = document.createElement('div');
178 container.className = 'bkbg-mdp-container';
179 container.style.cssText = 'border-color:' + a.borderColor + ';';
180 wrap.appendChild(container);
181
182 /* Toolbar */
183 if (a.showToolbar) {
184 var toolbar = document.createElement('div');
185 toolbar.className = 'bkbg-mdp-toolbar';
186 toolbar.style.cssText = 'background:' + a.toolbarBg + ';color:' + a.toolbarColor + ';';
187 container.appendChild(toolbar);
188
189 var tools = [
190 { label: 'B', title: 'Bold', prefix: '**', suffix: '**', wrap: true },
191 { label: 'I', title: 'Italic', prefix: '_', suffix: '_', wrap: true },
192 { label: 'S', title: 'Strikethrough',prefix: '~~', suffix: '~~', wrap: true },
193 { label: 'H1', title: 'Heading 1', line: '# ' },
194 { label: 'H2', title: 'Heading 2', line: '## ' },
195 { label: 'H3', title: 'Heading 3', line: '### ' },
196 { label: '{ }', title: 'Inline Code', prefix: '`', suffix: '`', wrap: true },
197 { label: '🔗', title: 'Link', prefix: '[', suffix: '](url)', wrap: true },
198 { label: '', title: 'Bullet List', line: '- ' },
199 { label: '1.', title: 'Ordered List', line: '1. ' },
200 { label: '', title: 'Blockquote', line: '> ' },
201 { label: '', title: 'Horizontal Rule', insert: '\n---\n' }
202 ];
203
204 tools.forEach(function (tool, idx) {
205 if (idx === 3 || idx === 6 || idx === 12) {
206 var sep = document.createElement('div');
207 sep.className = 'bkbg-mdp-toolbar-sep';
208 toolbar.appendChild(sep);
209 }
210 var btn = document.createElement('button');
211 btn.className = 'bkbg-mdp-tool-btn';
212 btn.style.color = a.toolbarColor;
213 btn.textContent = tool.label;
214 btn.title = tool.title;
215 btn.addEventListener('click', function () {
216 var ta = editorEl;
217 var start = ta.selectionStart, end = ta.selectionEnd;
218 var sel = ta.value.slice(start, end);
219 var before = ta.value.slice(0, start);
220 var after = ta.value.slice(end);
221 var newVal, newCaret;
222
223 if (tool.insert) {
224 newVal = before + tool.insert + after;
225 newCaret = start + tool.insert.length;
226 } else if (tool.wrap) {
227 var replacement = tool.prefix + (sel || 'text') + tool.suffix;
228 newVal = before + replacement + after;
229 newCaret = start + replacement.length;
230 } else if (tool.line) {
231 var lineStart = before.lastIndexOf('\n') + 1;
232 var lineBefore = ta.value.slice(0, lineStart);
233 var lineContent = ta.value.slice(lineStart);
234 newVal = lineBefore + tool.line + lineContent;
235 newCaret = lineStart + tool.line.length + (end - lineStart);
236 }
237
238 ta.value = newVal;
239 ta.selectionStart = ta.selectionEnd = newCaret;
240 ta.focus();
241 ta.dispatchEvent(new Event('input'));
242 });
243 toolbar.appendChild(btn);
244 });
245
246 /* Copy buttons */
247 if (a.showCopyBtn) {
248 var tbRight = document.createElement('div');
249 tbRight.className = 'bkbg-mdp-toolbar-right';
250 toolbar.appendChild(tbRight);
251
252 var copyMdBtn = document.createElement('button');
253 copyMdBtn.className = 'bkbg-mdp-copy-btn';
254 copyMdBtn.style.background = a.accentColor;
255 copyMdBtn.textContent = 'Copy MD';
256 copyMdBtn.addEventListener('click', function () {
257 navigator.clipboard && navigator.clipboard.writeText(editorEl.value).then(function () {
258 copyMdBtn.textContent = '✓ Copied';
259 setTimeout(function () { copyMdBtn.textContent = 'Copy MD'; }, 1500);
260 });
261 });
262
263 var copyHtmlBtn = document.createElement('button');
264 copyHtmlBtn.className = 'bkbg-mdp-copy-btn';
265 copyHtmlBtn.style.background = '#64748b';
266 copyHtmlBtn.textContent = 'Copy HTML';
267 copyHtmlBtn.addEventListener('click', function () {
268 navigator.clipboard && navigator.clipboard.writeText(previewEl.innerHTML).then(function () {
269 copyHtmlBtn.textContent = '✓ Copied';
270 setTimeout(function () { copyHtmlBtn.textContent = 'Copy HTML'; }, 1500);
271 });
272 });
273
274 tbRight.appendChild(copyMdBtn);
275 tbRight.appendChild(copyHtmlBtn);
276 }
277 }
278
279 /* Split */
280 var split = document.createElement('div');
281 split.className = 'bkbg-mdp-split';
282 split.style.height = a.editorHeight + 'px';
283 container.appendChild(split);
284
285 /* Editor pane */
286 var editorPane = document.createElement('div');
287 editorPane.className = 'bkbg-mdp-pane';
288 editorPane.style.cssText = 'background:' + a.editorBg + ';border-right:1px solid rgba(255,255,255,0.08);';
289 split.appendChild(editorPane);
290
291 var editorLabel = document.createElement('div');
292 editorLabel.className = 'bkbg-mdp-pane-label';
293 editorLabel.style.cssText = 'color:' + a.toolbarColor + ';background:' + a.toolbarBg + ';border-color:rgba(255,255,255,0.08);';
294 editorLabel.textContent = 'Markdown';
295 editorPane.appendChild(editorLabel);
296
297 var editorEl = document.createElement('textarea');
298 editorEl.className = 'bkbg-mdp-editor';
299 editorEl.style.cssText = 'background:' + a.editorBg + ';color:' + a.editorColor + ';';
300 editorEl.value = a.defaultContent;
301 editorEl.spellcheck = false;
302 editorPane.appendChild(editorEl);
303
304 /* Preview pane */
305 var previewPane = document.createElement('div');
306 previewPane.className = 'bkbg-mdp-pane';
307 previewPane.style.background = a.previewBg;
308 split.appendChild(previewPane);
309
310 var previewLabel = document.createElement('div');
311 previewLabel.className = 'bkbg-mdp-pane-label';
312 previewLabel.style.cssText = 'color:#6b7280;border-color:' + a.borderColor + ';background:#f9fafb;';
313 previewLabel.textContent = 'Preview';
314 previewPane.appendChild(previewLabel);
315
316 var previewEl = document.createElement('div');
317 previewEl.className = 'bkbg-mdp-preview-pane';
318 previewEl.style.color = a.previewColor;
319 previewPane.appendChild(previewEl);
320
321 /* Status bar */
322 if (a.showWordCount) {
323 var statusBar = document.createElement('div');
324 statusBar.className = 'bkbg-mdp-statusbar';
325 statusBar.style.cssText = 'background:' + a.toolbarBg + ';color:' + a.toolbarColor + ';border-color:rgba(255,255,255,0.08);';
326 var wordCountEl = document.createElement('span');
327 statusBar.appendChild(wordCountEl);
328 var charCountEl = document.createElement('span');
329 statusBar.appendChild(charCountEl);
330 container.appendChild(statusBar);
331 }
332
333 function updateWordCount(text) {
334 if (!a.showWordCount) return;
335 var words = text.trim() ? text.trim().split(/\s+/).length : 0;
336 wordCountEl.textContent = words + ' words';
337 charCountEl.textContent = text.length + ' chars';
338 }
339
340 function render() {
341 previewEl.innerHTML = parseMarkdown(editorEl.value);
342 updateWordCount(editorEl.value);
343 }
344
345 editorEl.addEventListener('input', function () { render(); });
346
347 /* Tab key support */
348 editorEl.addEventListener('keydown', function (e) {
349 if (e.key === 'Tab') {
350 e.preventDefault();
351 var start = editorEl.selectionStart, end = editorEl.selectionEnd;
352 editorEl.value = editorEl.value.slice(0, start) + ' ' + editorEl.value.slice(end);
353 editorEl.selectionStart = editorEl.selectionEnd = start + 2;
354 }
355 });
356
357 /* Sync scroll */
358 if (a.syncScroll) {
359 editorEl.addEventListener('scroll', function () {
360 var pct = editorEl.scrollTop / (editorEl.scrollHeight - editorEl.clientHeight || 1);
361 previewEl.scrollTop = pct * (previewEl.scrollHeight - previewEl.clientHeight);
362 });
363 }
364
365 render();
366 }
367
368 function init() {
369 document.querySelectorAll('.bkbg-mdp-app').forEach(initBlock);
370 }
371
372 if (document.readyState === 'loading') {
373 document.addEventListener('DOMContentLoaded', init);
374 } else {
375 init();
376 }
377 })();
378