@es-joy/jsoe 0.0.1

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.
Files changed (43) hide show
  1. package/.editorconfig +15 -0
  2. package/.eslintignore +7 -0
  3. package/.eslintrc.cjs +120 -0
  4. package/CHANGES.md +5 -0
  5. package/LICENSE-MIT.txt +22 -0
  6. package/README.md +82 -0
  7. package/package.json +101 -0
  8. package/rollup.config.js +29 -0
  9. package/server.js +25 -0
  10. package/src/deepEqual.js +72 -0
  11. package/src/formats/indexedDBKey.js +66 -0
  12. package/src/formats/json.js +64 -0
  13. package/src/formats/schemaAndArbitrary.js +20 -0
  14. package/src/formats/schemaOnly.js +529 -0
  15. package/src/formats/structuredCloning.js +372 -0
  16. package/src/formats.js +229 -0
  17. package/src/fundamentalTypes/BooleanObjectType.js +55 -0
  18. package/src/fundamentalTypes/NumberObjectType.js +41 -0
  19. package/src/fundamentalTypes/StringObjectType.js +37 -0
  20. package/src/fundamentalTypes/arrayReferenceType.js +203 -0
  21. package/src/fundamentalTypes/arrayType.js +898 -0
  22. package/src/fundamentalTypes/bigintType.js +53 -0
  23. package/src/fundamentalTypes/dateType.js +129 -0
  24. package/src/fundamentalTypes/nullType.js +33 -0
  25. package/src/fundamentalTypes/numberType.js +56 -0
  26. package/src/fundamentalTypes/objectReferenceType.js +51 -0
  27. package/src/fundamentalTypes/objectType.js +30 -0
  28. package/src/fundamentalTypes/regexpType.js +95 -0
  29. package/src/fundamentalTypes/sparseUndefinedType.js +43 -0
  30. package/src/fundamentalTypes/stringType.js +31 -0
  31. package/src/fundamentalTypes/undefinedType.js +37 -0
  32. package/src/index.js +7 -0
  33. package/src/subTypes/blobHTMLType.js +160 -0
  34. package/src/subTypes/falseType.js +42 -0
  35. package/src/subTypes/trueType.js +42 -0
  36. package/src/superTypes/InfinitiesSuperType.js +49 -0
  37. package/src/superTypes/SpecialNumberSuperType.js +62 -0
  38. package/src/typeChoices.js +125 -0
  39. package/src/types.js +665 -0
  40. package/src/utils/dialogs.js +115 -0
  41. package/src/utils/jsonPointer.js +89 -0
  42. package/src/utils/templateUtils.js +106 -0
  43. package/src/utils/types.js +6 -0
@@ -0,0 +1,160 @@
1
+ /* globals sceditor */
2
+ import {jml} from '../../node_modules/jamilih/dist/jml-es.js';
3
+ import {$e} from '../utils/templateUtils.js';
4
+
5
+ import dialogs from '../utils/dialogs.js';
6
+ import {isNullish} from '../utils/types.js';
7
+
8
+ const blobHTMLType = {
9
+ // Todo (low): Support other content-types
10
+ option: ['Blob (text/html)'],
11
+ stringRegex: /^data:text\/html(?:;base64)?,.*$/u,
12
+ valueMatch: (v) => v.type === 'text/html',
13
+ superType: 'blob',
14
+ toValue (s) {
15
+ // Todo (low): `Blob` untested; use https://stackoverflow.com/a/30407840/271577 ?
16
+ /**
17
+ *
18
+ * @param {string} dataURI
19
+ * @returns {Blob}
20
+ */
21
+ function dataURIToBlob (dataURI) {
22
+ // Adapted from https://stackoverflow.com/a/12300351/271577
23
+ const [mimeInfo, bytes] = dataURI.split(',');
24
+ const [mimeString, encoding] = mimeInfo.split(':')[1].split(';');
25
+ let ab;
26
+ if (encoding === 'base64') {
27
+ const byteString = atob(bytes);
28
+ ab = new ArrayBuffer(byteString.length);
29
+ const ia = new Uint8Array(ab);
30
+ for (let i = 0; i < byteString.length; i++) {
31
+ // eslint-disable-next-line unicorn/prefer-code-point -- Only a byte
32
+ ia[i] = byteString.charCodeAt(i);
33
+ }
34
+ } else {
35
+ ab = bytes;
36
+ }
37
+ return new Blob([ab], {type: mimeString});
38
+ }
39
+ return {
40
+ value: dataURIToBlob(s)
41
+ };
42
+ },
43
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
44
+ getInput ({root}) {
45
+ return $e(root, 'textarea');
46
+ },
47
+ getValue (/* {root} */) {
48
+ return this.toValue(
49
+ 'data:text/html,' + this.sceditorInstance.val()
50
+ // this.getInput({root}).value
51
+ ).value;
52
+ },
53
+ loadBlob (value) {
54
+ const reader = new FileReader();
55
+ // eslint-disable-next-line promise/avoid-new
56
+ return new Promise((resolve, reject) => {
57
+ reader.addEventListener('loadend', () => {
58
+ resolve(reader);
59
+ });
60
+ reader.addEventListener(
61
+ 'error',
62
+ /* istanbul ignore next -- How to simulate? */
63
+ (_e) => {
64
+ reject(reader.error);
65
+ }
66
+ );
67
+ reader.readAsText(value);
68
+ });
69
+ },
70
+ async setValue ({/* root, */ value}) {
71
+ const {result} = await this.loadBlob(value);
72
+ this.sceditorInstance.val(result);
73
+ // this.getInput({root}).value = result;
74
+ },
75
+ viewUI ({value}) {
76
+ let val;
77
+ const div = jml('div', {dataset: {type: 'blobHTML'}}, [
78
+ 'HTML: ',
79
+ ['button', {$on: {
80
+ click () {
81
+ dialogs.alert({message: ['div', [
82
+ 'Source: ',
83
+ ['textarea', {class: 'view-source'}, [val]]
84
+ ]]});
85
+ }
86
+ }}, ['View source']]
87
+ ]);
88
+ // eslint-disable-next-line promise/prefer-await-to-then
89
+ this.loadBlob(value).then(({
90
+ result
91
+ }) => {
92
+ val = result;
93
+ jml('iframe', {
94
+ sandbox: '',
95
+ srcdoc: val
96
+ }, div);
97
+ return undefined;
98
+ // eslint-disable-next-line promise/prefer-await-to-then
99
+ }).catch(
100
+ /* istanbul ignore next -- How to simulate? */
101
+ () => {
102
+ // Todo: Show an error message?
103
+ }
104
+ );
105
+ return [div];
106
+ // return ['i', [`data:text/html,${value}`]];
107
+ },
108
+ editUI ({typeNamespace, value}) {
109
+ const textarea = jml('textarea', {name: `${typeNamespace}-blobHTML`});
110
+ const root = jml('div', {dataset: {type: 'blobHTML'}}, [
111
+ textarea
112
+ ]);
113
+ setTimeout(() => {
114
+ // Push onto these: https://www.sceditor.com/documentation/formats/xhtml/
115
+ // sceditor.formats.xhtml.converters array
116
+ // sceditor.formats.xhtml.allowedAttribs object
117
+ // sceditor.formats.xhtml.disallowedAttribs object
118
+ // sceditor.formats.xhtml.allowedTags array
119
+ // sceditor.formats.xhtml.disallowedTags array
120
+ // console.log(
121
+ // 'sceditor.formats.xhtml.converters',
122
+ // sceditor.formats.xhtml.converters.map((c) => Object.keys(c.tags))
123
+ // );
124
+ // console.log(
125
+ // 'sceditor.formats.xhtml.converters',
126
+ // sceditor.formats.xhtml.converters.map((c) => c.conv.toString())
127
+ // );
128
+ sceditor.create(textarea, {
129
+ // Todo (low): "dragdrop" plugin is for file handling (could
130
+ // treat as Blobs but would need to reference them)
131
+ // "autoyoutube" plugin may be ok
132
+ // toolbarExclude (default of `null` doesn't exclude any)
133
+ // Must also add languages files (after editor files but
134
+ // before editor creation)
135
+ // locale: 'en',
136
+ // emoticons: {}, icons, //
137
+ // width, height, resizeMinWidth, resizeMinHeight, autoExpand: true,
138
+ // autofocus, autofocusEnd
139
+ // id, spellcheck, toolbarContainer, dropDownCss, fonts, colors
140
+ // disableBlockRemove, parserOptions
141
+ width: '1000px',
142
+ height: '225px',
143
+ // auto-updates original textbox when the editor loses focus
144
+ autoUpdate: true,
145
+ resizeMaxHeight: -1,
146
+ resizeMaxWidth: -1,
147
+ plugins: 'xhtml,plaintext,undo',
148
+ emoticonsRoot: 'node_modules/sceditor/',
149
+ style: 'node_modules/sceditor/minified/themes/content/default.min.css'
150
+ });
151
+ this.sceditorInstance = sceditor.instance(textarea);
152
+ if (!isNullish(value)) {
153
+ this.setValue({root, value});
154
+ }
155
+ });
156
+ return [root];
157
+ }
158
+ };
159
+
160
+ export default blobHTMLType;
@@ -0,0 +1,42 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const falseType = {
7
+ option: ['Boolean (false)', {value: 'false'}],
8
+ stringRegex: /^false$/u,
9
+ toValue: () => ({value: false}),
10
+ valueMatch: (v) => v === false,
11
+ superType: 'boolean',
12
+ getValue: () => false,
13
+ viewUI () {
14
+ return ['i', {dataset: {type: 'false'}}, ['false']];
15
+ },
16
+ ct: 0,
17
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
18
+ getInput ({root}) {
19
+ return $e(root, 'input');
20
+ },
21
+ editUI ({typeNamespace}) {
22
+ this.ct++;
23
+ return ['div', {dataset: {type: 'false'}}, [
24
+ ['label', [
25
+ 'True',
26
+ ['input', {
27
+ type: 'radio', name: `${typeNamespace}-false${this.ct}`,
28
+ value: 'true', disabled: true
29
+ }]
30
+ ]],
31
+ ['label', [
32
+ 'False',
33
+ ['input', {
34
+ type: 'radio', name: `${typeNamespace}-false${this.ct}`,
35
+ value: 'false', checked: true, disabled: true
36
+ }]
37
+ ]]
38
+ ]];
39
+ }
40
+ };
41
+
42
+ export default falseType;
@@ -0,0 +1,42 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {TypeObject}
5
+ */
6
+ const trueType = {
7
+ option: ['Boolean (true)', {value: 'true'}],
8
+ stringRegex: /^true$/u,
9
+ toValue: (_s) => ({value: true}),
10
+ valueMatch: (v) => v === true,
11
+ superType: 'boolean',
12
+ getValue: () => true,
13
+ viewUI () {
14
+ return ['i', {dataset: {type: 'true'}}, ['true']];
15
+ },
16
+ ct: 0,
17
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
18
+ getInput ({root}) {
19
+ return $e(root, 'input');
20
+ },
21
+ editUI ({typeNamespace}) {
22
+ this.ct++;
23
+ return ['div', {dataset: {type: 'true'}}, [
24
+ ['label', [
25
+ 'True',
26
+ ['input', {
27
+ type: 'radio', name: `${typeNamespace}-true${this.ct}`,
28
+ value: 'true', checked: true, disabled: true
29
+ }]
30
+ ]],
31
+ ['label', [
32
+ 'False',
33
+ ['input', {
34
+ type: 'radio', name: `${typeNamespace}-true${this.ct}`,
35
+ value: 'false', disabled: true
36
+ }]
37
+ ]]
38
+ ]];
39
+ }
40
+ };
41
+
42
+ export default trueType;
@@ -0,0 +1,49 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @type {SuperTypeObject}
5
+ */
6
+ const InfinitiesSuperType = {
7
+ option: ['Infinities', {title: '`Infinity`, `-Infinity`'}],
8
+ childTypes: ['infinity', 'negativeInfinity'],
9
+ stringRegex: /^-?Infinity$/u,
10
+ toValue (s) {
11
+ return {
12
+ value: s === 'Infinity'
13
+ ? Number.POSITIVE_INFINITY
14
+ : Number.NEGATIVE_INFINITY
15
+ };
16
+ },
17
+ getSelect ({root}) {
18
+ return $e(root, 'select');
19
+ },
20
+ getValue ({root}) {
21
+ return this.toValue(this.getSelect({root}).value).value;
22
+ },
23
+ setValue ({root, value}) {
24
+ this.getSelect({root}).value = String(value);
25
+ },
26
+ viewUI ({value}) {
27
+ return ['i', {dataset: {type: 'Infinities'}}, [String(value)]];
28
+ },
29
+ getInput ({root}) {
30
+ return $e(root, 'select');
31
+ },
32
+ editUI ({typeNamespace, value = Number.POSITIVE_INFINITY}) {
33
+ return ['div', {dataset: {type: 'Infinities'}}, [
34
+ ['label', [
35
+ 'Infinities: ',
36
+ ['select', {name: `${typeNamespace}-Infinities`}, [
37
+ ['option', {
38
+ value: 'Infinity', selected: value === Number.POSITIVE_INFINITY
39
+ }, ['Infinity']],
40
+ ['option', {
41
+ value: '-Infinity', selected: value === Number.NEGATIVE_INFINITY
42
+ }, ['-Infinity']]
43
+ ]]
44
+ ]]
45
+ ]];
46
+ }
47
+ };
48
+
49
+ export default InfinitiesSuperType;
@@ -0,0 +1,62 @@
1
+ import {$e} from '../utils/templateUtils.js';
2
+
3
+ /**
4
+ * @typedef {TypeObject} SuperTypeObject
5
+ * @property {string[]} childTypes
6
+ */
7
+
8
+ /**
9
+ * @type {SuperTypeObject}
10
+ */
11
+ const SpecialNumberSuperType = {
12
+ option: ['SpecialNumber', {title: '`NaN`, `Infinity`, `-Infinity`'}],
13
+ childTypes: ['infinity', 'negativeInfinity', 'nan'],
14
+ stringRegex: /^(?:NaN|-?Infinity)$/u,
15
+ toValue (s) {
16
+ return {
17
+ value: s === 'NaN'
18
+ ? Number.NaN
19
+ : s === 'Infinity'
20
+ ? Number.POSITIVE_INFINITY
21
+ : Number.NEGATIVE_INFINITY
22
+ };
23
+ },
24
+ getSelect ({root}) {
25
+ return $e(root, 'select');
26
+ },
27
+ /* istanbul ignore next -- No dupe keys, array refs, or validation */
28
+ getInput ({root}) {
29
+ return $e(root, 'select');
30
+ },
31
+ getValue ({root}) {
32
+ return this.toValue(this.getSelect({root}).value).value;
33
+ },
34
+ setValue ({root, value}) {
35
+ this.getSelect({root}).value = String(value);
36
+ },
37
+ viewUI ({value}) {
38
+ return ['i', {dataset: {type: 'SpecialNumber'}}, [String(value)]];
39
+ },
40
+ editUI ({typeNamespace, value = Number.NaN}) {
41
+ return ['div', {dataset: {type: 'SpecialNumber'}}, [
42
+ ['label', [
43
+ 'Special number: ',
44
+ ['select', {
45
+ name: `${typeNamespace}-SpecialNumber`
46
+ }, [
47
+ ['option', {
48
+ value: 'NaN', selected: Number.isNaN(value)
49
+ }, ['NaN']],
50
+ ['option', {
51
+ value: 'Infinity', selected: value === Number.POSITIVE_INFINITY
52
+ }, ['Infinity']],
53
+ ['option', {
54
+ value: '-Infinity', selected: value === Number.NEGATIVE_INFINITY
55
+ }, ['-Infinity']]
56
+ ]]
57
+ ]]
58
+ ]];
59
+ }
60
+ };
61
+
62
+ export default SpecialNumberSuperType;
@@ -0,0 +1,125 @@
1
+ import {jml} from '../node_modules/jamilih/dist/jml-es.js';
2
+ import Formats from './formats.js';
3
+ import {$e, DOM} from './utils/templateUtils.js';
4
+
5
+ /**
6
+ * @todo Compose from format metadata, so can make user customizable.
7
+ * @param {object} cfg
8
+ * @param {string} cfg.schema
9
+ * @param {boolean} cfg.hasKeyPath
10
+ * @returns {JamilihArray[]}
11
+ */
12
+ export const getFormatAndSchemaChoices = ({schema, hasKeyPath}) => {
13
+ const hasSchema = typeof schema === 'string';
14
+ return [
15
+ ['JSON only', {value: 'json'}],
16
+ ...(hasKeyPath
17
+ ? []
18
+ : [['IndexedDB key', {value: 'indexedDBKey'}]]),
19
+ ['Structured Clone (via Typeson JSON)', {
20
+ value: 'structuredCloning', selected: !hasSchema
21
+ }]
22
+ /* schema:
23
+ ...(hasSchema
24
+ ? [
25
+ [`Schema + arbitrary: ${schema}`, {
26
+ value: 'schemaAndArbitrary',
27
+ dataset: {schema}
28
+ }],
29
+ [`Schema only: ${schema}`, {
30
+ value: 'schemaOnly',
31
+ dataset: {schema},
32
+ selected: hasSchema
33
+ }]
34
+ ]
35
+ : []
36
+ )
37
+ */
38
+ /*
39
+ // This can be supported for editing only
40
+ ['Arbitrary (Non-Typeson-serializable will be read-only)', {
41
+ value: 'arbitrary',
42
+ title: 'Any value that the typeson-registry supports ' +
43
+ 'for structured cloning'
44
+ }]
45
+ */
46
+ ].map(([optText, optAtts]) => {
47
+ return ['option', optAtts, [optText]];
48
+ });
49
+ };
50
+
51
+ /**
52
+ * Builds a selector and container for types.
53
+ * @param {object} cfg
54
+ * @param {string} cfg.schema The schema name
55
+ * @param {object} cfg.schemaContent The schema content
56
+ * @param {boolean} cfg.hasValue If false and `hasKeyPath` is `true`,
57
+ * will initialize with an object
58
+ * @param {boolean} cfg.singleValue
59
+ * @param {boolean} cfg.hasKeyPath
60
+ * @param {string} cfg.typeNamespace
61
+ * @returns {{
62
+ * mainTypeChoices: HTMLSelectElement,
63
+ * typesHolder: HTMLDivElement
64
+ * }} The selector for types and the container for them
65
+ */
66
+ function typeChoices ({
67
+ schema,
68
+ schemaContent,
69
+ hasValue,
70
+ singleValue,
71
+ hasKeyPath,
72
+ typeNamespace
73
+ }) {
74
+ const mainTypeChoices = jml('select', {
75
+ class: 'mainTypeChoices',
76
+ hidden: singleValue,
77
+ // is: 'main-type-choices',
78
+ $custom: {
79
+ $setFormat (valueFormat) {
80
+ this.value = valueFormat;
81
+ this.$buildTypeChoices();
82
+ },
83
+ $buildTypeChoices () {
84
+ const typesHolder = this.nextElementSibling;
85
+ DOM.removeChildren(typesHolder);
86
+ jml({'#': Formats.buildTypeChoices({
87
+ topRoot: $e(typesHolder, 'div[data-type]'),
88
+ resultType: 'both',
89
+ format: this.value,
90
+ typeNamespace,
91
+ requireObject: hasKeyPath,
92
+ objectHasValue: hasValue,
93
+ schema,
94
+ schemaContent
95
+ })}, typesHolder);
96
+ }
97
+ },
98
+ $on: {change () {
99
+ this.$buildTypeChoices();
100
+ }}
101
+ }, getFormatAndSchemaChoices({schema, hasKeyPath}));
102
+ const typesHolder = jml('div', {class: 'typesHolder', $custom: {
103
+ $getTypeRoot () {
104
+ return $e(this, 'div[data-type]');
105
+ },
106
+ $getTypeSelect () {
107
+ return $e(this, `.typeChoices-${typeNamespace}`);
108
+ }
109
+ }});
110
+
111
+ jml({'#': Formats.buildTypeChoices({
112
+ resultType: 'both',
113
+ topRoot: $e(typesHolder, 'div[data-type]'),
114
+ format: 'structuredCloning',
115
+ typeNamespace,
116
+ requireObject: hasKeyPath,
117
+ objectHasValue: hasValue,
118
+ schema,
119
+ schemaContent
120
+ })}, typesHolder);
121
+
122
+ return {mainTypeChoices, typesHolder};
123
+ }
124
+
125
+ export default typeChoices;