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.
@@ -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.extension && !addedExtensions.has(feature.extension)) {
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 fields={previewFields} getField={(field) => form.useField(field.name, field.raw)} />
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 ? null : document.createElement(this.node.isInline ? 'span' : 'div');
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 && contentElement && !contentElement.contains(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 && this.decorations === decorations && this.innerDecorations === innerDecorations) {
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
- const reduceText = (prev, curr) => {
6
- if ('text' in curr) {
7
- prev += curr.text;
8
- }
9
- else if ('content' in curr) {
10
- return curr.content.reduce(reduceText, prev);
11
- }
12
- return prev;
13
- };
14
- return (typeof value === 'object' &&
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, 'slug');
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, 'text');
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, 'textarea');
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?: string): {
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?: string): {
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: string;
21
- ROOT: string;
22
- TIPTAP: string;
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(`${API_PROXY_KEY.toString()}.${key}`, apiProxy);
74
+ return setContext(key, apiProxy);
76
75
  }
77
76
  export function getAPIProxyContext(key = API_PROXY.ROOT) {
78
- return getContext(`${API_PROXY_KEY.toString()}.${key}`);
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 CONFIG_KEY = Symbol('rime.config');
22
+ export const CONFIG_CTX = Symbol('rime.config');
23
23
  export function setConfigContext(initial) {
24
24
  const store = createConfigStore(initial);
25
- return setContext(CONFIG_KEY, store);
25
+ return setContext(CONFIG_CTX, store);
26
26
  }
27
27
  export function getConfigContext() {
28
- return getContext(CONFIG_KEY);
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
  },
@@ -1,4 +1,5 @@
1
1
  import type { User } from '../../core/collections/auth/types.js';
2
+ export declare const USER_CTX: unique symbol;
2
3
  export declare function setUserContext(initial: User): {
3
4
  readonly attributes: User;
4
5
  };
@@ -7,11 +7,11 @@ function createUserStore(initial) {
7
7
  }
8
8
  };
9
9
  }
10
- const CONFIG_KEY = Symbol('rime.user');
10
+ export const USER_CTX = Symbol('rime.user');
11
11
  export function setUserContext(initial) {
12
12
  const user = createUserStore(initial);
13
- return setContext(CONFIG_KEY, user);
13
+ return setContext(USER_CTX, user);
14
14
  }
15
15
  export function getUserContext() {
16
- return getContext(CONFIG_KEY);
16
+ return getContext(USER_CTX);
17
17
  }
@@ -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;
@@ -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.22.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",