mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-07 01:56:24 +02:00
(refs #1241) Add new extension point to add completion proposals provider for the textarea
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util.EmojiUtil
|
||||
|
||||
trait CompletionProposalProvider {
|
||||
|
||||
val id: String
|
||||
val prefix: String
|
||||
val suffix: String = " "
|
||||
val values: Seq[String]
|
||||
|
||||
def template(implicit context: Context): String = "value"
|
||||
def additionalScript(implicit context: Context): String = ""
|
||||
}
|
||||
|
||||
class EmojiCompletionProposalProvider extends CompletionProposalProvider {
|
||||
override val id: String = "emoji"
|
||||
override val values: Seq[String] = EmojiUtil.emojis.toSeq
|
||||
override val prefix: String = ":"
|
||||
override val suffix: String = ": "
|
||||
override def template(implicit context: Context): String =
|
||||
s"""'<img src=\"${context.path}/assets/common/images/emojis/' + value + '.png\" class=\"emoji\"></img>' + value"""
|
||||
}
|
||||
|
||||
class UserCompletionProposalProvider extends CompletionProposalProvider {
|
||||
override val id: String = "user"
|
||||
override val values: Seq[String] = Nil
|
||||
override val prefix: String = "@"
|
||||
override def template(implicit context: Context): String = "'@' + value"
|
||||
override def additionalScript(implicit context: Context): String =
|
||||
s"""$$.get('${context.path}/_user/proposals', { query: '' }, function (data) { user = data.options; });"""
|
||||
}
|
||||
@@ -169,6 +169,16 @@ abstract class Plugin {
|
||||
*/
|
||||
def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = Nil
|
||||
|
||||
/**
|
||||
* Override to add completion proposal provider.
|
||||
*/
|
||||
val completionProposalProvider: Seq[CompletionProposalProvider] = Nil
|
||||
|
||||
/**
|
||||
* Override to add completion proposal provider.
|
||||
*/
|
||||
def completionProposalProvider(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[CompletionProposalProvider] = Nil
|
||||
|
||||
/**
|
||||
* This method is invoked in initialization of plugin system.
|
||||
* Register plugin functionality to PluginRegistry.
|
||||
@@ -219,6 +229,9 @@ abstract class Plugin {
|
||||
(textDecorators ++ textDecorators(registry, context, settings)).foreach { textDecorator =>
|
||||
registry.addTextDecorator(textDecorator)
|
||||
}
|
||||
(completionProposalProvider ++ completionProposalProvider(registry, context, settings)).foreach { completionProposalProvider =>
|
||||
registry.addCompletionProposalProvider(completionProposalProvider)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,9 +43,13 @@ class PluginRegistry {
|
||||
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
|
||||
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)]
|
||||
private val textDecorators = new ListBuffer[TextDecorator]
|
||||
// TODO
|
||||
textDecorators += new TextDecorator {
|
||||
override def decorate(text: String)(implicit context: Context): String = EmojiUtil.convertEmojis(text)
|
||||
}
|
||||
private val completionProposalProviders = new ListBuffer[CompletionProposalProvider]
|
||||
completionProposalProviders += new EmojiCompletionProposalProvider()
|
||||
completionProposalProviders += new UserCompletionProposalProvider()
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
|
||||
|
||||
@@ -136,6 +140,10 @@ class PluginRegistry {
|
||||
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
|
||||
|
||||
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
|
||||
|
||||
def addCompletionProposalProvider(completionProposalProvider: CompletionProposalProvider): Unit = completionProposalProviders += completionProposalProvider
|
||||
|
||||
def getCompletionProposalProviders: Seq[CompletionProposalProvider] = completionProposalProviders.toSeq
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -315,6 +315,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
case CommitState.FAILURE => "Failed"
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a given object as the JSON string.
|
||||
*/
|
||||
def json(obj: AnyRef): String = {
|
||||
implicit val formats = org.json4s.DefaultFormats
|
||||
org.json4s.jackson.Serialization.write(obj)
|
||||
}
|
||||
|
||||
// This pattern comes from: http://stackoverflow.com/a/4390768/1771641 (extract-url-from-string)
|
||||
private[this] val detectAndRenderLinksRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,13}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@(owner: String, repository: String, completion: Seq[String], generateScript: Boolean = true)(textarea: Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.util.{FileUtil, EmojiUtil}
|
||||
@import gitbucket.core.view.helpers
|
||||
<div class="muted attachable">
|
||||
@textarea
|
||||
<div class="clickable">Attach images or documents by dragging & dropping, or selecting them.</div>
|
||||
@@ -7,47 +8,32 @@
|
||||
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId =>
|
||||
<script>
|
||||
$(function(){
|
||||
@if(completion.contains("emoji")){
|
||||
var emojis = @Html(EmojiUtil.emojis.map("\"" + _ + "\"").mkString("[", ", ", "]"));
|
||||
}
|
||||
@if(completion.contains("user")){
|
||||
var users = [];
|
||||
$.get('@context.path/_user/proposals', { query: '' }, function (data) { users = data.options; });
|
||||
}
|
||||
|
||||
$('#@textareaId').textcomplete([
|
||||
@if(completion.contains("emoji")){
|
||||
{
|
||||
id: 'emoji',
|
||||
match: /\B:([\-+\w]*)$/,
|
||||
search: function (term, callback) {
|
||||
callback($.map(emojis, function (emoji) {
|
||||
return emoji.indexOf(term) === 0 ? emoji : null;
|
||||
}));
|
||||
},
|
||||
template: function (value) {
|
||||
return '<img src="@context.path/assets/common/images/emojis/' + value + '.png" class="emoji"></img>' + value;
|
||||
},
|
||||
replace: function (value) {
|
||||
return ':' + value + ': ';
|
||||
},
|
||||
index: 1
|
||||
},
|
||||
@gitbucket.core.plugin.PluginRegistry().getCompletionProposalProviders.map { provider =>
|
||||
@if(completion.contains(provider.id)){ // TODO Filter by context
|
||||
var @provider.id = @Html(helpers.json(provider.values));
|
||||
@Html(provider.additionalScript)
|
||||
}
|
||||
@if(completion.contains("user")){
|
||||
{
|
||||
id: 'user',
|
||||
match: /\B@@([\-+\w]*)$/,
|
||||
search: function (term, callback) {
|
||||
callback($.map(users, function (word) {
|
||||
return word.indexOf(term) === 0 ? word : null;
|
||||
}));
|
||||
}
|
||||
$('#@textareaId').textcomplete([
|
||||
@gitbucket.core.plugin.PluginRegistry().getCompletionProposalProviders.map { provider =>
|
||||
@if(completion.contains(provider.id)){ // TODO Filter by context
|
||||
{
|
||||
id: '@{provider.id}',
|
||||
match: /\B@{provider.prefix}([\-+\w]*)$/,
|
||||
search: function (term, callback) {
|
||||
callback($.map(@{provider.id}, function (proposal) {
|
||||
return proposal.indexOf(term) === 0 ? proposal : null;
|
||||
}));
|
||||
},
|
||||
template: function (value) {
|
||||
return @{Html(provider.template)};
|
||||
},
|
||||
replace: function (value) {
|
||||
return '@{provider.prefix}' + value + '@{provider.suffix}';
|
||||
},
|
||||
index: 1
|
||||
},
|
||||
index: 1,
|
||||
replace: function (word) {
|
||||
return '@@' + word + ' ';
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
], {
|
||||
onKeydown: function (e, commands) {
|
||||
|
||||
Reference in New Issue
Block a user