diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index 90d4516a3..f5a12d61d 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -440,20 +440,28 @@ pre.spolicyviodisp { font-weight: bold; color: #1a73e8; } +.decomment-editor { + margin-top: 10px; + border: 1px solid #ddd; + border-radius: 8px; + overflow: hidden; + transition: border-color 0.2s; +} +.decomment-editor:focus-within { + border-color: #709dff; +} .decommentinput { display: block; width: 100%; - margin-top: 10px; padding: 8px; font-size: 12px; - border: 1px solid #ddd; - border-radius: 8px; + border: none; + border-radius: 0; box-sizing: border-box; - transition: border-color 0.2s; + resize: vertical; } .decommentinput:focus { outline: none; - border-color: #709dff; } .decomment-alias-input-area { margin-top: 20px; @@ -526,6 +534,31 @@ pre.spolicyviodisp { .decommentmodal-close-btn:hover { background-color: #f0f0f0; } +.decomment-toolbar { + display: flex; + gap: 4px; + padding: 2px 6px; + background-color: #f6f8fa; + border-bottom: 1px solid #ddd; +} +.decomment-toolbar-btn { + width: 22px; + height: 22px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + cursor: pointer; + font-size: 11px; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.15s, border-color 0.15s; + padding: 0; +} +.decomment-toolbar-btn:hover { + background-color: #eaeef2; + border-color: #ddd; +} .decomment-user-input { display: none; } @@ -1001,7 +1034,12 @@ ${schemaPolicyDisplay.policyMessageHtmlPreText}
Input the new comment of
- +to prevent empty line when opening in plain schema html return result.replace(/^
/i, '').trim(); + }, + /** + * Render strikethrough markdown syntax (~~text~~) as HTML del tags for display. + * @param {string} string - decomment text where text wrapped in ~~ is the strikethrough target (e.g. ~~foo~~ renders foo as strikethrough) + * @returns {string} text with ~~text~~ converted to
text+ */ + this.renderStrikethrough = function(string) { + if (!string) { + return string; + } + return string.replace(/~~(.+?)~~/g, '$1'); } } @@ -2119,7 +2168,7 @@ PickupTableProperty.prototype = { var decomment = this.decomments[key]; // strip dfalias:{} or shalias:{} from display var cleanBody = DecommentUtil.removeAliasFromDecomment(decomment.body); - displayComment += cleanBody; + displayComment += DecommentUtil.renderStrikethrough(cleanBody); if (this.isConflict() && decomment.pieceGitBranch) { displayComment += '\n=======' + decomment.pieceGitBranch + ' (' + decomment.pieceOwner + ')'; if (key < commentCount - 1) { @@ -2164,11 +2213,15 @@ PickupTableProperty.prototype = { * @return {string} display html comment */ getDisplayHtmlComment: function() { - var child = this.selectTargetHtmlElements()[0].childNodes[0]; - if (child != null && child.nodeName === "#text") { - return child.nodeValue.trim(); + var element = this.selectTargetHtmlElements()[0]; + if (!element) { + return ""; } - return "" + var html = element.innerHTML.replace(/@author\([^)]*\)<\/span>/g, '').trim(); + if (html === ' ') { + return ''; + } + return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, /** @@ -2360,7 +2413,7 @@ PickupColumnProperty.prototype = { var decomment = this.decomments[key]; // strip dfalias:{} or shalias:{} from display var cleanBody = DecommentUtil.removeAliasFromDecomment(decomment.body); - displayComment += cleanBody; + displayComment += DecommentUtil.renderStrikethrough(cleanBody); if (this.isConflict() && decomment.pieceGitBranch) { displayComment += '\n=======' + decomment.pieceGitBranch + ' (' + decomment.pieceOwner + ')'; if (key < commentCount - 1) { @@ -2402,11 +2455,15 @@ PickupColumnProperty.prototype = { * @return {string} display html comment */ getDisplayHtmlComment: function() { - var child = this.selectTargetHtmlElement().childNodes[0]; - if (child != null && child.nodeName === "#text") { - return child.nodeValue.trim(); + var element = this.selectTargetHtmlElement(); + if (!element) { + return ""; + } + var html = element.innerHTML.replace(/@author\([^)]*\)<\/span>/g, '').trim(); + if (html === ' ') { + return ''; } - return "" + return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, /** @@ -2507,6 +2564,50 @@ PickupColumnProperty.prototype = { // /\ // // _/_/_/_/_/_/_/_/ +/** + * Apply formatting syntax around selected text in a textarea. + * If no text is selected, inserts the syntax markers and places the cursor between them. + * @param {HTMLTextAreaElement} textarea - the textarea element containing the text to format + * @param {string} syntax - the wrapping marker to apply around the selected text (e.g. '~~' for strikethrough) + */ +function applyFormatting(textarea, syntax) { + var start = textarea.selectionStart; + var end = textarea.selectionEnd; + var selectedText = textarea.value.substring(start, end); + + // focus first: execCommand requires the textarea to be focused to know where to act + textarea.focus(); + if (selectedText.length === 0) { + // no selection: insert empty markers and place cursor between them (e.g. "~~~~" -> cursor in middle) + document.execCommand('insertText', false, syntax + syntax); + textarea.setSelectionRange(start + syntax.length, start + syntax.length); + } else { + var lines = selectedText.split('\n'); + // toggle off only if every non-empty line is already wrapped + var allWrapped = lines.every(function(line) { + return line.length === 0 || (line.startsWith(syntax) && line.endsWith(syntax) && line.length > syntax.length * 2); + }); + if (allWrapped) { + // already wrapped on all lines: strip markers from each line individually + var unwrapped = lines.map(function(line) { + return line.length === 0 ? line : line.slice(syntax.length, -syntax.length); + }).join('\n'); + document.execCommand('insertText', false, unwrapped); + textarea.setSelectionRange(start, start + unwrapped.length); + } else { + // not wrapped: apply markers to each non-empty line individually, skipping already-wrapped lines + // execCommand is used instead of textarea.value= to preserve the browser's native undo stack (Cmd+Z) + var wrapped = lines.map(function(line) { + if (line.length === 0) { return line; } + if (line.startsWith(syntax) && line.endsWith(syntax) && line.length > syntax.length * 2) { return line; } + return syntax + line + syntax; + }).join('\n'); + document.execCommand('insertText', false, wrapped); + textarea.setSelectionRange(start, start + wrapped.length); + } + } +} + /** * @author deco * @author cabos @@ -2542,6 +2643,7 @@ Decomment.prototype = { } else { // #plain_opening this.reflectEmbeddedPickup(); } + this.setupFormattingToolbar(); }, /** @@ -2896,6 +2998,24 @@ Decomment.prototype = { return previousElement.value; }, + /** + * Setup formatting toolbar button events + */ + setupFormattingToolbar: function() { + var textarea = document.getElementById('decomment-input'); + var buttons = [ + { id: 'decomment-toolbar-strikethrough', syntax: '~~' } + ]; + buttons.forEach(function(btn) { + var el = document.getElementById(btn.id); + if (!el) { return; } + el.addEventListener('mousedown', function(e) { + e.preventDefault(); + applyFormatting(textarea, btn.syntax); + }); + }); + }, + /** * Check still conflicting * @param {string} decomment - decomment