Refactor skin editor with CodeMirror 6
This commit is contained in:
parent
439b2cce18
commit
f559d2f78e
8 changed files with 3005 additions and 160 deletions
16
client/babel.config.json
Normal file
16
client/babel.config.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"sourceMaps": true,
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-block-scoping"
|
||||
],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": "> 0.25%, not dead",
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
26
client/build.mjs
Executable file
26
client/build.mjs
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import babel from 'esbuild-plugin-babel';
|
||||
|
||||
esbuild.build({
|
||||
define: { 'process.env.NODE_ENV': '"production"' },
|
||||
outdir: 'static/js',
|
||||
entryPoints: [
|
||||
'./client/code-mirror.mjs'
|
||||
],
|
||||
entryNames: '[dir]/[name]-[hash]',
|
||||
target: ['es6'],
|
||||
format: 'esm',
|
||||
platform: 'browser',
|
||||
bundle: true,
|
||||
minify: true,
|
||||
keepNames: true,
|
||||
sourcemap: 'linked',
|
||||
plugins: [
|
||||
babel({
|
||||
filter: /\.m?js$/,
|
||||
configFile: './babel.config.json'
|
||||
})
|
||||
]
|
||||
}).catch(() => process.exit(1));
|
||||
4
client/code-mirror.mjs
Normal file
4
client/code-mirror.mjs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { MergeView } from "@codemirror/merge";
|
||||
export { basicSetup, EditorView } from "codemirror6";
|
||||
export { Compartment, EditorState } from "@codemirror/state";
|
||||
export { html } from "@codemirror/lang-html";
|
||||
|
|
@ -7,97 +7,81 @@
|
|||
</td>
|
||||
<td class='uk-text-muted uk-text-right'><% if <% skin.custom %> is true then "<i class='uk-icon-user'></i>" %></td>
|
||||
<td class='uk-text-right uk-text-nowrap;'>
|
||||
<a href='javascript:' class='av-clipboard-copy' data-text='<% gettext 'Press CTRL & C to copy to clipboard.' %>' data-value="<% skin.macro %>"><i class='uk-icon-clipboard'></i></a>
|
||||
<% skin.link compare "<i class='uk-icon-files-o'></i>"%>
|
||||
<% skin.link reset "<i class='uk-icon-undo'></i>" %>
|
||||
<a href='javascript:' class='av-clipboard-copy' data-text='<% gettext 'Press CTRL & C to copy to clipboard.' %>' data-value="<% skin.macro %>"><i class='uk-icon-clipboard'></i></a>
|
||||
<% skin.link edit "<i class='uk-icon-pencil'></i>" %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% #edit %>
|
||||
<form class='uk-form av-skin-editor' method="post" action="<% response.action %>">
|
||||
<div class='uk-margin-top uk-margin-left uk-margin-right uk-clearfix'>
|
||||
<script type='module'>
|
||||
import {
|
||||
basicSetup,
|
||||
Compartment,
|
||||
EditorView,
|
||||
EditorState,
|
||||
MergeView,
|
||||
html
|
||||
} from '<% root.static ../../js/code-mirror-U32JWJ6D.js %>';
|
||||
|
||||
const language = new Compartment();
|
||||
|
||||
window.view = new MergeView({
|
||||
parent: document.querySelector('.editor'),
|
||||
a: {
|
||||
doc: decodeURIComponent(`<% response.versionA %>`),
|
||||
extensions: [
|
||||
basicSetup,
|
||||
language.of(html())
|
||||
]
|
||||
},
|
||||
b: {
|
||||
doc: decodeURIComponent(`<% response.versionB %>`),
|
||||
extensions: [
|
||||
basicSetup,
|
||||
language.of(html()),
|
||||
EditorView.editable.of(false),
|
||||
EditorState.readOnly.of(true)
|
||||
]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
const setup = () => {
|
||||
return {
|
||||
submit(event) {
|
||||
const source = document.createElement('textarea');
|
||||
source.setAttribute('hidden', true);
|
||||
source.setAttribute('name', 'source');
|
||||
source.innerHTML = view.a.state.doc.toString();
|
||||
event.target.appendChild(source);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<form class='uk-form' method='post' action='<% response.action %>' x-data='setup()' @submit='submit'>
|
||||
<div class='uk-clearfix header'>
|
||||
<h1 class='uk-margin-large-right uk-float-left'><% response.title %></h1>
|
||||
<span style='line-height: 39px;'>
|
||||
<span class='uk-article-meta' style='vertical-align: bottom; display: inline-block;'>
|
||||
<span class='uk-article-meta metadata'>
|
||||
<% skin.skin $HopObject#meta %>
|
||||
</span>
|
||||
<% if <% skin.name %> is '' then
|
||||
<% skin.select prototype suffix=<% skin.input name class='uk-width-1-4' %> %>
|
||||
%>
|
||||
<button type="submit" name="save" value="1" class='uk-button uk-button-primary uk-margin-large-left'>
|
||||
<% gettext "Save" %>
|
||||
<button type='submit' name='save' value='1' class='uk-button uk-button-primary uk-margin-large-left'>
|
||||
<% gettext Save %>
|
||||
</button>
|
||||
<% skin.link compare <% gettext Compare %> class='uk-button' %>
|
||||
<button type='submit' name='compare' class='uk-button uk-margin-left'>
|
||||
<% gettext Compare %>
|
||||
</button>
|
||||
<select name='reference'>
|
||||
<option value='current' <% if <% request.reference %> is current then selected %>><% gettext Current %></option>
|
||||
<option value='original' <% if <% request.reference %> is original then selected %>><% gettext Original %></option>
|
||||
</select>
|
||||
<a href='<% layout.skins.href all %>' class='uk-button uk-button-link'><% gettext Cancel %></a>
|
||||
</span>
|
||||
<% response.message prefix="<div class='uk-alert' data-uk-alert>" suffix=</div> %>
|
||||
<% response.message prefix='<div class="uk-alert" data-uk-alert>' suffix=</div> %>
|
||||
</div>
|
||||
<% skin.textarea source %>
|
||||
<div class='editor'></div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
// Setup skin editor
|
||||
let mode = 'application/x-helma-skin';
|
||||
|
||||
if (location.href.indexOf('stylesheet') > -1) {
|
||||
mode = 'text/css';
|
||||
} else if (location.href.indexOf('javascript') > -1) {
|
||||
mode = 'text/javascript';
|
||||
}
|
||||
|
||||
CodeMirror.fromTextArea(document.querySelector('#source'), {
|
||||
autofocus: true,
|
||||
enterMode: 'keep',
|
||||
indentUnit: 3,
|
||||
indentWithTabs: false,
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
mode: mode,
|
||||
tabMode: 'shift',
|
||||
tabSize: 3,
|
||||
viewportMargin: Infinity
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<% #compare %>
|
||||
<form>
|
||||
<div class='uk-margin-top uk-margin-left uk-clearfix'>
|
||||
<h1 class='uk-margin-large-right uk-float-left'><% response.title %></h1>
|
||||
<span style='line-height: 42px;'>
|
||||
<% skin.link edit <% gettext Edit %> class='uk-button uk-button-primary' %>
|
||||
<% skin.link reset <% gettext Reset %> class='uk-button' %>
|
||||
<a href='<% request.http_referer %>' class='uk-button uk-button-link'><% gettext Cancel %></a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<% response.message prefix="<div class='uk-alert' data-uk-alert>" suffix=</div> %>
|
||||
<table class='uk-table uk-table-condensed uk-table-striped'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><% gettext 'Modified skin' %></th>
|
||||
<th></th>
|
||||
<th><% gettext 'Original skin' %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% response.diff %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<% #difference %>
|
||||
<tr>
|
||||
<td class='uk-text-muted uk-text-right'><% param.leftLineNumber %></td>
|
||||
<td class='uk-width-1-2 av-overflow <% param.leftStatus prefix='av-line-' %>'>
|
||||
<div class='av-line'><% param.left %></div>
|
||||
</td>
|
||||
<td class='uk-text-muted uk-text-right'><% param.rightLineNumber %></td>
|
||||
<td class='uk-width-1-2 av-overflow <% param.rightStatus prefix='av-line-' %>'>
|
||||
<div class='av-line'><% param.right %></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,19 @@ markgettext('Skin');
|
|||
markgettext('skin');
|
||||
markgettext('a skin // accusative');
|
||||
|
||||
/**
|
||||
* Get the source of a skin in the code directory
|
||||
* @param {String} prototype
|
||||
* @param {String} name
|
||||
* @returns String
|
||||
*/
|
||||
Skin.getSourceFromCode = function(prototype, name) {
|
||||
const file = java.io.File(app.dir, prototype + '/' + prototype + '.skin');
|
||||
const content = Packages.org.apache.commons.io.FileUtils.readFileToString(file, 'utf-8');
|
||||
const skin = createSkin(content);
|
||||
return skin.getSubskin(name).getSource() || '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} group
|
||||
|
|
@ -150,23 +163,24 @@ Skin.prototype.main_action = function() {
|
|||
}
|
||||
|
||||
Skin.prototype.edit_action = function() {
|
||||
if (req.postParams.save) {
|
||||
if (!!req.postParams.save) {
|
||||
try {
|
||||
var url = this.href(req.action);
|
||||
this.update(req.postParams);
|
||||
res.message = gettext('The changes were saved successfully.');
|
||||
if (req.postParams.save == 1) {
|
||||
res.redirect(url);
|
||||
} else {
|
||||
res.redirect(res.handlers.layout.skins.href('modified'));
|
||||
}
|
||||
res.redirect(url);
|
||||
} catch (ex) {
|
||||
res.message = ex;
|
||||
app.log(ex);
|
||||
}
|
||||
}
|
||||
res.data.action = this.href(req.action);
|
||||
res.data.title = gettext('Edit {0}.{1}', this.prototype, this.name);
|
||||
|
||||
const referenceSource = req.postParams.reference === 'original' ? this.source : this.getSource();
|
||||
const currentSource = req.data.source || referenceSource;
|
||||
|
||||
res.data.versionA = encodeURIComponent(currentSource);
|
||||
res.data.versionB = encodeURIComponent(referenceSource);
|
||||
res.data.title = gettext('Edit {0}', this.getTitle());
|
||||
res.data.body = this.renderSkinAsString('$Skin#edit');
|
||||
res.handlers.skins.renderSkin('$Skins#page');
|
||||
return;
|
||||
|
|
@ -214,61 +228,6 @@ Skin.prototype.reset_action = function() {
|
|||
return;
|
||||
}
|
||||
|
||||
Skin.prototype.compare_action = function() {
|
||||
var originalSkin = this.source || String.EMPTY;
|
||||
var diff = this.getSource().diff(originalSkin);
|
||||
if (!diff) {
|
||||
res.message = gettext('No differences were found.');
|
||||
} else {
|
||||
res.push();
|
||||
var param = {}, leftLineNumber = rightLineNumber = 0;
|
||||
for (let line of diff) {
|
||||
if (line.deleted) {
|
||||
param.right = encode(line.value);
|
||||
param.leftStatus = 'added';
|
||||
param.rightStatus = '';
|
||||
for (let i=0; i<line.deleted.length; i++) {
|
||||
leftLineNumber += 1;
|
||||
param.leftLineNumber = leftLineNumber;
|
||||
param.rightLineNumber = '';
|
||||
param.left = encode(line.deleted[i]);
|
||||
param.right = '';
|
||||
this.renderSkin('$Skin#difference', param);
|
||||
}
|
||||
}
|
||||
if (line.inserted) {
|
||||
param.left = encode(line.value);
|
||||
param.leftStatus = '';
|
||||
param.rightStatus = 'removed';
|
||||
for (let i=0; i<line.inserted.length; i++) {
|
||||
rightLineNumber += 1;
|
||||
param.leftLineNumber = '';
|
||||
param.rightLineNumber = rightLineNumber;
|
||||
param.left = '';
|
||||
param.right = encode(line.inserted[i]);
|
||||
this.renderSkin('$Skin#difference', param);
|
||||
}
|
||||
}
|
||||
if (line.value !== null) {
|
||||
leftLineNumber += 1;
|
||||
rightLineNumber += 1;
|
||||
param.leftLineNumber = leftLineNumber;
|
||||
param.rightLineNumber = rightLineNumber;
|
||||
param.leftStatus = param.rightStatus = '';
|
||||
param.left = encode(line.value);
|
||||
param.right = param.left;
|
||||
this.renderSkin('$Skin#difference', param);
|
||||
}
|
||||
}
|
||||
res.data.diff = res.pop();
|
||||
}
|
||||
|
||||
res.data.title = gettext('Compare {0}', this.getTitle());
|
||||
res.data.body = this.renderSkinAsString('$Skin#compare');
|
||||
res.handlers.skins.renderSkin('$Skins#page');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {String}
|
||||
|
|
@ -313,7 +272,7 @@ Skin.prototype.getSource = function() {
|
|||
if (skin) {
|
||||
return skin.getSource();
|
||||
}
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,29 +12,34 @@
|
|||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
.logo {
|
||||
position: sticky;
|
||||
top: 15px;
|
||||
z-index: 2;
|
||||
}
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 15px 15px 0;
|
||||
border-block-end: 1px solid #ccc;
|
||||
background-color: white;
|
||||
}
|
||||
.metadata {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.editor {
|
||||
border-block-end: 1px solid #ccc;
|
||||
}
|
||||
.cm-helma-macro {
|
||||
color: #000;
|
||||
}
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.av-line {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.av-line-removed {
|
||||
background-color: #fff1f0;
|
||||
}
|
||||
.av-line-added {
|
||||
background-color: #f2fae3;
|
||||
}
|
||||
</style>
|
||||
<script src='<% root.static ../../scripts/editor.min.js %>'></script>
|
||||
<script defer src='<% site.href main.js %>'></script>
|
||||
</head>
|
||||
<body>
|
||||
<span class='uk-margin-right uk-float-right'>
|
||||
<span class='uk-margin-right uk-float-right logo'>
|
||||
<% image /smallchaos.gif | link <% site.href %> %>
|
||||
</span>
|
||||
<% response.body %>
|
||||
|
|
|
|||
2846
package-lock.json
generated
2846
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
|
@ -11,7 +11,7 @@
|
|||
"claustra:add": "tools/claustra/add-claustra.js",
|
||||
"build": "run-p --continue-on-error --print-label build:*",
|
||||
"build:main.js": "browserify tools/client/main.js --outfile static/scripts/main.min.js",
|
||||
"build:editor.js": "browserify tools/client/editor.js --outfile static/scripts/editor.min.js",
|
||||
"build:editor.js": "client/build.mjs",
|
||||
"build:gallery.js": "browserify tools/client/gallery.js --outfile static/scripts/gallery.min.js",
|
||||
"build:main.css": "lessc --clean-css tools/client/main.less static/styles/main.min.css",
|
||||
"build:editor.css": "lessc --clean-css tools/client/editor.less static/styles/editor.min.css",
|
||||
|
|
@ -31,15 +31,26 @@
|
|||
"author": "The Antville People",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "6.4.9",
|
||||
"@codemirror/merge": "6.6.3",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"alpinejs": "3.14.9",
|
||||
"codemirror": "5.65.19",
|
||||
"codemirror6": "npm:codemirror@6.0.1",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-collagePlus": "github:antville/jquery-collagePlus#0.3.4",
|
||||
"js-md5": "0.8.3",
|
||||
"uikit": "2.27.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.24.7",
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/plugin-transform-block-scoping": "7.24.7",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"browserify": "17.0.1",
|
||||
"core-js": "3.37.1",
|
||||
"esbuild": "0.21.5",
|
||||
"esbuild-plugin-babel": "0.2.3",
|
||||
"generate-license-file": "4.0.0",
|
||||
"jsdoc": "4.0.4",
|
||||
"less": "4.3.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue