Apply Ace Editor to Wiki editing with keyboard handler support (#3925)

This commit is contained in:
Yasumichi Akahoshi
2026-01-23 09:09:40 +09:00
committed by GitHub
parent afc5c2d3d0
commit 0c9863f956
3 changed files with 166 additions and 77 deletions

View File

@@ -0,0 +1,74 @@
@()(implicit context: gitbucket.core.controller.Context)
<nav class="navbar navbar-default" style="margin-bottom: 0px;">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li>
<div class="btn-group" data-toggle="buttons-radio">
<input type="button" id="btn-code" class="btn btn-default btn-small active navbar-btn" value="Code">
<input type="button" id="btn-preview" class="btn btn-default btn-small navbar-btn" value="Preview">
</div>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<select id="aceKeyboardSelect" class="form-control navbar-form" style="font-weight: bold;">
<optgroup label="Keyboard">
<option value="">Default</option>
<option value="ace/keyboard/emacs">Emacs</option>
<option value="ace/keyboard/vim">Vim</option>
</optgroup>
</select>
</li>
<li>
<select id="theme" class="form-control navbar-form" style="margin-bottom: 0px; font-weight: bold;" aria-label="Theme">
<optgroup label="Editor Theme">
<option value="ambiance">Ambiance</option>
<option value="chaos">Chaos</option>
<option value="chrome">Chrome</option>
<option value="clouds">Clouds</option>
<option value="clouds_midnight">Clouds Midnight</option>
<option value="cobalt">Cobalt</option>
<option value="crimson_editor">Crimson</option>
<option value="dawn">Dawn</option>
<option value="dracula">Dracula</option>
<option value="dreamweaver">Dreamweaver</option>
<option value="eclipse">Eclipse</option>
<option value="github">GitHub</option>
<option value="gob">Gob</option>
<option value="gruvbox">Gruvbox</option>
<option value="idle_fingers">Idle Fingers</option>
<option value="iplastic">Iplastic</option>
<option value="katzenmilch">Katzenmilch</option>
<option value="kr_theme">Kr</option>
<option value="kuroir">Kuroir</option>
<option value="merbivore">Merbivore</option>
<option value="mono_industrial">Mono Industrial</option>
<option selected value="monokai">Monokai</option>
<option value="nord_dark">Nord Dark</option>
<option value="pastel_on_dark">Pastel on Dark</option>
<option value="solarized_dark">Solarized Dark</option>
<option value="solarized_light">Solarized Light</option>
<option value="sqlserver">Sqlserver</option>
<option value="terminal">Terminal</option>
<option value="textmate">Textmate</option>
<option value="tomorrow">Tomorrow</option>
<option value="tomorrow_night">Tomorrow Night</option>
<option value="tomorrow_night_bright">Tomorrow Night Bright</option>
<option value="tomorrow_night_eighties">Tomorrow Night Eighties</option>
<option value="twilight">Twilight</option>
<option value="vibrant_ink">Vibrant Ink</option>
<option value="xcode">Xcode</option>
</optgroup>
</select>
</li>
<li>
<select id="wrap" class="form-control navbar-form" style="margin-bottom: 0px; font-weight: bold;" aria-label="Wrap">
<optgroup label="Line Wrap Mode">
<option value="false">No wrap</option>
<option value="true">Soft wrap</option>
</optgroup>
</select>
</li>
</ul>
</div>
</nav>

View File

@@ -28,65 +28,8 @@
<input type="hidden" name="branch" id="branch" value="@branch"/>
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
</div>
@gitbucket.core.helper.html.acenavbar()
<table class="table table-bordered">
<tr>
<th>
<div class="pull-right">
<select id="wrap" class="form-control" style="margin-bottom: 0px; padding: 0px;" aria-label="Wrap">
<optgroup label="Line Wrap Mode">
<option value="false">No wrap</option>
<option value="true">Soft wrap</option>
</optgroup>
</select>
</div>
<div class="pull-right">
<select id="theme" class="form-control" style="margin-bottom: 0px; padding: 0px;" aria-label="Theme">
<optgroup label="Editor Theme">
<option value="ambiance">Ambiance</option>
<option value="chaos">Chaos</option>
<option value="chrome">Chrome</option>
<option value="clouds">Clouds</option>
<option value="clouds_midnight">Clouds Midnight</option>
<option value="cobalt">Cobalt</option>
<option value="crimson_editor">Crimson</option>
<option value="dawn">Dawn</option>
<option value="dracula">Dracula</option>
<option value="dreamweaver">Dreamweaver</option>
<option value="eclipse">Eclipse</option>
<option value="github">GitHub</option>
<option value="gob">Gob</option>
<option value="gruvbox">Gruvbox</option>
<option value="idle_fingers">Idle Fingers</option>
<option value="iplastic">Iplastic</option>
<option value="katzenmilch">Katzenmilch</option>
<option value="kr_theme">Kr</option>
<option value="kuroir">Kuroir</option>
<option value="merbivore">Merbivore</option>
<option value="mono_industrial">Mono Industrial</option>
<option selected value="monokai">Monokai</option>
<option value="nord_dark">Nord Dark</option>
<option value="pastel_on_dark">Pastel on Dark</option>
<option value="solarized_dark">Solarized Dark</option>
<option value="solarized_light">Solarized Light</option>
<option value="sqlserver">Sqlserver</option>
<option value="terminal">Terminal</option>
<option value="textmate">Textmate</option>
<option value="tomorrow">Tomorrow</option>
<option value="tomorrow_night">Tomorrow Night</option>
<option value="tomorrow_night_bright">Tomorrow Night Bright</option>
<option value="tomorrow_night_eighties">Tomorrow Night Eighties</option>
<option value="twilight">Twilight</option>
<option value="vibrant_ink">Vibrant Ink</option>
<option value="xcode">Xcode</option>
</optgroup>
</select>
</div>
<div class="btn-group" data-toggle="buttons-radio">
<input type="button" id="btn-code" class="btn btn-default btn-small active" value="Code">
<input type="button" id="btn-preview" class="btn btn-default btn-small" value="Preview">
</div>
</th>
</tr>
<tr>
<td>
<div id="editor" style="width: 100%; height: 600px;"></div>
@@ -139,6 +82,17 @@ $(function(){
$('#editor').text($('#initial').val());
var editor = ace.edit("editor");
var aceKeyboard = localStorage.getItem("gitbucket:editor:keyboard") || "";
editor.setKeyboardHandler(aceKeyboard == "" ? null : aceKeyboard);
var aceKeyboardSelect = document.getElementById("aceKeyboardSelect");
aceKeyboardSelect.value = aceKeyboard;
aceKeyboardSelect.addEventListener('change', () => {
editor.setKeyboardHandler(aceKeyboardSelect.value == "" ? null : aceKeyboardSelect.value);
localStorage.setItem("gitbucket:editor:keyboard", aceKeyboardSelect.value);
},true)
if(typeof localStorage.getItem('gitbucket:editor:theme') == "string"){
$('#theme').val(localStorage.getItem('gitbucket:editor:theme'));
}

View File

@@ -15,22 +15,11 @@
<form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true" autocomplete="off">
<span id="error-pageName" class="error"></span>
<input type="text" name="pageName" value="@pageName" class="form-control" style="font-weight: bold; margin-bottom: 10px;" placeholder="Input a page name." aria-label="Page name"/>
@gitbucket.core.helper.html.acenavbar()
<div class="muted attachable">
@gitbucket.core.helper.html.preview(
repository = repository,
content = page.map(_.content).getOrElse(""),
enableWikiLink = true,
enableRefsLink = false,
enableLineBreaks = false,
enableTaskList = false,
hasWritePermission = false,
completionContext = "wiki",
style = "height: 400px;",
styleClass = "monospace",
placeholder = "",
ariaLabel = "Page content",
uid = 1
)
<div id="editor" style="width: 100%; height: 600px;"></div>
<div class="clickable">Attach images or documents by dragging &amp; dropping, or selecting them.</div>
<div id="preview" style="width: 100%; display: none;"></div>
</div>
<div class="form-group">
<label for="message">Edit Message</label>
@@ -39,15 +28,87 @@
<div class="form-group pull-right">
<input type="hidden" name="currentPageName" value="@pageName"/>
<input type="hidden" name="id" value="@page.map(_.id)"/>
<input type="submit" value="Save" class="btn btn-success">
<input type="submit" id="saveButton" value="Save" class="btn btn-success">
<input type="hidden" id="content" name="content" value=""/>
<input type="hidden" id="initial" value='@page.map(_.content)'/>
</div>
</form>
<script src="@helpers.assets("/vendors/ace/ace.js")" type="text/javascript" charset="utf-8"></script>
<script src="@helpers.assets("/vendors/ace/ext-modelist.js")" type="text/javascript" charset="utf-8"></script>
}
}
<script>
$(function(){
$('#editor').text($('#initial').val());
var editor = ace.edit("editor");
var aceKeyboard = localStorage.getItem("gitbucket:editor:keyboard") || "";
editor.setKeyboardHandler(aceKeyboard == "" ? null : aceKeyboard);
var aceKeyboardSelect = document.getElementById("aceKeyboardSelect");
aceKeyboardSelect.value = aceKeyboard;
aceKeyboardSelect.addEventListener('change', () => {
editor.setKeyboardHandler(aceKeyboardSelect.value == "" ? null : aceKeyboardSelect.value);
localStorage.setItem("gitbucket:editor:keyboard", aceKeyboardSelect.value);
},true)
if(typeof localStorage.getItem('gitbucket:editor:theme') == "string"){
$('#theme').val(localStorage.getItem('gitbucket:editor:theme'));
}
editor.setTheme("ace/theme/" + $('#theme').val());
$('#theme').change(function(){
editor.setTheme("ace/theme/" + $('#theme').val());
localStorage.setItem('gitbucket:editor:theme', $('#theme').val());
});
if(localStorage.getItem('gitbucket:editor:wrap') == 'true'){
editor.getSession().setUseWrapMode(true);
$('#wrap').val('true');
}
var modelist = ace.require("ace/ext/modelist");
var mode = modelist.getModeForPath('@page.map(_.name).getOrElse("temporary.md")');
editor.getSession().setMode(mode.mode);
$('#saveButton').click(function(){
$('#content').val(editor.getValue());
});
$('#btn-code').click(function(){
$('#editor').show();
$('.clickable').show();
$('#preview').hide();
$('#btn-preview').removeClass('active');
});
$('#btn-preview').click(function(){
$('#editor').hide();
$('.clickable').hide();
$('#preview').show();
$('#btn-code').removeClass('active');
// update preview
$('#preview').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
$.post('@helpers.url(repository)/_preview', {
content : editor.getValue(),
enableWikiLink : true,
filename : '@page.map(_.name).getOrElse("temporary.md")',
enableRefsLink : false,
enableLineBreaks : false,
enableTaskList : false
}, function(data){
$('#preview').empty().append(
$('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data));
prettyPrint();
});
});
try {
$('#content1').dropzone({
$('#editor').dropzone({
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
maxFilesize: @{context.settings.upload.maxFileSize / 1024 / 1024},
timeout: @{context.settings.upload.timeout},
@@ -55,7 +116,7 @@ $(function(){
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
$('#content1').val($('#content1').val() + attachFile);
editor.session.insert(editor.selection.getCursor(), attachFile);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
});
@@ -66,7 +127,7 @@ $(function(){
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
$('#content1').val($('#content1').val() + attachFile);
editor.session.insert(editor.selection.getCursor(), attachFile);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
});