@openleaf-editor/vue 0.1.0-beta.2 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -11
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @openleaf-editor/vue
|
|
2
|
+
|
|
3
|
+
Vue 3 wrapper for the OpenLeaf custom element.
|
|
4
|
+
|
|
5
|
+
> **Editor output is untrusted input.** Whatever the editor produces — and
|
|
6
|
+
> whatever a user pasted into it — must be sanitized **on your server** before it
|
|
7
|
+
> is stored or rendered as HTML. Client-side sanitization is a user-experience
|
|
8
|
+
> feature, not a security control: anything the editor strips can be put back
|
|
9
|
+
> with developer tools, because the editor runs under the user's control.
|
|
10
|
+
>
|
|
11
|
+
> [`@openleaf-editor/sanitize`](https://github.com/PeytonNowlin/openleaf/tree/main/packages/sanitize) ships the
|
|
12
|
+
> canonical allowlist as data and generates configuration for DOMPurify, Python
|
|
13
|
+
> `bleach` and PHP HTMLPurifier from it, so client and server enforce the same
|
|
14
|
+
> rules. Read [SECURITY.md](https://github.com/PeytonNowlin/openleaf/blob/main/SECURITY.md) before you ship.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @openleaf-editor/vue@beta
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Keep every `@openleaf-editor/*` package on the same version. They pin each other
|
|
23
|
+
exactly, so mixing versions installs two copies of the schema and the toolbar
|
|
24
|
+
registry -- and a node built by one is not a node type the other accepts.
|
|
25
|
+
|
|
26
|
+
## Use it
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<script setup>
|
|
30
|
+
import { ref } from 'vue'
|
|
31
|
+
import { OpenLeafEditor } from '@openleaf-editor/vue'
|
|
32
|
+
|
|
33
|
+
const html = ref('<p>Hello</p>')
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<OpenLeafEditor v-model="html" toolbar="bold italic | link" />
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### If you use the custom element directly
|
|
42
|
+
|
|
43
|
+
This wrapper registers nothing with Vue's compiler, so it just works. Reaching
|
|
44
|
+
for `<openleaf-editor>` in a template instead means telling Vue that it is a
|
|
45
|
+
custom element, or you get `[Vue warn]: Failed to resolve component:
|
|
46
|
+
openleaf-editor`:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// vite.config.js
|
|
50
|
+
export default {
|
|
51
|
+
plugins: [
|
|
52
|
+
vue({
|
|
53
|
+
template: {
|
|
54
|
+
compilerOptions: { isCustomElement: (tag) => tag.startsWith('openleaf-') },
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## It is a wrapper, not a port
|
|
62
|
+
|
|
63
|
+
The editor is the `<openleaf-editor>` custom element. This forwards attributes,
|
|
64
|
+
keeps a controlled value in sync, and re-emits `openleaf:change`. Nothing about
|
|
65
|
+
editing lives here.
|
|
66
|
+
|
|
67
|
+
That is deliberate: three framework ports would be three copies of the schema and
|
|
68
|
+
three sets of bugs, and a node built by one is not a node type another accepts. If
|
|
69
|
+
you are not using Vue, skip this package -- the element works on its own
|
|
70
|
+
and that is the supported path, not a fallback.
|
|
71
|
+
|
|
72
|
+
Importing this module does not touch the DOM, so it is safe in a server render.
|
|
73
|
+
The element upgrades on the client.
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
Apache-2.0.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,0BAA0B,CAAA;AAGjC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4EA4EzB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `v-model` maps onto the element's `value`. The custom element remains the
|
|
5
5
|
* source of truth for editing; this wrapper does not reimplement the schema.
|
|
6
6
|
*/
|
|
7
|
-
import { defineComponent, h, onMounted, ref, watch } from 'vue';
|
|
7
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
8
8
|
import '@openleaf-editor/element';
|
|
9
9
|
export const OpenLeafEditor = defineComponent({
|
|
10
10
|
name: 'OpenLeafEditor',
|
|
@@ -20,15 +20,27 @@ export const OpenLeafEditor = defineComponent({
|
|
|
20
20
|
emits: ['update:modelValue'],
|
|
21
21
|
setup(props, { emit, attrs }) {
|
|
22
22
|
const el = ref(null);
|
|
23
|
+
const onChange = (event) => {
|
|
24
|
+
const detail = event.detail;
|
|
25
|
+
// The detail carries the already-serialized document. Reading `.value`
|
|
26
|
+
// back off the element serializes it a second time, per keystroke.
|
|
27
|
+
emit('update:modelValue', detail?.value ?? el.value?.value ?? '');
|
|
28
|
+
};
|
|
23
29
|
onMounted(() => {
|
|
24
30
|
const host = el.value;
|
|
25
31
|
if (!host)
|
|
26
32
|
return;
|
|
27
|
-
|
|
33
|
+
// `!== undefined`, not truthiness. `modelValue` defaults to `''`, and `''`
|
|
34
|
+
// is falsy -- so mounting with the declared default skipped the write
|
|
35
|
+
// entirely and left whatever stale `innerHTML` was in the template.
|
|
36
|
+
if (props.modelValue !== undefined)
|
|
28
37
|
host.value = props.modelValue;
|
|
29
|
-
host.addEventListener('openleaf:change',
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
host.addEventListener('openleaf:change', onChange);
|
|
39
|
+
});
|
|
40
|
+
// React and Angular both cleaned up; Vue did not, so every unmount left a
|
|
41
|
+
// listener holding a closure over the component's `emit` alive.
|
|
42
|
+
onBeforeUnmount(() => {
|
|
43
|
+
el.value?.removeEventListener('openleaf:change', onChange);
|
|
32
44
|
});
|
|
33
45
|
watch(() => props.modelValue, (html) => {
|
|
34
46
|
const host = el.value;
|
|
@@ -40,12 +52,28 @@ export const OpenLeafEditor = defineComponent({
|
|
|
40
52
|
return () => h('openleaf-editor', {
|
|
41
53
|
...attrs,
|
|
42
54
|
ref: el,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
// `^` forces `setAttribute` rather than a property write.
|
|
56
|
+
//
|
|
57
|
+
// Vue's `shouldSetAsProp` ends in `return key in el`, and every one of
|
|
58
|
+
// these names is now a real accessor on the element -- so without the
|
|
59
|
+
// prefix Vue takes the property path. That path is where `toolbar` used
|
|
60
|
+
// to be a SILENT no-op: the element exposed a getter-only `toolbar`
|
|
61
|
+
// accessor for plugin authors, `patchDOMProp` does
|
|
62
|
+
// `try { el[key] = value } catch {}`, assigning to a getter-only
|
|
63
|
+
// accessor throws in strict mode, and the throw was swallowed.
|
|
64
|
+
// `<OpenLeafEditor toolbar="bold italic" />` rendered the full
|
|
65
|
+
// 22-button default bar with no error anywhere.
|
|
66
|
+
//
|
|
67
|
+
// The element now has setters that reflect, so the property path works
|
|
68
|
+
// too. Forcing attributes anyway keeps this wrapper correct against an
|
|
69
|
+
// older element than itself, and keeps one spelling of the truth: the
|
|
70
|
+
// attribute is what the element reads.
|
|
71
|
+
'^toolbar': props.toolbar,
|
|
72
|
+
'^toolbar2': props.toolbar2,
|
|
73
|
+
'^menubar': props.menubar === true ? '' : props.menubar === false ? undefined : props.menubar,
|
|
74
|
+
'^formats': props.formats,
|
|
75
|
+
'^lang': props.lang,
|
|
76
|
+
'^for': props.for,
|
|
49
77
|
});
|
|
50
78
|
},
|
|
51
79
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openleaf-editor/vue",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "Vue 3 wrapper for the OpenLeaf custom element. The editor itself stays framework-free.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"url": "https://github.com/PeytonNowlin/openleaf/issues"
|
|
17
17
|
},
|
|
18
18
|
"type": "module",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
19
22
|
"main": "./dist/index.js",
|
|
20
23
|
"types": "./dist/index.d.ts",
|
|
21
24
|
"exports": {
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
"vue": "^3.4.0"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
|
-
"@openleaf-editor/element": "0.1.0-beta.
|
|
37
|
+
"@openleaf-editor/element": "0.1.0-beta.4"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"vue": "^3.5.13"
|