pict-section-formeditor 1.0.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/docs/.nojekyll +0 -0
  4. package/docs/README.md +162 -0
  5. package/docs/_sidebar.md +23 -0
  6. package/docs/_topbar.md +5 -0
  7. package/docs/cover.md +12 -0
  8. package/docs/css/docuserve.css +73 -0
  9. package/docs/index.html +39 -0
  10. package/docs/retold-catalog.json +224 -0
  11. package/docs/retold-keyword-index.json +46846 -0
  12. package/example_applications/form_editor/.quackage.json +10 -0
  13. package/example_applications/form_editor/FormEditor-Example-Application.js +226 -0
  14. package/example_applications/form_editor/html/icon-chooser.html +375 -0
  15. package/example_applications/form_editor/html/index.html +54 -0
  16. package/example_applications/form_editor/package.json +50 -0
  17. package/package.json +55 -0
  18. package/sample_manifests/Complex-Table.json +974 -0
  19. package/sample_manifests/Distill-Example.json +200 -0
  20. package/sample_manifests/Gradebook-Assignment.json +38 -0
  21. package/sample_manifests/Gradebook-Student.json +40 -0
  22. package/sample_manifests/Manyfest-Editor.json +347 -0
  23. package/sample_manifests/Simple-Form.json +232 -0
  24. package/sample_manifests/Simple-Table.json +79 -0
  25. package/source/Pict-Section-FormEditor-DefaultConfiguration.js +3321 -0
  26. package/source/Pict-Section-FormEditor.js +35 -0
  27. package/source/providers/Pict-Provider-ChildPictManager-Application.js +40 -0
  28. package/source/providers/Pict-Provider-ChildPictManager.js +238 -0
  29. package/source/providers/Pict-Provider-FormEditorDocumentation.js +356 -0
  30. package/source/providers/Pict-Provider-FormEditorDragDrop.js +535 -0
  31. package/source/providers/Pict-Provider-FormEditorIconography.js +1002 -0
  32. package/source/providers/Pict-Provider-FormEditorManifestOps.js +1443 -0
  33. package/source/providers/Pict-Provider-FormEditorRendering.js +730 -0
  34. package/source/providers/Pict-Provider-FormEditorUtilities.js +862 -0
  35. package/source/providers/Pict-Provider-PreviewCSS.js +42 -0
  36. package/source/views/PictView-FormEditor-InlineEditing.js +309 -0
  37. package/source/views/PictView-FormEditor-InputTypePicker.js +532 -0
  38. package/source/views/PictView-FormEditor-PropertiesPanel.js +7730 -0
  39. package/source/views/PictView-FormEditor.js +681 -0
  40. package/test/Pict-Section-FormEditor_tests.js +4102 -0
  41. package/user-documentation/.pict_documentation_topics.json +695 -0
  42. package/user-documentation/Getting-Started.md +32 -0
  43. package/user-documentation/Groups.md +52 -0
  44. package/user-documentation/Inputs.md +98 -0
  45. package/user-documentation/Sections.md +36 -0
  46. package/user-documentation/Shortcuts.md +44 -0
  47. package/user-documentation/Solver-Expression-Walkthrough.md +176 -0
  48. package/user-documentation/Solver-Expressions-Advanced.md +344 -0
  49. package/user-documentation/Solver-Functions.md +213 -0
  50. package/user-documentation/Solvers.md +81 -0
  51. package/user-documentation/ToC.md +18 -0
  52. package/user-documentation/solverfunctions/abs.md +84 -0
  53. package/user-documentation/solverfunctions/aggregationhistogram.md +83 -0
  54. package/user-documentation/solverfunctions/aggregationhistogrambyobject.md +64 -0
  55. package/user-documentation/solverfunctions/arrayconcat.md +64 -0
  56. package/user-documentation/solverfunctions/avg.md +81 -0
  57. package/user-documentation/solverfunctions/bucketset.md +69 -0
  58. package/user-documentation/solverfunctions/ceil.md +70 -0
  59. package/user-documentation/solverfunctions/cleanvaluearray.md +66 -0
  60. package/user-documentation/solverfunctions/cleanvalueobject.md +68 -0
  61. package/user-documentation/solverfunctions/colorgroupbackground.md +60 -0
  62. package/user-documentation/solverfunctions/colorinputbackground.md +62 -0
  63. package/user-documentation/solverfunctions/colorinputbackgroundtabular.md +64 -0
  64. package/user-documentation/solverfunctions/colorsectionbackground.md +59 -0
  65. package/user-documentation/solverfunctions/compare.md +72 -0
  66. package/user-documentation/solverfunctions/concat.md +73 -0
  67. package/user-documentation/solverfunctions/concatraw.md +73 -0
  68. package/user-documentation/solverfunctions/cos.md +75 -0
  69. package/user-documentation/solverfunctions/count.md +73 -0
  70. package/user-documentation/solverfunctions/countset.md +65 -0
  71. package/user-documentation/solverfunctions/countsetelements.md +63 -0
  72. package/user-documentation/solverfunctions/createarrayfromabsolutevalues.md +63 -0
  73. package/user-documentation/solverfunctions/createvalueobjectbyhashes.md +69 -0
  74. package/user-documentation/solverfunctions/cumulativesummation.md +96 -0
  75. package/user-documentation/solverfunctions/dateadddays.md +79 -0
  76. package/user-documentation/solverfunctions/dateaddhours.md +74 -0
  77. package/user-documentation/solverfunctions/dateaddmilliseconds.md +65 -0
  78. package/user-documentation/solverfunctions/dateaddminutes.md +72 -0
  79. package/user-documentation/solverfunctions/dateaddmonths.md +74 -0
  80. package/user-documentation/solverfunctions/dateaddseconds.md +66 -0
  81. package/user-documentation/solverfunctions/dateaddweeks.md +73 -0
  82. package/user-documentation/solverfunctions/dateaddyears.md +74 -0
  83. package/user-documentation/solverfunctions/datedaydifference.md +84 -0
  84. package/user-documentation/solverfunctions/datefromparts.md +81 -0
  85. package/user-documentation/solverfunctions/datehourdifference.md +64 -0
  86. package/user-documentation/solverfunctions/datemathadd.md +72 -0
  87. package/user-documentation/solverfunctions/datemilliseconddifference.md +64 -0
  88. package/user-documentation/solverfunctions/dateminutedifference.md +64 -0
  89. package/user-documentation/solverfunctions/datemonthdifference.md +66 -0
  90. package/user-documentation/solverfunctions/dateseconddifference.md +64 -0
  91. package/user-documentation/solverfunctions/dateweekdifference.md +65 -0
  92. package/user-documentation/solverfunctions/dateyeardifference.md +64 -0
  93. package/user-documentation/solverfunctions/differencearrays.md +59 -0
  94. package/user-documentation/solverfunctions/disablesolverordinal.md +58 -0
  95. package/user-documentation/solverfunctions/distributionhistogram.md +96 -0
  96. package/user-documentation/solverfunctions/distributionhistogrambyobject.md +64 -0
  97. package/user-documentation/solverfunctions/enablesolverordinal.md +57 -0
  98. package/user-documentation/solverfunctions/entryinset.md +72 -0
  99. package/user-documentation/solverfunctions/euler.md +77 -0
  100. package/user-documentation/solverfunctions/exp.md +74 -0
  101. package/user-documentation/solverfunctions/findfirstvaluebyexactmatch.md +67 -0
  102. package/user-documentation/solverfunctions/findfirstvaluebystringincludes.md +67 -0
  103. package/user-documentation/solverfunctions/flatten.md +76 -0
  104. package/user-documentation/solverfunctions/floor.md +70 -0
  105. package/user-documentation/solverfunctions/gaussianelimination.md +75 -0
  106. package/user-documentation/solverfunctions/generatearrayofobjectsfromsets.md +70 -0
  107. package/user-documentation/solverfunctions/generatehtmlhexcolor.md +67 -0
  108. package/user-documentation/solverfunctions/getvalue.md +90 -0
  109. package/user-documentation/solverfunctions/getvaluearray.md +64 -0
  110. package/user-documentation/solverfunctions/getvalueobject.md +67 -0
  111. package/user-documentation/solverfunctions/hidesections.md +58 -0
  112. package/user-documentation/solverfunctions/if.md +109 -0
  113. package/user-documentation/solverfunctions/iterativeseries.md +107 -0
  114. package/user-documentation/solverfunctions/join.md +75 -0
  115. package/user-documentation/solverfunctions/joinraw.md +64 -0
  116. package/user-documentation/solverfunctions/largestinset.md +63 -0
  117. package/user-documentation/solverfunctions/leastsquares.md +66 -0
  118. package/user-documentation/solverfunctions/linest.md +58 -0
  119. package/user-documentation/solverfunctions/log.md +74 -0
  120. package/user-documentation/solverfunctions/logvalues.md +65 -0
  121. package/user-documentation/solverfunctions/match.md +71 -0
  122. package/user-documentation/solverfunctions/matrixinverse.md +67 -0
  123. package/user-documentation/solverfunctions/matrixmultiply.md +71 -0
  124. package/user-documentation/solverfunctions/matrixtranspose.md +72 -0
  125. package/user-documentation/solverfunctions/matrixvectormultiply.md +69 -0
  126. package/user-documentation/solverfunctions/max.md +73 -0
  127. package/user-documentation/solverfunctions/mean.md +63 -0
  128. package/user-documentation/solverfunctions/median.md +79 -0
  129. package/user-documentation/solverfunctions/min.md +73 -0
  130. package/user-documentation/solverfunctions/mode.md +66 -0
  131. package/user-documentation/solverfunctions/objectkeystoarray.md +66 -0
  132. package/user-documentation/solverfunctions/objectvaluessortbyexternalobjectarray.md +65 -0
  133. package/user-documentation/solverfunctions/objectvaluestoarray.md +67 -0
  134. package/user-documentation/solverfunctions/percent.md +75 -0
  135. package/user-documentation/solverfunctions/pi.md +77 -0
  136. package/user-documentation/solverfunctions/polynomialregression.md +69 -0
  137. package/user-documentation/solverfunctions/predict.md +71 -0
  138. package/user-documentation/solverfunctions/rad.md +85 -0
  139. package/user-documentation/solverfunctions/randomfloat.md +63 -0
  140. package/user-documentation/solverfunctions/randomfloatbetween.md +72 -0
  141. package/user-documentation/solverfunctions/randomfloatupto.md +65 -0
  142. package/user-documentation/solverfunctions/randominteger.md +56 -0
  143. package/user-documentation/solverfunctions/randomintegerbetween.md +72 -0
  144. package/user-documentation/solverfunctions/randomintegerupto.md +64 -0
  145. package/user-documentation/solverfunctions/refreshtabularsection.md +57 -0
  146. package/user-documentation/solverfunctions/resolvehtmlentities.md +64 -0
  147. package/user-documentation/solverfunctions/round.md +111 -0
  148. package/user-documentation/solverfunctions/runsolvers.md +49 -0
  149. package/user-documentation/solverfunctions/setconcatenate.md +64 -0
  150. package/user-documentation/solverfunctions/setgroupvisibility.md +60 -0
  151. package/user-documentation/solverfunctions/setsectionvisibility.md +59 -0
  152. package/user-documentation/solverfunctions/setsolverordinalenabled.md +59 -0
  153. package/user-documentation/solverfunctions/settabularrowlength.md +57 -0
  154. package/user-documentation/solverfunctions/setvalue.md +65 -0
  155. package/user-documentation/solverfunctions/showsections.md +58 -0
  156. package/user-documentation/solverfunctions/sin.md +83 -0
  157. package/user-documentation/solverfunctions/slice.md +80 -0
  158. package/user-documentation/solverfunctions/smallestinset.md +63 -0
  159. package/user-documentation/solverfunctions/sortarray.md +58 -0
  160. package/user-documentation/solverfunctions/sorthistogram.md +70 -0
  161. package/user-documentation/solverfunctions/sorthistogrambykeys.md +69 -0
  162. package/user-documentation/solverfunctions/sortset.md +75 -0
  163. package/user-documentation/solverfunctions/sqrt.md +85 -0
  164. package/user-documentation/solverfunctions/stdev.md +81 -0
  165. package/user-documentation/solverfunctions/stdeva.md +58 -0
  166. package/user-documentation/solverfunctions/stdevp.md +83 -0
  167. package/user-documentation/solverfunctions/stringcountsegments.md +66 -0
  168. package/user-documentation/solverfunctions/stringgetsegments.md +74 -0
  169. package/user-documentation/solverfunctions/subtractingsummation.md +66 -0
  170. package/user-documentation/solverfunctions/sum.md +78 -0
  171. package/user-documentation/solverfunctions/tan.md +78 -0
  172. package/user-documentation/solverfunctions/tofixed.md +75 -0
  173. package/user-documentation/solverfunctions/unionarrays.md +59 -0
  174. package/user-documentation/solverfunctions/uniquearray.md +58 -0
  175. package/user-documentation/solverfunctions/var.md +67 -0
  176. package/user-documentation/solverfunctions/vara.md +58 -0
  177. package/user-documentation/solverfunctions/varp.md +66 -0
  178. package/user-documentation/solverfunctions/when.md +98 -0
@@ -0,0 +1,35 @@
1
+ // Pict Section: Form Editor
2
+ // A visual editor for pict-section-form configuration manifests.
3
+
4
+ // The main form editor view class
5
+ module.exports = require('./views/PictView-FormEditor.js');
6
+
7
+ // Default configuration
8
+ module.exports.default_configuration = require('./Pict-Section-FormEditor-DefaultConfiguration.js');
9
+
10
+ // Iconography provider for SVG icons
11
+ module.exports.IconographyProvider = require('./providers/Pict-Provider-FormEditorIconography.js');
12
+
13
+ // Rendering provider for visual editor HTML generation
14
+ module.exports.RenderingProvider = require('./providers/Pict-Provider-FormEditorRendering.js');
15
+
16
+ // Manifest operations provider for CRUD operations
17
+ module.exports.ManifestOpsProvider = require('./providers/Pict-Provider-FormEditorManifestOps.js');
18
+
19
+ // Drag-and-drop provider
20
+ module.exports.DragDropProvider = require('./providers/Pict-Provider-FormEditorDragDrop.js');
21
+
22
+ // Utilities provider for string helpers, InputType definitions, selection, and stats
23
+ module.exports.UtilitiesProvider = require('./providers/Pict-Provider-FormEditorUtilities.js');
24
+
25
+ // Properties panel view
26
+ module.exports.PropertiesPanel = require('./views/PictView-FormEditor-PropertiesPanel.js');
27
+
28
+ // Inline editing view
29
+ module.exports.InlineEditing = require('./views/PictView-FormEditor-InlineEditing.js');
30
+
31
+ // Input type picker view
32
+ module.exports.InputTypePicker = require('./views/PictView-FormEditor-InputTypePicker.js');
33
+
34
+ // Documentation provider for embedded help system
35
+ module.exports.DocumentationProvider = require('./providers/Pict-Provider-FormEditorDocumentation.js');
@@ -0,0 +1,40 @@
1
+ const libPictSectionForm = require('pict-section-form');
2
+
3
+ class ChildPictApplication extends libPictSectionForm.PictFormApplication
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+
9
+ // Trying this pattern -- it seems to make the most sense.
10
+ // MainViewportViewIdentifier: 'Default-View',
11
+ // MainViewportRenderableHash: false,
12
+ // MainViewportDestinationAddress: false,
13
+ // MainViewportDefaultDataAddress: false,
14
+
15
+ this.options.AutoSolveAfterInitialize = false;
16
+ this.options.AutoRenderMainViewportViewAfterInitialize = false;
17
+ this.options.AutoRenderViewsAfterInitialize = false;
18
+ this.options.AutoLoginAfterInitialize = false;
19
+ this.options.AutoLoadDataAfterLogin = false;
20
+ }
21
+
22
+ onBeforeInitialize()
23
+ {
24
+ this.log.trace(`Initializing embedded application.`);
25
+ return super.onBeforeInitialize();
26
+ }
27
+
28
+ onAfterInitialize()
29
+ {
30
+ this.log.trace(`Finished initializing embedded application.`);
31
+ return super.onAfterInitialize();
32
+ }
33
+
34
+ onAfterRender()
35
+ {
36
+ return super.onAfterRender();
37
+ }
38
+ }
39
+
40
+ module.exports = ChildPictApplication;
@@ -0,0 +1,238 @@
1
+ const libPictProvider = require('pict-provider');
2
+
3
+ const libPict = require('pict');
4
+ const libPictCustomApplication = require('./Pict-Provider-ChildPictManager-Application.js');
5
+
6
+ class ChildPictManager extends libPictProvider
7
+ {
8
+ constructor(pFable, pOptions, pServiceHash)
9
+ {
10
+ super(pFable, pOptions, pServiceHash);
11
+
12
+ this.serviceType = 'PictProvider';
13
+
14
+ // The cache location for other instances of pict
15
+ this._PictCache = {};
16
+ }
17
+
18
+ // Check if a pict instance exists for this cache
19
+ childApplicationExists(pFormHash)
20
+ {
21
+ const tmpFormHash = this.fable.DataFormat.sanitizeObjectKey(pFormHash);
22
+
23
+ if (this._PictCache[tmpFormHash])
24
+ {
25
+ return true;
26
+ }
27
+
28
+ return false;
29
+ }
30
+
31
+ childApplication(pFormHash)
32
+ {
33
+ const tmpFormHash = this.fable.DataFormat.sanitizeObjectKey(pFormHash);
34
+
35
+ if (this.childApplicationExists(tmpFormHash))
36
+ {
37
+ return this._PictCache[tmpFormHash];
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Destroy and remove a cached child application.
45
+ * Cleans up the child pict's views, providers, and application so
46
+ * nothing leaks between successive preview loads.
47
+ *
48
+ * @param {string} pFormHash - The form hash key for the cached application
49
+ */
50
+ destroyChildApplication(pFormHash)
51
+ {
52
+ const tmpFormHash = this.fable.DataFormat.sanitizeObjectKey(pFormHash);
53
+
54
+ let tmpChildPict = this._PictCache[tmpFormHash];
55
+ if (!tmpChildPict)
56
+ {
57
+ return;
58
+ }
59
+
60
+ // Tear down all views registered on the child pict
61
+ let tmpViewKeys = Object.keys(tmpChildPict.views);
62
+ for (let i = 0; i < tmpViewKeys.length; i++)
63
+ {
64
+ try
65
+ {
66
+ let tmpView = tmpChildPict.views[tmpViewKeys[i]];
67
+ if (tmpView && typeof tmpView.destroy === 'function')
68
+ {
69
+ tmpView.destroy();
70
+ }
71
+ }
72
+ catch (pError)
73
+ {
74
+ this.log.warn('Error destroying child pict view [' + tmpViewKeys[i] + ']: ' + pError);
75
+ }
76
+ }
77
+
78
+ // Remove the application reference
79
+ if (tmpChildPict.PictApplication)
80
+ {
81
+ try
82
+ {
83
+ if (typeof tmpChildPict.PictApplication.destroy === 'function')
84
+ {
85
+ tmpChildPict.PictApplication.destroy();
86
+ }
87
+ }
88
+ catch (pError)
89
+ {
90
+ this.log.warn('Error destroying child pict application: ' + pError);
91
+ }
92
+ }
93
+
94
+ delete this._PictCache[tmpFormHash];
95
+ }
96
+
97
+ // Initialize a new child application
98
+ initializeChildApplication(pFormHash, pPictSectionFormManifest)
99
+ {
100
+ const tmpFormHash = this.fable.DataFormat.sanitizeObjectKey(pFormHash);
101
+
102
+ try
103
+ {
104
+ // Construct a new pict instance
105
+ const tmpChildPictSettings =
106
+ {
107
+ Product: `Form-${tmpFormHash}`,
108
+ ProductVersion: '1.0.0',
109
+ DefaultFormManifest: pPictSectionFormManifest
110
+ };
111
+
112
+ let tmpChildPict = new libPict(tmpChildPictSettings);
113
+
114
+ this._PictCache[tmpFormHash] = tmpChildPict;
115
+
116
+ tmpChildPict.addApplication(tmpFormHash, {}, libPictCustomApplication);
117
+
118
+ tmpChildPict.PictApplication.initializeAsync(
119
+ function (pError)
120
+ {
121
+ if (pError)
122
+ {
123
+ console.log('Error initializing the pict application: ' + pError);
124
+ }
125
+ tmpChildPict.log.info('Loading the Application and associated views.');
126
+ });
127
+
128
+ // NOTICE: This application is initializing async
129
+ return tmpChildPict;
130
+ }
131
+ catch (pError)
132
+ {
133
+ this.log.error(`Error initializing child pict application for form ${pFormHash}: ${pError}`);
134
+ return null;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Initialize a child application configured for browser rendering.
140
+ * Sets BrowserAddress so the child pict is available on the window
141
+ * and {~P~} template expressions resolve correctly.
142
+ *
143
+ * @param {string} pFormHash - Cache key for the child application
144
+ * @param {Object} pPictSectionFormManifest - The form manifest (DefaultFormManifest)
145
+ * @param {string} pBrowserAddress - The global window address (e.g. 'window._ChildPict')
146
+ * @param {string} pDestinationSelector - CSS selector for the render target (e.g. '#Preview-Container')
147
+ * @param {Function} fCallback - Called with (pError, pChildPict) when initialization completes
148
+ */
149
+ initializeRenderableChildApplication(pFormHash, pPictSectionFormManifest, pBrowserAddress, pDestinationSelector, fCallback)
150
+ {
151
+ const tmpFormHash = this.fable.DataFormat.sanitizeObjectKey(pFormHash);
152
+
153
+ try
154
+ {
155
+ // Destroy any previous instance with this hash
156
+ this.destroyChildApplication(tmpFormHash);
157
+
158
+ // Clean up previous global reference if it exists
159
+ if (typeof window !== 'undefined' && pBrowserAddress)
160
+ {
161
+ let tmpAddressParts = pBrowserAddress.split('.');
162
+ if (tmpAddressParts.length === 2 && tmpAddressParts[0] === 'window')
163
+ {
164
+ delete window[tmpAddressParts[1]];
165
+ }
166
+ }
167
+
168
+ // Construct a new pict instance with BrowserAddress
169
+ const tmpChildPictSettings =
170
+ {
171
+ Product: `FormPreview-${tmpFormHash}`,
172
+ ProductVersion: '1.0.0',
173
+ BrowserAddress: pBrowserAddress || 'window._ChildPict',
174
+ DefaultFormManifest: pPictSectionFormManifest
175
+ };
176
+
177
+ let tmpChildPict = new libPict(tmpChildPictSettings);
178
+
179
+ // Disable CSS injection on the child pict so it does not
180
+ // overwrite the parent's #PICT-CSS style element.
181
+ // The parent already has all pict-section-form CSS loaded.
182
+ if (tmpChildPict.CSSMap)
183
+ {
184
+ tmpChildPict.CSSMap.injectCSS = function() {};
185
+ }
186
+
187
+ this._PictCache[tmpFormHash] = tmpChildPict;
188
+
189
+ // Set the global reference so {~P~} expressions resolve
190
+ if (typeof window !== 'undefined' && pBrowserAddress)
191
+ {
192
+ let tmpAddressParts = pBrowserAddress.split('.');
193
+ if (tmpAddressParts.length === 2 && tmpAddressParts[0] === 'window')
194
+ {
195
+ window[tmpAddressParts[1]] = tmpChildPict;
196
+ }
197
+ }
198
+
199
+ // Configure the application to render into the target container
200
+ let tmpAppOptions =
201
+ {
202
+ MainViewportDestinationAddress: pDestinationSelector,
203
+ AutoPopulateAfterRender: true
204
+ };
205
+
206
+ tmpChildPict.addApplication(tmpFormHash, tmpAppOptions, libPictCustomApplication);
207
+
208
+ let tmpSelf = this;
209
+ tmpChildPict.PictApplication.initializeAsync(
210
+ function (pError)
211
+ {
212
+ if (pError)
213
+ {
214
+ tmpSelf.log.error('Error initializing renderable child pict application: ' + pError);
215
+ }
216
+ if (typeof fCallback === 'function')
217
+ {
218
+ fCallback(pError, tmpChildPict);
219
+ }
220
+ });
221
+
222
+ return tmpChildPict;
223
+ }
224
+ catch (pError)
225
+ {
226
+ this.log.error(`Error initializing renderable child pict application for form ${pFormHash}: ${pError}`);
227
+ if (typeof fCallback === 'function')
228
+ {
229
+ fCallback(pError, null);
230
+ }
231
+ return null;
232
+ }
233
+ }
234
+
235
+ }
236
+
237
+ module.exports = ChildPictManager;
238
+ module.exports.default_configuration = {};
@@ -0,0 +1,356 @@
1
+ const libPictProvider = require('pict-provider');
2
+
3
+ /**
4
+ * Documentation Provider for the Form Editor.
5
+ *
6
+ * Manages fetching, caching, and navigation of markdown help articles.
7
+ * Delegates markdown parsing to PictContentProvider and HTML rendering
8
+ * to PictContentView (both from pict-section-content).
9
+ *
10
+ * By default loads articles from relative file paths (e.g. docs/ToC.md).
11
+ * Can be extended to use custom REST endpoints by overriding loadArticle().
12
+ */
13
+ class FormEditorDocumentation extends libPictProvider
14
+ {
15
+ constructor(pFable, pOptions, pServiceHash)
16
+ {
17
+ super(pFable, pOptions, pServiceHash);
18
+
19
+ this.serviceType = 'PictProvider';
20
+
21
+ // Back-reference to the parent FormEditor view (set after construction)
22
+ this._ParentFormEditor = null;
23
+
24
+ // Navigation state
25
+ this._NavigationStack = [];
26
+ this._CurrentPath = null;
27
+ this._CurrentTitle = 'Help';
28
+
29
+ // Base path for documentation files (relative to the served app)
30
+ this._BasePath = 'docs/';
31
+
32
+ // Cache of parsed HTML keyed by path
33
+ this._Cache = {};
34
+
35
+ // Whether the click handler has been wired on the help body container
36
+ this._ClickHandlerAttached = false;
37
+ }
38
+
39
+ /**
40
+ * Load and display a documentation article.
41
+ *
42
+ * Fetches the markdown file, parses it via PictContentProvider, and
43
+ * renders the HTML via PictContentView.displayContent().
44
+ *
45
+ * @param {string} pPath - Path to the markdown file (e.g. 'docs/ToC.md')
46
+ * @param {boolean} pPushToStack - If true, push the current article onto the navigation stack before navigating
47
+ */
48
+ loadArticle(pPath, pPushToStack)
49
+ {
50
+ if (!pPath)
51
+ {
52
+ pPath = this._BasePath + 'ToC.md';
53
+ }
54
+
55
+ if (!this._ParentFormEditor)
56
+ {
57
+ return;
58
+ }
59
+
60
+ // Push current location onto the stack before navigating
61
+ if (pPushToStack && this._CurrentPath)
62
+ {
63
+ this._NavigationStack.push(
64
+ {
65
+ path: this._CurrentPath,
66
+ title: this._CurrentTitle
67
+ });
68
+ }
69
+
70
+ this._CurrentPath = pPath;
71
+
72
+ // If cached, render immediately
73
+ if (this._Cache[pPath])
74
+ {
75
+ this._renderArticle(this._Cache[pPath].html, this._Cache[pPath].title);
76
+ return;
77
+ }
78
+
79
+ // Show loading state
80
+ let tmpContentView = this._ParentFormEditor._HelpContentView;
81
+ if (tmpContentView && typeof tmpContentView.showLoading === 'function')
82
+ {
83
+ let tmpBodyId = 'Pict-Content-Body';
84
+ tmpContentView.showLoading('Loading article...', tmpBodyId);
85
+ }
86
+
87
+ // Fetch the markdown file
88
+ let tmpXHR = new XMLHttpRequest();
89
+ tmpXHR.open('GET', pPath, true);
90
+ tmpXHR.onreadystatechange = () =>
91
+ {
92
+ if (tmpXHR.readyState === 4)
93
+ {
94
+ if (tmpXHR.status === 200)
95
+ {
96
+ let tmpMarkdown = tmpXHR.responseText;
97
+ let tmpTitle = this._extractTitle(tmpMarkdown);
98
+ let tmpContentProvider = this._ParentFormEditor._HelpContentProvider;
99
+
100
+ if (tmpContentProvider)
101
+ {
102
+ let tmpLinkResolver = (pHref, pLinkText) =>
103
+ {
104
+ return this._linkResolver(pHref, pLinkText);
105
+ };
106
+ let tmpHTML = tmpContentProvider.parseMarkdown(tmpMarkdown, tmpLinkResolver);
107
+
108
+ // Cache the result
109
+ this._Cache[pPath] = { html: tmpHTML, title: tmpTitle };
110
+
111
+ this._renderArticle(tmpHTML, tmpTitle);
112
+ }
113
+ }
114
+ else
115
+ {
116
+ this._renderError(pPath, tmpXHR.status);
117
+ }
118
+ }
119
+ };
120
+ tmpXHR.send();
121
+ }
122
+
123
+ /**
124
+ * Navigate back to the previous article.
125
+ */
126
+ navigateBack()
127
+ {
128
+ if (this._NavigationStack.length < 1)
129
+ {
130
+ return;
131
+ }
132
+
133
+ let tmpPrevious = this._NavigationStack.pop();
134
+ this.loadArticle(tmpPrevious.path, false);
135
+ }
136
+
137
+ /**
138
+ * Navigate to the table of contents.
139
+ */
140
+ navigateHome()
141
+ {
142
+ // Clear the stack and go home
143
+ this._NavigationStack = [];
144
+ this.loadArticle(this._BasePath + 'ToC.md', false);
145
+ }
146
+
147
+ /**
148
+ * Custom link resolver for parseMarkdown().
149
+ *
150
+ * Intercepts links to .md files and converts them to in-panel navigation.
151
+ * External links open in a new tab.
152
+ *
153
+ * @param {string} pHref - The href from the markdown link
154
+ * @param {string} pLinkText - The display text of the link
155
+ * @returns {object|null} Link attributes object or null for default behavior
156
+ */
157
+ _linkResolver(pHref, pLinkText)
158
+ {
159
+ if (!pHref)
160
+ {
161
+ return null;
162
+ }
163
+
164
+ // If it's a .md file, intercept for in-panel navigation.
165
+ // Use a #help: scheme rather than javascript: to avoid the href being
166
+ // corrupted by the markdown parser's italic regex (underscores in
167
+ // variable names like _DocumentationProvider get wrapped in <em> tags).
168
+ // A click handler on the help body container intercepts these links.
169
+ if (pHref.endsWith('.md'))
170
+ {
171
+ let tmpFullPath = this._BasePath + pHref;
172
+
173
+ return {
174
+ href: '#help:' + tmpFullPath
175
+ };
176
+ }
177
+
178
+ // External links open in new tab
179
+ if (pHref.startsWith('http://') || pHref.startsWith('https://'))
180
+ {
181
+ return {
182
+ href: pHref,
183
+ target: '_blank',
184
+ rel: 'noopener noreferrer'
185
+ };
186
+ }
187
+
188
+ return null;
189
+ }
190
+
191
+ /**
192
+ * Render the parsed HTML article into the help content area.
193
+ *
194
+ * @param {string} pHTML - The parsed HTML content
195
+ * @param {string} pTitle - The article title (from first heading)
196
+ */
197
+ _renderArticle(pHTML, pTitle)
198
+ {
199
+ this._CurrentTitle = pTitle || 'Help';
200
+
201
+ let tmpContentView = this._ParentFormEditor._HelpContentView;
202
+ if (tmpContentView)
203
+ {
204
+ let tmpBodyId = 'Pict-Content-Body';
205
+ tmpContentView.displayContent(pHTML, tmpBodyId);
206
+ }
207
+
208
+ this._attachClickHandler();
209
+ this._renderBreadcrumbs();
210
+ }
211
+
212
+ /**
213
+ * Attach a delegated click handler on the help body container to
214
+ * intercept clicks on #help: links. This avoids putting JavaScript
215
+ * in href attributes (which gets corrupted by the markdown parser's
216
+ * italic regex treating underscores in variable names as emphasis).
217
+ */
218
+ _attachClickHandler()
219
+ {
220
+ if (this._ClickHandlerAttached)
221
+ {
222
+ return;
223
+ }
224
+
225
+ if (typeof document === 'undefined')
226
+ {
227
+ return;
228
+ }
229
+
230
+ let tmpHash = this._ParentFormEditor.Hash;
231
+ let tmpBody = document.getElementById(`FormEditor-Help-Body-${tmpHash}`);
232
+
233
+ if (!tmpBody)
234
+ {
235
+ return;
236
+ }
237
+
238
+ let tmpSelf = this;
239
+ tmpBody.addEventListener('click', (pEvent) =>
240
+ {
241
+ // Walk up from the click target to find an anchor with #help: href
242
+ let tmpEl = pEvent.target;
243
+ while (tmpEl && tmpEl !== tmpBody)
244
+ {
245
+ if (tmpEl.tagName === 'A' && tmpEl.getAttribute('href') && tmpEl.getAttribute('href').indexOf('#help:') === 0)
246
+ {
247
+ pEvent.preventDefault();
248
+ let tmpPath = tmpEl.getAttribute('href').substring(6); // strip '#help:'
249
+ tmpSelf.loadArticle(tmpPath, true);
250
+ return;
251
+ }
252
+ tmpEl = tmpEl.parentElement;
253
+ }
254
+ });
255
+
256
+ this._ClickHandlerAttached = true;
257
+ }
258
+
259
+ /**
260
+ * Render an error message when an article fails to load.
261
+ *
262
+ * @param {string} pPath - The path that failed to load
263
+ * @param {number} pStatus - The HTTP status code
264
+ */
265
+ _renderError(pPath, pStatus)
266
+ {
267
+ let tmpHTML = '<div style="padding: 20px; color: #8A7F72; text-align: center;">';
268
+ tmpHTML += '<p>Could not load article.</p>';
269
+ tmpHTML += `<p style="font-size: 12px; color: #B0A89E;">${pPath} (HTTP ${pStatus})</p>`;
270
+ tmpHTML += '</div>';
271
+
272
+ let tmpContentView = this._ParentFormEditor._HelpContentView;
273
+ if (tmpContentView)
274
+ {
275
+ let tmpBodyId = 'Pict-Content-Body';
276
+ tmpContentView.displayContent(tmpHTML, tmpBodyId);
277
+ }
278
+
279
+ this._renderBreadcrumbs();
280
+ }
281
+
282
+ /**
283
+ * Render the breadcrumb navigation bar.
284
+ */
285
+ _renderBreadcrumbs()
286
+ {
287
+ let tmpHash = this._ParentFormEditor.Hash;
288
+ let tmpNavEl = `#FormEditor-Help-Nav-${tmpHash}`;
289
+ let tmpViewRef = this._ParentFormEditor._browserViewRef();
290
+
291
+ let tmpHTML = '';
292
+
293
+ // Home link
294
+ tmpHTML += `<a href="javascript:void(0)" onclick="${tmpViewRef}._DocumentationProvider.navigateHome()">Help</a>`;
295
+
296
+ // Stack entries
297
+ for (let i = 0; i < this._NavigationStack.length; i++)
298
+ {
299
+ let tmpEntry = this._NavigationStack[i];
300
+ tmpHTML += '<span class="pict-fe-help-nav-sep">\u203A</span>';
301
+ tmpHTML += `<a href="javascript:void(0)" onclick="${tmpViewRef}._DocumentationProvider.loadArticle('${tmpEntry.path}', false); ${tmpViewRef}._DocumentationProvider._NavigationStack.splice(${i});">${this._escapeHTML(tmpEntry.title)}</a>`;
302
+ }
303
+
304
+ // Current article
305
+ if (this._CurrentTitle && this._CurrentTitle !== 'Help')
306
+ {
307
+ tmpHTML += '<span class="pict-fe-help-nav-sep">\u203A</span>';
308
+ tmpHTML += `<span>${this._escapeHTML(this._CurrentTitle)}</span>`;
309
+ }
310
+
311
+ this.pict.ContentAssignment.assignContent(tmpNavEl, tmpHTML);
312
+ }
313
+
314
+ /**
315
+ * Extract the title from the first heading in a markdown string.
316
+ *
317
+ * @param {string} pMarkdown - The raw markdown text
318
+ * @returns {string} The title text, or 'Help' if no heading found
319
+ */
320
+ _extractTitle(pMarkdown)
321
+ {
322
+ if (!pMarkdown)
323
+ {
324
+ return 'Help';
325
+ }
326
+
327
+ let tmpMatch = pMarkdown.match(/^#\s+(.+)$/m);
328
+ if (tmpMatch)
329
+ {
330
+ return tmpMatch[1].trim();
331
+ }
332
+
333
+ return 'Help';
334
+ }
335
+
336
+ /**
337
+ * Escape HTML special characters in a string.
338
+ *
339
+ * @param {string} pString - The string to escape
340
+ * @returns {string} The escaped string
341
+ */
342
+ _escapeHTML(pString)
343
+ {
344
+ if (typeof pString !== 'string')
345
+ {
346
+ return '';
347
+ }
348
+ return pString
349
+ .replace(/&/g, '&amp;')
350
+ .replace(/</g, '&lt;')
351
+ .replace(/>/g, '&gt;')
352
+ .replace(/"/g, '&quot;');
353
+ }
354
+ }
355
+
356
+ module.exports = FormEditorDocumentation;