add markdown toolbar to comment (#3952)

This commit is contained in:
Yasumichi Akahoshi
2026-02-08 19:27:46 +09:00
committed by GitHub
parent 85d8432755
commit 6dce8672ab
2 changed files with 354 additions and 1 deletions

View File

@@ -16,9 +16,26 @@
uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
<div class="tabbable">
<ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
<ul class="nav nav-tabs" style="margin-bottom: 10px;">
<li class="active"><a href="#tab@uid" data-toggle="tab" id="write@uid">Write</a></li>
<li><a href="#tab@(uid + 1)" data-toggle="tab" id="preview@uid">Preview</a></li>
<li class="nav navbar-nav navbar-default" style="border: 1px solid gray;" id="markdown-toolbar@uid">
<div class="btn-group">
<label class="navbar-text" style="margin: 13px 5px;"><i class="octicon octicon-markdown" style="font-size: 24px;"></i></label>
<label class="navbar-text" style="margin-left: 0px;">inline</label>
<button class="btn btn-default navbar-btn" id="markdown-bold@uid" title="Bold"><i class="fa fa-bold"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-italic@uid" title="Italic"><i class="fa fa-italic"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-code@uid" title="Code"><i class="fa fa-code"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-link@uid" title="Link"><i class="fa fa-link"></i></button>
<label class="navbar-text">block</label>
<button class="btn btn-default navbar-btn" id="markdown-heading@uid" title="Heading"><i class="fa fa-header"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-quote@uid" title="Quote"><i class="fa fa-quote-right"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-list-ul@uid" title="Unordered List"><i class="fa fa-list-ul"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-list-ol@uid" title="Ordered List"><i class="fa fa-list-ol"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-task@uid" title="Task List"><i class="fa fa-check-square-o"></i></button>
<button class="btn btn-default navbar-btn" id="markdown-code-block@uid" title="Code Block"><i class="fa fa-file-code-o"></i></button>
</div>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
@@ -42,6 +59,78 @@
</div>
<script>
$(function(){
// load textarea.js
var script = document.getElementById("textareajs");
if (script == null) {
script = document.createElement("script");
script.id = "textareajs";
script.src = '@helpers.assets("/common/js/textarea.js")';
document.body.appendChild(script);
}
// target textarea
const textarea = document.getElementById("content@uid");
// for capture markdown bold button clicks
const markedownBold = document.getElementById("markdown-bold@uid");
markedownBold.addEventListener('click', (e) => {
surroundSelection(textarea, "**", "**", false);
}, true);
// for capture markdown italic button clicks
const markdownItalic = document.getElementById("markdown-italic@uid");
markdownItalic.addEventListener('click', (e) => {
surroundSelection(textarea, "*", "*", false);
}, true);
// for capture markdown code button clicks
const markdownCode = document.getElementById("markdown-code@uid");
markdownCode.addEventListener('click', (e) => {
surroundSelection(textarea, "`", "`", false);
}, true);
// for capture markdown link button clicks
const markdownLink = document.getElementById("markdown-link@uid");
markdownLink.addEventListener('click', (e) => {
surroundSelection(textarea, "[", "]()", true);
}, true);
// for capture markdown heading button clicks
const markdownHeading = document.getElementById("markdown-heading@uid");
markdownHeading.addEventListener('click', (e) => {
setHeadding(textarea);
}, true);
// for capture markdown quote button clicks
const markdownQuote =document.getElementById("markdown-quote@uid");
markdownQuote.addEventListener('click', (e) => {
setQuote(textarea);
}, true);
// for capture markdown list ul button clicks
const markdownListUl =document.getElementById("markdown-list-ul@uid");
markdownListUl.addEventListener('click', (e) => {
setUnorderedList(textarea);
}, true);
// for capture markdown list ul button clicks
const markdownListOl =document.getElementById("markdown-list-ol@uid");
markdownListOl.addEventListener('click', (e) => {
setOrderedList(textarea);
}, true);
// for capture markdown list ul button clicks
const markdownTask =document.getElementById("markdown-task@uid");
markdownTask.addEventListener('click', (e) => {
setTaskList(textarea);
}, true);
// for capture markdown list ul button clicks
const markdownCodeBlock =document.getElementById("markdown-code-block@uid");
markdownCodeBlock.addEventListener('click', (e) => {
setCodeBlock(textarea);
}, true);
@if(elastic){
$('#content@uid').elastic();
$('#content@uid').trigger('blur');
@@ -52,6 +141,7 @@ $(function(){
$('#write@uid').on('shown.bs.tab', function(){
$('#content@uid').trigger('focus');
$('#markdown-toolbar@uid').show()
});
$('#preview@uid').click(function(){
@@ -68,6 +158,7 @@ $(function(){
$('#preview-area@uid input').prop('disabled', true);
prettyPrint();
});
$('#markdown-toolbar@uid').hide();
});
});
</script>

View File

@@ -0,0 +1,262 @@
(function () {
/**
* constants for line type
*/
const headingPattern = /^#{1,6} /;
const quotePattern = /^((>+ )+|>+ )/;
const nestedQuotePattern = /^(> ?){2,}/;
const taskPattern = /^ *- \[ \] /;
const doneTaskPattern = /^ *- \[x\] /;
const ulPattern = /^ *- /;
const olPattern = /^ *[0-9]. /;
const TYPE_NONE = 0;
const TYPE_HEADING = 1;
const TYPE_QUOTE = 2;
const TYPE_TASK = 3;
const TYPE_DONE_TASK = 4;
const TYPE_UL = 5;
const TYPE_OL = 6;
/**
* Determine the type of line
*
* @param {*} line line of editor contents
* @returns [TYPE, match]
*/
const getLineType = function (line) {
if (m = line.match(headingPattern)) {
return [TYPE_HEADING, m];
} else if (m = line.match(quotePattern)) {
return [TYPE_QUOTE, m];
} else if (m = line.match(taskPattern)) {
return [TYPE_TASK, m];
} else if (m = line.match(doneTaskPattern)) {
return [TYPE_DONE_TASK, m];
} else if (m = line.match(ulPattern)) {
return [TYPE_UL, m];
} else if (m = line.match(olPattern)) {
return [TYPE_OL, m];
} else {
return [TYPE_NONE, null];
}
};
/**
* Wraps the textarea selection with the specified string
*
* @param {Element} textarea Target textarea element
* @param {string} prefix The string to insert before the selection
* @param {string} suffix The string to insert after the selection
* @param {boolean} allowNoSelectRange Allow insert even if not selected
*/
window.surroundSelection = function (textarea, prefix, suffix, allowNoSelectRange) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
if (!(selectedText.length == 0 && allowNoSelectRange == false)) {
textarea.value = textarea.value.substring(0, start) + prefix + selectedText + suffix + textarea.value.substring(end);
const newPosition = start + prefix.length + selectedText.length + suffix.length + (allowNoSelectRange ? -1 : 0);
textarea.setSelectionRange(newPosition, newPosition);
}
textarea.focus();
};
/**
* Extend selection to entireLine in textarea
*
* @param {Element} textarea Target textarea element
*/
window.extendSelectionToEntireLine = function (textarea) {
const text = textarea.value;
// Forward line break position (if not found it is -1, so add +1 to make it 0th character)
const start = text.lastIndexOf('\n', textarea.selectionStart - 1) + 1;
// Rear line break position (if not found, use end of string)
let end = text.indexOf('\n', textarea.selectionEnd);
if (end === -1) end = text.length;
// Update selection to entire row
textarea.setSelectionRange(start, end);
};
/**
* Make the textarea selection a heading element
*
* @param {Element} textarea
*/
window.setHeadding = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
selectedText.split('\n').forEach((line) => {
var headingText = "";
var lineType = getLineType(line);
switch(lineType[0]) {
case TYPE_HEADING:
var level = selectedText.indexOf(' ');
if (level < 6) {
headingText = "#" + line;
} else {
headingText = line.replace("###### ","");
}
break;
case TYPE_NONE:
headingText = "# " + line;
break;
default:
headingText = line.replace(lineType[1][0], "# ");
}
newText = newText + headingText + "\n";
});
textarea.value = textarea.value.substring(0, start) + newText.trimEnd() + textarea.value.substring(end);
textarea.setSelectionRange(start, start + newText.trimEnd().length);
textarea.focus();
};
/**
* Make the textarea selection a quote element
*
* @param {Element} textarea
*/
window.setQuote = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
selectedText.split('\n').forEach((line) => {
var quoteText = "";
var lineType = getLineType(line);
switch(lineType[0]) {
case TYPE_QUOTE:
if (m = line.match(nestedQuotePattern)) {
quoteText = line.replace(m[0], "");
} else {
quoteText = "> " + line;
}
break;
case TYPE_NONE:
quoteText = "> " + line;
break;
default:
quoteText = line.replace(lineType[1][0], "> ");
}
newText = newText + quoteText + "\n";
});
textarea.value = textarea.value.substring(0, start) + newText.trimEnd() + textarea.value.substring(end);
textarea.setSelectionRange(start, start + newText.trimEnd().length);
textarea.focus();
};
/**
* Make the textarea selection a unordered list element
*
* @param {Element} textarea
*/
window.setUnorderedList = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
selectedText.split('\n').forEach((line) => {
var unorderedList = "";
var lineType = getLineType(selectedText);
switch(lineType[0]) {
case TYPE_UL:
unorderedList = line.replace(lineType[1][0], "");
break;
case TYPE_NONE:
unorderedList = "- " + line;
break;
default:
unorderedList = line.replace(lineType[1][0], "- ");
}
newText = newText + unorderedList + "\n";
});
textarea.value = textarea.value.substring(0, start) + newText.trimEnd() + textarea.value.substring(end);
textarea.setSelectionRange(start, start + newText.trimEnd().length);
textarea.focus();
};
/**
* Make the textarea selection a ordered list element
*
* @param {Element} textarea
*/
window.setOrderedList = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
selectedText.split('\n').forEach((line) => {
var orderedList = "";
var lineType = getLineType(selectedText);
switch(lineType[0]) {
case TYPE_OL:
orderedList = line.replace(lineType[1][0], "");
break;
case TYPE_NONE:
orderedList = "1. " + line;
break;
default:
orderedList = line.replace(lineType[1][0], "1. ");
}
newText = newText + orderedList + "\n";
});
textarea.value = textarea.value.substring(0, start) + newText.trimEnd() + textarea.value.substring(end);
textarea.setSelectionRange(start, start + newText.trimEnd().length);
textarea.focus();
};
/**
* Make the textarea selection a task list element
*
* @param {Element} textarea
*/
window.setTaskList = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
selectedText.split('\n').forEach((line) => {
var taskList = "";
var lineType = getLineType(line);
switch(lineType[0]) {
case TYPE_DONE_TASK:
taskList = line.replace("[x]", "[ ]");
break;
case TYPE_TASK:
taskList = line.replace("[ ]", "[x]");
break;
case TYPE_NONE:
taskList = "- [ ] " + line;
break;
default:
taskList = line.replace(lineType[1][0], "- [ ] ");
}
newText = newText + taskList + "\n";
});
textarea.value = textarea.value.substring(0, start) + newText.trimEnd() + textarea.value.substring(end);
textarea.setSelectionRange(start, start + newText.trimEnd().length);
textarea.focus();
};
/**
* Make the textarea selection a unordered list element
*
* @param {Element} textarea
*/
window.setCodeBlock = function (textarea) {
extendSelectionToEntireLine(textarea);
var newText = "";
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) + "```\n" + selectedText + "\n```\n" + textarea.value.substring(end);
textarea.setSelectionRange(start + 3, start + 3);
textarea.focus();
};
})(window);