From 7420556d45f3ce0cc4a1ec4a29db10010c72d8e5 Mon Sep 17 00:00:00 2001 From: Brook Gagnon Date: Thu, 25 Jul 2024 01:23:42 +0000 Subject: [PATCH] some metadata field refactoring, update media edit / details to use new field elements --- html/media/addedit_form.html | 14 +- js/media/addedit.js | 23 +- js/media/details.js | 33 ++- js/media/settings.js | 2 +- package-lock.json | 440 ++++++++++++++++++++++++++++++++++- package.json | 2 + ui/base/element.js | 6 + ui/base/field.js | 65 +++--- ui/fields/bool.js | 38 +-- ui/fields/checkbox.js | 1 + ui/fields/country.js | 42 ++-- ui/fields/datetime.js | 53 +---- ui/fields/formatted.js | 62 +++-- ui/fields/group.js | 2 +- ui/fields/hidden.js | 1 + ui/fields/language.js | 5 +- ui/fields/media.js | 49 ++-- ui/fields/number.js | 29 +-- ui/fields/password.js | 45 ++-- ui/fields/playlist.js | 66 +++--- ui/fields/select.js | 22 +- ui/fields/tags.js | 28 ++- ui/fields/text.js | 26 ++- ui/fields/textarea.js | 46 +--- ui/fields/user.js | 4 +- ui/vendor.js | 2 + 26 files changed, 739 insertions(+), 367 deletions(-) diff --git a/html/media/addedit_form.html b/html/media/addedit_form.html index f7b89163..3062dd83 100644 --- a/html/media/addedit_form.html +++ b/html/media/addedit_form.html @@ -47,7 +47,7 @@
- +
@@ -57,6 +57,12 @@ +
+ + + +
+
@@ -69,12 +75,6 @@
-
- - - -
-
diff --git a/js/media/addedit.js b/js/media/addedit.js index ed781a08..9f777824 100644 --- a/js/media/addedit.js +++ b/js/media/addedit.js @@ -124,6 +124,7 @@ OB.Media.mediaAddeditForm = function (id, title, editing) { } // fill country list + /* for (var i in OB.Settings.countries) { $form .find(".country_field") @@ -135,6 +136,7 @@ OB.Media.mediaAddeditForm = function (id, title, editing) { "", ); } + */ // tie together genre list with category list on change $form.find(".category_field").change(function () { @@ -156,12 +158,10 @@ OB.Media.mediaAddeditForm = function (id, title, editing) { .setAttribute("data-id3-field", metadata.settings.id3_key); } - // set select field options - if (metadata.type == "select" && metadata.settings && metadata.settings.options) { - metadata.settings.options.forEach(function (option) { - let selectField = metadataField.querySelector(".metadata_name_field"); - selectField.innerHTML = selectField.innerHTML + ""; - }); + // settings + let selectField = metadataField.querySelector(".metadata_name_field"); + if (metadata.settings) { + selectField.settings = metadata.settings; } // change field name and description values @@ -189,12 +189,6 @@ OB.Media.mediaAddeditForm = function (id, title, editing) { "#media_addedit_" + id + " .metadata_" + metadata.name + "_field", ).suggestions = metadata.settings.suggestions; } - - // set options - if (metadata.type == "select" && metadata?.settings?.options) { - document.querySelector("#media_addedit_" + id + " .metadata_" + metadata.name + "_field").options = - metadata.settings.options; - } } }); @@ -452,8 +446,9 @@ OB.Media.save = function () { .val(); // media and playlist metadata fields return arrays, but will be a single item, so convert: - if (Array.isArray(metaItem) && (metadata.type == "media" || metadata.type == "playlist")) { - metaItem = metaItem[0]; + if (metadata.type == "media" || metadata.type == "playlist") { + if (!metaItem || !metaItem.length) metaItem = null; + else metaItem = metaItem[0]; } item["metadata_" + metadata.name] = metaItem; diff --git a/js/media/details.js b/js/media/details.js index 80f7c752..60185941 100644 --- a/js/media/details.js +++ b/js/media/details.js @@ -99,26 +99,19 @@ OB.Media.detailsPage = function (id) { labelElem.textContent = metadata.description; metaElem.appendChild(labelElem); - switch (metadata.type) { - case "media": - case "playlist": - const mediaPlaylistElem = document.createElement("ob-field-" + metadata.type); - mediaPlaylistElem.setAttribute("value", value); - mediaPlaylistElem.dataset.single = true; - metaElem.appendChild(mediaPlaylistElem); - break; - case "time": - case "date": - case "datetime": - const dateTimeElem = document.createElement("ob-field-" + metadata.type); - dateTimeElem.setAttribute("value", value); - metaElem.appendChild(dateTimeElem); - break; - default: - const spanElem = document.createElement("span"); - spanElem.textContent = value; - metaElem.appendChild(spanElem); - break; + // use custom html element if available + if (customElements.get("ob-field-" + metadata.type)) { + const fieldElem = document.createElement("ob-field-" + metadata.type); + fieldElem.value = value; + fieldElem.settings = metadata.settings; + metaElem.appendChild(fieldElem); + } + + // fallback to span + else { + const spanElem = document.createElement("span"); + spanElem.textContent = value; + metaElem.appendChild(spanElem); } document.querySelector("#media_details_metadata").appendChild(metaElem); diff --git a/js/media/settings.js b/js/media/settings.js index 23ecda42..db33180f 100644 --- a/js/media/settings.js +++ b/js/media/settings.js @@ -404,7 +404,7 @@ OB.Media.metadataAddEditTypeChange = function () { // handle select field var select_value = document.querySelector(".metadata_default_select").value; - var select_options = document.querySelector("#metadata_select_options").value.split("\n"); + var select_options = document.querySelector("#metadata_select_options").value?.split("\n"); document.querySelector(".metadata_default_select").options = select_options; document.querySelector(".metadata_default_select").value = select_value; diff --git a/package-lock.json b/package-lock.json index f7430c24..10b0c302 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,10 @@ "dayjs": "^1.11.11", "easymde": "^2.18.0", "htm": "^3.1.1", + "isomorphic-dompurify": "^2.13.0", "jquery": "^3.6.1", "jquery-migrate": "^3.4.0", + "marked": "^13.0.2", "sass": "^1.69.5", "video.js": "^8.10.0" }, @@ -109,6 +111,14 @@ "@types/tern": "*" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -162,6 +172,11 @@ "@types/estree": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, "node_modules/@videojs/http-streaming": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.12.1.tgz", @@ -464,6 +479,17 @@ "npm": ">=5" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -501,6 +527,11 @@ "node": ">= 8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -657,6 +688,17 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -698,16 +740,78 @@ "node": ">= 8" } }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/dayjs": { "version": "1.11.11", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" + }, "node_modules/easymde": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.18.0.tgz", @@ -720,6 +824,17 @@ "marked": "^4.1.0" } }, + "node_modules/easymde/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.817", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.817.tgz", @@ -739,6 +854,17 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", @@ -872,6 +998,19 @@ "flat": "cli.js" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -952,6 +1091,52 @@ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immutable": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", @@ -1066,6 +1251,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1087,6 +1277,19 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-dompurify": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.13.0.tgz", + "integrity": "sha512-jVxFnyOiA3fKPkteQjfIogww9T/BIX1Basuwt5D50MB3Sqvki9yBNq96ICLHpbiDY79jc6RC555DeBbTCt6i6A==", + "dependencies": { + "@types/dompurify": "^3.0.5", + "dompurify": "^3.1.6", + "jsdom": "^24.1.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -1114,6 +1317,45 @@ "jquery": ">=3 <4" } }, + "node_modules/jsdom": { + "version": "24.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", + "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -1196,14 +1438,14 @@ } }, "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.2.tgz", + "integrity": "sha512-J6CPjP8pS5sgrRqxVRvkCIkZ6MFdRIjDkwUwgJ9nL2fbmM6qGQeB2C16hi8Cc9BOzj6xXzy0jyi0iPIfnMHYzA==", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/merge-stream": { @@ -1216,7 +1458,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -1225,7 +1466,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -1255,6 +1495,11 @@ "mpd-to-m3u8-json": "bin/parse.js" } }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/mux.js": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.0.3.tgz", @@ -1291,6 +1536,11 @@ "node": ">=0.10.0" } }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==" + }, "node_modules/os-shim": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", @@ -1336,6 +1586,17 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1491,15 +1752,24 @@ "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1558,6 +1828,11 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -1596,6 +1871,11 @@ "node": ">=8" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "node_modules/rust-result": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", @@ -1632,6 +1912,11 @@ "rust-result": "^1.0.0" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/sass": { "version": "1.77.6", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", @@ -1648,6 +1933,17 @@ "node": ">=14.0.0" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -1788,6 +2084,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -1860,6 +2161,31 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -1877,6 +2203,14 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -1916,6 +2250,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url-toolkit": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", @@ -1976,6 +2319,17 @@ "global": "^4.3.1" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", @@ -1989,6 +2343,14 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.92.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz", @@ -2113,6 +2475,37 @@ "node": ">=10.13.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2134,6 +2527,39 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/package.json b/package.json index 7f53a25e..02507fae 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "dayjs": "^1.11.11", "easymde": "^2.18.0", "htm": "^3.1.1", + "isomorphic-dompurify": "^2.13.0", "jquery": "^3.6.1", "jquery-migrate": "^3.4.0", + "marked": "^13.0.2", "sass": "^1.69.5", "video.js": "^8.10.0" }, diff --git a/ui/base/element.js b/ui/base/element.js index d59abdb5..3ba05522 100644 --- a/ui/base/element.js +++ b/ui/base/element.js @@ -106,4 +106,10 @@ export class OBElement extends HTMLElement { return null; // Return null if no ancestor with the specified tag name is found } + + // add a listener if not already added + ensureListener(element, event, callback) { + element.removeEventListener(event, callback); + element.addEventListener(event, callback); + } } diff --git a/ui/base/field.js b/ui/base/field.js index 27a1685c..23de3b57 100644 --- a/ui/base/field.js +++ b/ui/base/field.js @@ -2,24 +2,26 @@ import { html, render } from "../vendor.js"; import { OBElement } from "../base/element.js"; export class OBField extends OBElement { + _value; + _editable; + _settings; + async connectedCallback() { if (this.connected) { await this.connected(); } - if (this.getAttribute("value")) { - this.value = this.getAttribute("value"); + if (this.getAttribute("value") && this._value === undefined) { + this._value = this.getAttribute("value"); } - this.resolveInitialized(); - } + if (this.hasAttribute("data-edit") && this._editable === undefined) { + this._editable = true; + } - renderView() { - render(html` ${this.value} `, this.root); - } + this.resolveInitialized(); - renderEdit() { - render(html``, this.root); + this.renderComponent(); } // Bind this to events inside the shadowroot to propagate them to outside @@ -37,37 +39,42 @@ export class OBField extends OBElement { } get value() { - if (this.root.querySelector("input")) { - return this.root.querySelector("input").value; - } + return this._value; } set value(value) { - if (!this.root.querySelector("input")) { - return; - } - - this.root.querySelector("input").value = value; - this.renderComponent(); + this._value = value; + this.refresh(); } get editable() { - return this.hasAttribute("data-edit"); + return this._editable; } - set editable(value) { - if (value) { - this.setAttribute("data-edit", ""); - } else { - this.removeAttribute("data-edit"); - } + set editable(editable) { + this._editable = !!editable; + this.refresh(); + } - this.renderComponent(); + get settings() { + return this._settings; + } + + set settings(settings) { + this._settings = settings; + this.refresh(); } async renderComponent() { - const edit = this.hasAttribute("data-edit"); - if (edit) this.renderEdit(); - else this.renderView(); + if (this._editable) await this.renderEdit(); + else await this.renderView(); + } + + async renderView() { + render(html`
${this._value}
`, this.root); + } + + async renderEdit() { + render(html``, this.root); } } diff --git a/ui/fields/bool.js b/ui/fields/bool.js index b1e0d9cf..076ed7d9 100644 --- a/ui/fields/bool.js +++ b/ui/fields/bool.js @@ -2,30 +2,17 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; class OBFieldBool extends OBField { - #init; - - async connectedCallback() { - if (this.#init) { - return; - } - this.#init = true; - - this.renderComponent().then(() => { - if (this.hasAttribute("value")) { - const value = this.getAttribute("value"); - if (value.toLowerCase() === "true" || value === "1") { - this.root.querySelector("input").checked = true; - } - } - }); + renderView() { + const output = this._value ? "Yes" : "No"; + render(html`${output}`, this.root); } renderEdit() { - render(html` `, this.root); + render(html``, this.root); } - renderView() { - render(html` `, this.root); + inputChange(event) { + this._value = event.target.checked; } scss() { @@ -39,19 +26,6 @@ class OBFieldBool extends OBField { } `; } - - get value() { - if (this.root.querySelector("input")) { - return this.root.querySelector("input").checked; - } else { - return false; - } - } - - set value(value) { - this.root.querySelector("input").checked = value; - this.renderComponent(); - } } customElements.define("ob-field-bool", OBFieldBool); diff --git a/ui/fields/checkbox.js b/ui/fields/checkbox.js index defd802a..96675ecc 100644 --- a/ui/fields/checkbox.js +++ b/ui/fields/checkbox.js @@ -1,6 +1,7 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; +// TODO remove, use ob-field-boolean instead. class OBFieldCheckbox extends OBField { #init; diff --git a/ui/fields/country.js b/ui/fields/country.js index c7a6dcee..d8263556 100644 --- a/ui/fields/country.js +++ b/ui/fields/country.js @@ -2,8 +2,6 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; class OBFieldCountry extends OBField { - #init; - static countries = null; async connected() { @@ -17,42 +15,42 @@ class OBFieldCountry extends OBField { OBFieldCountry.countries[country.country_id] = country.name; } } - - this.renderComponent().then(() => {}); } renderEdit() { - render(html` `, this.root); - - const fieldSelect = this.root.querySelector("ob-field-select"); - fieldSelect.options = OBFieldCountry.countries; - fieldSelect.refresh(); + render(html``, this.root); + this.fieldSelect = this.root.querySelector("ob-field-select"); + this.fieldSelect.options = OBFieldCountry.countries; + this.fieldSelect.value = this.value; + + if (this.initValue) { + const temp = this.initValue; + this.initValue = false; + this.value = temp; + } } renderView() { - render(html` `, this.root); - - const fieldSelect = this.root.querySelector("ob-field-select"); - fieldSelect.options = OBFieldCountry.countries; - fieldSelect.refresh(); + render(html`${this.currentCountryName()}`, this.root); } - currentCountryName() { + async currentCountryName() { + await this.initialized; return OBFieldCountry.countries[this.value]; } get value() { - const fieldSelect = this.root.querySelector("ob-field-select"); - if (fieldSelect) { - return fieldSelect.value; - } + return this.fieldSelect?.value; } set value(value) { - const fieldSelect = this.root.querySelector("ob-field-select"); - if (fieldSelect) { - fieldSelect.value = value; + if (this.fieldSelect) { + this.fieldSelect.value = value; + } else { + this.initValue = value; } + + this.refresh(); } } diff --git a/ui/fields/datetime.js b/ui/fields/datetime.js index 16671700..baf5009f 100644 --- a/ui/fields/datetime.js +++ b/ui/fields/datetime.js @@ -2,40 +2,24 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; class OBFieldDatetime extends OBField { - #init; - - #valueObject; - #valueString; - #value; - #setValue; - valueFormat = "YYYY-MM-DD HH:mm:ss"; valueStringFormat = "MMM D, YYYY h:mm A"; - async connected() { - if (this.#init) { - return; - } - this.#init = true; - - this.#valueObject = null; - this.#valueString = ""; - this.#value = null; - - this.renderComponent().then(() => {}); + renderView() { + render(html`
${this._valueString}
`, this.root); } renderEdit() { render( html` - + `, this.root, ); } - renderView() { - render(html`
${this.#valueString}
`, this.root); + inputChange(event) { + this.value = event.target.value; } scss() { @@ -56,30 +40,15 @@ class OBFieldDatetime extends OBField { `; } - get value() { - return this.#value; - } - set value(value) { - const inputElem = this.root.querySelector("#field"); - inputElem.value = value; - inputElem.dispatchEvent(new Event("change")); - } - - #updateValue(event) { - const value = event.target.value; const datetime = chrono.casual.parseDate(value); - if (datetime) { - this.#valueObject = datetime; - this.#valueString = dayjs(datetime).format(this.valueStringFormat); - this.#value = dayjs(datetime).format(this.valueFormat); - } else { - this.#valueObject = null; - this.#valueString = ""; - this.#value = null; - } + this._value = datetime ? dayjs(datetime).format(this.valueFormat) : ""; + this._valueString = datetime ? dayjs(datetime).format(this.valueStringFormat) : ""; + this.refresh(); + } - this.renderComponent(); + get value() { + return this._value; } } diff --git a/ui/fields/formatted.js b/ui/fields/formatted.js index fe8285ee..793e8587 100644 --- a/ui/fields/formatted.js +++ b/ui/fields/formatted.js @@ -1,17 +1,26 @@ -import { html, render } from "../vendor.js"; +import { html, render, marked, dompurify } from "../vendor.js"; import { OBField } from "../base/field.js"; class OBFieldFormatted extends OBField { #init; #editorInstance; - async connectedCallback() { - if (this.#init) { - return; - } - this.#init = true; + renderEdit() { + if (this.#editorInstance) { + // update value only + this.#editorInstance.value(this._value); + } else { + render( + html` + + `, + this.root, + ); - this.renderComponent().then(() => { this.#editorInstance = new EasyMDE({ element: this.root.querySelector("#edit"), minHeight: "200px", @@ -40,24 +49,15 @@ class OBFieldFormatted extends OBField { } }, }); - }); - } - renderEdit() { - render( - html` - - `, - this.root, - ); + this.#editorInstance.codemirror.on("change", () => { + this._value = this.#editorInstance.value(); + }); + } } renderView() { - render(html`
TODO
`, this.root); + this.root.innerHTML = `
${dompurify.sanitize(marked(this._value ?? ""))}
`; } scss() { @@ -66,7 +66,6 @@ class OBFieldFormatted extends OBField { display: inline-block; position: relative; z-index: 1000; - color: #000; max-width: 300px; } @@ -77,20 +76,15 @@ class OBFieldFormatted extends OBField { .editor-toolbar { background-color: #fff; } - `; - } - get value() { - return this.root.querySelector("#edit").value; - } + #view > *:first-child { + margin-top: 0; + } - set value(value) { - if (this.#editorInstance) { - this.#editorInstance.value(value); - } else { - this.root.querySelector("#edit").value = value; - } - this.renderComponent(); + #view > *:last-child { + margin-bottom: 0; + } + `; } } diff --git a/ui/fields/group.js b/ui/fields/group.js index d4ea4970..45b34a40 100644 --- a/ui/fields/group.js +++ b/ui/fields/group.js @@ -5,7 +5,7 @@ class OBFieldGroup extends OBField { // languages are common to all instances of this element static groups = null; - async connectedCallback() { + async connected() { if (OBFieldGroup.groups === null) { // prevent multiple calls if this element appears twice in one form OBFieldGroup.groups = {}; diff --git a/ui/fields/hidden.js b/ui/fields/hidden.js index 1b071ba0..e01cae15 100644 --- a/ui/fields/hidden.js +++ b/ui/fields/hidden.js @@ -1,6 +1,7 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; +// TODO is this need at all? will be removing "hidden" field type and replacing with hidden setting for other field types. class OBFieldHidden extends OBField { #init; diff --git a/ui/fields/language.js b/ui/fields/language.js index 4258f85d..e87238d5 100644 --- a/ui/fields/language.js +++ b/ui/fields/language.js @@ -5,9 +5,8 @@ class OBFieldLanguage extends OBField { // languages are common to all instances of this element static languages = null; static popularLanguages = null; - valuePending = false; - async connectedCallback() { + async connected() { if (OBFieldLanguage.languages === null) { // prevent multiple calls if this element appears twice in one form OBFieldLanguage.languages = {}; @@ -29,8 +28,6 @@ class OBFieldLanguage extends OBField { // convert to array OBFieldLanguage.popularLanguages = Object.values(popularLanguages); } - - this.renderComponent(); } get value() { diff --git a/ui/fields/media.js b/ui/fields/media.js index 81adbb8e..b3f39872 100644 --- a/ui/fields/media.js +++ b/ui/fields/media.js @@ -55,7 +55,9 @@ class OBFieldMedia extends OBField { }); } - renderEdit() { + async renderEdit() { + await this.mediaContent(); + render( html`
- ${this.#mediaItems.map( + ${this._value && + this._value.map( (mediaItem) => html`
${this.#mediaContent[mediaItem]} @@ -82,7 +85,8 @@ class OBFieldMedia extends OBField { ` : html``}
- ${this.#mediaItems.length === 0 && + ${this._value && + this._value.length === 0 && this.dataset.hasOwnProperty("single") && this.dataset.hasOwnProperty("record") && html` @@ -131,11 +135,13 @@ class OBFieldMedia extends OBField { ); } - renderView() { + async renderView() { + await this.mediaContent(); + render( html`
- ${this.#mediaItems.map( + ${this._value?.map( (mediaItem) => html`
${this.#mediaContent[mediaItem]}
`, @@ -149,7 +155,7 @@ class OBFieldMedia extends OBField { scss() { return ` :host { - #media:empty { + .media-editable:empty { border: 2px dashed #eeeeec; } @@ -257,6 +263,7 @@ class OBFieldMedia extends OBField { } } + /* .media-viewable:empty::after { content: "No Media"; display: block; @@ -267,6 +274,7 @@ class OBFieldMedia extends OBField { .media-viewable[data-single="true"]:empty::after { content: "No Media (Single)"; } + */ #media { box-sizing: border-box; @@ -288,7 +296,7 @@ class OBFieldMedia extends OBField { /*min-height: 180px;*/ } - .media-item { + .media-editable .media-item { background-color: #eeeeec; color: #000; padding: 5px; @@ -361,7 +369,7 @@ class OBFieldMedia extends OBField { return false; } - var selectedMedia = this.#mediaItems; + var selectedMedia = this._value; Object.keys(window.dragHelperData).forEach((key) => { if (!window.dragHelperData[key].dataset) { @@ -379,8 +387,10 @@ class OBFieldMedia extends OBField { } async mediaContent() { + if (!this._value || !this.#mediaContent) return; + return Promise.all( - this.#mediaItems.map(async (mediaItem) => { + this._value.map(async (mediaItem) => { if (this.#mediaContent[mediaItem]) { return; } @@ -393,7 +403,12 @@ class OBFieldMedia extends OBField { } const data = result.data; - this.#mediaContent[mediaItem] = data.artist + " - " + data.title; + + const name = []; + if (data.artist) name.push(data.artist); + if (data.title) name.push(data.title); + + this.#mediaContent[mediaItem] = name.join(" - "); this.refresh(); }), ); @@ -435,7 +450,7 @@ class OBFieldMedia extends OBField { } mediaRemove(event) { - const newItems = this.#mediaItems.filter((item) => { + const newItems = this._value.filter((item) => { return item !== parseInt(event.target.parentElement.dataset.id); }); this.value = newItems; @@ -703,7 +718,7 @@ class OBFieldMedia extends OBField { } get value() { - return this.#mediaItems; + return this._value; } set value(value) { @@ -725,15 +740,9 @@ class OBFieldMedia extends OBField { this.#recordUrl = ""; this.#recordData = []; - this.#mediaItems = value; - this.mediaContent().then(() => { - this.#mediaItems = this.#mediaItems.filter((item) => { - return Object.keys(this.#mediaContent).includes(item.toString()); - }); - this.refresh(); + this._value = value; - this.dispatchEvent(new Event("change")); - }); + this.refresh(); } } diff --git a/ui/fields/number.js b/ui/fields/number.js index cd97955f..bfc9903a 100644 --- a/ui/fields/number.js +++ b/ui/fields/number.js @@ -2,25 +2,28 @@ import { OBField } from "../base/field.js"; import { html, render } from "../vendor.js"; class OBFieldNumber extends OBField { - #init; + renderEdit() { + render( + html` `, + this.root, + ); + } - async connectedCallback() { - if (this.#init) { - return; - } + renderView() { + render(html`
${this._value}
`, this.root); + } - this.#init = true; - this.renderComponent().then(() => { - // do stuff - }); + inputChange(event) { + this.value = event.target.value; } - renderView() { - render(html` ${this.value} `, this.root); + set value(value) { + this._value = parseInt(value); + this.refresh(); } - renderEdit() { - render(html` `, this.root); + get value() { + return this._value; } scss() { diff --git a/ui/fields/password.js b/ui/fields/password.js index 3202b846..ce6e67cc 100644 --- a/ui/fields/password.js +++ b/ui/fields/password.js @@ -2,27 +2,27 @@ import { html, render } from "../vendor.js"; import { OBField } from "../base/field.js"; class OBFieldPassword extends OBField { - #init; - - async connected() { - if (this.#init) { - return; - } - this.#init = true; - - this.renderComponent().then(() => { - if (this.getAttribute("placeholder")) { - this.root.querySelector("input").placeholder = this.getAttribute("placeholder"); - } - }); + renderView() { + render(html` ********* `, this.root); } renderEdit() { - render(html` `, this.root); + render( + html` + + `, + this.root, + ); } - renderView() { - render(html` `, this.root); + inputChange(event) { + this._value = event.target.value; } scss() { @@ -42,19 +42,6 @@ class OBFieldPassword extends OBField { } `; } - - get value() { - if (this.root.querySelector("input")) { - return this.root.querySelector("input").value; - } else { - return ""; - } - } - - set value(value) { - this.root.querySelector("input").value = value; - this.renderComponent(); - } } customElements.define("ob-field-password", OBFieldPassword); diff --git a/ui/fields/playlist.js b/ui/fields/playlist.js index b0cdedbe..c22a6268 100644 --- a/ui/fields/playlist.js +++ b/ui/fields/playlist.js @@ -16,7 +16,9 @@ class OBFieldPlaylist extends OBField { }); } - renderEdit() { + async renderEdit() { + await this.playlistContent(); + render( html`
- ${this.#playlistItems.map( + ${this._value?.map( (playlistItem) => html`
${this.#playlistContent[playlistItem]} @@ -39,11 +41,13 @@ class OBFieldPlaylist extends OBField { ); } - renderView() { + async renderView() { + await this.playlistContent(); + render( html`
- ${this.#playlistItems.map( + ${this._value?.map( (playlistItem) => html`
${this.#playlistContent[playlistItem]} @@ -59,7 +63,7 @@ class OBFieldPlaylist extends OBField { scss() { return ` :host { - #playlist:empty { + .playlist-editable#playlist:empty { border: 2px dashed #eeeeec; } @@ -78,14 +82,16 @@ class OBFieldPlaylist extends OBField { border: 2px dashed #e09529; } + /* .playlist-viewable:empty::after { content: "No Playlists"; display: block; text-align: center; line-height: 96px; } + */ - #playlist { + .playlist-editable#playlist { box-sizing: border-box; width: 350px; max-width: 350px; @@ -97,7 +103,7 @@ class OBFieldPlaylist extends OBField { min-height: auto; } - .playlist-item { + .playlist-editable .playlist-item { background-color: #eeeeec; color: #000; padding: 5px; @@ -129,7 +135,7 @@ class OBFieldPlaylist extends OBField { onDragStart(event) { let editable = this.root.querySelector("#playlist.playlist-editable"); - if (!editable) { + if (!this._editable) { return false; } @@ -139,7 +145,7 @@ class OBFieldPlaylist extends OBField { onDragEnd(event) { let editable = this.root.querySelector("#playlist.playlist-editable"); - if (!editable) { + if (!this._editable) { return false; } @@ -150,14 +156,15 @@ class OBFieldPlaylist extends OBField { if (!window.dragHelperData || !window.dragHelperData[0].classList.contains("sidebar_search_playlist_result")) { return false; } - var selectedPlaylist = this.#playlistItems; + + var selectedPlaylist = this._value || []; Object.keys(window.dragHelperData).forEach((key) => { if (!window.dragHelperData[key].dataset) { return false; } - if (selectedPlaylist.includes(parseInt(window.dragHelperData[key].dataset.id))) { + if (selectedPlaylist?.includes(parseInt(window.dragHelperData[key].dataset.id))) { return false; } @@ -168,7 +175,9 @@ class OBFieldPlaylist extends OBField { } async playlistContent() { - let playlistItemPromises = this.#playlistItems.map((playlistItem) => { + if (!this._value || !this.#playlistContent) return; + + let playlistItemPromises = this._value.map((playlistItem) => { return new Promise((resolve) => { if (this.#playlistContent[playlistItem]) { resolve(); @@ -193,36 +202,38 @@ class OBFieldPlaylist extends OBField { } playlistRemove(event) { - const newItems = this.#playlistItems.filter((item) => { + const newItems = this._value.filter((item) => { return item !== parseInt(event.target.parentElement.dataset.id); }); this.value = newItems; } get value() { - return this.#playlistItems; + return this._value; } set value(value) { - this.#setValue = value; + // this.initialized.then(() => { - this.initialized.then(() => { - value = this.#setValue; + if (!Array.isArray(value)) { + value = [value]; + } - if (!Array.isArray(value)) { - value = [value]; - } + value = value.map((x) => parseInt(x)); - value = value.map((x) => parseInt(x)); + if (!value.every(Number.isInteger)) { + return false; + } - if (!value.every(Number.isInteger)) { - return false; - } + if (this.dataset.hasOwnProperty("single")) { + value = value.slice(-1); + } - if (this.dataset.hasOwnProperty("single")) { - value = value.slice(-1); - } + this._value = value; + + this.refresh(); + /* this.#playlistItems = value; this.playlistContent().then(() => { this.#playlistItems = this.#playlistItems.filter((item) => { @@ -233,6 +244,7 @@ class OBFieldPlaylist extends OBField { this.dispatchEvent(new Event("change")); }); }); + */ } } diff --git a/ui/fields/select.js b/ui/fields/select.js index 59f9699d..4af3348a 100644 --- a/ui/fields/select.js +++ b/ui/fields/select.js @@ -5,10 +5,6 @@ class OBFieldSelect extends OBField { #options; filterVal = ""; - async connected() { - this.renderComponent().then(() => {}); - } - addSelected(option) { if (this.multiple && !this.selected.includes(option)) { // add option to selected @@ -182,7 +178,7 @@ class OBFieldSelect extends OBField { left: 0; right: 0; border: var(--field-border); - z-index: 1; + z-index: 100000; margin-top: 0; box-sizing: border-box; max-height: 300px; @@ -300,7 +296,7 @@ class OBFieldSelect extends OBField { set value(value) { this.selected = value; - this.updateSelected(); + if (this._editable) this.updateSelected(); } get options() { @@ -314,6 +310,15 @@ class OBFieldSelect extends OBField { this.renderEdit(); } + set settings(value) { + this._settings = value; + if (this._settings.options) this.options = this._settings.options; + } + + get settings() { + return this._settings; + } + renderEdit() { // get options from data-options and json decode, or use inner elements if (!this.#options) this.#options = JSON.parse(this.getAttribute("data-options")); @@ -375,6 +380,11 @@ class OBFieldSelect extends OBField { this.updateSelected(); } + renderView() { + const output = this.#options ? this.#options[this.selected] : ""; + render(html`${output}`, this.root); + } + #loadInnerOptions() { var options = {}; diff --git a/ui/fields/tags.js b/ui/fields/tags.js index a0299114..4f6a9af1 100644 --- a/ui/fields/tags.js +++ b/ui/fields/tags.js @@ -7,7 +7,10 @@ class OBFieldTags extends OBField { #suggestions; #currentTag; - async connectedCallback() { + // TODO loading tags and suggestions via OB-OPTION and OB-TAGS temporarily disabled. + // fix needed as these are currently overwriting set value. + /* + async connected() { if (this.#init) { return; } @@ -20,8 +23,9 @@ class OBFieldTags extends OBField { this.#tags = this.#loadInnerTags(); this.#suggestions = this.#loadInnerSuggestions(); - this.renderComponent(); + this.refresh(); } + */ renderEdit() { if (!this.#tags) { @@ -64,7 +68,8 @@ class OBFieldTags extends OBField { } renderView() { - render(html`
Tags view (TODO)
`, this.root); + const output = this.#tags?.join(", "); + render(html`
${output}
`, this.root); } scss() { @@ -175,19 +180,19 @@ class OBFieldTags extends OBField { } } - this.renderComponent(); + this.refresh(); } tagsDelete(tag) { this.#tags = this.#tags.filter((elem) => elem != tag); - this.renderComponent(); + this.refresh(); } tagsAdd(tag) { if (this.#tags.find((elem) => elem === tag) === undefined) { this.#tags.push(tag); } - this.renderComponent(); + this.refresh(); } get value() { @@ -195,16 +200,19 @@ class OBFieldTags extends OBField { } set value(value) { - if (Array.isArray(value)) { - this.#tags = [...new Set(value)]; - this.renderComponent(); + let tags = value; + if (!Array.isArray(tags)) { + tags = tags.split(",").map((tag) => tag.trim()); } + + this.#tags = [...new Set(tags)]; + this.refresh(); } set suggestions(value) { if (Array.isArray(value)) { this.#suggestions = [...new Set(value)]; - this.renderComponent(); + this.refresh(); } } diff --git a/ui/fields/text.js b/ui/fields/text.js index 88ea13dc..c6f89ff6 100644 --- a/ui/fields/text.js +++ b/ui/fields/text.js @@ -1,19 +1,21 @@ import { OBField } from "../base/field.js"; +import { html, render } from "../vendor.js"; class OBFieldText extends OBField { - #init; - - async connectedCallback() { - if (this.#init) { - return; - } + renderEdit() { + render( + html``, + this.root, + ); + } - this.#init = true; - this.renderComponent().then(() => { - if (this.hasAttribute("maxlength")) { - this.root.querySelector("input").setAttribute("maxlength", this.getAttribute("maxlength")); - } - }); + inputChange(event) { + this._value = event.target.value; } scss() { diff --git a/ui/fields/textarea.js b/ui/fields/textarea.js index 40b8858b..3cea27dc 100644 --- a/ui/fields/textarea.js +++ b/ui/fields/textarea.js @@ -2,46 +2,20 @@ import { OBField } from "../base/field.js"; import { html, render } from "../vendor.js"; class OBFieldTextarea extends OBField { - #init; - - async connectedCallback() { - if (this.#init) { - return; - } - - this.#init = true; - this.renderComponent().then(() => { - if (this.hasAttribute("wrap")) { - this.root.querySelector("textarea").setAttribute("wrap", this.getAttribute("wrap")); - } - - this.root.querySelector("textarea").addEventListener("change", this.propagateEvent.bind(this)); - }); - } - renderView() { - render(html` ${this.value} `, this.root); + render(html`
${this.value}
`, this.root); } renderEdit() { - render(html` `, this.root); + const wrap = this.getAttribute("wrap"); + render( + html``, + this.root, + ); } - get value() { - if (this.root.querySelector("textarea")) { - return this.root.querySelector("textarea").value; - } - } - - set value(value) { - if (!this.root.querySelector("textarea")) { - return; - } - - this.root.querySelector("textarea").value = value; - this.renderComponent().then(() => { - this.propagateEvent("change"); - }); + textareaChange(event) { + this.value = event.target.value; } scss() { @@ -63,6 +37,10 @@ class OBFieldTextarea extends OBField { height: 100%; } } + + #view { + white-space: pre-line; + } `; } } diff --git a/ui/fields/user.js b/ui/fields/user.js index 07934d57..d34993a0 100644 --- a/ui/fields/user.js +++ b/ui/fields/user.js @@ -5,7 +5,7 @@ class OBFieldUser extends OBField { // languages are common to all instances of this element static users = null; - async connectedCallback() { + async connected() { if (OBFieldUser.users === null) { // prevent multiple calls if this element appears twice in one form OBFieldUser.users = {}; @@ -19,8 +19,6 @@ class OBFieldUser extends OBField { OBFieldUser.users[user.id] = user.display_name + " (" + user.email + ")"; } } - - this.renderComponent(); } get value() { diff --git a/ui/vendor.js b/ui/vendor.js index 307e52dc..4e1187c0 100644 --- a/ui/vendor.js +++ b/ui/vendor.js @@ -1,4 +1,6 @@ export { html, render } from "../node_modules/htm/preact/standalone.module.js"; +export { marked } from "../node_modules/marked/lib/marked.esm.js"; +export { default as dompurify } from "../node_modules/dompurify/dist/purify.es.mjs"; // note this uses import map already (see index.php) export * as sass from "sass";