Refactor skin editor with CodeMirror 6

This commit is contained in:
Tobi Schäfer 2024-09-21 23:09:17 +02:00
parent 439b2cce18
commit f559d2f78e
Signed by: tobi
GPG key ID: 91FAE6FE2EBAC4C8
8 changed files with 3005 additions and 160 deletions

16
client/babel.config.json Normal file
View 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
View 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
View 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";

View file

@ -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>

View file

@ -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 '';
}
/**

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",