retold-facto 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- package/test/model/fable-configuration.json +14 -0
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
const libPictSectionMarkdownEditor = require('pict-section-markdowneditor');
|
|
3
|
+
const libPictSectionContent = require('pict-section-content');
|
|
4
|
+
|
|
5
|
+
const _ViewConfiguration =
|
|
6
|
+
{
|
|
7
|
+
ViewIdentifier: "Facto-Full-SourceEditor",
|
|
8
|
+
|
|
9
|
+
DefaultRenderable: "Facto-Full-SourceEditor-Content",
|
|
10
|
+
DefaultDestinationAddress: "#Facto-SourceDetail-EditorContainer",
|
|
11
|
+
|
|
12
|
+
AutoRender: false,
|
|
13
|
+
|
|
14
|
+
CSS: /*css*/`
|
|
15
|
+
.facto-doc-editor-wrap {
|
|
16
|
+
background: var(--facto-bg-elevated, #1a1e2a);
|
|
17
|
+
border: 1px solid var(--facto-border-subtle, #2a2e3a);
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
padding: 1em;
|
|
20
|
+
min-height: 200px;
|
|
21
|
+
}
|
|
22
|
+
.facto-doc-toolbar {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0.75em;
|
|
26
|
+
margin-bottom: 0.75em;
|
|
27
|
+
}
|
|
28
|
+
.facto-doc-name {
|
|
29
|
+
font-size: 0.9em;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
color: var(--facto-text-heading, #eee);
|
|
32
|
+
}
|
|
33
|
+
.facto-doc-name-input {
|
|
34
|
+
font-size: 0.9em;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
color: var(--facto-text-heading, #eee);
|
|
37
|
+
background: var(--facto-bg-input, #0d1117);
|
|
38
|
+
border: 1px solid var(--facto-border, #3a3e4a);
|
|
39
|
+
border-radius: 4px;
|
|
40
|
+
padding: 0.2em 0.5em;
|
|
41
|
+
width: 250px;
|
|
42
|
+
}
|
|
43
|
+
.facto-doc-name-input:focus {
|
|
44
|
+
border-color: var(--facto-brand, #4a90d9);
|
|
45
|
+
outline: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Editor toolbar controls */
|
|
49
|
+
.facto-editor-controls {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 0.25em;
|
|
53
|
+
margin-left: auto;
|
|
54
|
+
}
|
|
55
|
+
.facto-editor-ctrl-btn {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 0.3em;
|
|
59
|
+
padding: 0.2em 0.55em;
|
|
60
|
+
font-size: 0.72em;
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
border: 1px solid var(--facto-border-subtle, #2a2e3a);
|
|
64
|
+
background: transparent;
|
|
65
|
+
color: var(--facto-text-tertiary, #888);
|
|
66
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
}
|
|
69
|
+
.facto-editor-ctrl-btn:hover {
|
|
70
|
+
border-color: var(--facto-border, #3a3e4a);
|
|
71
|
+
color: var(--facto-text-secondary, #aaa);
|
|
72
|
+
}
|
|
73
|
+
.facto-editor-ctrl-btn.active {
|
|
74
|
+
background: var(--facto-brand-a12);
|
|
75
|
+
border-color: var(--facto-brand, #4a90d9);
|
|
76
|
+
color: var(--facto-brand, #4a90d9);
|
|
77
|
+
}
|
|
78
|
+
.facto-editor-ctrl-sep {
|
|
79
|
+
width: 1px;
|
|
80
|
+
height: 16px;
|
|
81
|
+
background: var(--facto-border-subtle, #2a2e3a);
|
|
82
|
+
margin: 0 0.25em;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Settings gear & flyout */
|
|
86
|
+
.facto-settings-wrap {
|
|
87
|
+
position: relative;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
}
|
|
91
|
+
.facto-settings-gear {
|
|
92
|
+
background: transparent;
|
|
93
|
+
border: none;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
padding: 4px;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
color: var(--facto-text-tertiary, #888);
|
|
101
|
+
transition: color 0.15s;
|
|
102
|
+
}
|
|
103
|
+
.facto-settings-gear:hover,
|
|
104
|
+
.facto-settings-gear.active {
|
|
105
|
+
color: var(--facto-brand, #4a90d9);
|
|
106
|
+
}
|
|
107
|
+
.facto-settings-gear svg {
|
|
108
|
+
width: 18px;
|
|
109
|
+
height: 18px;
|
|
110
|
+
fill: currentColor;
|
|
111
|
+
}
|
|
112
|
+
.facto-settings-overlay {
|
|
113
|
+
display: none;
|
|
114
|
+
position: fixed;
|
|
115
|
+
top: 0;
|
|
116
|
+
left: 0;
|
|
117
|
+
right: 0;
|
|
118
|
+
bottom: 0;
|
|
119
|
+
z-index: 999;
|
|
120
|
+
}
|
|
121
|
+
.facto-settings-overlay.open {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
.facto-settings-flyout {
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 36px;
|
|
127
|
+
right: 0;
|
|
128
|
+
width: 260px;
|
|
129
|
+
background: var(--facto-bg-elevated, #1a1e2a);
|
|
130
|
+
border: 1px solid var(--facto-border, #3a3e4a);
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
133
|
+
z-index: 1000;
|
|
134
|
+
opacity: 0;
|
|
135
|
+
transform: translateY(-4px);
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
138
|
+
}
|
|
139
|
+
.facto-settings-flyout.open {
|
|
140
|
+
opacity: 1;
|
|
141
|
+
transform: translateY(0);
|
|
142
|
+
pointer-events: auto;
|
|
143
|
+
}
|
|
144
|
+
.facto-settings-flyout::before {
|
|
145
|
+
content: '';
|
|
146
|
+
position: absolute;
|
|
147
|
+
top: -6px;
|
|
148
|
+
right: 10px;
|
|
149
|
+
width: 10px;
|
|
150
|
+
height: 10px;
|
|
151
|
+
background: var(--facto-bg-elevated, #1a1e2a);
|
|
152
|
+
border-left: 1px solid var(--facto-border, #3a3e4a);
|
|
153
|
+
border-top: 1px solid var(--facto-border, #3a3e4a);
|
|
154
|
+
transform: rotate(45deg);
|
|
155
|
+
}
|
|
156
|
+
.facto-settings-section {
|
|
157
|
+
padding: 8px 12px;
|
|
158
|
+
}
|
|
159
|
+
.facto-settings-label {
|
|
160
|
+
font-size: 0.68rem;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
letter-spacing: 0.5px;
|
|
164
|
+
color: var(--facto-text-tertiary, #888);
|
|
165
|
+
margin-bottom: 6px;
|
|
166
|
+
}
|
|
167
|
+
.facto-settings-divider {
|
|
168
|
+
height: 1px;
|
|
169
|
+
background: var(--facto-border-subtle, #2a2e3a);
|
|
170
|
+
margin: 2px 8px;
|
|
171
|
+
}
|
|
172
|
+
.facto-settings-row {
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
justify-content: space-between;
|
|
176
|
+
gap: 8px;
|
|
177
|
+
margin-bottom: 5px;
|
|
178
|
+
}
|
|
179
|
+
.facto-settings-row:last-child {
|
|
180
|
+
margin-bottom: 0;
|
|
181
|
+
}
|
|
182
|
+
.facto-settings-checkbox {
|
|
183
|
+
width: 15px;
|
|
184
|
+
height: 15px;
|
|
185
|
+
accent-color: var(--facto-brand, #4a90d9);
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
flex-shrink: 0;
|
|
188
|
+
}
|
|
189
|
+
.facto-settings-checkbox-label {
|
|
190
|
+
font-size: 0.82rem;
|
|
191
|
+
color: var(--facto-text-secondary, #aaa);
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
user-select: none;
|
|
194
|
+
}
|
|
195
|
+
.facto-settings-select {
|
|
196
|
+
width: 130px;
|
|
197
|
+
padding: 4px 6px;
|
|
198
|
+
border: 1px solid var(--facto-border, #3a3e4a);
|
|
199
|
+
border-radius: 4px;
|
|
200
|
+
background: var(--facto-bg-input, #0d1117);
|
|
201
|
+
font-size: 0.78rem;
|
|
202
|
+
color: var(--facto-text-secondary, #aaa);
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
.facto-settings-select:disabled {
|
|
206
|
+
opacity: 0.35;
|
|
207
|
+
cursor: not-allowed;
|
|
208
|
+
}
|
|
209
|
+
.facto-settings-select-label {
|
|
210
|
+
font-size: 0.78rem;
|
|
211
|
+
color: var(--facto-text-tertiary, #888);
|
|
212
|
+
white-space: nowrap;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Override MarkdownEditor default light theme for dark theme.
|
|
216
|
+
Selectors must match or exceed library specificity. */
|
|
217
|
+
.facto-doc-editor-wrap .pict-mde {
|
|
218
|
+
background: transparent;
|
|
219
|
+
}
|
|
220
|
+
.facto-doc-editor-wrap .pict-mde-segment {
|
|
221
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
222
|
+
}
|
|
223
|
+
.facto-doc-editor-wrap .pict-mde-segment-body {
|
|
224
|
+
background: var(--facto-bg-input, #0d1117);
|
|
225
|
+
}
|
|
226
|
+
.facto-doc-editor-wrap .pict-mde-segment.pict-mde-active .pict-mde-segment-body {
|
|
227
|
+
background: var(--facto-bg-input, #0d1117);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Drag handle */
|
|
231
|
+
.facto-doc-editor-wrap .pict-mde-drag-handle {
|
|
232
|
+
background: var(--facto-border-subtle, #2a2e3a);
|
|
233
|
+
}
|
|
234
|
+
.facto-doc-editor-wrap .pict-mde-drag-handle:hover {
|
|
235
|
+
background: var(--facto-border, #3a3e4a);
|
|
236
|
+
}
|
|
237
|
+
.facto-doc-editor-wrap .pict-mde-segment.pict-mde-active .pict-mde-drag-handle {
|
|
238
|
+
background: var(--facto-brand-a25);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Left control buttons */
|
|
242
|
+
.facto-doc-editor-wrap .pict-mde-left-btn {
|
|
243
|
+
color: var(--facto-text-tertiary, #666);
|
|
244
|
+
}
|
|
245
|
+
.facto-doc-editor-wrap .pict-mde-left-btn:hover {
|
|
246
|
+
color: var(--facto-text-heading, #eee);
|
|
247
|
+
}
|
|
248
|
+
.facto-doc-editor-wrap .pict-mde-btn-remove:hover {
|
|
249
|
+
color: var(--facto-error, #dc3545);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* CodeMirror editor — selectors must include .cm-editor to beat
|
|
253
|
+
library's .pict-mde-segment-editor .cm-editor .cm-* specificity */
|
|
254
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor {
|
|
255
|
+
background: var(--facto-bg-input, #0d1117);
|
|
256
|
+
color: var(--facto-text-heading, #eee);
|
|
257
|
+
}
|
|
258
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-gutters {
|
|
259
|
+
background: var(--facto-bg-elevated, #1a1e2a);
|
|
260
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
261
|
+
color: var(--facto-text-tertiary, #666);
|
|
262
|
+
}
|
|
263
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-activeLine {
|
|
264
|
+
background: var(--facto-brand-a05);
|
|
265
|
+
}
|
|
266
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-activeLineGutter {
|
|
267
|
+
background: var(--facto-brand-a10);
|
|
268
|
+
}
|
|
269
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-cursor {
|
|
270
|
+
border-left-color: var(--facto-text-heading, #eee);
|
|
271
|
+
}
|
|
272
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-selectionBackground {
|
|
273
|
+
background: var(--facto-brand-a20) !important;
|
|
274
|
+
}
|
|
275
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-content {
|
|
276
|
+
color: var(--facto-text-heading, #eee);
|
|
277
|
+
}
|
|
278
|
+
.facto-doc-editor-wrap .pict-mde-segment-editor .cm-editor .cm-lineNumbers .cm-gutterElement {
|
|
279
|
+
color: var(--facto-text-tertiary, #555);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* Rich preview — must match library's two-class specificity */
|
|
283
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview.pict-mde-has-rich-preview {
|
|
284
|
+
background: var(--facto-bg-surface, #161a24);
|
|
285
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
286
|
+
color: var(--facto-text-secondary, #aaa);
|
|
287
|
+
}
|
|
288
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview h1,
|
|
289
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview h2,
|
|
290
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview h3,
|
|
291
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview h4 {
|
|
292
|
+
color: var(--facto-text-heading, #eee);
|
|
293
|
+
}
|
|
294
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview code {
|
|
295
|
+
background: var(--facto-brand-a10);
|
|
296
|
+
color: var(--facto-brand, #4a90d9);
|
|
297
|
+
}
|
|
298
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview pre {
|
|
299
|
+
background: var(--facto-bg-input, #0d1117);
|
|
300
|
+
border: 1px solid var(--facto-border-subtle, #2a2e3a);
|
|
301
|
+
color: var(--facto-text-heading, #eee);
|
|
302
|
+
}
|
|
303
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview a {
|
|
304
|
+
color: var(--facto-brand, #4a90d9);
|
|
305
|
+
}
|
|
306
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview blockquote {
|
|
307
|
+
border-left-color: var(--facto-brand, #4a90d9);
|
|
308
|
+
color: var(--facto-text-tertiary, #888);
|
|
309
|
+
}
|
|
310
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview table th,
|
|
311
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview table td {
|
|
312
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
313
|
+
}
|
|
314
|
+
.facto-doc-editor-wrap .pict-mde-rich-preview hr {
|
|
315
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Image preview */
|
|
319
|
+
.facto-doc-editor-wrap .pict-mde-image-preview.pict-mde-has-images {
|
|
320
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
321
|
+
}
|
|
322
|
+
.facto-doc-editor-wrap .pict-mde-image-preview-label {
|
|
323
|
+
color: var(--facto-text-tertiary, #666);
|
|
324
|
+
background: var(--facto-bg-surface, #161a24);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Add-segment button */
|
|
328
|
+
.facto-doc-editor-wrap .pict-mde-btn-add {
|
|
329
|
+
background: var(--facto-bg-surface, #161a24);
|
|
330
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
331
|
+
color: var(--facto-text-tertiary, #666);
|
|
332
|
+
}
|
|
333
|
+
.facto-doc-editor-wrap .pict-mde-btn-add:hover {
|
|
334
|
+
border-color: var(--facto-brand, #4a90d9);
|
|
335
|
+
color: var(--facto-brand, #4a90d9);
|
|
336
|
+
background: var(--facto-brand-a05);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Sidebar buttons */
|
|
340
|
+
.facto-doc-editor-wrap .pict-mde-sidebar-btn {
|
|
341
|
+
background: var(--facto-bg-surface, #161a24);
|
|
342
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
343
|
+
color: var(--facto-text-tertiary, #666);
|
|
344
|
+
}
|
|
345
|
+
.facto-doc-editor-wrap .pict-mde-sidebar-btn:hover {
|
|
346
|
+
border-color: var(--facto-brand, #4a90d9);
|
|
347
|
+
color: var(--facto-brand, #4a90d9);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Drag-over indicators */
|
|
351
|
+
.facto-doc-editor-wrap .pict-mde-segment.pict-mde-drag-over-top {
|
|
352
|
+
border-top-color: var(--facto-brand, #4a90d9);
|
|
353
|
+
}
|
|
354
|
+
.facto-doc-editor-wrap .pict-mde-segment.pict-mde-drag-over-bottom {
|
|
355
|
+
border-bottom-color: var(--facto-brand, #4a90d9);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Rendered view (full-document preview mode) */
|
|
359
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view {
|
|
360
|
+
background: var(--facto-bg-surface, #161a24);
|
|
361
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
362
|
+
color: var(--facto-text-secondary, #aaa);
|
|
363
|
+
}
|
|
364
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view h1,
|
|
365
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view h2,
|
|
366
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view h3,
|
|
367
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view h4 {
|
|
368
|
+
color: var(--facto-text-heading, #eee);
|
|
369
|
+
}
|
|
370
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view p {
|
|
371
|
+
color: var(--facto-text-secondary, #bbb);
|
|
372
|
+
}
|
|
373
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view a {
|
|
374
|
+
color: var(--facto-brand, #4a90d9);
|
|
375
|
+
}
|
|
376
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view code {
|
|
377
|
+
background: var(--facto-brand-a10);
|
|
378
|
+
color: var(--facto-brand, #4a90d9);
|
|
379
|
+
}
|
|
380
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view pre {
|
|
381
|
+
background: var(--facto-bg-input, #0d1117);
|
|
382
|
+
border: 1px solid var(--facto-border-subtle, #2a2e3a);
|
|
383
|
+
color: var(--facto-text-heading, #eee);
|
|
384
|
+
}
|
|
385
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view pre code {
|
|
386
|
+
background: transparent;
|
|
387
|
+
color: inherit;
|
|
388
|
+
}
|
|
389
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view blockquote {
|
|
390
|
+
border-left-color: var(--facto-brand, #4a90d9);
|
|
391
|
+
color: var(--facto-text-tertiary, #888);
|
|
392
|
+
}
|
|
393
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view table th,
|
|
394
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view table td {
|
|
395
|
+
border-color: var(--facto-border-subtle, #2a2e3a);
|
|
396
|
+
}
|
|
397
|
+
.facto-doc-editor-wrap .pict-mde-rendered-view img {
|
|
398
|
+
max-width: 100%;
|
|
399
|
+
height: auto;
|
|
400
|
+
border-radius: 4px;
|
|
401
|
+
}
|
|
402
|
+
`,
|
|
403
|
+
|
|
404
|
+
Templates:
|
|
405
|
+
[
|
|
406
|
+
{
|
|
407
|
+
Hash: "Facto-Full-SourceEditor-Template",
|
|
408
|
+
Template: /*html*/`
|
|
409
|
+
<div class="facto-doc-editor-wrap">
|
|
410
|
+
<div class="facto-doc-toolbar">
|
|
411
|
+
<span class="facto-doc-name" id="Facto-SourceDetail-DocName"></span>
|
|
412
|
+
<div class="facto-editor-controls" id="Facto-SourceDetail-EditorControls">
|
|
413
|
+
<button class="facto-editor-ctrl-btn active" id="Facto-EditorCtrl-Preview" title="Toggle rich previews below each segment" onclick="{~P~}.views['Facto-Full-SourceEditor'].toggleEditorPreview()">◎ Preview</button>
|
|
414
|
+
<button class="facto-editor-ctrl-btn active" id="Facto-EditorCtrl-LineNums" title="Toggle line numbers and sidebar controls" onclick="{~P~}.views['Facto-Full-SourceEditor'].toggleEditorControls()">⊞ Controls</button>
|
|
415
|
+
<div class="facto-editor-ctrl-sep"></div>
|
|
416
|
+
<button class="facto-editor-ctrl-btn" id="Facto-EditorCtrl-Rendered" title="Preview the full document as rendered markdown" onclick="{~P~}.views['Facto-Full-SourceEditor'].toggleEditorRenderedView()">▣ Full Preview</button>
|
|
417
|
+
<div class="facto-editor-ctrl-sep"></div>
|
|
418
|
+
<div class="facto-settings-wrap">
|
|
419
|
+
<button class="facto-settings-gear" id="Facto-EditorSettings-Gear" title="Editor settings"
|
|
420
|
+
onclick="{~P~}.views['Facto-Full-SourceEditor'].toggleSettingsPanel()">
|
|
421
|
+
<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.48.48 0 0 0-.48-.41h-3.84a.48.48 0 0 0-.48.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 0 0-.59.22L2.74 8.87a.48.48 0 0 0 .12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.26.41.48.41h3.84c.24 0 .44-.17.48-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1 1 12 8.4a3.6 3.6 0 0 1 0 7.2z"/></svg>
|
|
422
|
+
</button>
|
|
423
|
+
<div class="facto-settings-overlay" id="Facto-EditorSettings-Overlay"
|
|
424
|
+
onclick="{~P~}.views['Facto-Full-SourceEditor'].closeSettingsPanel()"></div>
|
|
425
|
+
<div class="facto-settings-flyout" id="Facto-EditorSettings-Flyout">
|
|
426
|
+
<div class="facto-settings-section">
|
|
427
|
+
<div class="facto-settings-label">Segmentation</div>
|
|
428
|
+
<div class="facto-settings-row">
|
|
429
|
+
<label class="facto-settings-checkbox-label"
|
|
430
|
+
for="Facto-Setting-AutoSegment">Auto Segment Markdown</label>
|
|
431
|
+
<input type="checkbox" class="facto-settings-checkbox"
|
|
432
|
+
id="Facto-Setting-AutoSegment"
|
|
433
|
+
onchange="{~P~}.views['Facto-Full-SourceEditor'].onAutoSegmentChanged(this.checked)">
|
|
434
|
+
</div>
|
|
435
|
+
<div class="facto-settings-row">
|
|
436
|
+
<span class="facto-settings-select-label">Segment Depth</span>
|
|
437
|
+
<select class="facto-settings-select"
|
|
438
|
+
id="Facto-Setting-SegmentDepth"
|
|
439
|
+
disabled
|
|
440
|
+
onchange="{~P~}.views['Facto-Full-SourceEditor'].onSegmentDepthChanged(this.value)">
|
|
441
|
+
<option value="1">Depth 1: Blocks</option>
|
|
442
|
+
<option value="2" selected>Depth 2: ##</option>
|
|
443
|
+
<option value="3">Depth 3: ###</option>
|
|
444
|
+
<option value="4">Depth 4: ####</option>
|
|
445
|
+
<option value="5">Depth 5: #####</option>
|
|
446
|
+
<option value="6">Depth 6: ######</option>
|
|
447
|
+
</select>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="facto-settings-divider"></div>
|
|
451
|
+
<div class="facto-settings-section">
|
|
452
|
+
<div class="facto-settings-label">Word Wrap</div>
|
|
453
|
+
<div class="facto-settings-row">
|
|
454
|
+
<label class="facto-settings-checkbox-label"
|
|
455
|
+
for="Facto-Setting-WordWrap">Markdown Word Wrap</label>
|
|
456
|
+
<input type="checkbox" class="facto-settings-checkbox"
|
|
457
|
+
id="Facto-Setting-WordWrap"
|
|
458
|
+
onchange="{~P~}.views['Facto-Full-SourceEditor'].onWordWrapChanged(this.checked)">
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
<button class="facto-btn facto-btn-primary facto-btn-small" onclick="{~P~}.views['Facto-Full-SourceEditor'].saveDocument()">Save</button>
|
|
465
|
+
</div>
|
|
466
|
+
<div id="Facto-SourceDetail-MarkdownEditor-Container"></div>
|
|
467
|
+
</div>
|
|
468
|
+
`
|
|
469
|
+
}
|
|
470
|
+
],
|
|
471
|
+
|
|
472
|
+
Renderables:
|
|
473
|
+
[
|
|
474
|
+
{
|
|
475
|
+
RenderableHash: "Facto-Full-SourceEditor-Content",
|
|
476
|
+
TemplateHash: "Facto-Full-SourceEditor-Template",
|
|
477
|
+
DestinationAddress: "#Facto-SourceDetail-EditorContainer",
|
|
478
|
+
RenderMethod: "replace"
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
class FactoFullSourceEditorView extends libPictView
|
|
484
|
+
{
|
|
485
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
486
|
+
{
|
|
487
|
+
super(pFable, pOptions, pServiceHash);
|
|
488
|
+
|
|
489
|
+
this._CurrentIDSource = null;
|
|
490
|
+
this._CurrentIDDoc = null;
|
|
491
|
+
this._CurrentDocName = '';
|
|
492
|
+
this._CurrentDocContent = '';
|
|
493
|
+
|
|
494
|
+
// Settings state
|
|
495
|
+
this._SettingsOpen = false;
|
|
496
|
+
this._AutoSegment = false;
|
|
497
|
+
this._AutoSegmentDepth = 2;
|
|
498
|
+
this._WordWrap = false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
onBeforeInitialize()
|
|
502
|
+
{
|
|
503
|
+
super.onBeforeInitialize();
|
|
504
|
+
|
|
505
|
+
// Register the MarkdownEditor view type if not already present
|
|
506
|
+
if (!this.fable.servicesMap.hasOwnProperty('PictViewMarkdownEditor'))
|
|
507
|
+
{
|
|
508
|
+
this.fable.addServiceType('PictViewMarkdownEditor', libPictSectionMarkdownEditor);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Register the Content provider for markdown rendering
|
|
512
|
+
if (!this.pict.providers.PictContent)
|
|
513
|
+
{
|
|
514
|
+
this.pict.addProvider('PictContent', { ProviderIdentifier: 'PictContent' }, libPictSectionContent.PictContentProvider);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Open the editor for a specific document.
|
|
522
|
+
* Called by the coordinator when switching to edit mode.
|
|
523
|
+
*/
|
|
524
|
+
openEditor(pIDSource, pIDDoc, pDocName, pDocContent)
|
|
525
|
+
{
|
|
526
|
+
this._CurrentIDSource = pIDSource;
|
|
527
|
+
this._CurrentIDDoc = pIDDoc;
|
|
528
|
+
this._CurrentDocName = pDocName || '';
|
|
529
|
+
this._CurrentDocContent = pDocContent || '';
|
|
530
|
+
|
|
531
|
+
// Segment the content for the markdown editor
|
|
532
|
+
this.pict.AppData.Facto.CurrentDocumentSegments = this._segmentMarkdownContent(this._CurrentDocContent);
|
|
533
|
+
|
|
534
|
+
this.render();
|
|
535
|
+
|
|
536
|
+
// Show editable name input
|
|
537
|
+
let tmpNameEl = document.getElementById('Facto-SourceDetail-DocName');
|
|
538
|
+
if (tmpNameEl)
|
|
539
|
+
{
|
|
540
|
+
tmpNameEl.innerHTML = '<input type="text" class="facto-doc-name-input" id="Facto-SourceDetail-DocNameInput" value="' + (this._CurrentDocName || '').replace(/"/g, '"') + '">';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this._renderMarkdownEditor();
|
|
544
|
+
this._syncEditorToolbarState();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Close the editor and marshal content back.
|
|
549
|
+
* Returns the current document content and name.
|
|
550
|
+
*/
|
|
551
|
+
closeEditor()
|
|
552
|
+
{
|
|
553
|
+
// If in rendered view mode, exit it first so MDE state is clean
|
|
554
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
555
|
+
if (tmpEditorView && tmpEditorView._renderedViewActive)
|
|
556
|
+
{
|
|
557
|
+
tmpEditorView.toggleRenderedView(false);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Marshal content from editor back to raw string
|
|
561
|
+
this._marshalEditorContent();
|
|
562
|
+
|
|
563
|
+
// Capture any name change from the input
|
|
564
|
+
let tmpNameInput = document.getElementById('Facto-SourceDetail-DocNameInput');
|
|
565
|
+
if (tmpNameInput)
|
|
566
|
+
{
|
|
567
|
+
let tmpNewName = tmpNameInput.value.trim();
|
|
568
|
+
if (tmpNewName && tmpNewName !== this._CurrentDocName)
|
|
569
|
+
{
|
|
570
|
+
this._CurrentDocName = tmpNewName;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
Content: this._CurrentDocContent,
|
|
576
|
+
Name: this._CurrentDocName
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Sync the editor toolbar toggle buttons with the MDE's current state.
|
|
582
|
+
*/
|
|
583
|
+
_syncEditorToolbarState()
|
|
584
|
+
{
|
|
585
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
586
|
+
if (!tmpEditorView) return;
|
|
587
|
+
|
|
588
|
+
let tmpPreviewBtn = document.getElementById('Facto-EditorCtrl-Preview');
|
|
589
|
+
if (tmpPreviewBtn)
|
|
590
|
+
{
|
|
591
|
+
tmpPreviewBtn.classList.toggle('active', tmpEditorView._previewsVisible);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
let tmpControlsBtn = document.getElementById('Facto-EditorCtrl-LineNums');
|
|
595
|
+
if (tmpControlsBtn)
|
|
596
|
+
{
|
|
597
|
+
tmpControlsBtn.classList.toggle('active', tmpEditorView._controlsVisible);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
let tmpRenderedBtn = document.getElementById('Facto-EditorCtrl-Rendered');
|
|
601
|
+
if (tmpRenderedBtn)
|
|
602
|
+
{
|
|
603
|
+
tmpRenderedBtn.classList.toggle('active', tmpEditorView._renderedViewActive);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// -- Editor toolbar toggles --
|
|
608
|
+
|
|
609
|
+
toggleEditorPreview()
|
|
610
|
+
{
|
|
611
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
612
|
+
if (!tmpEditorView) return;
|
|
613
|
+
|
|
614
|
+
tmpEditorView.togglePreview();
|
|
615
|
+
|
|
616
|
+
let tmpBtn = document.getElementById('Facto-EditorCtrl-Preview');
|
|
617
|
+
if (tmpBtn)
|
|
618
|
+
{
|
|
619
|
+
tmpBtn.classList.toggle('active', tmpEditorView._previewsVisible);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
toggleEditorControls()
|
|
624
|
+
{
|
|
625
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
626
|
+
if (!tmpEditorView) return;
|
|
627
|
+
|
|
628
|
+
tmpEditorView.toggleControls();
|
|
629
|
+
|
|
630
|
+
let tmpBtn = document.getElementById('Facto-EditorCtrl-LineNums');
|
|
631
|
+
if (tmpBtn)
|
|
632
|
+
{
|
|
633
|
+
tmpBtn.classList.toggle('active', tmpEditorView._controlsVisible);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
toggleEditorRenderedView()
|
|
638
|
+
{
|
|
639
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
640
|
+
if (!tmpEditorView) return;
|
|
641
|
+
|
|
642
|
+
tmpEditorView.toggleRenderedView();
|
|
643
|
+
|
|
644
|
+
let tmpBtn = document.getElementById('Facto-EditorCtrl-Rendered');
|
|
645
|
+
if (tmpBtn)
|
|
646
|
+
{
|
|
647
|
+
tmpBtn.classList.toggle('active', tmpEditorView._renderedViewActive);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// -- Settings gear flyout --
|
|
652
|
+
|
|
653
|
+
toggleSettingsPanel()
|
|
654
|
+
{
|
|
655
|
+
if (this._SettingsOpen)
|
|
656
|
+
{
|
|
657
|
+
this.closeSettingsPanel();
|
|
658
|
+
}
|
|
659
|
+
else
|
|
660
|
+
{
|
|
661
|
+
this.openSettingsPanel();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
openSettingsPanel()
|
|
666
|
+
{
|
|
667
|
+
this._SettingsOpen = true;
|
|
668
|
+
|
|
669
|
+
let tmpFlyout = document.getElementById('Facto-EditorSettings-Flyout');
|
|
670
|
+
let tmpOverlay = document.getElementById('Facto-EditorSettings-Overlay');
|
|
671
|
+
let tmpGear = document.getElementById('Facto-EditorSettings-Gear');
|
|
672
|
+
|
|
673
|
+
if (tmpFlyout) tmpFlyout.classList.add('open');
|
|
674
|
+
if (tmpOverlay) tmpOverlay.classList.add('open');
|
|
675
|
+
if (tmpGear) tmpGear.classList.add('active');
|
|
676
|
+
|
|
677
|
+
// Sync checkboxes/selects with current state
|
|
678
|
+
let tmpAutoSeg = document.getElementById('Facto-Setting-AutoSegment');
|
|
679
|
+
if (tmpAutoSeg) tmpAutoSeg.checked = this._AutoSegment;
|
|
680
|
+
|
|
681
|
+
let tmpDepth = document.getElementById('Facto-Setting-SegmentDepth');
|
|
682
|
+
if (tmpDepth)
|
|
683
|
+
{
|
|
684
|
+
tmpDepth.value = String(this._AutoSegmentDepth);
|
|
685
|
+
tmpDepth.disabled = !this._AutoSegment;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
let tmpWrap = document.getElementById('Facto-Setting-WordWrap');
|
|
689
|
+
if (tmpWrap) tmpWrap.checked = this._WordWrap;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
closeSettingsPanel()
|
|
693
|
+
{
|
|
694
|
+
this._SettingsOpen = false;
|
|
695
|
+
|
|
696
|
+
let tmpFlyout = document.getElementById('Facto-EditorSettings-Flyout');
|
|
697
|
+
let tmpOverlay = document.getElementById('Facto-EditorSettings-Overlay');
|
|
698
|
+
let tmpGear = document.getElementById('Facto-EditorSettings-Gear');
|
|
699
|
+
|
|
700
|
+
if (tmpFlyout) tmpFlyout.classList.remove('open');
|
|
701
|
+
if (tmpOverlay) tmpOverlay.classList.remove('open');
|
|
702
|
+
if (tmpGear) tmpGear.classList.remove('active');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
onAutoSegmentChanged(pChecked)
|
|
706
|
+
{
|
|
707
|
+
this._AutoSegment = pChecked;
|
|
708
|
+
|
|
709
|
+
let tmpDepthSelect = document.getElementById('Facto-Setting-SegmentDepth');
|
|
710
|
+
if (tmpDepthSelect)
|
|
711
|
+
{
|
|
712
|
+
tmpDepthSelect.disabled = !pChecked;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// If turning on, re-segment the current document and rebuild the editor
|
|
716
|
+
if (pChecked && this._CurrentDocContent)
|
|
717
|
+
{
|
|
718
|
+
this._resegmentAndRebuildEditor();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
onSegmentDepthChanged(pValue)
|
|
723
|
+
{
|
|
724
|
+
this._AutoSegmentDepth = parseInt(pValue, 10) || 2;
|
|
725
|
+
|
|
726
|
+
// Re-segment if auto-segment is active
|
|
727
|
+
if (this._AutoSegment && this._CurrentDocContent)
|
|
728
|
+
{
|
|
729
|
+
this._resegmentAndRebuildEditor();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
onWordWrapChanged(pChecked)
|
|
734
|
+
{
|
|
735
|
+
this._WordWrap = pChecked;
|
|
736
|
+
|
|
737
|
+
// Live-apply to all CodeMirror editors
|
|
738
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
739
|
+
if (tmpEditorView && tmpEditorView._segmentEditors)
|
|
740
|
+
{
|
|
741
|
+
for (let tmpKey in tmpEditorView._segmentEditors)
|
|
742
|
+
{
|
|
743
|
+
let tmpEditor = tmpEditorView._segmentEditors[tmpKey];
|
|
744
|
+
if (tmpEditor && tmpEditor.contentDOM)
|
|
745
|
+
{
|
|
746
|
+
if (pChecked)
|
|
747
|
+
{
|
|
748
|
+
tmpEditor.contentDOM.classList.add('cm-lineWrapping');
|
|
749
|
+
}
|
|
750
|
+
else
|
|
751
|
+
{
|
|
752
|
+
tmpEditor.contentDOM.classList.remove('cm-lineWrapping');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Re-segment the current document content and rebuild the editor.
|
|
761
|
+
* Called when auto-segment is toggled on or the depth changes.
|
|
762
|
+
*/
|
|
763
|
+
_resegmentAndRebuildEditor()
|
|
764
|
+
{
|
|
765
|
+
// First marshal current editor content back to the raw string
|
|
766
|
+
this._marshalEditorContent();
|
|
767
|
+
|
|
768
|
+
// Re-segment
|
|
769
|
+
let tmpSegments = this._segmentMarkdownContent(this._CurrentDocContent);
|
|
770
|
+
this.pict.AppData.Facto.CurrentDocumentSegments = tmpSegments;
|
|
771
|
+
|
|
772
|
+
// Force the editor to rebuild with the new segments
|
|
773
|
+
let tmpEditorView = this.pict.views['Facto-SourceDetail-MarkdownEditor'];
|
|
774
|
+
if (tmpEditorView)
|
|
775
|
+
{
|
|
776
|
+
tmpEditorView.marshalToView();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Segment markdown content based on the auto-segment settings.
|
|
782
|
+
*
|
|
783
|
+
* When AutoSegment is enabled, splits the content into segments
|
|
784
|
+
* at the configured heading depth.
|
|
785
|
+
*
|
|
786
|
+
* Depth 1 splits every top-level block (paragraphs, code fences,
|
|
787
|
+
* headings, etc.) into its own segment. Depth 2+ splits at the
|
|
788
|
+
* corresponding heading level, keeping everything between two
|
|
789
|
+
* headings of that level (or higher) in the same segment.
|
|
790
|
+
*
|
|
791
|
+
* @param {string} pContent - Raw markdown text
|
|
792
|
+
* @returns {Array} Array of { Content: string } segment objects
|
|
793
|
+
*/
|
|
794
|
+
_segmentMarkdownContent(pContent)
|
|
795
|
+
{
|
|
796
|
+
if (!this._AutoSegment || !pContent)
|
|
797
|
+
{
|
|
798
|
+
return [{ Content: pContent || '' }];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
let tmpDepth = this._AutoSegmentDepth;
|
|
802
|
+
|
|
803
|
+
if (tmpDepth === 1)
|
|
804
|
+
{
|
|
805
|
+
// Depth 1: every block is its own segment.
|
|
806
|
+
// Split on blank lines, preserving fenced code blocks.
|
|
807
|
+
let tmpLines = pContent.split('\n');
|
|
808
|
+
let tmpSegments = [];
|
|
809
|
+
let tmpCurrent = [];
|
|
810
|
+
let tmpInFence = false;
|
|
811
|
+
|
|
812
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
813
|
+
{
|
|
814
|
+
let tmpLine = tmpLines[i];
|
|
815
|
+
|
|
816
|
+
if (/^(`{3,}|~{3,})/.test(tmpLine.trim()))
|
|
817
|
+
{
|
|
818
|
+
tmpInFence = !tmpInFence;
|
|
819
|
+
tmpCurrent.push(tmpLine);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (tmpInFence)
|
|
824
|
+
{
|
|
825
|
+
tmpCurrent.push(tmpLine);
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (tmpLine.trim() === '')
|
|
830
|
+
{
|
|
831
|
+
if (tmpCurrent.length > 0)
|
|
832
|
+
{
|
|
833
|
+
tmpSegments.push({ Content: tmpCurrent.join('\n') });
|
|
834
|
+
tmpCurrent = [];
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
tmpCurrent.push(tmpLine);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (tmpCurrent.length > 0)
|
|
843
|
+
{
|
|
844
|
+
tmpSegments.push({ Content: tmpCurrent.join('\n') });
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return tmpSegments.length > 0 ? tmpSegments : [{ Content: '' }];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Depth 2+: split at headings of that level or higher.
|
|
851
|
+
let tmpHeadingPattern = new RegExp('^(#{1,' + tmpDepth + '})\\s');
|
|
852
|
+
let tmpLines = pContent.split('\n');
|
|
853
|
+
let tmpSegments = [];
|
|
854
|
+
let tmpCurrent = [];
|
|
855
|
+
|
|
856
|
+
for (let i = 0; i < tmpLines.length; i++)
|
|
857
|
+
{
|
|
858
|
+
let tmpLine = tmpLines[i];
|
|
859
|
+
|
|
860
|
+
if (tmpHeadingPattern.test(tmpLine.trim()) && tmpCurrent.length > 0)
|
|
861
|
+
{
|
|
862
|
+
tmpSegments.push({ Content: tmpCurrent.join('\n') });
|
|
863
|
+
tmpCurrent = [];
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
tmpCurrent.push(tmpLine);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (tmpCurrent.length > 0)
|
|
870
|
+
{
|
|
871
|
+
tmpSegments.push({ Content: tmpCurrent.join('\n') });
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return tmpSegments.length > 0 ? tmpSegments : [{ Content: '' }];
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Marshal content out of the markdown editor and back into _CurrentDocContent.
|
|
879
|
+
*/
|
|
880
|
+
_marshalEditorContent()
|
|
881
|
+
{
|
|
882
|
+
let tmpViewHash = 'Facto-SourceDetail-MarkdownEditor';
|
|
883
|
+
let tmpEditorView = this.pict.views[tmpViewHash];
|
|
884
|
+
if (!tmpEditorView) return;
|
|
885
|
+
|
|
886
|
+
tmpEditorView.marshalFromView();
|
|
887
|
+
|
|
888
|
+
let tmpSegments = this.pict.AppData.Facto.CurrentDocumentSegments || [];
|
|
889
|
+
let tmpParts = [];
|
|
890
|
+
for (let i = 0; i < tmpSegments.length; i++)
|
|
891
|
+
{
|
|
892
|
+
if (tmpSegments[i] && tmpSegments[i].Content)
|
|
893
|
+
{
|
|
894
|
+
tmpParts.push(tmpSegments[i].Content);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
this._CurrentDocContent = tmpParts.join('\n\n');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
_renderMarkdownEditor()
|
|
901
|
+
{
|
|
902
|
+
let tmpViewHash = 'Facto-SourceDetail-MarkdownEditor';
|
|
903
|
+
let tmpContainerId = 'Facto-SourceDetail-MarkdownEditor-Container';
|
|
904
|
+
|
|
905
|
+
// If editor view already exists, re-render it with new data
|
|
906
|
+
if (this.pict.views[tmpViewHash])
|
|
907
|
+
{
|
|
908
|
+
this.pict.views[tmpViewHash].marshalToView();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Create a new MarkdownEditor view instance
|
|
913
|
+
let tmpEditorConfig =
|
|
914
|
+
{
|
|
915
|
+
ViewIdentifier: tmpViewHash,
|
|
916
|
+
DefaultDestinationAddress: '#' + tmpContainerId,
|
|
917
|
+
TargetElementAddress: '#' + tmpContainerId,
|
|
918
|
+
ContentDataAddress: 'AppData.Facto.CurrentDocumentSegments',
|
|
919
|
+
ReadOnly: false,
|
|
920
|
+
EnableRichPreview: true,
|
|
921
|
+
ImageBaseURL: '',
|
|
922
|
+
Renderables:
|
|
923
|
+
[
|
|
924
|
+
{
|
|
925
|
+
RenderableHash: tmpViewHash + '-Renderable',
|
|
926
|
+
TemplateHash: 'MarkdownEditor-Container',
|
|
927
|
+
DestinationAddress: '#' + tmpContainerId,
|
|
928
|
+
RenderMethod: 'replace'
|
|
929
|
+
}
|
|
930
|
+
]
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
this.pict.addView(tmpViewHash, tmpEditorConfig, libPictSectionMarkdownEditor);
|
|
934
|
+
|
|
935
|
+
// Must explicitly trigger initialization for dynamically added views
|
|
936
|
+
let tmpEditorView = this.pict.views[tmpViewHash];
|
|
937
|
+
|
|
938
|
+
// Override onImageUpload to upload files to the server instead of inlining base64
|
|
939
|
+
let tmpSelf = this;
|
|
940
|
+
tmpEditorView.onImageUpload = function(pFile, pSegmentIndex, fCallback)
|
|
941
|
+
{
|
|
942
|
+
let tmpReader = new FileReader();
|
|
943
|
+
tmpReader.onload = function()
|
|
944
|
+
{
|
|
945
|
+
// Strip the data:...;base64, prefix
|
|
946
|
+
let tmpBase64 = tmpReader.result.split(',')[1];
|
|
947
|
+
tmpSelf.pict.providers.Facto.uploadSourceFile(
|
|
948
|
+
tmpSelf._CurrentIDSource,
|
|
949
|
+
pFile.name,
|
|
950
|
+
pFile.type,
|
|
951
|
+
tmpBase64
|
|
952
|
+
).then(
|
|
953
|
+
function(pResponse)
|
|
954
|
+
{
|
|
955
|
+
if (pResponse && pResponse.Success && pResponse.URL)
|
|
956
|
+
{
|
|
957
|
+
fCallback(null, pResponse.URL);
|
|
958
|
+
}
|
|
959
|
+
else
|
|
960
|
+
{
|
|
961
|
+
fCallback((pResponse && pResponse.Error) || 'Upload failed');
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
};
|
|
965
|
+
tmpReader.onerror = function()
|
|
966
|
+
{
|
|
967
|
+
fCallback('Failed to read file');
|
|
968
|
+
};
|
|
969
|
+
tmpReader.readAsDataURL(pFile);
|
|
970
|
+
return true;
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
tmpEditorView.onBeforeInitialize();
|
|
974
|
+
tmpEditorView.render();
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
saveDocument()
|
|
978
|
+
{
|
|
979
|
+
if (!this._CurrentIDDoc || !this._CurrentIDSource) return;
|
|
980
|
+
|
|
981
|
+
// Marshal from editor
|
|
982
|
+
this._marshalEditorContent();
|
|
983
|
+
|
|
984
|
+
// Check if name was changed via the input
|
|
985
|
+
let tmpUpdateData = { Content: this._CurrentDocContent };
|
|
986
|
+
let tmpNameInput = document.getElementById('Facto-SourceDetail-DocNameInput');
|
|
987
|
+
if (tmpNameInput)
|
|
988
|
+
{
|
|
989
|
+
let tmpNewName = tmpNameInput.value.trim();
|
|
990
|
+
if (tmpNewName && tmpNewName !== this._CurrentDocName)
|
|
991
|
+
{
|
|
992
|
+
tmpUpdateData.Name = tmpNewName;
|
|
993
|
+
this._CurrentDocName = tmpNewName;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
let tmpProvider = this.pict.providers.Facto;
|
|
998
|
+
tmpProvider.updateSourceDocument(this._CurrentIDSource, this._CurrentIDDoc, tmpUpdateData).then(
|
|
999
|
+
(pResponse) =>
|
|
1000
|
+
{
|
|
1001
|
+
if (pResponse && pResponse.Success)
|
|
1002
|
+
{
|
|
1003
|
+
this._setEditorStatus('Saved', 'ok');
|
|
1004
|
+
|
|
1005
|
+
// Notify coordinator to refresh doc list if name changed
|
|
1006
|
+
if (tmpUpdateData.Name)
|
|
1007
|
+
{
|
|
1008
|
+
let tmpCoordinator = this.pict.views['Facto-Full-SourceDetail'];
|
|
1009
|
+
if (tmpCoordinator)
|
|
1010
|
+
{
|
|
1011
|
+
tmpCoordinator.onDocumentNameChanged(this._CurrentIDDoc, tmpUpdateData.Name);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
else
|
|
1016
|
+
{
|
|
1017
|
+
this._setEditorStatus('Error saving: ' + ((pResponse && pResponse.Error) || 'Unknown'), 'error');
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
_setEditorStatus(pMessage, pType)
|
|
1023
|
+
{
|
|
1024
|
+
let tmpNameEl = document.getElementById('Facto-SourceDetail-DocName');
|
|
1025
|
+
if (tmpNameEl)
|
|
1026
|
+
{
|
|
1027
|
+
let tmpOriginal = this._CurrentDocName;
|
|
1028
|
+
tmpNameEl.textContent = tmpOriginal + ' \u2014 ' + pMessage;
|
|
1029
|
+
setTimeout(() => { if (tmpNameEl) tmpNameEl.textContent = tmpOriginal; }, 2000);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
module.exports = FactoFullSourceEditorView;
|
|
1035
|
+
|
|
1036
|
+
module.exports.default_configuration = _ViewConfiguration;
|