mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-02-17 12:07:04 +01:00
add markdown toolbar to comment (#3952)
This commit is contained in:
committed by
GitHub
parent
85d8432755
commit
6dce8672ab
@@ -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>
|
||||
|
||||
262
src/main/webapp/assets/common/js/textarea.js
Normal file
262
src/main/webapp/assets/common/js/textarea.js
Normal 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);
|
||||
Reference in New Issue
Block a user