163 lines
7.9 KiB
JavaScript
163 lines
7.9 KiB
JavaScript
function getTiptap() {
|
|
return window.Tiptap || {};
|
|
}
|
|
|
|
function buildTpToolbar(mini, isOption) {
|
|
var btns;
|
|
if (isOption) {
|
|
btns = '<button type="button" class="tp-btn" data-cmd="bold" title="Bold"><i class="fas fa-bold"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="italic" title="Italic"><i class="fas fa-italic"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="link" title="Link"><i class="fas fa-link"></i></button>';
|
|
} else if (mini) {
|
|
btns = '<button type="button" class="tp-btn" data-cmd="bold" title="Bold"><i class="fas fa-bold"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="italic" title="Italic"><i class="fas fa-italic"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="bulletList" title="List"><i class="fas fa-list-ul"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="link" title="Link"><i class="fas fa-link"></i></button>';
|
|
} else {
|
|
btns = '<button type="button" class="tp-btn" data-cmd="bold" title="Bold"><i class="fas fa-bold"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="italic" title="Italic"><i class="fas fa-italic"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="underline" title="Underline"><i class="fas fa-underline"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="strike" title="Strike"><i class="fas fa-strikethrough"></i></button>' +
|
|
'<span class="tp-sep"></span>' +
|
|
'<button type="button" class="tp-btn" data-cmd="h2" title="Heading 2">H2</button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="h3" title="Heading 3">H3</button>' +
|
|
'<span class="tp-sep"></span>' +
|
|
'<button type="button" class="tp-btn" data-cmd="bulletList" title="Bullet List"><i class="fas fa-list-ul"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="orderedList" title="Numbered List"><i class="fas fa-list-ol"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="blockquote" title="Blockquote"><i class="fas fa-quote-left"></i></button>' +
|
|
'<button type="button" class="tp-btn" data-cmd="codeBlock" title="Code"><i class="fas fa-code"></i></button>' +
|
|
'<span class="tp-sep"></span>' +
|
|
'<button type="button" class="tp-btn" data-cmd="link" title="Link"><i class="fas fa-link"></i></button>' +
|
|
'<span class="tp-sep"></span>' +
|
|
'<button type="button" class="tp-btn" data-cmd="clear" title="Clear formatting"><i class="fas fa-remove-format"></i></button>';
|
|
}
|
|
return '<div class="tp-toolbar' + (mini ? ' tp-toolbar-mini' : '') + '">' + btns + '</div>' +
|
|
'<div class="tp-link-bar" style="display:none;">' +
|
|
'<input type="url" class="tp-link-input" placeholder="https://">' +
|
|
'<button type="button" class="tp-link-apply">Apply</button>' +
|
|
'<button type="button" class="tp-link-remove">Remove</button>' +
|
|
'<button type="button" class="tp-link-cancel">×</button>' +
|
|
'</div>';
|
|
}
|
|
|
|
function wireTpToolbar(wrap, editor) {
|
|
var linkBar = wrap.querySelector('.tp-link-bar');
|
|
var linkInput = wrap.querySelector('.tp-link-input');
|
|
|
|
wrap.querySelector('.tp-toolbar').addEventListener('mousedown', function(e) {
|
|
var btn = e.target.closest('.tp-btn[data-cmd]');
|
|
if (!btn) return;
|
|
e.preventDefault();
|
|
var cmd = btn.dataset.cmd;
|
|
switch (cmd) {
|
|
case 'bold': editor.chain().focus().toggleBold().run(); break;
|
|
case 'italic': editor.chain().focus().toggleItalic().run(); break;
|
|
case 'underline': editor.chain().focus().toggleUnderline().run(); break;
|
|
case 'strike': editor.chain().focus().toggleStrike().run(); break;
|
|
case 'h2': editor.chain().focus().toggleHeading({ level: 2 }).run(); break;
|
|
case 'h3': editor.chain().focus().toggleHeading({ level: 3 }).run(); break;
|
|
case 'bulletList': editor.chain().focus().toggleBulletList().run(); break;
|
|
case 'orderedList': editor.chain().focus().toggleOrderedList().run(); break;
|
|
case 'blockquote': editor.chain().focus().toggleBlockquote().run(); break;
|
|
case 'codeBlock': editor.chain().focus().toggleCodeBlock().run(); break;
|
|
case 'clear': editor.chain().focus().unsetAllMarks().clearNodes().run(); break;
|
|
case 'link':
|
|
if (linkBar.style.display === 'none') {
|
|
linkInput.value = editor.getAttributes('link').href || '';
|
|
linkBar.style.display = 'flex';
|
|
setTimeout(function() { linkInput.focus(); }, 0);
|
|
} else {
|
|
linkBar.style.display = 'none';
|
|
}
|
|
break;
|
|
}
|
|
updateTpState(wrap, editor);
|
|
});
|
|
|
|
wrap.querySelector('.tp-link-apply').addEventListener('mousedown', function(e) {
|
|
e.preventDefault();
|
|
var url = linkInput.value.trim();
|
|
if (url) editor.chain().focus().setLink({ href: url }).run();
|
|
linkBar.style.display = 'none';
|
|
});
|
|
wrap.querySelector('.tp-link-remove').addEventListener('mousedown', function(e) {
|
|
e.preventDefault();
|
|
editor.chain().focus().unsetLink().run();
|
|
linkBar.style.display = 'none';
|
|
});
|
|
wrap.querySelector('.tp-link-cancel').addEventListener('mousedown', function(e) {
|
|
e.preventDefault();
|
|
linkBar.style.display = 'none';
|
|
});
|
|
linkInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') { e.preventDefault(); wrap.querySelector('.tp-link-apply').dispatchEvent(new MouseEvent('mousedown')); }
|
|
if (e.key === 'Escape') linkBar.style.display = 'none';
|
|
});
|
|
}
|
|
|
|
function updateTpState(wrap, editor) {
|
|
wrap.querySelectorAll('.tp-btn[data-cmd]').forEach(function(btn) {
|
|
var a = false;
|
|
switch (btn.dataset.cmd) {
|
|
case 'bold': a = editor.isActive('bold'); break;
|
|
case 'italic': a = editor.isActive('italic'); break;
|
|
case 'underline': a = editor.isActive('underline'); break;
|
|
case 'strike': a = editor.isActive('strike'); break;
|
|
case 'h2': a = editor.isActive('heading', { level: 2 }); break;
|
|
case 'h3': a = editor.isActive('heading', { level: 3 }); break;
|
|
case 'bulletList': a = editor.isActive('bulletList'); break;
|
|
case 'orderedList': a = editor.isActive('orderedList'); break;
|
|
case 'blockquote': a = editor.isActive('blockquote'); break;
|
|
case 'codeBlock': a = editor.isActive('codeBlock'); break;
|
|
case 'link': a = editor.isActive('link'); break;
|
|
}
|
|
btn.classList.toggle('active', a);
|
|
});
|
|
}
|
|
|
|
export function makeTpEditor(wrap, existingHtml, mini, isOption) {
|
|
var T = getTiptap();
|
|
wrap.innerHTML = buildTpToolbar(mini, isOption) + '<div class="tp-content"></div>';
|
|
var ed = new T.Editor({
|
|
element: wrap.querySelector('.tp-content'),
|
|
extensions: [
|
|
T.StarterKit,
|
|
T.Link.configure({ openOnClick: false, autolink: true }),
|
|
T.Underline
|
|
],
|
|
content: existingHtml || '',
|
|
onUpdate: function() { updateTpState(wrap, ed); },
|
|
onSelectionUpdate: function() { updateTpState(wrap, ed); }
|
|
});
|
|
wireTpToolbar(wrap, ed);
|
|
return ed;
|
|
}
|
|
|
|
export function getTpHTML(editor) {
|
|
if (!editor) return '';
|
|
var html = editor.getHTML();
|
|
return (html === '<p></p>' || html === '') ? '' : html;
|
|
}
|
|
|
|
export function destroyTpEditor(editor) {
|
|
if (editor && typeof editor.destroy === 'function') editor.destroy();
|
|
}
|
|
|
|
export function destroyOptionEditor(row) {
|
|
if (!row || !row._optEditor) return;
|
|
destroyTpEditor(row._optEditor);
|
|
row._optEditor = null;
|
|
}
|
|
|
|
export function destroyQuestionBlockEditors(block) {
|
|
if (!block) return;
|
|
destroyTpEditor(block._questionEditor);
|
|
block._questionEditor = null;
|
|
block.querySelectorAll('.lh-option-row').forEach(destroyOptionEditor);
|
|
}
|
|
|
|
export function clearQuestionBlocks(container) {
|
|
if (!container) return;
|
|
container.querySelectorAll('.lh-question-block').forEach(destroyQuestionBlockEditors);
|
|
container.innerHTML = '';
|
|
}
|