@sutrata/editor 0.1.2 → 0.3.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.
Files changed (125) hide show
  1. package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
  2. package/dist/ai/VoiceConfirmDialog.js +6 -6
  3. package/dist/ai/VoiceToolbar.d.ts +1 -0
  4. package/dist/ai/VoiceToolbar.js +22 -16
  5. package/dist/ai/clean-response.d.ts +7 -0
  6. package/dist/ai/clean-response.js +58 -0
  7. package/dist/ai/format-transcript.d.ts +3 -0
  8. package/dist/ai/format-transcript.js +7 -0
  9. package/dist/ai/prompts.d.ts +8 -1
  10. package/dist/ai/prompts.js +34 -7
  11. package/dist/autocomplete/suggestions.js +1 -1
  12. package/dist/context/DocumentContext.d.ts +34 -5
  13. package/dist/context/DocumentContext.js +152 -51
  14. package/dist/editor/EditorView.js +35 -5
  15. package/dist/editor/SelectionMenu.js +5 -3
  16. package/dist/editor/editor-bus.d.ts +3 -0
  17. package/dist/editor/editor-bus.js +13 -0
  18. package/dist/editor/external-change.d.ts +11 -0
  19. package/dist/editor/external-change.js +105 -0
  20. package/dist/editor/external-decorations-plugin.d.ts +23 -0
  21. package/dist/editor/external-decorations-plugin.js +151 -0
  22. package/dist/editor/frontmatter-field-view.js +3 -0
  23. package/dist/editor/omitted-scene-plugin.d.ts +9 -0
  24. package/dist/editor/omitted-scene-plugin.js +44 -0
  25. package/dist/editor/plugins.js +4 -0
  26. package/dist/editor/private-metadata-plugin.d.ts +13 -0
  27. package/dist/editor/private-metadata-plugin.js +100 -0
  28. package/dist/editor/prosemirror-to-sutra.js +179 -74
  29. package/dist/editor/scene-heading-view.js +2 -0
  30. package/dist/editor/schema.js +74 -13
  31. package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
  32. package/dist/editor/sutra-to-prosemirror.js +32 -34
  33. package/dist/extensions/ai-provider.d.ts +44 -0
  34. package/dist/extensions/ai-provider.js +27 -0
  35. package/dist/extensions/collab.d.ts +21 -0
  36. package/dist/extensions/collab.js +6 -0
  37. package/dist/extensions/command-registry.d.ts +49 -0
  38. package/dist/extensions/command-registry.js +28 -0
  39. package/dist/extensions/decorations.d.ts +48 -0
  40. package/dist/extensions/decorations.js +6 -0
  41. package/dist/extensions/export-registry.d.ts +59 -0
  42. package/dist/extensions/export-registry.js +10 -0
  43. package/dist/extensions/panel-registry.d.ts +30 -0
  44. package/dist/extensions/panel-registry.js +16 -0
  45. package/dist/extensions/registries.d.ts +15 -0
  46. package/dist/extensions/registries.js +12 -0
  47. package/dist/extensions/registry-store.d.ts +26 -0
  48. package/dist/extensions/registry-store.js +35 -0
  49. package/dist/extensions/session.d.ts +32 -0
  50. package/dist/extensions/session.js +20 -0
  51. package/dist/extensions/speech-provider.d.ts +35 -0
  52. package/dist/extensions/speech-provider.js +17 -0
  53. package/dist/extensions/storage-adapter.d.ts +36 -1
  54. package/dist/file/builtin-exporters.d.ts +9 -0
  55. package/dist/file/builtin-exporters.js +72 -0
  56. package/dist/file/docx-exporter.js +7 -4
  57. package/dist/file/docx-importer.d.ts +9 -0
  58. package/dist/file/docx-importer.js +47 -19
  59. package/dist/file/scene-number.d.ts +5 -0
  60. package/dist/file/scene-number.js +8 -0
  61. package/dist/file/workflow-reports.js +13 -7
  62. package/dist/findreplace/FindReplace.js +5 -2
  63. package/dist/import/AIImportDialog.d.ts +11 -0
  64. package/dist/import/AIImportDialog.js +112 -0
  65. package/dist/import/ai-import.d.ts +51 -0
  66. package/dist/import/ai-import.js +200 -0
  67. package/dist/import/apply-import.d.ts +7 -0
  68. package/dist/import/apply-import.js +35 -0
  69. package/dist/import/extract.d.ts +17 -0
  70. package/dist/import/extract.js +18 -0
  71. package/dist/index.d.ts +18 -3
  72. package/dist/index.js +7 -0
  73. package/dist/locales/bn.json +8 -0
  74. package/dist/locales/en.json +8 -0
  75. package/dist/locales/gu.json +8 -0
  76. package/dist/locales/hi.json +8 -0
  77. package/dist/locales/kn.json +8 -0
  78. package/dist/locales/ml.json +8 -0
  79. package/dist/locales/mr.json +8 -0
  80. package/dist/locales/or.json +8 -0
  81. package/dist/locales/pa.json +8 -0
  82. package/dist/locales/si.json +8 -0
  83. package/dist/locales/ta.json +8 -0
  84. package/dist/locales/te.json +8 -0
  85. package/dist/navigator/SceneNavigator.js +125 -131
  86. package/dist/navigator/StatisticsDialog.js +5 -1
  87. package/dist/navigator/scene-edits.d.ts +18 -0
  88. package/dist/navigator/scene-edits.js +143 -0
  89. package/dist/navigator/scene-list.js +4 -4
  90. package/dist/navigator/scene-numbering.d.ts +37 -0
  91. package/dist/navigator/scene-numbering.js +88 -0
  92. package/dist/settings/SettingsDialog.js +8 -120
  93. package/dist/shell/AppBar.js +25 -4
  94. package/dist/shell/AppShell.js +18 -4
  95. package/dist/shell/ElementToolbar.d.ts +5 -0
  96. package/dist/shell/ElementToolbar.js +35 -25
  97. package/dist/shell/LanguageStatus.js +6 -0
  98. package/dist/shell/StatusBar.js +11 -6
  99. package/dist/shell/builtin-commands.d.ts +10 -0
  100. package/dist/shell/builtin-commands.js +43 -0
  101. package/dist/shell/icons.d.ts +6 -0
  102. package/dist/shell/icons.js +6 -0
  103. package/dist/shell/use-commands.d.ts +7 -0
  104. package/dist/shell/use-commands.js +24 -0
  105. package/dist/shell/use-panel-context.d.ts +3 -0
  106. package/dist/shell/use-panel-context.js +39 -0
  107. package/dist/source/SourceView.js +39 -3
  108. package/dist/styles/screenplay.css +114 -1
  109. package/dist/titlepage/ExportDialog.d.ts +5 -0
  110. package/dist/titlepage/ExportDialog.js +59 -96
  111. package/dist/titlepage/frontmatter-form.js +1 -1
  112. package/package.json +4 -4
  113. package/dist/ai/OnboardingDialog.d.ts +0 -6
  114. package/dist/ai/OnboardingDialog.js +0 -115
  115. package/dist/ai/ai-client.d.ts +0 -71
  116. package/dist/ai/ai-client.js +0 -494
  117. package/dist/ai/generated-providers.json +0 -3696
  118. package/dist/ai/voice-service.d.ts +0 -15
  119. package/dist/ai/voice-service.js +0 -81
  120. package/dist/editor/character-view.d.ts +0 -35
  121. package/dist/editor/character-view.js +0 -112
  122. package/dist/editor/transliterate-input.d.ts +0 -10
  123. package/dist/editor/transliterate-input.js +0 -88
  124. package/dist/extensions/types.d.ts +0 -73
  125. package/dist/extensions/types.js +0 -12
@@ -0,0 +1,151 @@
1
+ import { Plugin, PluginKey } from 'prosemirror-state';
2
+ import { Decoration, DecorationSet } from 'prosemirror-view';
3
+ /**
4
+ * Renders DecorationProvider output (extensions/decorations.ts) in the
5
+ * formatted editor. Anchors are { sceneId, from?, to? } with offsets into the
6
+ * scene's text; this plugin maps them to document positions, recomputes on
7
+ * every document change and whenever a provider signals a change, and routes
8
+ * clicks on decorated elements to the spec's onClick (in any session mode).
9
+ */
10
+ export const externalDecorationsKey = new PluginKey('externalDecorations');
11
+ const REFRESH = 'refresh';
12
+ const CLICK_ATTR = 'data-cs-deco';
13
+ /** Top-level position range of every scene with an id, heading through the block before the next heading. */
14
+ export function sceneRanges(doc) {
15
+ const ranges = new Map();
16
+ let open = null;
17
+ doc.forEach((node, offset) => {
18
+ if (node.type.name !== 'scene_heading')
19
+ return;
20
+ if (open?.id && !ranges.has(open.id))
21
+ ranges.set(open.id, { from: open.from, to: offset });
22
+ open = { id: node.attrs['id'] ?? null, from: offset };
23
+ });
24
+ const last = open;
25
+ if (last?.id && !ranges.has(last.id))
26
+ ranges.set(last.id, { from: last.from, to: doc.content.size });
27
+ return ranges;
28
+ }
29
+ /** Scene text as anchors see it: blocks joined with '\n', line breaks as '\n'. */
30
+ export function textOf(doc, range, to = range.to) {
31
+ return doc.textBetween(range.from, to, '\n', '\n');
32
+ }
33
+ /**
34
+ * Document position for a scene-text offset. `start` bias picks the first
35
+ * position at which `offset` characters precede (so an offset at a block
36
+ * boundary lands at the start of the next block); `end` bias the last one.
37
+ */
38
+ function posAt(doc, range, offset, bias) {
39
+ // Smallest position p with at least `target` characters of scene text before it.
40
+ const smallest = (target) => {
41
+ let lo = range.from;
42
+ let hi = range.to;
43
+ while (lo < hi) {
44
+ const mid = (lo + hi) >> 1;
45
+ if (textOf(doc, range, mid).length >= target)
46
+ hi = mid;
47
+ else
48
+ lo = mid + 1;
49
+ }
50
+ return lo;
51
+ };
52
+ if (bias === 'end' || offset >= textOf(doc, range).length)
53
+ return smallest(offset);
54
+ // Just before character `offset`: one position before the point where it has been passed.
55
+ return Math.max(range.from, smallest(offset + 1) - 1);
56
+ }
57
+ /** The top-level block containing `pos`, as a node-decoration range. */
58
+ function blockAt(doc, pos) {
59
+ let found = null;
60
+ doc.forEach((node, offset) => {
61
+ if (!found && pos >= offset && pos <= offset + node.nodeSize)
62
+ found = { from: offset, to: offset + node.nodeSize };
63
+ });
64
+ return found;
65
+ }
66
+ function toDecorations(doc, specs, ranges, onClick, keyPrefix) {
67
+ const out = [];
68
+ specs.forEach((spec, i) => {
69
+ const range = ranges.get(spec.anchor.sceneId);
70
+ if (!range)
71
+ return; // unknown id, or a scene without {#id}
72
+ const clamp = (n) => Math.max(0, Math.min(n, textOf(doc, range).length));
73
+ const key = `${keyPrefix}:${i}`;
74
+ if (spec.kind === 'widget') {
75
+ const pos = spec.anchor.from === undefined ? range.from : posAt(doc, range, clamp(spec.anchor.from), 'start');
76
+ out.push(Decoration.widget(pos, () => spec.render(), { side: spec.side ?? -1, key }));
77
+ return;
78
+ }
79
+ const attrs = { ...(spec.attrs ?? {}) };
80
+ if (spec.className)
81
+ attrs['class'] = spec.className;
82
+ if (spec.onClick) {
83
+ attrs[CLICK_ATTR] = key;
84
+ onClick.set(key, spec.onClick);
85
+ }
86
+ if (spec.kind === 'node') {
87
+ const block = spec.anchor.from === undefined
88
+ ? { from: range.from, to: range.from + doc.nodeAt(range.from).nodeSize }
89
+ : blockAt(doc, posAt(doc, range, clamp(spec.anchor.from), 'start'));
90
+ if (block)
91
+ out.push(Decoration.node(block.from, block.to, attrs));
92
+ return;
93
+ }
94
+ const from = spec.anchor.from === undefined ? range.from : posAt(doc, range, clamp(spec.anchor.from), 'start');
95
+ const to = spec.anchor.to === undefined ? range.to : posAt(doc, range, clamp(spec.anchor.to), 'end');
96
+ if (to > from)
97
+ out.push(Decoration.inline(from, to, attrs));
98
+ });
99
+ return out;
100
+ }
101
+ export function createExternalDecorationsPlugin(providers, getAst) {
102
+ const onClick = new Map();
103
+ const compute = (state) => {
104
+ onClick.clear();
105
+ const ranges = sceneRanges(state.doc);
106
+ const ctx = {
107
+ ast: getAst(),
108
+ sceneText: id => {
109
+ const range = ranges.get(id);
110
+ return range ? textOf(state.doc, range) : null;
111
+ },
112
+ };
113
+ const decorations = providers.flatMap(p => {
114
+ try {
115
+ return toDecorations(state.doc, p.getDecorations(ctx), ranges, onClick, p.id);
116
+ }
117
+ catch (e) {
118
+ console.error(`DecorationProvider ${p.id} failed:`, e);
119
+ return [];
120
+ }
121
+ });
122
+ return DecorationSet.create(state.doc, decorations);
123
+ };
124
+ return new Plugin({
125
+ key: externalDecorationsKey,
126
+ state: {
127
+ init: (_, state) => compute(state),
128
+ apply: (tr, set, _old, state) => (tr.docChanged || tr.getMeta(externalDecorationsKey) === REFRESH ? compute(state) : set),
129
+ },
130
+ view(view) {
131
+ const offs = providers.map(p => p.subscribe(() => {
132
+ if (!view.isDestroyed)
133
+ view.dispatch(view.state.tr.setMeta(externalDecorationsKey, REFRESH));
134
+ }));
135
+ return { destroy: () => offs.forEach(off => off()) };
136
+ },
137
+ props: {
138
+ decorations: state => externalDecorationsKey.getState(state),
139
+ handleDOMEvents: {
140
+ click(_view, event) {
141
+ const el = event.target?.closest?.(`[${CLICK_ATTR}]`);
142
+ const handler = el && onClick.get(el.getAttribute(CLICK_ATTR) ?? '');
143
+ if (!handler)
144
+ return false;
145
+ handler(event);
146
+ return true;
147
+ },
148
+ },
149
+ },
150
+ });
151
+ }
@@ -6,6 +6,9 @@ export class FrontmatterFieldView {
6
6
  this.dom = document.createElement('div');
7
7
  this.dom.className = 'cs-fm-field';
8
8
  this.dom.dataset['key'] = node.attrs['fmKey'];
9
+ // Tool-private `x-` keys (format spec §5, §7.1) are hidden.
10
+ if (node.attrs['fmKey'].startsWith('x-'))
11
+ this.dom.dataset['private'] = 'true';
9
12
  this.contentDOM = document.createElement('span');
10
13
  this.contentDOM.className = 'cs-fm-field-content';
11
14
  this.dom.appendChild(this.contentDOM);
@@ -0,0 +1,9 @@
1
+ import { Plugin, PluginKey } from 'prosemirror-state';
2
+ import { DecorationSet } from 'prosemirror-view';
3
+ /**
4
+ * Omitted scenes (format spec §7.4: `& status: omitted`): the heading is
5
+ * struck through and followed by an OMITTED badge. The heading, number and
6
+ * any body kept in a comment stay editable.
7
+ */
8
+ export declare const omittedSceneKey: PluginKey<DecorationSet>;
9
+ export declare function createOmittedScenePlugin(): Plugin<DecorationSet>;
@@ -0,0 +1,44 @@
1
+ import { Plugin, PluginKey } from 'prosemirror-state';
2
+ import { Decoration, DecorationSet } from 'prosemirror-view';
3
+ /**
4
+ * Omitted scenes (format spec §7.4: `& status: omitted`): the heading is
5
+ * struck through and followed by an OMITTED badge. The heading, number and
6
+ * any body kept in a comment stay editable.
7
+ */
8
+ export const omittedSceneKey = new PluginKey('omittedScene');
9
+ function decorate(doc) {
10
+ const decorations = [];
11
+ let heading = null;
12
+ doc.forEach((node, pos) => {
13
+ if (node.type.name === 'scene_heading') {
14
+ heading = { pos, node };
15
+ return;
16
+ }
17
+ if (node.type.name !== 'scene_metadata') {
18
+ heading = null;
19
+ return;
20
+ }
21
+ const h = heading;
22
+ if (h && node.attrs['metaKey'] === 'status' && node.textContent.trim().toLowerCase() === 'omitted') {
23
+ decorations.push(Decoration.node(h.pos, h.pos + h.node.nodeSize, { class: 'cs-scene-omitted' }));
24
+ decorations.push(Decoration.widget(h.pos + h.node.nodeSize - 1, () => {
25
+ const badge = document.createElement('span');
26
+ badge.className = 'cs-omitted-badge';
27
+ badge.contentEditable = 'false';
28
+ badge.textContent = 'OMITTED';
29
+ return badge;
30
+ }, { side: 1, key: `omitted-${h.pos}` }));
31
+ }
32
+ });
33
+ return DecorationSet.create(doc, decorations);
34
+ }
35
+ export function createOmittedScenePlugin() {
36
+ return new Plugin({
37
+ key: omittedSceneKey,
38
+ state: {
39
+ init: (_, state) => decorate(state.doc),
40
+ apply: (tr, set) => (tr.docChanged ? decorate(tr.doc) : set),
41
+ },
42
+ props: { decorations: state => omittedSceneKey.getState(state) },
43
+ });
44
+ }
@@ -9,9 +9,12 @@ import { createAutocompletePlugin } from '../autocomplete/AutocompletePlugin';
9
9
  import { createSpellcheckPlugin } from '../spellcheck/spellcheck-plugin';
10
10
  import { createAttrPencilPlugin } from './attr-pencil-plugin';
11
11
  import { createFindHighlightPlugin } from './find-highlight-plugin';
12
+ import { createOmittedScenePlugin } from './omitted-scene-plugin';
13
+ import { createPrivateMetadataPlugin } from './private-metadata-plugin';
12
14
  export function buildPlugins() {
13
15
  return [
14
16
  history(),
17
+ createPrivateMetadataPlugin(), // before the keymaps, so Backspace/Delete can't merge hidden x- blocks
15
18
  buildKeymap(),
16
19
  keymap(baseKeymap),
17
20
  createLanguageClassPlugin(),
@@ -21,5 +24,6 @@ export function buildPlugins() {
21
24
  createSpellcheckPlugin(),
22
25
  createAttrPencilPlugin(),
23
26
  createFindHighlightPlugin(),
27
+ createOmittedScenePlugin(),
24
28
  ];
25
29
  }
@@ -0,0 +1,13 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import type { Node as PmNode } from 'prosemirror-model';
3
+ /**
4
+ * Tool-private `x-` scene metadata and frontmatter keys (format spec §5, §7.1)
5
+ * stay in the document but are hidden (`data-private`, see schema.ts). This
6
+ * plugin keeps the caret out of those hidden blocks and title-page fields and
7
+ * stops Backspace/Delete from merging text into or out of them; a range
8
+ * deletion that spans one still removes it.
9
+ */
10
+ export declare function isPrivateBlock(node: PmNode | null | undefined): boolean;
11
+ /** A hidden `x-` field of the title page. */
12
+ export declare function isPrivateField(node: PmNode | null | undefined): boolean;
13
+ export declare function createPrivateMetadataPlugin(): Plugin;
@@ -0,0 +1,100 @@
1
+ import { Plugin, Selection, TextSelection } from 'prosemirror-state';
2
+ /**
3
+ * Tool-private `x-` scene metadata and frontmatter keys (format spec §5, §7.1)
4
+ * stay in the document but are hidden (`data-private`, see schema.ts). This
5
+ * plugin keeps the caret out of those hidden blocks and title-page fields and
6
+ * stops Backspace/Delete from merging text into or out of them; a range
7
+ * deletion that spans one still removes it.
8
+ */
9
+ export function isPrivateBlock(node) {
10
+ return !!node && node.type.name === 'scene_metadata' && node.attrs['metaKey'].startsWith('x-');
11
+ }
12
+ /** A hidden `x-` field of the title page. */
13
+ export function isPrivateField(node) {
14
+ return !!node && node.type.name === 'frontmatter_field' && node.attrs['fmKey'].startsWith('x-');
15
+ }
16
+ function isHidden($pos) {
17
+ return ($pos.depth >= 1 && isPrivateBlock($pos.node(1))) || isPrivateField($pos.parent);
18
+ }
19
+ /** The nearest text selection outside hidden blocks and fields, from a hidden field at `$head`. */
20
+ function leaveField(state, $head, dir) {
21
+ for (const d of [dir, -dir]) {
22
+ let pos = d === 1 ? $head.after() : $head.before();
23
+ for (;;) {
24
+ const sel = Selection.findFrom(state.doc.resolve(pos), d, true);
25
+ if (!sel)
26
+ break;
27
+ if (!isHidden(sel.$head))
28
+ return state.tr.setSelection(sel);
29
+ pos = d === 1 ? sel.$head.after() : sel.$head.before();
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ /** Position just inside the nearest visible top-level block from `index` in `dir`, or null. */
35
+ function visibleBlockPos(doc, index, dir) {
36
+ for (let i = index; i >= 0 && i < doc.childCount; i += dir) {
37
+ if (isPrivateBlock(doc.child(i)))
38
+ continue;
39
+ let pos = 0;
40
+ for (let j = 0; j < i; j++)
41
+ pos += doc.child(j).nodeSize;
42
+ return dir === 1 ? pos + 1 : pos + doc.child(i).nodeSize - 1;
43
+ }
44
+ return null;
45
+ }
46
+ function moveOut(state, index, dir) {
47
+ const pos = visibleBlockPos(state.doc, index, dir) ?? visibleBlockPos(state.doc, index, dir === 1 ? -1 : 1);
48
+ return pos === null ? null : state.tr.setSelection(TextSelection.near(state.doc.resolve(pos), dir));
49
+ }
50
+ export function createPrivateMetadataPlugin() {
51
+ return new Plugin({
52
+ appendTransaction(trs, oldState, state) {
53
+ const { $head, empty } = state.selection;
54
+ if (!empty || !trs.some(tr => tr.selectionSet || tr.docChanged))
55
+ return null;
56
+ const dir = $head.pos < oldState.selection.head ? -1 : 1;
57
+ if (isPrivateField($head.parent))
58
+ return leaveField(state, $head, dir);
59
+ if ($head.depth < 1 || !isPrivateBlock($head.node(1)))
60
+ return null;
61
+ return moveOut(state, $head.index(0), dir);
62
+ },
63
+ props: {
64
+ handleKeyDown(view, event) {
65
+ if (event.key !== 'Backspace' && event.key !== 'Delete')
66
+ return false;
67
+ const { $head, empty } = view.state.selection;
68
+ if (!empty)
69
+ return false;
70
+ const back = event.key === 'Backspace';
71
+ if ($head.parent.type.name === 'frontmatter_field') {
72
+ // Between title-page fields: step over hidden ones instead of merging.
73
+ if (back ? $head.parentOffset !== 0 : $head.parentOffset !== $head.parent.content.size)
74
+ return false;
75
+ const page = $head.node($head.depth - 1);
76
+ const neighbour = $head.index($head.depth - 1) + (back ? -1 : 1);
77
+ if (neighbour < 0 || neighbour >= page.childCount || !isPrivateField(page.child(neighbour)))
78
+ return false;
79
+ const tr = leaveField(view.state, view.state.doc.resolve(back ? $head.before() - 1 : $head.after() + 1), back ? -1 : 1);
80
+ if (tr)
81
+ view.dispatch(tr);
82
+ return true;
83
+ }
84
+ if ($head.depth !== 1)
85
+ return false;
86
+ if (back ? $head.parentOffset !== 0 : $head.parentOffset !== $head.parent.content.size)
87
+ return false;
88
+ const index = $head.index(0);
89
+ const neighbour = back ? index - 1 : index + 1;
90
+ if (neighbour < 0 || neighbour >= view.state.doc.childCount || !isPrivateBlock(view.state.doc.child(neighbour)))
91
+ return false;
92
+ // Step over the hidden block(s) instead of merging with them.
93
+ const tr = moveOut(view.state, neighbour, back ? -1 : 1);
94
+ if (tr)
95
+ view.dispatch(tr);
96
+ return true;
97
+ },
98
+ },
99
+ });
100
+ }