From 7a1387553ec43ef4bdc244ff47a468a8103a344e Mon Sep 17 00:00:00 2001 From: shiny Date: Fri, 13 Mar 2026 23:25:03 +0900 Subject: [PATCH 1/6] feat: add toolbar with strikthrough button to enable strikethroughs in decomment dbflute/dbflute-intro#55 --- .../embedded/templates/doc/html/datamodel.vm | 125 +++++++++++++++--- 1 file changed, 109 insertions(+), 16 deletions(-) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index 90d4516a3..67406a6a2 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}
x

Input the new comment of

- +
+
+ +
+ +
@@ -1577,6 +1615,17 @@ var DecommentUtil = new function(){ var result = decomment.replace(/(?:dfalias|shalias):\s*\{[^}]*\}\s*\n?/g, ''); // remove leading
to prevent empty line when opening in plain schema html return result.replace(/^/i, '').trim(); + }, + /** + * Render strikethrough (~~text~~) as HTML del tags + * @param {string} string - text + * @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,12 @@ 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(); + return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, /** @@ -2360,7 +2410,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 +2452,12 @@ 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 ""; } - return "" + var html = element.innerHTML.replace(/@author\([^)]*\)<\/span>/g, '').trim(); + return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, /** @@ -2507,6 +2558,29 @@ 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 + * @param {string} syntax - the wrapping marker (e.g. '~~') + */ +function applyFormatting(textarea, syntax) { + var start = textarea.selectionStart; + var end = textarea.selectionEnd; + var selectedText = textarea.value.substring(start, end); + var before = textarea.value.substring(0, start); + var after = textarea.value.substring(end); + textarea.value = before + syntax + selectedText + syntax + after; + if (selectedText.length === 0) { + // place cursor between the markers + textarea.setSelectionRange(start + syntax.length, start + syntax.length); + } else { + // keep the wrapped text selected + textarea.setSelectionRange(start, end + syntax.length * 2); + } + textarea.focus(); +} + /** * @author deco * @author cabos @@ -2542,6 +2616,7 @@ Decomment.prototype = { } else { // #plain_opening this.reflectEmbeddedPickup(); } + this.setupFormattingToolbar(); }, /** @@ -2896,6 +2971,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 From 49b8bd439c04626390d40dcc636e6cb279f69d63 Mon Sep 17 00:00:00 2001 From: shinyonogi Date: Fri, 27 Mar 2026 22:02:04 +0900 Subject: [PATCH 2/6] fix: strikethrough appears in between when no selected and enables unwrap dbflute/dbflute-intro#55 --- .../embedded/templates/doc/html/datamodel.vm | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index 67406a6a2..de555876a 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -2568,17 +2568,22 @@ function applyFormatting(textarea, syntax) { var start = textarea.selectionStart; var end = textarea.selectionEnd; var selectedText = textarea.value.substring(start, end); - var before = textarea.value.substring(0, start); - var after = textarea.value.substring(end); - textarea.value = before + syntax + selectedText + syntax + after; + + textarea.focus(); if (selectedText.length === 0) { - // place cursor between the markers + // no selection: insert markers and place cursor between them + document.execCommand('insertText', false, syntax + syntax); textarea.setSelectionRange(start + syntax.length, start + syntax.length); + } else if (selectedText.startsWith(syntax) && selectedText.endsWith(syntax) && selectedText.length > syntax.length * 2) { + // already wrapped: unwrap + var unwrapped = selectedText.slice(syntax.length, -syntax.length); + document.execCommand('insertText', false, unwrapped); + textarea.setSelectionRange(start, start + unwrapped.length); } else { - // keep the wrapped text selected + // not wrapped: wrap + document.execCommand('insertText', false, syntax + selectedText + syntax); textarea.setSelectionRange(start, end + syntax.length * 2); } - textarea.focus(); } /** From d22599aeabc190667f1e4b1ef78f68bfb5c46a64 Mon Sep 17 00:00:00 2001 From: shinyonogi Date: Fri, 24 Apr 2026 22:17:27 +0900 Subject: [PATCH 3/6] fix: format indent and add detailed jsdoc comments dbflute/dbflute-intro#55 --- .../embedded/templates/doc/html/datamodel.vm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index de555876a..bc4ec373b 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -1617,8 +1617,8 @@ var DecommentUtil = new function(){ return result.replace(/^/i, '').trim(); }, /** - * Render strikethrough (~~text~~) as HTML del tags - * @param {string} string - text + * 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) { @@ -2561,8 +2561,8 @@ 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 - * @param {string} syntax - the wrapping marker (e.g. '~~') + * @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; @@ -2989,7 +2989,7 @@ Decomment.prototype = { if (!el) { return; } el.addEventListener('mousedown', function(e) { e.preventDefault(); - applyFormatting(textarea, btn.syntax); + applyFormatting(textarea, btn.syntax); }); }); }, From 688248c9b2c9dd3a8271534e0e226c97ddb7daea Mon Sep 17 00:00:00 2001 From: shinyonogi Date: Fri, 24 Apr 2026 22:25:06 +0900 Subject: [PATCH 4/6] fix: prevents to show   when empty decomment --- dbflute-engine/embedded/templates/doc/html/datamodel.vm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index bc4ec373b..cf6f313d1 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -2218,6 +2218,9 @@ PickupTableProperty.prototype = { return ""; } var html = element.innerHTML.replace(/@author\([^)]*\)<\/span>/g, '').trim(); + if (html === ' ') { + return ''; + } return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, @@ -2457,6 +2460,9 @@ PickupColumnProperty.prototype = { return ""; } var html = element.innerHTML.replace(/@author\([^)]*\)<\/span>/g, '').trim(); + if (html === ' ') { + return ''; + } return html.replace(/([\s\S]+?)<\/del>/g, '~~$1~~'); }, From 3bf6c4e635f13c935a3086e2869271dec096310f Mon Sep 17 00:00:00 2001 From: shinyonogi Date: Fri, 24 Apr 2026 22:26:11 +0900 Subject: [PATCH 5/6] feat: strikethrough supports multiple lines dbflute/dbflute-intro#55 --- .../embedded/templates/doc/html/datamodel.vm | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index cf6f313d1..3737bfad2 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -2575,20 +2575,34 @@ function applyFormatting(textarea, syntax) { 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 markers and place cursor between them + // 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 if (selectedText.startsWith(syntax) && selectedText.endsWith(syntax) && selectedText.length > syntax.length * 2) { - // already wrapped: unwrap - var unwrapped = selectedText.slice(syntax.length, -syntax.length); - document.execCommand('insertText', false, unwrapped); - textarea.setSelectionRange(start, start + unwrapped.length); } else { - // not wrapped: wrap - document.execCommand('insertText', false, syntax + selectedText + syntax); - textarea.setSelectionRange(start, end + syntax.length * 2); + 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 + // execCommand is used instead of textarea.value= to preserve the browser's native undo stack (Cmd+Z) + var wrapped = lines.map(function(line) { + return line.length === 0 ? line : syntax + line + syntax; + }).join('\n'); + document.execCommand('insertText', false, wrapped); + textarea.setSelectionRange(start, start + wrapped.length); + } } } From 9c160dd316f8685fe3cbbc55a74f64f5e713f8ac Mon Sep 17 00:00:00 2001 From: shinyonogi Date: Fri, 24 Apr 2026 22:34:03 +0900 Subject: [PATCH 6/6] fix: when selecting multiple strikethrough lines and one is already striked through then it ignores dbflute/dbflute-intro#55 --- dbflute-engine/embedded/templates/doc/html/datamodel.vm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dbflute-engine/embedded/templates/doc/html/datamodel.vm b/dbflute-engine/embedded/templates/doc/html/datamodel.vm index 3737bfad2..f5a12d61d 100644 --- a/dbflute-engine/embedded/templates/doc/html/datamodel.vm +++ b/dbflute-engine/embedded/templates/doc/html/datamodel.vm @@ -2595,10 +2595,12 @@ function applyFormatting(textarea, syntax) { document.execCommand('insertText', false, unwrapped); textarea.setSelectionRange(start, start + unwrapped.length); } else { - // not wrapped: apply markers to each non-empty line individually + // 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) { - return line.length === 0 ? line : syntax + line + syntax; + 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);