Change diff widget to webcomponent, update diff library

This commit is contained in:
nathan 2024-12-13 16:23:56 -07:00
parent 0c98dc30a5
commit 0c387604f8
12 changed files with 329 additions and 241 deletions

View File

@ -41,7 +41,7 @@ module.exports = function (grunt) {
files: {
"pixelegg/css/fancy.min.css": [
"node_modules/flatpickr/dist/themes/light.css",
"vendor/bower-asset/diff2html/dist/diff2html.css",
"node_modules/diff2html/bundles/css/diff2html.min.css",
"vendor/bower-asset/cropper/dist/cropper.min.css",
"api/templates/default/css/flags.css",
"api/templates/default/css/htmlarea.css",
@ -52,7 +52,7 @@ module.exports = function (grunt) {
],
"pixelegg/css/pixelegg.min.css": [
"node_modules/flatpickr/dist/themes/light.css",
"vendor/bower-asset/diff2html/dist/diff2html.css",
"node_modules/diff2html/bundles/css/diff2html.min.css",
"vendor/bower-asset/cropper/dist/cropper.min.css",
"api/templates/default/css/flags.css",
"api/templates/default/css/htmlarea.css",
@ -63,7 +63,7 @@ module.exports = function (grunt) {
],
"pixelegg/css/mobile.min.css": [
"node_modules/flatpickr/dist/themes/light.css",
"vendor/bower-asset/diff2html/dist/diff2html.css",
"node_modules/diff2html/bundles/css/diff2html.min.css",
"vendor/bower-asset/cropper/dist/cropper.min.css",
"api/templates/default/css/flags.css",
"api/templates/default/css/htmlarea.css",
@ -74,7 +74,7 @@ module.exports = function (grunt) {
],
"pixelegg/mobile/fw_mobile.min.css": [
"node_modules/flatpickr/dist/themes/light.css",
"api/js/etemplate/lib/jsdifflib/diffview.css",
"node_modules/diff2html/bundles/css/diff2html.min.css",
"vendor/bower-asset/cropper/dist/cropper.min.css",
"api/templates/default/css/flags.css",
"api/templates/default/css/htmlarea.css",

View File

@ -0,0 +1,45 @@
```html:preview
<et2-diff class="diff-example"></et2-diff>
<script>
document.querySelector(".diff-example").value = `--- diff
+++ diff
@@ -1,2 +1,3 @@
Diff widget shows changes
-highlighting removed words
+highlighting added words
+and lines
`;
</script>
```
Shows a snippet of a [diff](https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html), and if you
click on it shows a dialog with the whole diff.
## Examples
### noDialog
Use the noDialog property to disable the size limit and show the whole diff
```html:preview
<et2-diff noDialog class="big-diff"></et2-diff>
<script>
document.querySelector(".big-diff").value = `
--- a/doc/etemplate2/etemplate2.0.dtd (revision 30665eb1c58c6d903dde091382c7121f5cf5de88)
+++ b/doc/etemplate2/etemplate2.0.dtd (revision e96e8d9469bf4bc5f52a58d0dd8d9c2b87bc914e)
@@ -27,21 +27,22 @@
|barcode|itempicker|script|countdown|customfields-types|nextmatch|nextmatch-header
|nextmatch-customfields|nextmatch-sortheader|et2-nextmatch-header-account|et2-appicon|et2-avatar
|et2-avatar-group|et2-box|et2-button|et2-button-copy|et2-button-icon|et2-button-scroll
- |et2-button-timestamp|et2-category-tag|et2-checkbox|et2-colorpicker|et2-nextmatch-columnselection
- |et2-nextmatch-header-custom|et2-date|et2-date-duration|et2-date-range|et2-date-since|et2-date-time
- |et2-date-timeonly|et2-date-time-today|et2-description|et2-description-expose|et2-details|et2-dialog
- |et2-dropdown-button|et2-email|et2-email-tag|et2-nextmatch-header-entry|et2-favorites
+ |et2-button-timestamp|et2-button-toggle|et2-category-tag|et2-checkbox|et2-colorpicker
+ |et2-nextmatch-columnselection|et2-nextmatch-header-custom|et2-date|et2-date-duration|et2-date-range
+ |et2-date-since|et2-date-time|et2-date-timeonly|et2-date-time-today|et2-description
+ |et2-description-expose|et2-details|et2-dialog|et2-dropdown|et2-dropdown-button|et2-email
+ |et2-email-tag|et2-nextmatch-header-entry|et2-favorites|et2-favorites-menu
`;
</script>
```

View File

@ -0,0 +1,172 @@
import {customElement} from "lit/decorators/custom-element.js";
import {css, html, LitElement, nothing, PropertyValueMap, render} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {unsafeHTML} from "lit/directives/unsafe-html.js";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import * as Diff2Html from "diff2html";
import {Diff2HtmlConfig} from "diff2html";
import {ColorSchemeType} from "diff2html/lib/types";
import {property} from "lit/decorators/property.js";
import shoelace from "../Styles/shoelace";
import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
/**
* Show a nicely formatted diff
*/
@customElement("et2-diff")
export class Et2Diff extends Et2InputWidget(LitElement)
{
@property({type: Boolean, reflect: true})
open = false;
/**
* Disable the dialog and show the whole diff
*
* @type {boolean}
*/
@property({type: Boolean, reflect: true})
noDialog = false;
// CSS in etemplate2.css due to library
static get styles()
{
return [
shoelace,
...super.styles,
css`
:host {
position: relative;
}
.expand-icon {
display: none;
position: absolute;
bottom: var(--sl-spacing-medium);
right: var(--sl-spacing-medium);
background-color: var(--sl-panel-background-color);
z-index: 1;
}
:host(:hover) {
.expand-icon {
display: initial;
}
}
:host(:not([open])) {
cursor: pointer;
}
:host(:not([noDialog])) .form-control-input {
max-height: 9em;
overflow: hidden;
}
`
];
}
private readonly diff_options : Diff2HtmlConfig = {
matching: "words",
drawFileList: false,
colorScheme: ColorSchemeType.AUTO
};
updated(changedProperties : PropertyValueMap<any>)
{
if(changedProperties.has("value") || this.value && this.childElementCount == 0)
{
// Put diff into lightDOM so styles can leak, since we can't import the library CSS into the component
render(html`${unsafeHTML(Diff2Html.html(this.value ?? "", this.diff_options))}`, this, {host: this});
}
}
set value(value : string)
{
if(typeof value == 'string')
{
// Diff2Html likes to have files, we don't have them
if(value.indexOf('---') !== 0)
{
value = "--- diff\n+++ diff\n" + value;
}
super.value = value;
this.requestUpdate("value");
}
}
_handleClick(e)
{
const oldValue = this.getAttribute("open")
this.toggleAttribute("open");
this.requestUpdate("open", oldValue);
}
getDetachedAttributes(attrs)
{
attrs.push("id", "value", "class");
}
getDetachedNodes() : HTMLElement[]
{
return [<HTMLElement><unknown>this];
}
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
{
for(let attr in _values)
{
this[attr] = _values[attr];
}
}
render()
{
const labelTemplate = this._labelTemplate();
const helpTextTemplate = this._helpTextTemplate();
return html`
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true,
'form-control--has-label': labelTemplate !== nothing,
'form-control--has-help-text': helpTextTemplate !== nothing
})}
>
${labelTemplate}
<div part="form-control-input" class="form-control-input">
<!-- Actual diff goes into lightDOM since we can't import the CSS directly -->
${this.open && !this.noDialog ?
html`
<et2-dialog
part=dialog" label="Diff" open
buttons=${Et2Dialog.BUTTONS_OK}
@click=${(e) =>
{
// Stop bubble or it will re-show dialog
e.stopPropagation()
}}
@close=${() =>
{
this.removeAttribute("open");
this.requestUpdate("open", true);
}}
>
<slot></slot>
</et2-dialog>` : html`
${!this.noDialog ? html`
<et2-button-icon
part="expand-icon"
class="expand-icon"
image="arrows-fullscreen" label="View" noSubmit></et2-button-icon>` : nothing}
<slot></slot>`
}
</div>
${helpTextTemplate}
</div>
`;
}
}

View File

@ -9,202 +9,15 @@
* @copyright Nathan Gray 2012
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/vendor/bower-asset/diff2html/dist/diff2html.min.js;
et2_core_valueWidget;
*/
import "../../../vendor/bower-asset/diff2html/dist/diff2html.min";
import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_valueWidget} from "./et2_core_valueWidget";
import {et2_IDetachedDOM} from "./et2_core_interfaces";
import {Et2Dialog} from "./Et2Dialog/Et2Dialog";
import {et2_register_widget} from "./et2_core_widget";
import {Et2Diff} from "./Et2Diff/Et2Diff";
/**
* Class that displays the diff between two [text] values
*
* @augments et2_valueWidget
*/
export class et2_diff extends et2_valueWidget implements et2_IDetachedDOM
export class et2_diff extends Et2Diff
{
static readonly _attributes = {
"value": {
"type": "any"
}
};
private readonly diff_options: {
"inputFormat":"diff",
"matching": "words"
};
private div: HTMLDivElement;
private mini: boolean = true;
/**
* Constructor
*/
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
{
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_diff._attributes, _child || {}));
// included via etemplate2.css
//this.egw().includeCSS('../../../vendor/bower-asset/dist/dist2html.css');
this.div = document.createElement("div");
jQuery(this.div).addClass('et2_diff');
}
set_value( value)
{
jQuery(this.div).empty();
if(typeof value == 'string') {
// Diff2Html likes to have files, we don't have them
if(value.indexOf('---') !== 0)
{
value = "--- diff\n+++ diff\n"+value;
}
// @ts-ignore
var diff = Diff2Html.getPrettyHtml(value, this.diff_options);
// var ui = new Diff2HtmlUI({diff: diff});
// ui.draw(jQuery(this.div), this.diff_options);
jQuery(this.div).append(diff);
}
else if(typeof value != 'object')
{
jQuery(this.div).append(value);
}
this.check_mini();
}
check_mini( )
{
if(!this.mini)
{
return false;
}
var view = jQuery(this.div).children();
this.minify(view);
var self = this;
jQuery('<span class="ui-icon ui-icon-circle-plus">&nbsp;</span>')
.appendTo(self.div)
.css("cursor", "pointer")
.click({diff: view, div: self.div, label: self.options.label}, function(e)
{
var diff = e.data.diff;
var div = e.data.div;
self.un_minify(diff);
let dialog = new Et2Dialog(self.egw());
dialog.transformAttributes({
title: e.data.label,
//modal: true,
buttons: [{label: 'ok'}],
class: "et2_diff",
});
diff.attr("slot", "content");
dialog.addEventListener("open", () =>
{
diff.appendTo(dialog);
if(jQuery(this).parent().height() > jQuery(window).height())
{
jQuery(this).height(jQuery(window).height() * 0.7);
}
});
dialog.addEventListener("close", () =>
{
// Need to destroy the dialog, etemplate widget needs divs back where they were
self.minify(this);
// Put it back where it came from, or et2 will error when clear() is called
diff.prependTo(div);
});
document.body.appendChild(dialog);
});
}
set_label( _label)
{
this.options.label = _label;
}
/**
* Make the diff into a mini-diff
*
* @param {DOMNode|String} view
*/
minify( view)
{
view = jQuery(view)
.addClass('mini')
// Dialog changes these, if resized
.css('height', 'inherit')
.show();
jQuery('th', view).hide();
jQuery('td.equal',view).hide()
.prevAll().hide();
}
/**
* Expand mini-diff
*
* @param {DOMNode|String} view
*/
un_minify( view)
{
jQuery(view).removeClass('mini').show();
jQuery('th',view).show();
jQuery('td.equal',view).show();
}
/**
* Code for implementing et2_IDetachedDOM
* Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
*/
/**
* Build a list of attributes which can be set when working in the
* "detached" mode in the _attrs array which is provided
* by the calling code.
*
* @param {object} _attrs
*/
getDetachedAttributes(_attrs)
{
_attrs.push("value", "label");
}
/**
* Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
* passed to the "setDetachedAttributes" function in the same order.
*/
getDetachedNodes()
{
return [this.div];
}
/**
* Sets the given associative attribute->value array and applies the
* attributes to the given DOM-Node.
*
* @param _nodes is an array of nodes which has to be in the same order as
* the nodes returned by "getDetachedNodes"
* @param _values is an associative array which contains a subset of attributes
* returned by the "getDetachedAttributes" function and sets them to the
* given values.
*/
setDetachedAttributes(_nodes, _values)
{
this.div = _nodes[0];
if(typeof _values['label'] != 'undefined')
{
this.set_label(_values['label']);
}
if(typeof _values['value'] != 'undefined')
{
this.set_value(_values['value']);
}
}
}
et2_register_widget(et2_diff, ["diff"]);

View File

@ -25,12 +25,12 @@ import {et2_valueWidget} from "./et2_core_valueWidget";
import {et2_dataview} from "./et2_dataview";
import {et2_dataview_column} from "./et2_dataview_model_columns";
import {et2_dataview_controller} from "./et2_dataview_controller";
import {et2_diff} from "./et2_widget_diff";
import {et2_IDetachedDOM, et2_IResizeable} from "./et2_core_interfaces";
import {et2_customfields_list} from "./et2_extension_customfields";
import {et2_selectbox} from "./et2_widget_selectbox";
import {loadWebComponent} from "./Et2Widget/Et2Widget";
import {cleanSelectOptions, SelectOption} from "./Et2Select/FindSelectOptions";
import {Et2Diff} from "./Et2Diff/Et2Diff";
/**
* eTemplate history log widget displays a list of changes to the current record.
@ -98,7 +98,7 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
private dataview: et2_dataview;
private controller: et2_dataview_controller;
private fields: any;
private diff: et2_diff;
private diff : { nodes : Et2Diff, widget : Et2Diff };
/**
* Constructor
@ -399,11 +399,10 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
};
}
// Widget for text diffs
const diff = et2_createWidget('diff', {}, this);
const diff = loadWebComponent('et2-diff', {}, this);
this.diff = {
// @ts-ignore
widget: diff,
nodes: jQuery(diff.getDetachedNodes())
nodes: diff
};
}
@ -656,7 +655,7 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
widget = self.diff.widget;
nodes = self.diff.nodes.clone();
if(widget) widget.setDetachedAttributes(nodes, {
if(nodes) nodes.setDetachedAttributes(nodes, {
value: value,
label: jthis.parents("td").prev().text()
});
@ -678,7 +677,11 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
{
const id = widget._children[j].id;
const widget_value = value ? value[id] || "" : "";
widget._children[j].setDetachedAttributes(nodes[j], {value: widget_value});
widget._children[j].setDetachedAttributes(widget._children[j], {value: widget_value});
if(widget._children[j] instanceof Et2Diff)
{
self._spanValueColumns(jQuery(this));
}
}
}
else
@ -754,7 +757,7 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
if(this.dataview)
{
const columns = this.dataview.getColumnMgr();
jQuery('.et2_diff', this.div).closest('.innerContainer')
jQuery('[colspan=2]', this.div).find('.innerContainer')
.width(columns.getColumnWidth(et2_historylog.NEW_VALUE) + columns.getColumnWidth(et2_historylog.OLD_VALUE));
}
}

View File

@ -54,6 +54,7 @@ import './Et2Date/Et2DateTimeToday';
import './Et2Description/Et2Description';
import './Et2Dialog/Et2Dialog';
import './Et2Dialog/Et2MergeDialog';
import './Et2Diff/Et2Diff';
import './Et2DropdownButton/Et2DropdownButton';
import './Et2Email/Et2Email';
import './Expose/Et2ImageExpose';

View File

@ -883,62 +883,42 @@ for printing
/**
* Diff widget
*/
div.et2_diff {
width: 100%;
}
.et2_diff thead,
.author,
.d2h-file-header,
.d2h-file-info,
.d2h-info,
.et2_diff:not(.ui-dialog-content) .d2h-cntx {
et2-diff thead,
et2-diff .author,
et2-diff .d2h-file-header,
et2-diff .d2h-file-info,
et2-diff .d2h-info {
display: none;
}
.d2h-file-diff {
et2-diff .d2h-file-diff {
white-space: normal;
}
.et2_diff .d2h-file-wrapper {
}
.et2_diff .d2h-file-diff {
et2-diff .d2h-file-diff {
overflow-x: hidden;
}
.et2_diff .d2h-code-wrapper {
et2-diff .d2h-code-wrapper {
position: relative;
}
.ui-widget-content .d2h-code-line-ctn {
white-space: normal;
et2-diff .d2h-del, et2-diff.d2h-del.d2h-change, et2-diff .d2h-file-diff .d2h-del.d2h-change {
background-color: var(--sl-color-red-100, #ffeef0);
}
.ui-widget-content .d2h-file-diff {
overflow-x: visible;
overflow-y: visible;
et2-diff .d2h-code-line del, et2-diff .d2h-code-side-line del {
background-color: var(--sl-color-red-300, #fdb8c0);
}
.et2_diff .ui-icon {
margin-top: -14px;
float: right;
et2-diff .d2h-ins, et2-diff.d2h-ins.d2h-change, et2-diff .d2h-file-diff .d2h-ins.d2h-change {
background-color: var(--sl-color-green-100, #e6ffed);
}
.et2_diff .d2h-del, .et2_diff.d2h-del.d2h-change, .et2_diff .d2h-file-diff .d2h-del.d2h-change {
background-color: #ffeef0;
}
.et2_diff .d2h-code-line del, .et2_diff .d2h-code-side-line del {
background-color: #fdb8c0;
}
.et2_diff .d2h-ins, .et2_diff.d2h-ins.d2h-change, .et2_diff .d2h-file-diff .d2h-ins.d2h-change {
background-color: #e6ffed;
}
.et2_diff .d2h-code-line ins, .et2_diff .d2h-code-side-line ins {
background-color: #acf2bd;
et2-diff .d2h-code-line ins, et2-diff .d2h-code-side-line ins {
background-color: var(--sl-color-green-300, #acf2bd);
}
/** Display a loading icon **/

View File

@ -75,7 +75,6 @@
"async-aws/s3": "^2.0",
"bigbluebutton/bigbluebutton-api-php": "^2.0",
"bower-asset/cropper": "2.3.*",
"bower-asset/diff2html": "^2.7",
"bower-asset/jquery": "^1.12.4",
"defuse/php-encryption": "^2.4",
"egroupware/activesync": "self.version",

View File

@ -43,6 +43,7 @@
<!-- Breaks page scrolling <link rel="stylesheet" href="{{ assetUrl('styles/monochrome.css') }}" /> -->
<script src="{{ assetUrl('scripts/sub/dir/etemplate/etemplate2.js') }}" type="module" id="egw_script_id"></script>
<link rel="stylesheet" href="{{assetUrl("styles/bootstrap-icons.min.css")}}" />
<link rel="stylesheet" href="{{assetUrl("styles/diff2html.min.css")}}" />
{# Set the initial theme and menu states here to prevent flashing #}
<script>

View File

@ -70,7 +70,6 @@ module.exports = function (eleventyConfig)
eleventyConfig.addPassthroughCopy({
"../../vendor/bower-asset/jquery/dist/jquery.min.js": "assets/scripts/vendor/bower-asset/jquery/dist/jquery.min.js",
"../../vendor/bower-asset/cropper/dist/cropper.min.js": "assets/scripts/vendor/bower-asset/cropper/dist/cropper.min.js",
"../../vendor/bower-asset/diff2html/dist/diff2html.min.js": "assets/scripts/vendor/bower-asset/diff2html/dist/diff2html.min.js",
"../../vendor/tinymce/tinymce/tinymce.min.js": "assets/scripts/vendor/tinymce/tinymce/tinymce.min.js",
})
@ -78,6 +77,7 @@ module.exports = function (eleventyConfig)
eleventyConfig.addPassthroughCopy({"../../chunks": "assets/scripts/chunks"});
eleventyConfig.addPassthroughCopy({"../../api/js/etemplate/etemplate2.js": "assets/scripts/sub/dir/etemplate/etemplate2.js"});
eleventyConfig.addPassthroughCopy({"../../node_modules/bootstrap-icons/font/bootstrap-icons.min.css": "assets/styles/bootstrap-icons.min.css"});
eleventyConfig.addPassthroughCopy({"../../node_modules/diff2html/bundles/css/diff2html.min.css": "assets/styles/diff2html.min.css"});
//eleventyConfig.addPassthroughCopy({"../../vendor/**/*min.js": "assets/scripts/vendor/"});
//eleventyConfig.addPassthroughCopy("../dist/etemplate2.js", "assets/scripts/etemplate2.js");

73
package-lock.json generated
View File

@ -18,6 +18,7 @@
"colortranslator": "^1.9.2",
"core-js": "^3.29.1",
"dexie": "^3.2.4",
"diff2html": "^3.4.48",
"imask": "^7.6.1",
"lit": "^2.7.5",
"lit-flatpickr": "^0.3.0",
@ -7258,6 +7259,31 @@
"node": ">=0.3.1"
}
},
"node_modules/diff2html": {
"version": "3.4.48",
"resolved": "https://registry.npmjs.org/diff2html/-/diff2html-3.4.48.tgz",
"integrity": "sha512-1lzNSg0G0VPKZPTyi4knzV2nAWTXBy/QaWCKzDto6iEIlcuOJEG0li4bElJfpHNz+pBqPu4AcC1i9ZCo9KMUOg==",
"license": "MIT",
"dependencies": {
"diff": "5.1.0",
"hogan.js": "3.0.2"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"highlight.js": "11.9.0"
}
},
"node_modules/diff2html/node_modules/diff": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -9248,6 +9274,53 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true
},
"node_modules/highlight.js": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/hogan.js": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
"dependencies": {
"mkdirp": "0.3.0",
"nopt": "1.0.10"
},
"bin": {
"hulk": "bin/hulk"
}
},
"node_modules/hogan.js/node_modules/mkdirp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
"license": "MIT/X11",
"engines": {
"node": "*"
}
},
"node_modules/hogan.js/node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"license": "MIT",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",

View File

@ -90,6 +90,7 @@
"colortranslator": "^1.9.2",
"core-js": "^3.29.1",
"dexie": "^3.2.4",
"diff2html": "^3.4.48",
"imask": "^7.6.1",
"lit": "^2.7.5",
"lit-flatpickr": "^0.3.0",