diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b9d78..d79be68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for malle +## 2.3.0 + +* Add `returnedValueIsTrustedHtml` so the function used on original element is `innerHTML` instead of the safer `innerText`. + ## 2.2.0 * Add `color`, `date`, `time` input types. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 9a79869..eb4161f 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -140,6 +140,13 @@ default: `true` By default, the `fun` function won't be called in the input value is the same as the original value. Set to `false` to always call `fun` regardless of input. +#### returnedValueIsTrustedHtml +boolean + +default: `false` + +If `true`, `innerHTML` is used instead of `innerText` with the returned value from the server. Helps with html-encoded strings ending up wrongly displayed after an edit/save. Only set to `true` if you sanitize output and have a strict CSP. + #### selectOptions `Array` diff --git a/package-lock.json b/package-lock.json index 81dceb1..33a3786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@deltablot/malle", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@deltablot/malle", - "version": "2.2.0", + "version": "2.3.0", "license": "MIT", "devDependencies": { "@types/jest": "^27.4.1", diff --git a/package.json b/package.json index 0e47464..ff7ac16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deltablot/malle", - "version": "2.2.0", + "version": "2.3.0", "description": "Make text elements malleable, without dependencies.", "main": "dist/main.js", "typings": "dist/main.d.ts", diff --git a/src/main.ts b/src/main.ts index d663cf3..92269b9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -55,6 +55,7 @@ export interface Options { onEnter?: Action; placeholder?: string; requireDiff?: boolean; + returnedValueIsTrustedHtml?: boolean; selectOptions?: Array | Promise>; selectOptionsValueKey?: string; selectOptionsTextKey?: string; @@ -69,6 +70,7 @@ export class Malle { opt: Options; original: HTMLElement; input: HTMLInputElement|HTMLSelectElement; + innerFun: string; constructor(options: Options) { this.opt = this.normalizeOptions(options); @@ -76,6 +78,9 @@ export class Malle { if (this.opt.listenNow) { this.listen(); } + // by default we use innerText to insert the return value, but if we know it's trusted html we get back, we allow using innerHTML instead + // once setHTML() becomes more widespread, we'll use that instead + this.innerFun = this.opt.returnedValueIsTrustedHtml ? 'innerHTML' : 'innerText'; } /** @@ -102,6 +107,7 @@ export class Malle { onEnter: Action.Submit, placeholder: '', requireDiff: true, + returnedValueIsTrustedHtml: false, selectOptions: [], selectOptionsValueKey: 'value', selectOptionsTextKey: 'text', @@ -164,7 +170,7 @@ export class Malle { } } this.opt.fun.call(this, this.input.value, this.original, event, this.input).then((value: string) => { - this.original.innerText = this.opt.inputType === InputType.Select ? (this.input as HTMLSelectElement).options[(this.input as HTMLSelectElement).selectedIndex].text : value; + this.original[this.innerFun] = this.opt.inputType === InputType.Select ? (this.input as HTMLSelectElement).options[(this.input as HTMLSelectElement).selectedIndex].text : value; this.form.replaceWith(this.original); // execute the after hook if (typeof this.opt.after === 'function') { @@ -268,8 +274,8 @@ export class Malle { o.forEach(o => { const option = document.createElement('option'); option.value = o[this.opt.selectOptionsValueKey]; - option.innerText = o[this.opt.selectOptionsTextKey]; - option.selected = (o.selected ?? false) || this.original.innerText === o[this.opt.selectOptionsTextKey]; + option[this.innerFun] = o[this.opt.selectOptionsTextKey]; + option.selected = (o.selected ?? false) || this.original[this.innerFun] === o[this.opt.selectOptionsTextKey]; input.appendChild(option); }); });