@dxos/react-ui-editor 0.3.10-main.11cdcf4 → 0.3.10-main.279fbbe

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 (74) hide show
  1. package/dist/lib/browser/index.mjs +319 -210
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +3 -0
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +1 -0
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts +3 -2
  9. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +14 -0
  11. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/extensions/index.d.ts +1 -0
  14. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
  15. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +1 -1
  16. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +1 -1
  17. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +1 -1
  18. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +1 -1
  19. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +1 -4
  20. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +1 -1
  21. package/dist/types/src/components/TextEditor/themes/default.d.ts +3 -0
  22. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +1 -1
  23. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +1 -0
  24. package/dist/types/src/hooks/automerge/amToCodemirror.d.ts.map +1 -0
  25. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -0
  26. package/dist/types/src/hooks/automerge/codeMirrorToAm.d.ts.map +1 -0
  27. package/dist/types/src/hooks/automerge/handle.d.ts.map +1 -0
  28. package/dist/types/src/hooks/automerge/index.d.ts.map +1 -0
  29. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -0
  30. package/dist/types/src/hooks/index.d.ts +0 -1
  31. package/dist/types/src/hooks/index.d.ts.map +1 -1
  32. package/dist/types/src/hooks/useTextModel.d.ts +3 -4
  33. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  34. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  35. package/package.json +19 -19
  36. package/src/components/TextEditor/MarkdownEditor.stories.tsx +37 -6
  37. package/src/components/TextEditor/TextEditor.tsx +21 -17
  38. package/src/components/TextEditor/extensions/autocomplete.ts +5 -3
  39. package/src/components/TextEditor/extensions/comments.ts +173 -0
  40. package/src/components/TextEditor/extensions/experimental.tsx +1 -0
  41. package/src/components/TextEditor/extensions/index.ts +1 -0
  42. package/src/components/TextEditor/extensions/link.ts +10 -2
  43. package/src/components/TextEditor/extensions/listener.ts +1 -0
  44. package/src/components/TextEditor/extensions/markdown/bundle.ts +6 -14
  45. package/src/components/TextEditor/extensions/markdown/highlight.ts +2 -1
  46. package/src/components/TextEditor/extensions/tasklist.ts +73 -79
  47. package/src/components/TextEditor/themes/default.ts +15 -0
  48. package/src/{automerge → hooks/automerge}/automerge.stories.tsx +2 -1
  49. package/src/hooks/index.ts +0 -1
  50. package/src/hooks/useTextModel.ts +49 -41
  51. package/src/testing/replicator.ts +6 -7
  52. package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
  53. package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
  54. package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
  55. package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
  56. package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
  57. package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
  58. package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
  59. package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
  60. package/dist/types/src/hooks/useCollaboration.d.ts.map +0 -1
  61. package/src/hooks/useCollaboration.ts +0 -45
  62. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.d.ts +0 -0
  63. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.d.ts +0 -0
  64. /package/dist/types/src/{automerge → hooks/automerge}/automerge.stories.d.ts +0 -0
  65. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.d.ts +0 -0
  66. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/handle.d.ts +0 -0
  67. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/index.d.ts +0 -0
  68. /package/dist/types/src/{automerge/automerge-plugin → hooks/automerge}/plugin.d.ts +0 -0
  69. /package/src/{automerge/automerge-plugin → hooks/automerge}/PatchSemaphore.ts +0 -0
  70. /package/src/{automerge/automerge-plugin → hooks/automerge}/amToCodemirror.ts +0 -0
  71. /package/src/{automerge/automerge-plugin → hooks/automerge}/codeMirrorToAm.ts +0 -0
  72. /package/src/{automerge/automerge-plugin → hooks/automerge}/handle.ts +0 -0
  73. /package/src/{automerge/automerge-plugin → hooks/automerge}/index.ts +0 -0
  74. /package/src/{automerge/automerge-plugin → hooks/automerge}/plugin.ts +0 -0
@@ -2,9 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { useMemo } from 'react';
5
+ import { type Extension } from '@codemirror/state';
6
+ import get from 'lodash.get';
7
+ import { useEffect, useState } from 'react';
8
+ import { yCollab } from 'y-codemirror.next';
6
9
  import type * as awarenessProtocol from 'y-protocols/awareness';
7
10
 
11
+ import { invariant } from '@dxos/invariant';
8
12
  import {
9
13
  type DocAccessor,
10
14
  type Space,
@@ -16,18 +20,20 @@ import {
16
20
  import { type Identity } from '@dxos/react-client/halo';
17
21
  import type { YText, YXmlFragment } from '@dxos/text-model';
18
22
 
23
+ import { automergePlugin } from './automerge';
19
24
  import { SpaceAwarenessProvider } from './yjs';
20
25
 
21
26
  // TODO(burdon): Move.
22
27
  type Awareness = awarenessProtocol.Awareness;
23
- type Provider = { awareness: Awareness };
24
28
 
25
29
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
26
30
  export type EditorModel = {
27
31
  id: string;
32
+ // TODO(burdon): Remove.
28
33
  content: string | YText | YXmlFragment | DocAccessor;
29
34
  text: () => string;
30
- provider?: Provider;
35
+ extension?: Extension;
36
+ awareness?: Awareness;
31
37
  peer?: {
32
38
  id: string;
33
39
  name?: string;
@@ -41,57 +47,59 @@ export type UseTextModelOptions = {
41
47
  text?: TextObject;
42
48
  };
43
49
 
44
- // TODO(burdon): Don't support returning undefined (and remove checks from calling code).
45
- // TODO(burdon): Decouple space (make Editor less dependent on entire stack)?
50
+ // TODO(burdon): Remove YJS/Automerge deps (from UI component -- create abstraction; incl. all ECHO/Space deps).
46
51
  // TODO(wittjosiah): Factor out to common package? @dxos/react-client?
47
52
  export const useTextModel = ({ identity, space, text }: UseTextModelOptions): EditorModel | undefined => {
48
- // TODO(burdon): Remove?
49
- // eslint-disable-next-line unused-imports/no-unused-vars
50
- const provider = useMemo(() => {
51
- if (isActualAutomergeObject(text)) {
52
- return undefined;
53
- }
53
+ const [model, setModel] = useState<EditorModel | undefined>(() => createModel({ identity, space, text }));
54
+ useEffect(() => {
55
+ setModel(createModel({ identity, space, text }));
56
+ }, [identity, space, text]);
57
+ return model;
58
+ };
54
59
 
55
- if (!space || !text?.doc) {
60
+ const createModel = (options: UseTextModelOptions) => {
61
+ const { text } = options;
62
+ if (isActualAutomergeObject(text)) {
63
+ return createAutomergeModel(options);
64
+ } else {
65
+ if (!text?.doc) {
56
66
  return undefined;
57
67
  }
58
68
 
59
- return new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` });
60
- }, [identity, space, text, text?.doc]);
61
-
62
- const content = useMemo(() => {
63
- if (isActualAutomergeObject(text)) {
64
- const obj = text as any as AutomergeTextCompat;
65
- return getRawDoc(obj, [obj.field]);
66
- } else {
67
- return text?.content;
68
- }
69
- }, [text]);
70
-
71
- if (!text?.doc || !text?.content) {
72
- return undefined;
69
+ return createYjsModel(options);
73
70
  }
71
+ };
74
72
 
75
- if (isActualAutomergeObject(text)) {
76
- const obj = text as any as AutomergeTextCompat;
77
- return {
78
- id: obj.id,
79
- content: content!,
80
- text: () => content!.toString(),
81
- provider: undefined,
82
- peer: identity
83
- ? {
84
- id: identity.identityKey.toHex(),
85
- name: identity.profile?.displayName,
86
- }
87
- : undefined,
88
- };
89
- }
73
+ const createYjsModel = ({ identity, space, text }: UseTextModelOptions): EditorModel => {
74
+ invariant(text?.doc && text?.content);
75
+ const provider = space
76
+ ? new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` })
77
+ : undefined;
90
78
 
91
79
  return {
92
80
  id: text.doc.guid,
93
81
  content: text.content,
94
82
  text: () => text.content!.toString(),
83
+ extension: yCollab(text.content as YText, provider?.awareness),
84
+ awareness: provider?.awareness,
85
+ peer: identity
86
+ ? {
87
+ id: identity.identityKey.toHex(),
88
+ name: identity.profile?.displayName,
89
+ }
90
+ : undefined,
91
+ };
92
+ };
93
+
94
+ const createAutomergeModel = ({ identity, text }: UseTextModelOptions): EditorModel => {
95
+ const obj = text as any as AutomergeTextCompat;
96
+ const doc = getRawDoc(obj, [obj.field]);
97
+
98
+ return {
99
+ id: obj.id,
100
+ content: doc,
101
+ text: () => get(doc.handle.docSync(), doc.path),
102
+ extension: automergePlugin(doc.handle, doc.path),
95
103
  peer: identity
96
104
  ? {
97
105
  id: identity.identityKey.toHex(),
@@ -163,17 +163,17 @@ export class Replicator {
163
163
  colorLight: cursorColor.light,
164
164
  });
165
165
 
166
- // TODO(burdon): Create concrete class that implements SyncModel?
166
+ const field = 'content';
167
+ const content = this._kind === TextKind.PLAIN ? doc.getText(field) : doc.getXmlFragment(field);
167
168
  const model: EditorModel = {
168
169
  id: doc.guid,
169
- content: this._kind === TextKind.PLAIN ? doc.getText('content') : doc.getXmlFragment('content'),
170
- text: () => model.content.toString(), // TODO(burdon): Fix.
171
- provider,
170
+ text: () => content.toString(),
171
+ content,
172
+ awareness: provider.awareness,
172
173
  peer: { id },
173
174
  };
174
175
 
175
176
  this._peers.push(model);
176
-
177
177
  return model;
178
178
  };
179
179
  }
@@ -185,6 +185,5 @@ export type UseYjsModelOptions = {
185
185
  };
186
186
 
187
187
  export const useYjsModel = ({ replicator, id, doc }: UseYjsModelOptions): EditorModel => {
188
- const peer = useMemo(() => replicator.createPeer(id, doc), [doc]);
189
- return peer;
188
+ return useMemo(() => replicator.createPeer(id, doc), [doc]);
190
189
  };
@@ -1 +0,0 @@
1
- {"version":3,"file":"PatchSemaphore.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/PatchSemaphore.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI9D,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,KAAK,KAAK,EAA8E,MAAM,UAAU,CAAC;AAElH,KAAK,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/B,KAAK,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;AAE7B,KAAK,QAAQ,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,KAAK,GAAG,SAAS,CAAC;AAE3F,qBAAa,cAAc;IACzB,MAAM,EAAG,KAAK,CAAC;IACf,YAAY,UAAS;IACrB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAM;gBAEjB,KAAK,CAAC,EAAE,KAAK;IAMzB,SAAS,WAAY,UAAU,QAAQ,UAAU,UA2C/C;CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"amToCodemirror.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/amToCodemirror.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8B,KAAK,eAAe,EAAoB,MAAM,mBAAmB,CAAC;AACvG,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,IAAI,EAGV,MAAM,2BAA2B,CAAC;+BAIb,UAAU,aAAa,eAAe,UAAU,IAAI,EAAE,WAAW,KAAK,EAAE;AAA9F,wBAgBE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"codeMirrorToAm.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/codeMirrorToAm.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAa,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElF,OAAO,EAAc,KAAK,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAEnE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;+CAI5B,UAAU,gBACJ,WAAW,EAAE,SACpB,WAAW,KACjB,KAAK,GAAG,SAAS;AALpB,wBA6BE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/handle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,CAAC,MAAM,2BAA2B,CAAC;AAEpD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAAI;IAChC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACpE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAEtG,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC7D,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/plugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,WAAW,EACX,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,SAAS,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,KAAK,GAAG;IAClB,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,IAAI,EAAE,CAAC;IACb,wBAAwB,EAAE,WAAW,EAAE,CAAC;CACzC,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,UAAU,0DAAsC,CAAC;AAE9D,eAAO,MAAM,WAAW,sCAAsB,YAAY,WAAW,CAAgC,CAAC;AAEtG,eAAO,MAAM,YAAY,UAAW,WAAW,uCAAsD,CAAC;AAEtG,eAAO,MAAM,OAAO,UAAW,WAAW,mBAAiB,IAAI,EAA6B,CAAC;AAE7F,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;AAMtC,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,eAAe,WAAY,UAAU,QAAQ,IAAI,EAAE,KAAG,eA8DlE,CAAC;AAEF,eAAO,MAAM,uBAAuB,qDAA+B,CAAC;AAEpE,eAAO,MAAM,aAAa,OAAQ,WAAW,KAAG,OAAmD,CAAC;AAEpG,eAAO,MAAM,aAAa,OAAQ,eAAe,SAchD,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge.stories.d.ts","sourceRoot":"","sources":["../../../../src/automerge/automerge.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,uBAAuB,CAAC;;;;AA8E/B,wBAEE;AAEF,eAAO,MAAM,aAAa;;CAEzB,CAAC"}
@@ -1,9 +0,0 @@
1
- import type { Extension } from '@codemirror/state';
2
- import type { ThemeMode } from '@dxos/react-ui';
3
- import type { EditorModel } from './useTextModel';
4
- /**
5
- * Replication and awareness (incl. remote selection).
6
- * https://codemirror.net/docs/ref/#collab
7
- */
8
- export declare const useCollaboration: (model: EditorModel, themeMode: ThemeMode) => Extension | undefined;
9
- //# sourceMappingURL=useCollaboration.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useCollaboration.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useCollaboration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAMnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,UAAW,WAAW,aAAa,SAAS,KAAG,SAAS,GAAG,SAuBvF,CAAC"}
@@ -1,45 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import type { Extension } from '@codemirror/state';
6
- import { useEffect, useState } from 'react';
7
- import { yCollab } from 'y-codemirror.next';
8
-
9
- import { generateName } from '@dxos/display-name';
10
- import { isDocAccessor } from '@dxos/echo-schema';
11
- import type { ThemeMode } from '@dxos/react-ui';
12
- import { getColorForValue } from '@dxos/react-ui-theme';
13
- import { YText } from '@dxos/text-model';
14
-
15
- import type { EditorModel } from './useTextModel';
16
- import { automergePlugin } from '../automerge/automerge-plugin';
17
-
18
- /**
19
- * Replication and awareness (incl. remote selection).
20
- * https://codemirror.net/docs/ref/#collab
21
- */
22
- export const useCollaboration = (model: EditorModel, themeMode: ThemeMode): Extension | undefined => {
23
- const { content, provider, peer } = model;
24
- const [extension] = useState(() => {
25
- if (content instanceof YText) {
26
- return yCollab(content, provider?.awareness);
27
- } else if (isDocAccessor(content)) {
28
- return automergePlugin(content.handle, content.path);
29
- } else {
30
- return undefined;
31
- }
32
- });
33
-
34
- useEffect(() => {
35
- if (provider && peer) {
36
- provider.awareness.setLocalStateField('user', {
37
- name: peer.name ?? generateName(peer.id),
38
- color: getColorForValue({ value: peer.id, type: 'color' }),
39
- colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
40
- });
41
- }
42
- }, [provider, peer, themeMode]);
43
-
44
- return extension;
45
- };