pict-section-form 1.1.0 → 1.1.2
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/docs/_cover.md +3 -0
- package/docs/_sidebar.md +5 -0
- package/docs/_version.json +3 -3
- package/docs/examples/README.md +3 -0
- package/docs/examples/change_tracking/change_tracking.js +9437 -9427
- package/docs/examples/diagram_form/README.md +116 -0
- package/docs/examples/diagram_form/diagram_form_example.min.js +42 -0
- package/docs/examples/diagram_form/excalidraw-iframe-host.html +25 -0
- package/docs/examples/diagram_form/excalidraw-wrapper.css +1 -0
- package/docs/examples/diagram_form/excalidraw-wrapper.min.js +3327 -0
- package/docs/examples/diagram_form/index.html +116 -0
- package/docs/examples/diagram_form/manifest.json +13 -0
- package/docs/examples/diagram_form/react-vendor.min.js +9 -0
- package/docs/examples/dynamic_analysis/dynamic_analysis_application.js +6549 -6550
- package/docs/examples/gradebook/gradebook_application.min.js +2 -2
- package/docs/examples/ndt_field_test/ndt_field_test.js +9437 -9427
- package/docs/examples/richtext_form/README.md +132 -0
- package/docs/examples/richtext_form/index.html +82 -0
- package/docs/examples/richtext_form/richtext_form_example.min.js +42 -0
- package/docs/examples/scope_mathematics/scope_mathematics.js +9437 -9427
- package/docs/examples/simple_table/simple_tabular_application.min.js +2 -2
- package/docs/examples/superhero_studio/README.md +199 -0
- package/docs/examples/superhero_studio/excalidraw-iframe-host.html +25 -0
- package/docs/examples/superhero_studio/excalidraw-wrapper.css +1 -0
- package/docs/examples/superhero_studio/excalidraw-wrapper.min.js +3327 -0
- package/docs/examples/superhero_studio/index.html +166 -0
- package/docs/examples/superhero_studio/manifest.json +13 -0
- package/docs/examples/superhero_studio/react-vendor.min.js +9 -0
- package/docs/examples/superhero_studio/superhero_studio_example.min.js +42 -0
- package/docs/index.html +2 -2
- package/docs/retold-catalog.json +45 -1
- package/docs/retold-keyword-index.json +11843 -7677
- package/example_applications/diagram_form/DiagramForm-Example-Application.js +145 -0
- package/example_applications/diagram_form/html/index.html +116 -0
- package/example_applications/diagram_form/package.json +29 -0
- package/example_applications/richtext_form/RichTextForm-Example-Application.js +176 -0
- package/example_applications/richtext_form/html/index.html +82 -0
- package/example_applications/richtext_form/package.json +28 -0
- package/example_applications/superhero_studio/SuperheroStudio-Example-Application.js +313 -0
- package/example_applications/superhero_studio/html/index.html +166 -0
- package/example_applications/superhero_studio/package.json +29 -0
- package/example_applications/superhero_studio/superheroes/Superheroes.js +386 -0
- package/package.json +3 -9
- package/source/providers/inputs/Pict-Provider-Input-Diagram.js +1 -31
- package/source/providers/inputs/Pict-Provider-Input-RichText.js +1 -32
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiagramForm-Example-Application.js
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the Diagram InputType from pict-section-excalidraw inside a
|
|
5
|
+
* pict-section-form. The form boots with every Diagram field in VIEW mode
|
|
6
|
+
* (inline themed SVG). A page button toggles into edit mode (Excalidraw).
|
|
7
|
+
*
|
|
8
|
+
* Theme demo:
|
|
9
|
+
*
|
|
10
|
+
* The saved SVG carries `var(--diagram-ink, #1B1F23)` etc., with hex
|
|
11
|
+
* fallbacks. A "Theme" button on the page swaps a <style> tag that defines
|
|
12
|
+
* those custom properties — the view-mode SVG visibly recolors without
|
|
13
|
+
* re-rendering anything. No editor bundle touched.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const libPictApplication = require('pict-application');
|
|
17
|
+
const libPictSectionForm = require('../../source/Pict-Section-Form.js');
|
|
18
|
+
|
|
19
|
+
const _FormDescriptors =
|
|
20
|
+
{
|
|
21
|
+
'ArchDiagram':
|
|
22
|
+
{
|
|
23
|
+
Name: 'Architecture Diagram',
|
|
24
|
+
Hash: 'ArchDiagram',
|
|
25
|
+
DataType: 'String',
|
|
26
|
+
PictForm:
|
|
27
|
+
{
|
|
28
|
+
Section: 'Diagram',
|
|
29
|
+
Row: 1,
|
|
30
|
+
Width: 12,
|
|
31
|
+
InputType: 'Diagram',
|
|
32
|
+
Diagram:
|
|
33
|
+
{
|
|
34
|
+
ThemeColors: true,
|
|
35
|
+
Height: '480px',
|
|
36
|
+
EditorImplementation: 'react'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// A minimal pre-baked SVG (notebook style, hex fallbacks already in place via
|
|
43
|
+
// themeifySVG) so the form has something to display in view mode at boot. In a
|
|
44
|
+
// real app this would come from the record being edited.
|
|
45
|
+
const _DemoSvg =
|
|
46
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 200">
|
|
47
|
+
<metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-start --> e30= <!-- payload-end --></metadata>
|
|
48
|
+
<g>
|
|
49
|
+
<rect x="20" y="40" width="100" height="50" rx="6" stroke="var(--diagram-ink, #1B1F23)" fill="var(--diagram-paper, #FDFCF7)" stroke-width="2"/>
|
|
50
|
+
<text x="70" y="72" text-anchor="middle" style="fill: var(--diagram-ink, #1B1F23); font-family: sans-serif;">Client</text>
|
|
51
|
+
|
|
52
|
+
<path d="M 120 65 L 220 65" stroke="var(--diagram-accent, #E66C2C)" fill="none" stroke-width="2" marker-end="url(#arrow)"/>
|
|
53
|
+
|
|
54
|
+
<rect x="220" y="40" width="120" height="50" rx="6" stroke="var(--diagram-ink, #1B1F23)" fill="var(--diagram-paper, #FDFCF7)" stroke-width="2"/>
|
|
55
|
+
<text x="280" y="72" text-anchor="middle" style="fill: var(--diagram-ink, #1B1F23); font-family: sans-serif;">API Server</text>
|
|
56
|
+
|
|
57
|
+
<defs>
|
|
58
|
+
<marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
|
|
59
|
+
<path d="M 0 0 L 8 4 L 0 8 z" fill="var(--diagram-accent, #E66C2C)"/>
|
|
60
|
+
</marker>
|
|
61
|
+
</defs>
|
|
62
|
+
</g>
|
|
63
|
+
</svg>`;
|
|
64
|
+
|
|
65
|
+
const _FormManifest =
|
|
66
|
+
{
|
|
67
|
+
Scope: 'DiagramDemoForm',
|
|
68
|
+
Sections:
|
|
69
|
+
[
|
|
70
|
+
{ Hash: 'Diagram', Name: 'Architecture' }
|
|
71
|
+
],
|
|
72
|
+
Descriptors: _FormDescriptors
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
class DiagramFormApplication extends libPictSectionForm.PictFormApplication
|
|
76
|
+
{
|
|
77
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
78
|
+
{
|
|
79
|
+
super(pFable, pOptions, pServiceHash);
|
|
80
|
+
|
|
81
|
+
// Register the Diagram input provider. It lives in pict-section-form
|
|
82
|
+
// and lazy-requires pict-section-excalidraw on the first edit toggle —
|
|
83
|
+
// the view-mode SVG path doesn't load the editor bundle at all.
|
|
84
|
+
let libDiagramInput = libPictSectionForm.DiagramInput;
|
|
85
|
+
this.pict.addProvider(
|
|
86
|
+
libDiagramInput.default_configuration.ProviderIdentifier,
|
|
87
|
+
libDiagramInput.default_configuration,
|
|
88
|
+
libDiagramInput
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
this.pict.AppData.DiagramDemoForm = { ArchDiagram: _DemoSvg };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onAfterInitializeAsync(fCallback)
|
|
95
|
+
{
|
|
96
|
+
if (this.pict.views.PictFormMetacontroller)
|
|
97
|
+
{
|
|
98
|
+
this.pict.views.PictFormMetacontroller.viewMarshalDestination = 'AppData.DiagramDemoForm';
|
|
99
|
+
}
|
|
100
|
+
super.onAfterInitializeAsync(() =>
|
|
101
|
+
{
|
|
102
|
+
try { this.marshalDataFromAppDataToDynamicViews(); }
|
|
103
|
+
catch (pErr) { if (this.log) this.log.warn('[diagram_form] initial marshal failed', { error: pErr.message }); }
|
|
104
|
+
return fCallback();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
demo_toggleMode(pInputHash)
|
|
109
|
+
{
|
|
110
|
+
let tmpProvider = this.pict.providers['Pict-Input-Diagram'];
|
|
111
|
+
if (!tmpProvider) return;
|
|
112
|
+
tmpProvider.toggleMode(pInputHash, (pErr) =>
|
|
113
|
+
{
|
|
114
|
+
if (pErr && this.log) this.log.warn('[diagram_form demo] toggleMode error', { error: pErr.message });
|
|
115
|
+
this._refreshModeLabel(pInputHash);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_refreshModeLabel(pInputHash)
|
|
120
|
+
{
|
|
121
|
+
let tmpProvider = this.pict.providers['Pict-Input-Diagram'];
|
|
122
|
+
if (!tmpProvider) return;
|
|
123
|
+
let tmpMode = tmpProvider.getMode(pInputHash);
|
|
124
|
+
let tmpLabel = (tmpMode === 'edit') ? 'Done' : 'Edit';
|
|
125
|
+
let tmpBtn = (typeof document !== 'undefined') ? document.getElementById('toggle-' + pInputHash) : null;
|
|
126
|
+
if (tmpBtn) tmpBtn.textContent = tmpLabel;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = DiagramFormApplication;
|
|
131
|
+
|
|
132
|
+
// Extend the parent's default_configuration so MainViewportViewIdentifier
|
|
133
|
+
// (= "PictFormMetacontroller") survives — without it the form has no
|
|
134
|
+
// auto-render target and the page comes up blank.
|
|
135
|
+
module.exports.default_configuration = Object.assign({},
|
|
136
|
+
libPictSectionForm.PictFormApplication.default_configuration,
|
|
137
|
+
{
|
|
138
|
+
Name: 'Diagram Form Example',
|
|
139
|
+
Hash: 'DiagramFormExample',
|
|
140
|
+
pict_configuration:
|
|
141
|
+
{
|
|
142
|
+
Product: 'DiagramForm-Example',
|
|
143
|
+
DefaultFormManifest: _FormManifest
|
|
144
|
+
}
|
|
145
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Diagram Form Input — Pict Example</title>
|
|
7
|
+
|
|
8
|
+
<style id="PICT-CSS"></style>
|
|
9
|
+
<style id="diagram-theme">
|
|
10
|
+
/* Theme tokens that the saved SVG resolves to. Default = notebook. */
|
|
11
|
+
:root
|
|
12
|
+
{
|
|
13
|
+
--diagram-ink: #1B1F23;
|
|
14
|
+
--diagram-paper: #FDFCF7;
|
|
15
|
+
--diagram-accent: #E66C2C;
|
|
16
|
+
--diagram-highlight: #FFD966;
|
|
17
|
+
--diagram-deemphasis: #A0A0A0;
|
|
18
|
+
--diagram-link: #2E7D74;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
<style>
|
|
22
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
23
|
+
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FAF8F4; color: #2A2520; }
|
|
24
|
+
|
|
25
|
+
.pict-example-header { display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
|
|
26
|
+
.pict-example-badge { background: #E76F51; color: #FAEDCD; padding: 0.6rem 1rem; font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; }
|
|
27
|
+
.pict-example-app-name { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; }
|
|
28
|
+
.pict-example-module { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; letter-spacing: 0.03em; align-self: center; }
|
|
29
|
+
|
|
30
|
+
.page-content { padding: 2rem; max-width: 920px; margin: 0 auto; }
|
|
31
|
+
.intro { color: #5A4A3C; font-size: 0.9rem; line-height: 1.6; margin-bottom: 24px; }
|
|
32
|
+
.intro code { background: #F0E8DA; padding: 1px 6px; border-radius: 3px; font-family: 'SFMono-Regular', Menlo, monospace; font-size: 0.85em; }
|
|
33
|
+
|
|
34
|
+
.controls { background: #FFFFFF; border: 1px solid #D4C4A8; border-radius: 6px; padding: 14px 16px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 12px; align-items: center; font-size: 0.85rem; }
|
|
35
|
+
.controls button { padding: 6px 14px; font-size: 0.85rem; font-weight: 600; color: #FAEDCD; background: #264653; border: none; border-radius: 4px; cursor: pointer; }
|
|
36
|
+
.controls button:hover { background: #E76F51; }
|
|
37
|
+
.controls .swatch { display: inline-flex; gap: 6px; align-items: center; padding: 4px 10px; background: #F4EEE2; border-radius: 4px; cursor: pointer; user-select: none; }
|
|
38
|
+
.controls .swatch::before { content: ''; display: inline-block; width: 12px; height: 12px; border-radius: 2px; border: 1px solid rgba(0,0,0,0.1); background: var(--swatch-color, #fff); }
|
|
39
|
+
|
|
40
|
+
#Pict-Form-Container { background: #FFFFFF; border: 1px solid #D4C4A8; border-radius: 6px; padding: 20px; min-height: 280px; }
|
|
41
|
+
|
|
42
|
+
.notes { margin-top: 24px; padding: 14px 16px; background: #FFF8E7; border-left: 3px solid #E9C46A; border-radius: 3px; font-size: 0.82rem; color: #5A4A3C; }
|
|
43
|
+
.notes strong { color: #2A2520; }
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<header class="pict-example-header">
|
|
48
|
+
<span class="pict-example-badge">Pict Example</span>
|
|
49
|
+
<span class="pict-example-app-name">Diagram Form Input</span>
|
|
50
|
+
<span class="pict-example-module">pict-section-excalidraw</span>
|
|
51
|
+
</header>
|
|
52
|
+
|
|
53
|
+
<main class="page-content">
|
|
54
|
+
<p class="intro">
|
|
55
|
+
A pict-section-form field whose <code>InputType</code> is <code>Diagram</code>. Boots in
|
|
56
|
+
<strong>view</strong> mode — the saved SVG is written inline, with theme-driven colors.
|
|
57
|
+
Click <em>Edit</em> to flip into Excalidraw (lazy-loads the editor bundle the first time).
|
|
58
|
+
Click a theme swatch — the view-mode SVG visibly re-colors without a re-render.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<div class="controls">
|
|
62
|
+
<button id="toggle-ArchDiagram" onclick="_Pict.PictApplication.demo_toggleMode('ArchDiagram')">Edit</button>
|
|
63
|
+
|
|
64
|
+
<span class="swatch" style="--swatch-color:#1B1F23" onclick="window.applyTheme('notebook')">Notebook</span>
|
|
65
|
+
<span class="swatch" style="--swatch-color:#EAEAEA" onclick="window.applyTheme('dark')">Dark</span>
|
|
66
|
+
<span class="swatch" style="--swatch-color:#5DA9E9" onclick="window.applyTheme('blueprint')">Blueprint</span>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div id="Pict-Form-Container"></div>
|
|
70
|
+
|
|
71
|
+
<div class="notes">
|
|
72
|
+
<strong>Verifying:</strong>
|
|
73
|
+
In view mode, change theme — the inline SVG re-colors via CSS only. In edit mode, draw
|
|
74
|
+
something + click <em>Done</em>; the new SVG is themeified on save, so it picks up the
|
|
75
|
+
same CSS variables. Inspect the hidden input's value in DevTools to see the
|
|
76
|
+
<code>var(--diagram-ink, #...)</code> rewriting in action.
|
|
77
|
+
</div>
|
|
78
|
+
</main>
|
|
79
|
+
|
|
80
|
+
<link rel="stylesheet" href="./excalidraw-wrapper.css">
|
|
81
|
+
<script src="./pict.min.js"></script>
|
|
82
|
+
<!-- Excalidraw vendor bundle (React + Excalidraw). The Diagram InputType's
|
|
83
|
+
editor mode looks for window.PictSectionExcalidrawVendor; without these
|
|
84
|
+
tags, switching the field to edit shows a "wrapper not loaded" notice. -->
|
|
85
|
+
<script src="./react-vendor.min.js"></script>
|
|
86
|
+
<script src="./excalidraw-wrapper.min.js"></script>
|
|
87
|
+
<script src="./diagram_form_example.min.js"></script>
|
|
88
|
+
<script>
|
|
89
|
+
const _Themes =
|
|
90
|
+
{
|
|
91
|
+
notebook: { ink:'#1B1F23', paper:'#FDFCF7', accent:'#E66C2C', highlight:'#FFD966', deemphasis:'#A0A0A0', link:'#2E7D74' },
|
|
92
|
+
dark: { ink:'#EAEAEA', paper:'#1B1F23', accent:'#FFB570', highlight:'#FFD966', deemphasis:'#666666', link:'#7FC4BD' },
|
|
93
|
+
blueprint: { ink:'#08296B', paper:'#E5F0FB', accent:'#5DA9E9', highlight:'#F4D35E', deemphasis:'#8AA3B6', link:'#1F77B4' }
|
|
94
|
+
};
|
|
95
|
+
window.applyTheme = function(pName)
|
|
96
|
+
{
|
|
97
|
+
let tmpStyle = document.getElementById('diagram-theme');
|
|
98
|
+
let tmpT = _Themes[pName] || _Themes.notebook;
|
|
99
|
+
tmpStyle.textContent =
|
|
100
|
+
':root { ' +
|
|
101
|
+
'--diagram-ink:' + tmpT.ink + ';' +
|
|
102
|
+
'--diagram-paper:' + tmpT.paper + ';' +
|
|
103
|
+
'--diagram-accent:' + tmpT.accent + ';' +
|
|
104
|
+
'--diagram-highlight:' + tmpT.highlight + ';' +
|
|
105
|
+
'--diagram-deemphasis:' + tmpT.deemphasis + ';' +
|
|
106
|
+
'--diagram-link:' + tmpT.link + ';' +
|
|
107
|
+
' }';
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
Pict.safeOnDocumentReady(() =>
|
|
111
|
+
{
|
|
112
|
+
Pict.safeLoadPictApplication(window.DiagramFormExample, 1);
|
|
113
|
+
});
|
|
114
|
+
</script>
|
|
115
|
+
</body>
|
|
116
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diagram_form_example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pict-section-form Diagram InputType demo — Excalidraw (edit mode) + inline themed SVG (view mode).",
|
|
5
|
+
"main": "DiagramForm-Example-Application.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node DiagramForm-Example-Application.js",
|
|
8
|
+
"build": "npx quack build && npx quack copy"
|
|
9
|
+
},
|
|
10
|
+
"author": "steven velozo <steven@velozo.com>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"devDependencies": {},
|
|
13
|
+
"copyFilesSettings": {
|
|
14
|
+
"whenFileExists": "overwrite"
|
|
15
|
+
},
|
|
16
|
+
"copyFiles": [
|
|
17
|
+
{ "from": "./html/*", "to": "./dist/" },
|
|
18
|
+
{ "from": "../../node_modules/pict/dist/*", "to": "./dist/" },
|
|
19
|
+
{ "from": "../../node_modules/pict-section-excalidraw/vendor/excalidraw-built/*", "to": "./dist/" }
|
|
20
|
+
],
|
|
21
|
+
"retold": {
|
|
22
|
+
"ExampleApplication": {
|
|
23
|
+
"Stage": true,
|
|
24
|
+
"Title": "Diagram Form Input",
|
|
25
|
+
"Summary": "A pict-section-form field whose InputType is Diagram — boots in view mode (inline SVG, theme-recolorable), toggles into edit mode (Excalidraw) on demand. Demonstrates the themeify pipeline: change the --diagram-* CSS variables and the view-mode SVG visibly re-colors without a re-render.",
|
|
26
|
+
"Complexity": "Intermediate"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RichTextForm-Example-Application.js
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the RichText InputType from pict-section-markdowneditor inside
|
|
5
|
+
* a pict-section-form. The form boots with every RichText field in VIEW mode
|
|
6
|
+
* (rendered markdown). A "Edit" button on the page calls
|
|
7
|
+
* `provider.setMode(hash, 'edit')` to flip a single field into the markdown
|
|
8
|
+
* editor; "Done" flips it back.
|
|
9
|
+
*
|
|
10
|
+
* Image handling has three live demo modes:
|
|
11
|
+
* - AllowImages=false → image paste is rejected with a toast
|
|
12
|
+
* - AllowImages=true, no uploader → base64 inline (default editor behaviour)
|
|
13
|
+
* - AllowImages=true + uploader → a stubbed `uploadImage` on the application
|
|
14
|
+
* hands back a fake CDN URL after 600ms.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const libPictApplication = require('pict-application');
|
|
18
|
+
const libPictSectionForm = require('../../source/Pict-Section-Form.js');
|
|
19
|
+
|
|
20
|
+
const _FormDescriptors =
|
|
21
|
+
{
|
|
22
|
+
'Body':
|
|
23
|
+
{
|
|
24
|
+
Name: 'Body',
|
|
25
|
+
Hash: 'Body',
|
|
26
|
+
DataType: 'String',
|
|
27
|
+
PictForm:
|
|
28
|
+
{
|
|
29
|
+
Section: 'Article',
|
|
30
|
+
Row: 1,
|
|
31
|
+
Width: 12,
|
|
32
|
+
InputType: 'RichText',
|
|
33
|
+
RichText:
|
|
34
|
+
{
|
|
35
|
+
AllowImages: true,
|
|
36
|
+
ImageUploader: 'uploadImage',
|
|
37
|
+
Height: '320px'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const _FormManifest =
|
|
44
|
+
{
|
|
45
|
+
Scope: 'RichTextDemoForm',
|
|
46
|
+
Sections:
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
Hash: 'Article',
|
|
50
|
+
Name: 'Article Body'
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
Descriptors: _FormDescriptors
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
class RichTextFormApplication extends libPictSectionForm.PictFormApplication
|
|
57
|
+
{
|
|
58
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
59
|
+
{
|
|
60
|
+
super(pFable, pOptions, pServiceHash);
|
|
61
|
+
|
|
62
|
+
// Register the RichText input provider. The provider lives in
|
|
63
|
+
// pict-section-form (where the InputType + form templates are defined);
|
|
64
|
+
// it lazy-requires pict-section-markdowneditor on the first edit toggle
|
|
65
|
+
// so a never-edited form pays nothing for CodeMirror.
|
|
66
|
+
let libRichTextInput = libPictSectionForm.RichTextInput;
|
|
67
|
+
this.pict.addProvider(
|
|
68
|
+
libRichTextInput.default_configuration.ProviderIdentifier,
|
|
69
|
+
libRichTextInput.default_configuration,
|
|
70
|
+
libRichTextInput
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Seed AppData with a record so the form has something to render in view mode.
|
|
74
|
+
this.pict.AppData.RichTextDemoForm =
|
|
75
|
+
{
|
|
76
|
+
Body: '## Welcome\n\nThis form is in **view** mode by default. Click *Edit* to swap a field into the markdown editor.\n\n- Boot-time DOM is just rendered markdown — no CodeMirror loaded.\n- First `setMode("edit")` lazy-loads the editor bundle.\n- Image uploads are routed through `uploadImage(file, descriptor, cb)`.'
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Allow-images toggle backing flag (read by uploadImage stub).
|
|
80
|
+
this._imageUploaderEnabled = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onAfterInitializeAsync(fCallback)
|
|
84
|
+
{
|
|
85
|
+
if (this.pict.views.PictFormMetacontroller)
|
|
86
|
+
{
|
|
87
|
+
this.pict.views.PictFormMetacontroller.viewMarshalDestination = 'AppData.RichTextDemoForm';
|
|
88
|
+
}
|
|
89
|
+
super.onAfterInitializeAsync(() =>
|
|
90
|
+
{
|
|
91
|
+
try { this.marshalDataFromAppDataToDynamicViews(); }
|
|
92
|
+
catch (pErr) { if (this.log) this.log.warn('[richtext_form] initial marshal failed', { error: pErr.message }); }
|
|
93
|
+
return fCallback();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Stubbed image uploader. Resolves to a fake CDN URL after a brief delay so
|
|
99
|
+
* the demo shows the upload-in-progress UX. Real apps wire this to their own
|
|
100
|
+
* storage (S3, a content-delivery API, etc.).
|
|
101
|
+
*
|
|
102
|
+
* The signature matches the pict-section-markdowneditor onImageUpload
|
|
103
|
+
* contract: return true if we'll handle it; call fCallback(err, url).
|
|
104
|
+
*/
|
|
105
|
+
uploadImage(pFile, pDescriptor, fCallback)
|
|
106
|
+
{
|
|
107
|
+
if (!this._imageUploaderEnabled)
|
|
108
|
+
{
|
|
109
|
+
// Returning false from the inner provider tells the markdown editor
|
|
110
|
+
// to fall through to base64 inline.
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setTimeout(() =>
|
|
115
|
+
{
|
|
116
|
+
let tmpFakeURL = '/uploads/' + Date.now() + '-' + (pFile.name || 'image.png').replace(/[^A-Za-z0-9._-]/g, '_');
|
|
117
|
+
fCallback(null, tmpFakeURL);
|
|
118
|
+
if (this.log) this.log.info('[richtext_form demo] uploadImage resolved', { url: tmpFakeURL });
|
|
119
|
+
}, 600);
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ----------------------------------------------------------------------------
|
|
125
|
+
// Demo control surface — invoked from inline onclick handlers in index.html.
|
|
126
|
+
// ----------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
demo_toggleMode(pInputHash)
|
|
129
|
+
{
|
|
130
|
+
let tmpProvider = this.pict.providers['Pict-Input-RichText'];
|
|
131
|
+
if (!tmpProvider) return;
|
|
132
|
+
tmpProvider.toggleMode(pInputHash, (pErr) =>
|
|
133
|
+
{
|
|
134
|
+
if (pErr && this.log) this.log.warn('[richtext_form demo] toggleMode error', { error: pErr.message });
|
|
135
|
+
this._refreshModeLabel(pInputHash);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
demo_setAllowImages(pInputHash, pAllowed)
|
|
140
|
+
{
|
|
141
|
+
let tmpDescriptor = _FormDescriptors[pInputHash];
|
|
142
|
+
if (tmpDescriptor) tmpDescriptor.PictForm.RichText.AllowImages = !!pAllowed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
demo_setUploaderEnabled(pEnabled)
|
|
146
|
+
{
|
|
147
|
+
this._imageUploaderEnabled = !!pEnabled;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_refreshModeLabel(pInputHash)
|
|
151
|
+
{
|
|
152
|
+
let tmpProvider = this.pict.providers['Pict-Input-RichText'];
|
|
153
|
+
if (!tmpProvider) return;
|
|
154
|
+
let tmpMode = tmpProvider.getMode(pInputHash);
|
|
155
|
+
let tmpLabel = (tmpMode === 'edit') ? 'Done' : 'Edit';
|
|
156
|
+
let tmpBtn = (typeof document !== 'undefined') ? document.getElementById('toggle-' + pInputHash) : null;
|
|
157
|
+
if (tmpBtn) tmpBtn.textContent = tmpLabel;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = RichTextFormApplication;
|
|
162
|
+
|
|
163
|
+
// Extend the parent's default_configuration so MainViewportViewIdentifier
|
|
164
|
+
// (= "PictFormMetacontroller") survives — without it the form has no
|
|
165
|
+
// auto-render target and the page comes up blank.
|
|
166
|
+
module.exports.default_configuration = Object.assign({},
|
|
167
|
+
libPictSectionForm.PictFormApplication.default_configuration,
|
|
168
|
+
{
|
|
169
|
+
Name: 'RichText Form Example',
|
|
170
|
+
Hash: 'RichTextFormExample',
|
|
171
|
+
pict_configuration:
|
|
172
|
+
{
|
|
173
|
+
Product: 'RichTextForm-Example',
|
|
174
|
+
DefaultFormManifest: _FormManifest
|
|
175
|
+
}
|
|
176
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>RichText Form Input — Pict Example</title>
|
|
7
|
+
|
|
8
|
+
<style id="PICT-CSS"></style>
|
|
9
|
+
<style>
|
|
10
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
11
|
+
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FAF8F4; color: #2A2520; }
|
|
12
|
+
|
|
13
|
+
.pict-example-header { display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
|
|
14
|
+
.pict-example-badge { background: #E76F51; color: #FAEDCD; padding: 0.6rem 1rem; font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; }
|
|
15
|
+
.pict-example-app-name { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; }
|
|
16
|
+
.pict-example-module { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; letter-spacing: 0.03em; align-self: center; }
|
|
17
|
+
|
|
18
|
+
.page-content { padding: 2rem; max-width: 920px; margin: 0 auto; }
|
|
19
|
+
.intro { color: #5A4A3C; font-size: 0.9rem; line-height: 1.6; margin-bottom: 24px; }
|
|
20
|
+
.intro code { background: #F0E8DA; padding: 1px 6px; border-radius: 3px; font-family: 'SFMono-Regular', Menlo, monospace; font-size: 0.85em; }
|
|
21
|
+
|
|
22
|
+
.controls { background: #FFFFFF; border: 1px solid #D4C4A8; border-radius: 6px; padding: 14px 16px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 16px; align-items: center; font-size: 0.85rem; }
|
|
23
|
+
.controls label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
24
|
+
.controls button { padding: 6px 14px; font-size: 0.85rem; font-weight: 600; color: #FAEDCD; background: #264653; border: none; border-radius: 4px; cursor: pointer; }
|
|
25
|
+
.controls button:hover { background: #E76F51; }
|
|
26
|
+
|
|
27
|
+
#Pict-Form-Container { background: #FFFFFF; border: 1px solid #D4C4A8; border-radius: 6px; padding: 20px; }
|
|
28
|
+
|
|
29
|
+
/* The form has one section + one input. */
|
|
30
|
+
.notes { margin-top: 24px; padding: 14px 16px; background: #FFF8E7; border-left: 3px solid #E9C46A; border-radius: 3px; font-size: 0.82rem; color: #5A4A3C; }
|
|
31
|
+
.notes strong { color: #2A2520; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<header class="pict-example-header">
|
|
36
|
+
<span class="pict-example-badge">Pict Example</span>
|
|
37
|
+
<span class="pict-example-app-name">RichText Form Input</span>
|
|
38
|
+
<span class="pict-example-module">pict-section-markdowneditor</span>
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<main class="page-content">
|
|
42
|
+
<p class="intro">
|
|
43
|
+
A pict-section-form field whose <code>InputType</code> is <code>RichText</code>. Boots in
|
|
44
|
+
<strong>view</strong> mode — just rendered markdown, no CodeMirror loaded. Click <em>Edit</em>
|
|
45
|
+
to flip into the editor (lazy-loads the markdown editor bundle on first toggle).
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<div class="controls">
|
|
49
|
+
<button id="toggle-Body" onclick="_Pict.PictApplication.demo_toggleMode('Body')">Edit</button>
|
|
50
|
+
|
|
51
|
+
<label>
|
|
52
|
+
<input type="checkbox" id="allow-images" checked onchange="_Pict.PictApplication.demo_setAllowImages('Body', this.checked)">
|
|
53
|
+
Allow image paste
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<label>
|
|
57
|
+
<input type="checkbox" id="use-uploader" checked onchange="_Pict.PictApplication.demo_setUploaderEnabled(this.checked)">
|
|
58
|
+
Use ImageUploader stub
|
|
59
|
+
<span style="color:#8A7F72; margin-left:6px; font-size:0.75rem;">(off = base64 inline)</span>
|
|
60
|
+
</label>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div id="Pict-Form-Container"></div>
|
|
64
|
+
|
|
65
|
+
<div class="notes">
|
|
66
|
+
<strong>Verifying:</strong>
|
|
67
|
+
Open DevTools → Network. With <em>Edit</em> never clicked, no CodeMirror request fires. Click <em>Edit</em>
|
|
68
|
+
once and the markdown-editor bundle loads. Click <em>Done</em> to return to view mode — the editor view
|
|
69
|
+
is torn down, but its content lives in the field's hidden form input.
|
|
70
|
+
</div>
|
|
71
|
+
</main>
|
|
72
|
+
|
|
73
|
+
<script src="./pict.min.js"></script>
|
|
74
|
+
<script src="./richtext_form_example.min.js"></script>
|
|
75
|
+
<script>
|
|
76
|
+
Pict.safeOnDocumentReady(() =>
|
|
77
|
+
{
|
|
78
|
+
Pict.safeLoadPictApplication(window.RichtextFormExample || window.RichTextFormExample, 1);
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "richtext_form_example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pict-section-form RichText InputType demo — markdown editor (edit mode) + rendered markdown (view mode), with pluggable image uploader.",
|
|
5
|
+
"main": "RichTextForm-Example-Application.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node RichTextForm-Example-Application.js",
|
|
8
|
+
"build": "npx quack build && npx quack copy"
|
|
9
|
+
},
|
|
10
|
+
"author": "steven velozo <steven@velozo.com>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"devDependencies": {},
|
|
13
|
+
"copyFilesSettings": {
|
|
14
|
+
"whenFileExists": "overwrite"
|
|
15
|
+
},
|
|
16
|
+
"copyFiles": [
|
|
17
|
+
{ "from": "./html/*", "to": "./dist/" },
|
|
18
|
+
{ "from": "../../node_modules/pict/dist/*", "to": "./dist/" }
|
|
19
|
+
],
|
|
20
|
+
"retold": {
|
|
21
|
+
"ExampleApplication": {
|
|
22
|
+
"Stage": true,
|
|
23
|
+
"Title": "RichText Form Input",
|
|
24
|
+
"Summary": "A pict-section-form field whose InputType is RichText — boots in view mode (rendered markdown), toggles into edit mode (CodeMirror) on demand. Demonstrates the AllowImages knob and the pluggable ImageUploader override.",
|
|
25
|
+
"Complexity": "Intermediate"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|