rimecms 0.22.0 → 0.23.0
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/dist/core/operations/hooks/before-read/process-document-fields.server.js +15 -16
- package/dist/fields/email/index.js +4 -0
- package/dist/fields/link/index.d.ts +1 -0
- package/dist/fields/link/index.js +19 -0
- package/dist/fields/rich-text/core/config-builders.js +21 -1
- package/dist/fields/rich-text/core/features/fields/fields.svelte +5 -2
- package/dist/fields/rich-text/core/svelte/node-view-renderer.svelte.d.ts +1 -0
- package/dist/fields/rich-text/core/svelte/node-view-renderer.svelte.js +15 -3
- package/dist/fields/rich-text/index.d.ts +1 -0
- package/dist/fields/rich-text/index.js +26 -17
- package/dist/fields/rich-text/sanitize.d.ts +1 -0
- package/dist/fields/rich-text/sanitize.js +25 -0
- package/dist/fields/slug/index.d.ts +1 -0
- package/dist/fields/slug/index.js +10 -8
- package/dist/fields/text/index.d.ts +1 -0
- package/dist/fields/text/index.js +8 -2
- package/dist/fields/textarea/index.d.ts +1 -0
- package/dist/fields/textarea/index.js +8 -2
- package/dist/panel/context/api-proxy.svelte.d.ts +5 -5
- package/dist/panel/context/api-proxy.svelte.js +6 -6
- package/dist/panel/context/config.svelte.d.ts +1 -0
- package/dist/panel/context/config.svelte.js +3 -3
- package/dist/panel/context/form.svelte.d.ts +2 -0
- package/dist/panel/context/form.svelte.js +19 -0
- package/dist/panel/context/user.svelte.d.ts +1 -0
- package/dist/panel/context/user.svelte.js +3 -3
- package/dist/util/string.d.ts +4 -0
- package/dist/util/string.js +9 -0
- package/package.json +2 -1
|
@@ -10,6 +10,21 @@ export const processDocumentFields = Hooks.beforeRead(async (args) => {
|
|
|
10
10
|
for (const [key, config] of Object.entries(configMap)) {
|
|
11
11
|
let value = getValueAtPath(key, doc);
|
|
12
12
|
let isEmpty;
|
|
13
|
+
if (config.access && config.access.read) {
|
|
14
|
+
const authorized = config.access.read(event.locals.user);
|
|
15
|
+
if (!authorized) {
|
|
16
|
+
doc = deleteValueAtPath(doc, key);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (config.hooks?.beforeRead) {
|
|
21
|
+
if (value) {
|
|
22
|
+
for (const hook of config.hooks.beforeRead) {
|
|
23
|
+
value = await hook(value, { event, config, operation: args.context, documentId: doc.id });
|
|
24
|
+
doc = setValueAtPath(key, doc, value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
13
28
|
try {
|
|
14
29
|
isEmpty = config.isEmpty(value);
|
|
15
30
|
}
|
|
@@ -41,22 +56,6 @@ export const processDocumentFields = Hooks.beforeRead(async (args) => {
|
|
|
41
56
|
doc = setValueAtPath(key, doc, withoutResidualBlock);
|
|
42
57
|
}
|
|
43
58
|
}
|
|
44
|
-
if (config.access && config.access.read) {
|
|
45
|
-
const authorized = config.access.read(event.locals.user);
|
|
46
|
-
if (!authorized) {
|
|
47
|
-
doc = deleteValueAtPath(doc, key);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (config.hooks?.beforeRead) {
|
|
52
|
-
let value = getValueAtPath(key, doc);
|
|
53
|
-
if (value) {
|
|
54
|
-
for (const hook of config.hooks.beforeRead) {
|
|
55
|
-
value = await hook(value, { event, config, operation: args.context, documentId: doc.id });
|
|
56
|
-
doc = setValueAtPath(key, doc, value);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
59
|
}
|
|
61
60
|
return { ...args, doc };
|
|
62
61
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
|
+
import { sanitize } from '../../util/string';
|
|
2
3
|
import validate from '../../util/validate.js';
|
|
3
4
|
import EmailComp from './component/Email.svelte';
|
|
4
5
|
class EmailFieldBuilder extends FormFieldBuilder {
|
|
@@ -7,6 +8,9 @@ class EmailFieldBuilder extends FormFieldBuilder {
|
|
|
7
8
|
constructor(name) {
|
|
8
9
|
super(name, 'email');
|
|
9
10
|
this.field.validate = validate.email;
|
|
11
|
+
this.field.hooks = {
|
|
12
|
+
beforeSave: [sanitize]
|
|
13
|
+
};
|
|
10
14
|
}
|
|
11
15
|
layout(layout) {
|
|
12
16
|
this.field.layout = layout;
|
|
@@ -11,6 +11,7 @@ export declare class LinkFieldBuilder extends FormFieldBuilder<LinkField> {
|
|
|
11
11
|
layout(str: 'compact' | 'default'): this;
|
|
12
12
|
defaultValue(value: Link | DefaultValueFn<Link>): this;
|
|
13
13
|
types(...values: LinkType[]): this;
|
|
14
|
+
static readonly sanitize: (link: unknown) => unknown;
|
|
14
15
|
compile(): {
|
|
15
16
|
type: "link";
|
|
16
17
|
live?: boolean | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
|
+
import { sanitize } from '../../util/string';
|
|
2
3
|
import validate from '../../util/validate.js';
|
|
3
4
|
import Cell from './component/Cell.svelte';
|
|
4
5
|
import LinkComp from './component/Link.svelte';
|
|
@@ -11,9 +12,13 @@ export class LinkFieldBuilder extends FormFieldBuilder {
|
|
|
11
12
|
this.field.validate = validate.link;
|
|
12
13
|
this.field.layout = 'default';
|
|
13
14
|
this.field.types = ['url'];
|
|
15
|
+
this.field.hooks = {
|
|
16
|
+
beforeSave: [LinkFieldBuilder.sanitize]
|
|
17
|
+
};
|
|
14
18
|
if (import.meta.env.SSR && import.meta.url) {
|
|
15
19
|
import('./index.server.js').then((module) => {
|
|
16
20
|
this.field.hooks = {
|
|
21
|
+
...this.field.hooks,
|
|
17
22
|
beforeRead: [module.populateRessourceURL]
|
|
18
23
|
};
|
|
19
24
|
});
|
|
@@ -37,6 +42,20 @@ export class LinkFieldBuilder extends FormFieldBuilder {
|
|
|
37
42
|
this.field.types = values;
|
|
38
43
|
return this;
|
|
39
44
|
}
|
|
45
|
+
static sanitize = (link) => {
|
|
46
|
+
if (!link)
|
|
47
|
+
return link;
|
|
48
|
+
const isLinkValue = (v) => typeof link === 'object' && !Array.isArray(link) && 'value' in link;
|
|
49
|
+
if (typeof link === 'string')
|
|
50
|
+
return sanitize(link);
|
|
51
|
+
if (isLinkValue(link) && link.value) {
|
|
52
|
+
return {
|
|
53
|
+
...link,
|
|
54
|
+
url: link.url ? sanitize(link.url) : undefined,
|
|
55
|
+
value: sanitize(link.value)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
40
59
|
compile() {
|
|
41
60
|
if (!this.field.defaultValue) {
|
|
42
61
|
this.field.defaultValue = { value: '', target: '_self', type: this.field.types[0] };
|
|
@@ -5,6 +5,9 @@ import { ListItem } from '@tiptap/extension-list';
|
|
|
5
5
|
import Text from '@tiptap/extension-text';
|
|
6
6
|
import Typography from '@tiptap/extension-typography';
|
|
7
7
|
import { Dropcursor, Gapcursor, Placeholder, UndoRedo } from '@tiptap/extensions';
|
|
8
|
+
import { API_PROXY, getAPIProxyContext } from '../../../panel/context/api-proxy.svelte.js';
|
|
9
|
+
import { CONFIG_CTX, getConfigContext } from '../../../panel/context/config.svelte.js';
|
|
10
|
+
import { getUserContext, USER_CTX } from '../../../panel/context/user.svelte.js';
|
|
8
11
|
import { hasSuggestion } from '../util.js';
|
|
9
12
|
import { CurrentNodeAttribute } from './extensions/current-node/current-node.js';
|
|
10
13
|
import { defaultFeatures } from './features/index.js';
|
|
@@ -50,8 +53,25 @@ export function buildEditorConfig(args) {
|
|
|
50
53
|
// Add all feature extensions to the editor
|
|
51
54
|
// We need to track which extensions we've already added to avoid duplicates
|
|
52
55
|
const addedExtensions = new Set();
|
|
56
|
+
const hasExtension = (f) => !!f.extension;
|
|
53
57
|
features.forEach((feature) => {
|
|
54
|
-
if (feature
|
|
58
|
+
if (hasExtension(feature) && !addedExtensions.has(feature.extension)) {
|
|
59
|
+
// Populate contexts
|
|
60
|
+
if ('addNodeView' in feature.extension.config) {
|
|
61
|
+
const originalAddOption = feature.extension.config.addOptions || (() => ({}));
|
|
62
|
+
const contexts = new Map();
|
|
63
|
+
const configContext = getConfigContext();
|
|
64
|
+
const apiProxyContext = getAPIProxyContext(API_PROXY.DOCUMENT);
|
|
65
|
+
const userContext = getUserContext();
|
|
66
|
+
contexts.set(CONFIG_CTX, configContext);
|
|
67
|
+
contexts.set(API_PROXY.DOCUMENT, apiProxyContext);
|
|
68
|
+
contexts.set(USER_CTX, userContext);
|
|
69
|
+
feature.extension.config.addOptions = () => {
|
|
70
|
+
// @ts-expect-error
|
|
71
|
+
return { ...originalAddOption(), contexts };
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Push extension
|
|
55
75
|
baseEditorConfig.extensions?.push(feature.extension);
|
|
56
76
|
addedExtensions.add(feature.extension);
|
|
57
77
|
}
|
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
|
|
47
47
|
<NodeViewWrapper>
|
|
48
48
|
<FieldsPreviewTrigger class="rz-rich-text-fields-preview" onclick={() => (isSheetOpen = true)}>
|
|
49
|
-
<FieldsPreview
|
|
49
|
+
<FieldsPreview
|
|
50
|
+
fields={previewFields}
|
|
51
|
+
getField={(field) => form.useField(field.name, field.raw)}
|
|
52
|
+
/>
|
|
50
53
|
</FieldsPreviewTrigger>
|
|
51
54
|
</NodeViewWrapper>
|
|
52
55
|
|
|
@@ -54,7 +57,7 @@
|
|
|
54
57
|
<Sheet.Content side="right" class="rz-rich-text-sheet">
|
|
55
58
|
{#each previewFields || [] as field, index (index)}
|
|
56
59
|
{@const FieldComponent = field.component}
|
|
57
|
-
<FieldComponent config={field.raw} {form} />
|
|
60
|
+
<FieldComponent path={field.raw.name} config={field.raw} {form} />
|
|
58
61
|
{/each}
|
|
59
62
|
</Sheet.Content>
|
|
60
63
|
</Sheet.Root>
|
|
@@ -19,6 +19,7 @@ export interface SvelteNodeViewRendererOptions extends NodeViewRendererOptions {
|
|
|
19
19
|
update: ((props: RendererUpdateProps) => boolean) | null;
|
|
20
20
|
as?: string;
|
|
21
21
|
attrs?: AttrProps;
|
|
22
|
+
context?: Map<string, any>;
|
|
22
23
|
}
|
|
23
24
|
declare const SvelteNodeViewRenderer: (component: Component<NodeViewProps>, options?: Partial<SvelteNodeViewRendererOptions>) => NodeViewRenderer;
|
|
24
25
|
export default SvelteNodeViewRenderer;
|
|
@@ -24,7 +24,9 @@ class SvelteNodeView extends NodeView {
|
|
|
24
24
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
25
25
|
deleteNode: () => this.deleteNode()
|
|
26
26
|
});
|
|
27
|
-
this.contentDOMElement = this.node.isLeaf
|
|
27
|
+
this.contentDOMElement = this.node.isLeaf
|
|
28
|
+
? null
|
|
29
|
+
: document.createElement(this.node.isInline ? 'span' : 'div');
|
|
28
30
|
if (this.contentDOMElement) {
|
|
29
31
|
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
30
32
|
// With this fix it seems to work fine
|
|
@@ -35,6 +37,12 @@ class SvelteNodeView extends NodeView {
|
|
|
35
37
|
context.set(TIPTAP_NODE_VIEW, {
|
|
36
38
|
onDragStart: this.onDragStart.bind(this)
|
|
37
39
|
});
|
|
40
|
+
if (this.extension.options.contexts) {
|
|
41
|
+
for (const [key, ctx] of this.extension.options.contexts.entries()) {
|
|
42
|
+
context.set(key, ctx);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.log(context);
|
|
38
46
|
const as = this.options.as ?? (this.node.isInline ? 'span' : 'div');
|
|
39
47
|
const target = document.createElement(as);
|
|
40
48
|
target.classList.add(`node-${this.node.type.name}`);
|
|
@@ -58,7 +66,9 @@ class SvelteNodeView extends NodeView {
|
|
|
58
66
|
}
|
|
59
67
|
appendContendDom() {
|
|
60
68
|
const contentElement = this.dom.querySelector('[data-node-view-content]');
|
|
61
|
-
if (this.contentDOMElement &&
|
|
69
|
+
if (this.contentDOMElement &&
|
|
70
|
+
contentElement &&
|
|
71
|
+
!contentElement.contains(this.contentDOMElement)) {
|
|
62
72
|
contentElement.appendChild(this.contentDOMElement);
|
|
63
73
|
}
|
|
64
74
|
}
|
|
@@ -122,7 +132,9 @@ class SvelteNodeView extends NodeView {
|
|
|
122
132
|
if (node.type !== this.node.type) {
|
|
123
133
|
return false;
|
|
124
134
|
}
|
|
125
|
-
if (node === this.node &&
|
|
135
|
+
if (node === this.node &&
|
|
136
|
+
this.decorations === decorations &&
|
|
137
|
+
this.innerDecorations === innerDecorations) {
|
|
126
138
|
return true;
|
|
127
139
|
}
|
|
128
140
|
this.node = node;
|
|
@@ -24,6 +24,7 @@ export declare class RichTextFieldBuilder extends FormFieldBuilder<RichTextField
|
|
|
24
24
|
*/
|
|
25
25
|
features(...features: Array<RichTextFeature>): this;
|
|
26
26
|
static readonly jsonParse: (value: string) => string;
|
|
27
|
+
static readonly sanitize: (value: unknown) => any;
|
|
27
28
|
static readonly stringify: (value: string) => string;
|
|
28
29
|
defaultValue(value: RichTextContent | DefaultValueFn<RichTextContent>): this;
|
|
29
30
|
}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
|
+
import { sanitize } from '../../util/string.js';
|
|
2
3
|
import Cell from './component/Cell.svelte';
|
|
3
4
|
import RichText from './component/RichText.svelte';
|
|
5
|
+
import { sanitizeJSONContent } from './sanitize';
|
|
4
6
|
const isEmpty = (value) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
value !== null &&
|
|
16
|
-
!Array.isArray(value) &&
|
|
17
|
-
Object.getPrototypeOf(value) === Object.prototype &&
|
|
18
|
-
'content' in value &&
|
|
19
|
-
Array.isArray(value.content) &&
|
|
20
|
-
value.content.reduce(reduceText, '') === '');
|
|
7
|
+
if (!value)
|
|
8
|
+
return true;
|
|
9
|
+
// Check for content length in pm object
|
|
10
|
+
if (typeof value === 'object' && !Array.isArray(value) && 'content' in value) {
|
|
11
|
+
return Array.isArray(value.content) && value.content.length === 0;
|
|
12
|
+
}
|
|
13
|
+
// Allow simple string
|
|
14
|
+
if (typeof value === 'string' && !!value)
|
|
15
|
+
return false;
|
|
16
|
+
return true;
|
|
21
17
|
};
|
|
22
18
|
export class RichTextFieldBuilder extends FormFieldBuilder {
|
|
23
19
|
//
|
|
@@ -27,7 +23,7 @@ export class RichTextFieldBuilder extends FormFieldBuilder {
|
|
|
27
23
|
this.field.isEmpty = isEmpty;
|
|
28
24
|
this.field.hooks = {
|
|
29
25
|
beforeRead: [RichTextFieldBuilder.jsonParse],
|
|
30
|
-
beforeSave: [RichTextFieldBuilder.stringify],
|
|
26
|
+
beforeSave: [RichTextFieldBuilder.sanitize, RichTextFieldBuilder.stringify],
|
|
31
27
|
beforeValidate: []
|
|
32
28
|
};
|
|
33
29
|
}
|
|
@@ -71,6 +67,19 @@ export class RichTextFieldBuilder extends FormFieldBuilder {
|
|
|
71
67
|
}
|
|
72
68
|
return value;
|
|
73
69
|
};
|
|
70
|
+
static sanitize = (value) => {
|
|
71
|
+
if (!value)
|
|
72
|
+
return value;
|
|
73
|
+
// Handle string input - just sanitize as plain text
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
return sanitize(value);
|
|
76
|
+
}
|
|
77
|
+
// Handle JSONContent object
|
|
78
|
+
if (typeof value === 'object' && value !== null && 'content' in value) {
|
|
79
|
+
return sanitizeJSONContent(value);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
};
|
|
74
83
|
static stringify = (value) => {
|
|
75
84
|
if (typeof value === 'string')
|
|
76
85
|
return value;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sanitizeJSONContent: (content: any) => any;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { sanitize } from '../../util/string.js';
|
|
2
|
+
// Helper function to sanitize JSONContent recursively
|
|
3
|
+
export const sanitizeJSONContent = (content) => {
|
|
4
|
+
if (!content)
|
|
5
|
+
return content;
|
|
6
|
+
// Handle text nodes - sanitize the text content
|
|
7
|
+
if (content.type === 'text' && content.text) {
|
|
8
|
+
return {
|
|
9
|
+
...content,
|
|
10
|
+
text: sanitize(content.text)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// Skip sanitization for code blocks (preserve original content)
|
|
14
|
+
if (content.type === 'codeBlock' || content.type === 'code') {
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
17
|
+
// Recursively handle content array
|
|
18
|
+
if (content.content && Array.isArray(content.content)) {
|
|
19
|
+
return {
|
|
20
|
+
...content,
|
|
21
|
+
content: content.content.map(sanitizeJSONContent)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return content;
|
|
25
|
+
};
|
|
@@ -3,6 +3,7 @@ import type { DefaultValueFn, FormField } from '../types.js';
|
|
|
3
3
|
export declare const slug: (name: string) => SlugFieldBuilder;
|
|
4
4
|
export declare class SlugFieldBuilder extends FormFieldBuilder<SlugField> {
|
|
5
5
|
_metaUrl: string;
|
|
6
|
+
constructor(name: string);
|
|
6
7
|
get component(): import("svelte").Component<{
|
|
7
8
|
path: string;
|
|
8
9
|
config: SlugField;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
2
|
import { validate } from '../../util/index.js';
|
|
3
|
-
import { slugify } from '../../util/string.js';
|
|
3
|
+
import { sanitize, slugify } from '../../util/string.js';
|
|
4
4
|
import Cell from './component/Cell.svelte';
|
|
5
5
|
import Slug from './component/Slug.svelte';
|
|
6
|
-
export const slug = (name) => new SlugFieldBuilder(name
|
|
6
|
+
export const slug = (name) => new SlugFieldBuilder(name);
|
|
7
7
|
export class SlugFieldBuilder extends FormFieldBuilder {
|
|
8
8
|
//
|
|
9
9
|
_metaUrl = import.meta.url;
|
|
10
|
+
constructor(name) {
|
|
11
|
+
super(name, 'slug');
|
|
12
|
+
this.field.validate = validate.slug;
|
|
13
|
+
this.field.isEmpty = (value) => !value;
|
|
14
|
+
this.field.hooks = {
|
|
15
|
+
beforeSave: [sanitize]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
10
18
|
get component() {
|
|
11
19
|
return Slug;
|
|
12
20
|
}
|
|
@@ -38,15 +46,9 @@ export class SlugFieldBuilder extends FormFieldBuilder {
|
|
|
38
46
|
return this;
|
|
39
47
|
}
|
|
40
48
|
compile() {
|
|
41
|
-
if (!this.field.validate) {
|
|
42
|
-
this.field.validate = validate.slug;
|
|
43
|
-
}
|
|
44
49
|
if (!this.field.placeholder) {
|
|
45
50
|
this.field.placeholder = slugify(this.field.label || this.field.name);
|
|
46
51
|
}
|
|
47
|
-
if (!this.field.isEmpty) {
|
|
48
|
-
this.field.isEmpty = (value) => !value;
|
|
49
|
-
}
|
|
50
52
|
return super.compile();
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -3,6 +3,7 @@ import type { DefaultValueFn, FormField } from '../types.js';
|
|
|
3
3
|
/****************************************************/
|
|
4
4
|
export declare class TextFieldBuilder extends FormFieldBuilder<TextField> {
|
|
5
5
|
_metaUrl: string;
|
|
6
|
+
constructor(name: string);
|
|
6
7
|
unique(bool?: boolean): this;
|
|
7
8
|
get component(): import("svelte").Component<import("./component/props").TextFieldProps, {}, "">;
|
|
8
9
|
get cell(): null;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
|
-
import { capitalize } from '../../util/string.js';
|
|
2
|
+
import { capitalize, sanitize } from '../../util/string.js';
|
|
3
3
|
import Text from './component/Text.svelte';
|
|
4
4
|
/****************************************************/
|
|
5
5
|
export class TextFieldBuilder extends FormFieldBuilder {
|
|
6
6
|
//
|
|
7
7
|
_metaUrl = import.meta.url;
|
|
8
|
+
constructor(name) {
|
|
9
|
+
super(name, 'text');
|
|
10
|
+
this.field.hooks = {
|
|
11
|
+
beforeSave: [sanitize]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
8
14
|
unique(bool) {
|
|
9
15
|
this.field.unique = typeof bool === 'boolean' ? bool : true;
|
|
10
16
|
return this;
|
|
@@ -47,4 +53,4 @@ export class TextFieldBuilder extends FormFieldBuilder {
|
|
|
47
53
|
return this;
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
export const text = (name) => new TextFieldBuilder(name
|
|
56
|
+
export const text = (name) => new TextFieldBuilder(name);
|
|
@@ -3,6 +3,7 @@ import type { DefaultValueFn, FormField } from '../types.js';
|
|
|
3
3
|
export declare const textarea: (name: string) => TextAreaFieldBuilder;
|
|
4
4
|
export declare class TextAreaFieldBuilder extends FormFieldBuilder<TextAreaField> {
|
|
5
5
|
_metaUrl: string;
|
|
6
|
+
constructor(name: string);
|
|
6
7
|
get component(): import("svelte").Component<import("./component/props").TextAreaFieldProps, {}, "">;
|
|
7
8
|
get cell(): null;
|
|
8
9
|
defaultValue(value: string | DefaultValueFn<string>): this;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { FormFieldBuilder } from '../../core/fields/builders/form-field-builder.js';
|
|
2
|
-
import { capitalize } from '../../util/string.js';
|
|
2
|
+
import { capitalize, sanitize } from '../../util/string.js';
|
|
3
3
|
import TextArea from './component/TextArea.svelte';
|
|
4
|
-
export const textarea = (name) => new TextAreaFieldBuilder(name
|
|
4
|
+
export const textarea = (name) => new TextAreaFieldBuilder(name);
|
|
5
5
|
export class TextAreaFieldBuilder extends FormFieldBuilder {
|
|
6
6
|
//
|
|
7
7
|
_metaUrl = import.meta.url;
|
|
8
|
+
constructor(name) {
|
|
9
|
+
super(name, 'textarea');
|
|
10
|
+
this.field.hooks = {
|
|
11
|
+
beforeSave: [sanitize]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
8
14
|
get component() {
|
|
9
15
|
return TextArea;
|
|
10
16
|
}
|
|
@@ -8,17 +8,17 @@ export type Resource<T = any> = {
|
|
|
8
8
|
type GetResourcesOptions<T> = {
|
|
9
9
|
transformData?: (input: any) => T;
|
|
10
10
|
};
|
|
11
|
-
export declare function setAPIProxyContext(key?:
|
|
11
|
+
export declare function setAPIProxyContext(key?: symbol): {
|
|
12
12
|
getRessource: <T>(url: string, options?: GetResourcesOptions<T>) => Resource<T>;
|
|
13
13
|
invalidateAll: () => void;
|
|
14
14
|
};
|
|
15
|
-
export declare function getAPIProxyContext(key?:
|
|
15
|
+
export declare function getAPIProxyContext(key?: symbol): {
|
|
16
16
|
getRessource: <T>(url: string, options?: GetResourcesOptions<T>) => Resource<T>;
|
|
17
17
|
invalidateAll: () => void;
|
|
18
18
|
};
|
|
19
19
|
export declare const API_PROXY: {
|
|
20
|
-
DOCUMENT:
|
|
21
|
-
ROOT:
|
|
22
|
-
TIPTAP:
|
|
20
|
+
DOCUMENT: symbol;
|
|
21
|
+
ROOT: symbol;
|
|
22
|
+
TIPTAP: symbol;
|
|
23
23
|
};
|
|
24
24
|
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
|
-
const API_PROXY_KEY = Symbol('api-proxy');
|
|
3
2
|
function createAPIProxy() {
|
|
4
3
|
// Use explicit type instead of ReturnType
|
|
5
4
|
const resources = $state(new Map());
|
|
@@ -72,13 +71,14 @@ function createAPIProxy() {
|
|
|
72
71
|
}
|
|
73
72
|
export function setAPIProxyContext(key = API_PROXY.ROOT) {
|
|
74
73
|
const apiProxy = createAPIProxy();
|
|
75
|
-
return setContext(
|
|
74
|
+
return setContext(key, apiProxy);
|
|
76
75
|
}
|
|
77
76
|
export function getAPIProxyContext(key = API_PROXY.ROOT) {
|
|
78
|
-
return getContext(
|
|
77
|
+
return getContext(key);
|
|
79
78
|
}
|
|
79
|
+
// @TODO why multiple APIProxy ROOT everywhere should work
|
|
80
80
|
export const API_PROXY = {
|
|
81
|
-
DOCUMENT: 'document',
|
|
82
|
-
ROOT: 'root',
|
|
83
|
-
TIPTAP: 'tiptap'
|
|
81
|
+
DOCUMENT: Symbol('api-proxy.document'),
|
|
82
|
+
ROOT: Symbol('api-proxy.root'),
|
|
83
|
+
TIPTAP: Symbol('api-proxy.tiptap')
|
|
84
84
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BuiltAreaClient, BuiltCollectionClient, BuiltConfigClient } from '../../core/config/types.js';
|
|
2
2
|
import type { Prototype, PrototypeSlug } from '../../core/types/doc.js';
|
|
3
|
+
export declare const CONFIG_CTX: unique symbol;
|
|
3
4
|
export declare function setConfigContext(initial: BuiltConfigClient): {
|
|
4
5
|
readonly raw: BuiltConfigClient;
|
|
5
6
|
getArea: (slug: string) => BuiltAreaClient;
|
|
@@ -19,11 +19,11 @@ function createConfigStore(config) {
|
|
|
19
19
|
getDocumentConfig
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
const
|
|
22
|
+
export const CONFIG_CTX = Symbol('rime.config');
|
|
23
23
|
export function setConfigContext(initial) {
|
|
24
24
|
const store = createConfigStore(initial);
|
|
25
|
-
return setContext(
|
|
25
|
+
return setContext(CONFIG_CTX, store);
|
|
26
26
|
}
|
|
27
27
|
export function getConfigContext() {
|
|
28
|
-
return getContext(
|
|
28
|
+
return getContext(CONFIG_CTX);
|
|
29
29
|
}
|
|
@@ -12,6 +12,7 @@ export declare function setFormContext(initial: Dic, key: string): {
|
|
|
12
12
|
};
|
|
13
13
|
readOnly: boolean;
|
|
14
14
|
enhance: SubmitFunction;
|
|
15
|
+
getRawValue: <T>(path: string) => NonNullable<T> | null;
|
|
15
16
|
readonly canSubmit: boolean;
|
|
16
17
|
readonly errors: {
|
|
17
18
|
readonly length: number;
|
|
@@ -50,6 +51,7 @@ export declare function getFormContext(key: string): {
|
|
|
50
51
|
};
|
|
51
52
|
readOnly: boolean;
|
|
52
53
|
enhance: SubmitFunction;
|
|
54
|
+
getRawValue: <T>(path: string) => NonNullable<T> | null;
|
|
53
55
|
readonly canSubmit: boolean;
|
|
54
56
|
readonly errors: {
|
|
55
57
|
readonly length: number;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { applyAction } from '$app/forms';
|
|
2
|
+
import { getValueAtPath } from '../../util/object';
|
|
3
|
+
import { snapshot } from '../../util/state';
|
|
2
4
|
import { diff } from 'deep-object-diff';
|
|
3
5
|
import { getContext, setContext } from 'svelte';
|
|
4
6
|
import { setErrorsContext } from './errors.svelte';
|
|
@@ -15,6 +17,22 @@ function createFormStore(initial, key) {
|
|
|
15
17
|
errors.value = {};
|
|
16
18
|
}
|
|
17
19
|
});
|
|
20
|
+
/**
|
|
21
|
+
* Function that return an unreactive snapshot of a value given a path.
|
|
22
|
+
*
|
|
23
|
+
* @param path Field path ex: blocks.0.title
|
|
24
|
+
* @returns an unreactive snapshot
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const form = getDocumentFormContext()
|
|
28
|
+
* const initialValue = form.getRawValue('blocks.0.title')
|
|
29
|
+
*
|
|
30
|
+
* // value will not update if doc.blocks.0.title update
|
|
31
|
+
*/
|
|
32
|
+
function getRawValue(path) {
|
|
33
|
+
console.log(path);
|
|
34
|
+
return snapshot(getValueAtPath(path, form)) || null;
|
|
35
|
+
}
|
|
18
36
|
function setValue(path, value) {
|
|
19
37
|
status = undefined;
|
|
20
38
|
form = { ...form, [path]: value };
|
|
@@ -104,6 +122,7 @@ function createFormStore(initial, key) {
|
|
|
104
122
|
useField,
|
|
105
123
|
readOnly: false,
|
|
106
124
|
enhance,
|
|
125
|
+
getRawValue,
|
|
107
126
|
get canSubmit() {
|
|
108
127
|
return canSubmit;
|
|
109
128
|
},
|
|
@@ -7,11 +7,11 @@ function createUserStore(initial) {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
const
|
|
10
|
+
export const USER_CTX = Symbol('rime.user');
|
|
11
11
|
export function setUserContext(initial) {
|
|
12
12
|
const user = createUserStore(initial);
|
|
13
|
-
return setContext(
|
|
13
|
+
return setContext(USER_CTX, user);
|
|
14
14
|
}
|
|
15
15
|
export function getUserContext() {
|
|
16
|
-
return getContext(
|
|
16
|
+
return getContext(USER_CTX);
|
|
17
17
|
}
|
package/dist/util/string.d.ts
CHANGED
|
@@ -92,3 +92,7 @@ export declare const isCamelCase: (str: string) => boolean;
|
|
|
92
92
|
* isValidSlug("hello world");
|
|
93
93
|
*/
|
|
94
94
|
export declare const isValidSlug: (str: string) => boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Sanitizing using DOMPurify
|
|
97
|
+
*/
|
|
98
|
+
export declare const sanitize: (value?: string) => string | undefined;
|
package/dist/util/string.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import camelCase from 'camelcase';
|
|
2
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
2
3
|
/**
|
|
3
4
|
* Capitalizes the first letter of a string.
|
|
4
5
|
*
|
|
@@ -161,3 +162,11 @@ export const isCamelCase = (str) => /^[a-z][a-zA-Z0-9]*$/.test(str);
|
|
|
161
162
|
* isValidSlug("hello world");
|
|
162
163
|
*/
|
|
163
164
|
export const isValidSlug = (str) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(str);
|
|
165
|
+
/**
|
|
166
|
+
* Sanitizing using DOMPurify
|
|
167
|
+
*/
|
|
168
|
+
export const sanitize = (value) => {
|
|
169
|
+
if (!value)
|
|
170
|
+
return value;
|
|
171
|
+
return DOMPurify.sanitize(value);
|
|
172
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rimecms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"homepage": "https://github.com/bienbiendev/rime",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -206,6 +206,7 @@
|
|
|
206
206
|
"dotenv": "^16.4.7",
|
|
207
207
|
"drizzle-orm": "^0.44.2",
|
|
208
208
|
"flat": "^6.0.1",
|
|
209
|
+
"isomorphic-dompurify": "^2.31.0",
|
|
209
210
|
"js-cookie": "^3.0.5",
|
|
210
211
|
"magic-string-ast": "^1.0.2",
|
|
211
212
|
"nodemailer": "^6.10.0",
|