mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 22:45:51 +01:00
Implement the feature "Task List"
This commit is contained in:
@@ -186,7 +186,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("title" -> x.title,
|
Map("title" -> x.title,
|
||||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
||||||
repository, false, true)
|
repository, false, true, true)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
@@ -203,7 +203,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("content" -> view.Markdown.toHtml(x.content,
|
Map("content" -> view.Markdown.toHtml(x.content,
|
||||||
repository, false, true)
|
repository, false, true, true)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
contentType = "text/html"
|
contentType = "text/html"
|
||||||
view.helpers.markdown(params("content"), repository,
|
view.helpers.markdown(params("content"), repository,
|
||||||
params("enableWikiLink").toBoolean,
|
params("enableWikiLink").toBoolean,
|
||||||
params("enableRefsLink").toBoolean)
|
params("enableRefsLink").toBoolean,
|
||||||
|
params("enableTaskList").toBoolean)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.text.Normalizer
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import service.{RequestCache, WikiService}
|
import service.{RepositoryService, RequestCache, WikiService}
|
||||||
|
|
||||||
object Markdown {
|
object Markdown {
|
||||||
|
|
||||||
@@ -19,17 +19,22 @@ object Markdown {
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*/
|
*/
|
||||||
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
|
enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean = false)(implicit context: app.Context): String = {
|
||||||
// escape issue id
|
// escape issue id
|
||||||
val source = if(enableRefsLink){
|
val s = if(enableRefsLink){
|
||||||
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
||||||
} else markdown
|
} else markdown
|
||||||
|
|
||||||
|
// escape task list
|
||||||
|
val source = if(enableTaskList){
|
||||||
|
GitBucketHtmlSerializer.escapeTaskList(s)
|
||||||
|
} else s
|
||||||
|
|
||||||
val rootNode = new PegDownProcessor(
|
val rootNode = new PegDownProcessor(
|
||||||
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
||||||
).parseMarkdown(source.toCharArray)
|
).parseMarkdown(source.toCharArray)
|
||||||
|
|
||||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink).toHtml(rootNode)
|
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList).toHtml(rootNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +88,12 @@ class GitBucketHtmlSerializer(
|
|||||||
markdown: String,
|
markdown: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean,
|
enableWikiLink: Boolean,
|
||||||
enableRefsLink: Boolean
|
enableRefsLink: Boolean,
|
||||||
|
enableTaskList: Boolean
|
||||||
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
||||||
new GitBucketLinkRender(context, repository, enableWikiLink),
|
new GitBucketLinkRender(context, repository, enableWikiLink),
|
||||||
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
||||||
) with LinkConverter with RequestCache {
|
) with RepositoryService with LinkConverter with RequestCache {
|
||||||
|
|
||||||
override protected def printImageTag(imageNode: SuperNode, url: String): Unit =
|
override protected def printImageTag(imageNode: SuperNode, url: String): Unit =
|
||||||
printer.print("<img src=\"").print(fixUrl(url)).print("\" alt=\"").printEncoded(printChildrenToString(imageNode)).print("\"/>")
|
printer.print("<img src=\"").print(fixUrl(url)).print("\" alt=\"").printEncoded(printChildrenToString(imageNode)).print("\"/>")
|
||||||
@@ -130,7 +136,10 @@ class GitBucketHtmlSerializer(
|
|||||||
|
|
||||||
override def visit(node: TextNode): Unit = {
|
override def visit(node: TextNode): Unit = {
|
||||||
// convert commit id and username to link.
|
// convert commit id and username to link.
|
||||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||||
|
|
||||||
|
// convert task list to checkbox.
|
||||||
|
val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission(repository.owner, repository.name, context.loginAccount)) else t
|
||||||
|
|
||||||
if (abbreviations.isEmpty) {
|
if (abbreviations.isEmpty) {
|
||||||
printer.print(text)
|
printer.print(text)
|
||||||
@@ -154,4 +163,10 @@ object GitBucketHtmlSerializer {
|
|||||||
def escapeTaskList(text: String): String = {
|
def escapeTaskList(text: String): String = {
|
||||||
Pattern.compile("""^ *- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("task:$1: ")
|
Pattern.compile("""^ *- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("task:$1: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def convertCheckBox(text: String, hasWritePermission: Boolean): String = {
|
||||||
|
val disabled = if (hasWritePermission) "" else "disabled"
|
||||||
|
text.replaceAll("task:x:", """<input type="checkbox" checked="checked" """ + disabled + "/>")
|
||||||
|
.replaceAll("task: :", """<input type="checkbox" """ + disabled + "/>")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*/
|
*/
|
||||||
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
|
enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean = false)(implicit context: app.Context): Html =
|
||||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
|
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList))
|
||||||
|
|
||||||
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean,
|
@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean,
|
||||||
style: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false)(implicit context: app.Context)
|
style: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@@ -36,7 +36,8 @@ $(function(){
|
|||||||
$.post('@url(repository)/_preview', {
|
$.post('@url(repository)/_preview', {
|
||||||
content : $('#content').val(),
|
content : $('#content').val(),
|
||||||
enableWikiLink : @enableWikiLink,
|
enableWikiLink : @enableWikiLink,
|
||||||
enableRefsLink : @enableRefsLink
|
enableRefsLink : @enableRefsLink,
|
||||||
|
enableTaskList : @enableTaskList
|
||||||
}, function(data){
|
}, function(data){
|
||||||
$('#preview-area').html(data);
|
$('#preview-area').html(data);
|
||||||
prettyPrint();
|
prettyPrint();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||||
<div class="box issue-comment-box">
|
<div class="box issue-comment-box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
@helper.html.preview(repository, "", false, true, "width: 680px; height: 100px; max-height: 150px;", elastic = true)
|
@helper.html.preview(repository, "", false, true, true, "width: 680px; height: 100px; max-height: 150px;", elastic = true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||||
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
||||||
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
|
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
|
||||||
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true)
|
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@if(comment.action == "refer"){
|
@if(comment.action == "refer"){
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@markdown(comment.content, repository, false, true)
|
@markdown(comment.content, repository, false, true, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -109,5 +109,44 @@ $(function(){
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var extractMarkdown = function(data){
|
||||||
|
$('body').append('<div id="tmp"></div>');
|
||||||
|
$('#tmp').html(data);
|
||||||
|
var markdown = $('#tmp textarea').val();
|
||||||
|
$('#tmp').remove();
|
||||||
|
return markdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
$('div[id^=commentContent-').on('click', ':checkbox', function(ev){
|
||||||
|
var $commentContent = $(ev.target).parents('div[id^=commentContent-]'),
|
||||||
|
commentId = $commentContent.attr('id').replace(/commentContent-/, ''),
|
||||||
|
checkboxes = $commentContent.find(':checkbox');
|
||||||
|
$.get('@url(repository)/issue_comments/_data/' + commentId,
|
||||||
|
{
|
||||||
|
dataType : 'html'
|
||||||
|
},
|
||||||
|
function(data){
|
||||||
|
var ss = [],
|
||||||
|
markdown = extractMarkdown(data),
|
||||||
|
xs = markdown.split(/- \[[x| ]\]/g);
|
||||||
|
for (var i=0; i<xs.length; i++) {
|
||||||
|
ss.push(xs[i]);
|
||||||
|
if (checkboxes.eq(i).prop('checked')) ss.push('- [x]');
|
||||||
|
else ss.push('- [ ]');
|
||||||
|
}
|
||||||
|
ss.pop();
|
||||||
|
$.ajax({
|
||||||
|
url: '@url(repository)/issue_comments/edit/' + commentId,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
issueId : 0,
|
||||||
|
content : ss.join('')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
@helper.html.preview(repository, "", false, true, "width: 600px; height: 200px; max-height: 250px;", elastic = true)
|
@helper.html.preview(repository, "", false, true, true, "width: 600px; height: 200px; max-height: 250px;", elastic = true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="issue-content" id="issueContent">
|
<div class="issue-content" id="issueContent">
|
||||||
@markdown(issue.content getOrElse "No description given.", repository, false, true)
|
@markdown(issue.content getOrElse "No description given.", repository, false, true, true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,5 +141,41 @@ $(function(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var extractMarkdown = function(data){
|
||||||
|
$('body').append('<div id="tmp"></div>');
|
||||||
|
$('#tmp').html(data);
|
||||||
|
var markdown = $('#tmp textarea').val();
|
||||||
|
$('#tmp').remove();
|
||||||
|
return markdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#issueContent').on('click', ':checkbox', function(ev){
|
||||||
|
var checkboxes = $('#issueContent :checkbox');
|
||||||
|
$.get('@url(repository)/issues/_data/@issue.issueId',
|
||||||
|
{
|
||||||
|
dataType : 'html'
|
||||||
|
},
|
||||||
|
function(data){
|
||||||
|
var ss = [],
|
||||||
|
markdown = extractMarkdown(data),
|
||||||
|
xs = markdown.split(/- \[[x| ]\]/g);
|
||||||
|
for (var i=0; i<xs.length; i++) {
|
||||||
|
ss.push(xs[i]);
|
||||||
|
if (checkboxes.eq(i).prop('checked')) ss.push('- [x]');
|
||||||
|
else ss.push('- [ ]');
|
||||||
|
}
|
||||||
|
ss.pop();
|
||||||
|
$.ajax({
|
||||||
|
url: '@url(repository)/issues/edit/@issue.issueId',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
title : $('#issueTitle').text(),
|
||||||
|
content : ss.join('')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<div style="width: 620px; border-right: 1px solid #d4d4d4;">
|
<div style="width: 620px; border-right: 1px solid #d4d4d4;">
|
||||||
<span class="error" id="error-title"></span>
|
<span class="error" id="error-title"></span>
|
||||||
<input type="text" name="title" style="width: 600px" placeholder="Title"/>
|
<input type="text" name="title" style="width: 600px" placeholder="Title"/>
|
||||||
@helper.html.preview(repository, "", false, true, "width: 600px; height: 200px;")
|
@helper.html.preview(repository, "", false, true, true, "width: 600px; height: 200px;")
|
||||||
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
|
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
|
||||||
<input type="hidden" name="targetBranch" value="@originId"/>
|
<input type="hidden" name="targetBranch" value="@originId"/>
|
||||||
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
|
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
||||||
<span id="error-pageName" class="error"></span>
|
<span id="error-pageName" class="error"></span>
|
||||||
<input type="text" name="pageName" value="@pageName" style="width: 900px; font-weight: bold;" placeholder="Input a page name."/>
|
<input type="text" name="pageName" value="@pageName" style="width: 900px; font-weight: bold;" placeholder="Input a page name."/>
|
||||||
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, "width: 900px; height: 400px;", "")
|
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, false, "width: 900px; height: 400px;", "")
|
||||||
<input type="text" name="message" value="" style="width: 900px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
<input type="text" name="message" value="" style="width: 900px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
||||||
<input type="hidden" name="currentPageName" value="@pageName"/>
|
<input type="hidden" name="currentPageName" value="@pageName"/>
|
||||||
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user