simplyflow 0.8.1 → 0.9.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.
- package/dist/simply.flow.js +272 -152
- package/dist/simply.flow.min.js +1 -1
- package/dist/simply.flow.min.js.map +4 -4
- package/package.json +1 -1
- package/src/bind.mjs +3 -5
- package/src/bind.render.mjs +21 -64
- package/src/dom.mjs +107 -6
- package/src/edit/anchor.mjs +130 -0
- package/src/edit/toolbars.mjs +315 -0
- package/src/edit.mjs +168 -83
- package/src/model.mjs +2 -1
- package/src/state.mjs +205 -95
- package/src/symbols.mjs +8 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import anchor from './anchor.mjs'
|
|
2
|
+
import '../flow.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const simplyToolbarCSS = css`
|
|
6
|
+
:host {
|
|
7
|
+
--simply-button-font: arial, helvetica, sans-serif;
|
|
8
|
+
--simply-button-font-size: 11px;
|
|
9
|
+
--simply-button-width: 50px;
|
|
10
|
+
--simply-button-height: 50px;
|
|
11
|
+
--simply-button-color: #333;
|
|
12
|
+
--simply-button-primary: #ea5922;
|
|
13
|
+
}
|
|
14
|
+
.simply-button {
|
|
15
|
+
height: var(--simply-button-height);
|
|
16
|
+
border-top: 1px solid transparent;
|
|
17
|
+
border-bottom: 2px solid transparent;
|
|
18
|
+
transition: background 0.2s ease;
|
|
19
|
+
font-size: var(--simply-button-font-size);
|
|
20
|
+
letter-spacing: 0;
|
|
21
|
+
font-family: var(--simply-button-font);
|
|
22
|
+
white-space: nowrap;
|
|
23
|
+
user-select: none;
|
|
24
|
+
vertical-align: top;
|
|
25
|
+
min-width: var(--simply-button-width);
|
|
26
|
+
text-align: center;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
padding: 0 4px;
|
|
29
|
+
text-transform: none;
|
|
30
|
+
background: transparent;
|
|
31
|
+
outline: none;
|
|
32
|
+
box-shadow: none;
|
|
33
|
+
border-radius: 0;
|
|
34
|
+
color: var(--simply-button-color);
|
|
35
|
+
position: relative;
|
|
36
|
+
}
|
|
37
|
+
.simply-button:hover {
|
|
38
|
+
border-bottom: 2px solid var(--simply-button-primary);
|
|
39
|
+
box-shadow: none;
|
|
40
|
+
}
|
|
41
|
+
.simply-button .ds-icon {
|
|
42
|
+
height: 26px;
|
|
43
|
+
font-size: 26px;
|
|
44
|
+
padding: 0 4px;
|
|
45
|
+
display: block;
|
|
46
|
+
margin: -2px auto -2px;
|
|
47
|
+
position: relative;
|
|
48
|
+
}
|
|
49
|
+
.simply-button.ds-selected {
|
|
50
|
+
border-top-color: var(--ds-grey-40);
|
|
51
|
+
background-color: var(--ds-grey-light);
|
|
52
|
+
border-left: 1px solid var(--ds-grey-40);
|
|
53
|
+
border-right: 1px solid var(--ds-white);
|
|
54
|
+
}
|
|
55
|
+
.simply-button:active {
|
|
56
|
+
border-bottom: 2px solid var(--ds-primary);
|
|
57
|
+
box-shadow: none;
|
|
58
|
+
}
|
|
59
|
+
.simply-toolbar {
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
min-width: 100%;
|
|
62
|
+
min-height: 50px;
|
|
63
|
+
display: flex;
|
|
64
|
+
position: relative;
|
|
65
|
+
}
|
|
66
|
+
.simply-toolbar-main {
|
|
67
|
+
border-top: 2px solid var(--simply-button-primary);
|
|
68
|
+
background: linear-gradient(180deg, white 0, white 95%, #CCC 100%);
|
|
69
|
+
}
|
|
70
|
+
.simply-toolbar-inline {
|
|
71
|
+
min-width: 100px;
|
|
72
|
+
}
|
|
73
|
+
.simply-toolbar-inline .ds-button,
|
|
74
|
+
.simply-toolbar .ds-button {
|
|
75
|
+
margin: 0;
|
|
76
|
+
}
|
|
77
|
+
.simply-toolbar-sub .simply-toolbar {
|
|
78
|
+
background: #EEE;
|
|
79
|
+
min-height: 40px;
|
|
80
|
+
}
|
|
81
|
+
.simply-toolbar-sub .simply-button {
|
|
82
|
+
height: 40px;
|
|
83
|
+
min-width: 40px;
|
|
84
|
+
}
|
|
85
|
+
.simply-toolbar-sub .simply-button .ds-icon {
|
|
86
|
+
height: 20px;
|
|
87
|
+
font-size: 20px;
|
|
88
|
+
}
|
|
89
|
+
.simply-toolbar-highlight {
|
|
90
|
+
background: var(--ds-primary-gradient-bump);
|
|
91
|
+
color: var(--ds-primary-contrast);
|
|
92
|
+
}
|
|
93
|
+
.simply-toolbar .simply-toolbar-title {
|
|
94
|
+
margin-top: 0;
|
|
95
|
+
}
|
|
96
|
+
.simply-toolbar-spacer {
|
|
97
|
+
border-left: 1px solid #ccc;
|
|
98
|
+
height: 60px;
|
|
99
|
+
position: absolute;
|
|
100
|
+
display: inline-block;
|
|
101
|
+
}
|
|
102
|
+
.simply-button-expands:not(.ds-selected)::after {
|
|
103
|
+
content: "";
|
|
104
|
+
display: block;
|
|
105
|
+
position: absolute;
|
|
106
|
+
bottom: 2px;
|
|
107
|
+
left: 50%;
|
|
108
|
+
margin-left: -3px;
|
|
109
|
+
width: 0;
|
|
110
|
+
border-top: 3px solid #888;
|
|
111
|
+
border-bottom: 0;
|
|
112
|
+
border-left: 3px solid transparent;
|
|
113
|
+
border-right: 3px solid transparent;
|
|
114
|
+
}
|
|
115
|
+
.simply-button-expanded {
|
|
116
|
+
background: #EEE;
|
|
117
|
+
}
|
|
118
|
+
.simply-button.simply-button-expanded::after {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
.simply-toolbar .simply-push-right {
|
|
122
|
+
margin-left: auto;
|
|
123
|
+
}
|
|
124
|
+
.simply-toolbar input[type="text"] {
|
|
125
|
+
margin-right:0;
|
|
126
|
+
margin-bottom: 0;
|
|
127
|
+
margin-top: 10px;
|
|
128
|
+
font-size: small;
|
|
129
|
+
line-height: 1.2em;
|
|
130
|
+
height: 35px;
|
|
131
|
+
}
|
|
132
|
+
.simply-toolbar-header {
|
|
133
|
+
border-top-width: 5px;
|
|
134
|
+
}
|
|
135
|
+
.ds-nightmode .simply-toolbar {
|
|
136
|
+
background: linear-gradient(var(--ds-grey-90) 0%, var(--ds-grey-90) 95%, black 100%);
|
|
137
|
+
color: var(--ds-white);
|
|
138
|
+
}
|
|
139
|
+
.ds-nightmode .simply-button {
|
|
140
|
+
color: var(--ds-white);
|
|
141
|
+
}
|
|
142
|
+
.ds-nightmode .simply-button.ds-selected {
|
|
143
|
+
background-color: var(--ds-grey-80);
|
|
144
|
+
border-left-color: var(--ds-black);
|
|
145
|
+
border-top-color: var(--ds-black);
|
|
146
|
+
border-right-color: var(--ds-grey-60);
|
|
147
|
+
}
|
|
148
|
+
.ds-nightmode .simply-button[disabled] {
|
|
149
|
+
background-color: transparent;
|
|
150
|
+
color: var(--ds-grey-60);
|
|
151
|
+
}
|
|
152
|
+
.simply-toolbar.ds-hidden {
|
|
153
|
+
height: 0px;
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
min-height: 0px;
|
|
156
|
+
}`
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
//TODO: allow app to specify which toolbar to show instead of fixed toolbars.floatToolbarText.buttons
|
|
160
|
+
const simplyToolbarContents = html`
|
|
161
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@muze-nl/theds@0.2.7/dist/theds.css">
|
|
162
|
+
<style>
|
|
163
|
+
${simplyToolbarCSS}
|
|
164
|
+
</style>
|
|
165
|
+
<nav class="simply-toolbar simply-toolbar-main simply-toolbar-inline" data-flow-list="toolbars.floatToolbarText.buttons">
|
|
166
|
+
<template rel="simply-toolbar"></template>
|
|
167
|
+
</nav>
|
|
168
|
+
<div class="simply-toolbar-sub" data-flow-map="toolbars.floatToolbarText.toolbars">
|
|
169
|
+
<template>
|
|
170
|
+
<nav class="simply-toolbar ds-hidden" data-flow-field=":key" data-flow-transform="simplyToolbar">
|
|
171
|
+
<div data-flow-list="buttons">
|
|
172
|
+
<template rel="simply-toolbar"></template>
|
|
173
|
+
</div>
|
|
174
|
+
</nav>
|
|
175
|
+
</template>
|
|
176
|
+
</div>`
|
|
177
|
+
|
|
178
|
+
export default {
|
|
179
|
+
css: {
|
|
180
|
+
simplyToolbarFloat: css`
|
|
181
|
+
:root {
|
|
182
|
+
--ds-shadow-light: rgba(0,0,0,0.07);
|
|
183
|
+
--ds-shadow-middle: rgba(0,0,0,0.09);
|
|
184
|
+
--ds-shadow-dark: rgba(0,0,0,0.11);
|
|
185
|
+
--ds-shadow-small:
|
|
186
|
+
0 1px 1px var(--ds-shadow-dark),
|
|
187
|
+
0 2px 2px var(--ds-shadow-middle),
|
|
188
|
+
0 4px 4px var(--ds-shadow-light)
|
|
189
|
+
;
|
|
190
|
+
}
|
|
191
|
+
.simply-toolbar-float {
|
|
192
|
+
margin: 0;
|
|
193
|
+
padding: 0;
|
|
194
|
+
border: 0;
|
|
195
|
+
width: auto;
|
|
196
|
+
position-anchor: --cursor-anchor;
|
|
197
|
+
position-area: end span-all;
|
|
198
|
+
position: absolute;
|
|
199
|
+
min-width:100px;
|
|
200
|
+
min-height: 50px;
|
|
201
|
+
background: white;
|
|
202
|
+
z-index: 10000;
|
|
203
|
+
margin-top: -4px;
|
|
204
|
+
box-shadow: var(--ds-shadow-small);
|
|
205
|
+
}`
|
|
206
|
+
},
|
|
207
|
+
html: {
|
|
208
|
+
'simply-toolbar':
|
|
209
|
+
html`<button class="ds-button simply-button" data-flow-field=":value" data-flow-transform="simplyToolbarButton">
|
|
210
|
+
<svg class="ds-icon ds-icon-feather">
|
|
211
|
+
<use xlink:href="feather-sprite.svg#x" data-flow-transform="simplyIcon" data-flow-field="icon">
|
|
212
|
+
</use></svg>
|
|
213
|
+
<span data-flow-field="label"></span>
|
|
214
|
+
</button>`,
|
|
215
|
+
'simply-toolbar-float':
|
|
216
|
+
html`<div class="simply-toolbar simply-toolbar-float simply-toolbar-inline" popover="manual"></div>`
|
|
217
|
+
},
|
|
218
|
+
transformers: {
|
|
219
|
+
simplyToolbar: function(context, next) {
|
|
220
|
+
context.element.id = context.value
|
|
221
|
+
},
|
|
222
|
+
simplyToolbarButton: function(context, next) {
|
|
223
|
+
const el = context.element
|
|
224
|
+
el.value = context.value.command
|
|
225
|
+
if (context.value.command=="expand") {
|
|
226
|
+
el.classList.add('simply-button-expands')
|
|
227
|
+
}
|
|
228
|
+
if (context.value.command) {
|
|
229
|
+
el.dataset.simplyCommand = context.value.command
|
|
230
|
+
}
|
|
231
|
+
if (context.value.value) {
|
|
232
|
+
el.value = context.value.value
|
|
233
|
+
}
|
|
234
|
+
// skip next()
|
|
235
|
+
},
|
|
236
|
+
simplyIcon: function(context, next) {
|
|
237
|
+
const url = new URL(context.element.getAttribute('xlink:href'), document.location)
|
|
238
|
+
url.hash = context.value
|
|
239
|
+
context.element.setAttribute('xlink:href', url.href)
|
|
240
|
+
// skip next()
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
commands: {
|
|
244
|
+
toggle: function(el, value) {
|
|
245
|
+
|
|
246
|
+
},
|
|
247
|
+
align: function(el, value) {
|
|
248
|
+
|
|
249
|
+
},
|
|
250
|
+
expand: function(el, value) {
|
|
251
|
+
const toolbar = el.closest('.simply-toolbar')
|
|
252
|
+
const subToolbars = toolbar.nextElementSibling;
|
|
253
|
+
if (!subToolbars) {
|
|
254
|
+
console.error('no subtoolbars')
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
const current = Array.from(subToolbars.querySelectorAll('.simply-toolbar:not(.ds-hidden)'))
|
|
258
|
+
for( let t of current) {
|
|
259
|
+
t.classList.add('ds-hidden')
|
|
260
|
+
}
|
|
261
|
+
const selectedToolbar = subToolbars.querySelector('#'+value)
|
|
262
|
+
if (selectedToolbar) {
|
|
263
|
+
selectedToolbar.classList.remove('ds-hidden')
|
|
264
|
+
const buttons = Array.from(toolbar.querySelectorAll('.simply-button-expanded'))
|
|
265
|
+
for (let button of buttons) {
|
|
266
|
+
button.classList.remove('simply-button-expanded')
|
|
267
|
+
}
|
|
268
|
+
el.classList.add('simply-button-expanded')
|
|
269
|
+
} else {
|
|
270
|
+
console.error('toolbar '+value+' not found')
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
actions: {
|
|
275
|
+
showToolbar: function(position) {
|
|
276
|
+
this.state.toolbar.showPopover()
|
|
277
|
+
},
|
|
278
|
+
hideToolbar: function() {
|
|
279
|
+
this.state.toolbar.hidePopover()
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
hooks: {
|
|
283
|
+
start: function() {
|
|
284
|
+
this.state.toolbar = this.container.querySelector('simply-edit-focus-toolbar')
|
|
285
|
+
if (!this.state.toolbar) {
|
|
286
|
+
this.container.insertAdjacentHTML('beforeend','<simply-render rel="simply-toolbar-float"></simply-render>')
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
const toolbar = document.querySelector('.simply-toolbar-float')
|
|
289
|
+
const shadow = toolbar.attachShadow({ mode: "open"})
|
|
290
|
+
shadow.innerHTML = simplyToolbarContents
|
|
291
|
+
this.state.toolbar = toolbar
|
|
292
|
+
simply.state.effect(() => {
|
|
293
|
+
let visible = this.state.anchor.visible
|
|
294
|
+
if (visible) {
|
|
295
|
+
this.actions.showToolbar()
|
|
296
|
+
} else {
|
|
297
|
+
this.actions.hideToolbar()
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
// databinding doesn't reach into shadowRoot by default, so set it up here
|
|
301
|
+
simply.bind({
|
|
302
|
+
root: this.state,
|
|
303
|
+
container: toolbar.shadowRoot,
|
|
304
|
+
transformers: this.transformers
|
|
305
|
+
})
|
|
306
|
+
// same for commands, set container explicitly to the shadowRoot
|
|
307
|
+
simply.command({ app: this, container: toolbar.shadowRoot, commands: this.commands})
|
|
308
|
+
}, 100)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
components: {
|
|
313
|
+
anchor
|
|
314
|
+
}
|
|
315
|
+
}
|
package/src/edit.mjs
CHANGED
|
@@ -1,108 +1,193 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* the given element. The x and y position are calculated relative to the top
|
|
4
|
-
* left of the given element. This function does not alter the DOM in any way.
|
|
5
|
-
*/
|
|
6
|
-
function getCursorPosition(element) {
|
|
7
|
-
const selection = window.getSelection();
|
|
8
|
-
if (!selection.rangeCount) return null;
|
|
1
|
+
import toolbars from './edit/toolbars.mjs'
|
|
2
|
+
import '../src/flow.mjs'
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
range.setStart(selection.focusNode, selection.focusOffset);
|
|
12
|
-
range.collapse(true);
|
|
4
|
+
const editSet = new Set()
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
const elementRect = element.getBoundingClientRect();
|
|
16
|
-
|
|
17
|
-
const cursorNode = selection.focusNode;
|
|
18
|
-
const cursorElement = cursorNode.nodeType === Node.TEXT_NODE
|
|
19
|
-
? cursorNode.parentElement
|
|
20
|
-
: cursorNode;
|
|
21
|
-
|
|
22
|
-
let x,y,height;
|
|
23
|
-
const rects = range.getClientRects();
|
|
24
|
-
if (rects.length > 0) {
|
|
25
|
-
x = rects[0].left - elementRect.left
|
|
26
|
-
y = rects[0].top - elementRect.top
|
|
27
|
-
height = rects[0].height
|
|
28
|
-
} else {
|
|
29
|
-
// Fallback for truly empty element: use padding from CSS
|
|
30
|
-
const style = window.getComputedStyle(cursorElement);
|
|
31
|
-
const lineHeight = parseFloat(style.lineHeight);
|
|
32
|
-
height = isNaN(lineHeight) ? parseFloat(style.fontSize) : lineHeight
|
|
33
|
-
const cursorElementRect = cursorElement.getBoundingClientRect();
|
|
34
|
-
x = cursorElementRect.left - elementRect.left + parseFloat(style.paddingLeft)
|
|
35
|
-
y = cursorElementRect.top - elementRect.top + parseFloat(style.paddingTop)
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
x,
|
|
39
|
-
y,
|
|
40
|
-
height,
|
|
41
|
-
element: cursorElement
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function edit(element)
|
|
6
|
+
export function edit(rootElement)
|
|
46
7
|
{
|
|
47
8
|
return simply.app({
|
|
48
|
-
container:
|
|
9
|
+
container: rootElement,
|
|
49
10
|
actions: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.toolbar.style.display = 'block'
|
|
11
|
+
editAll: function(elements) {
|
|
12
|
+
for (let el of elements) {
|
|
13
|
+
this.actions.edit(el)
|
|
14
|
+
}
|
|
55
15
|
},
|
|
56
|
-
|
|
57
|
-
|
|
16
|
+
edit: function(el) {
|
|
17
|
+
el.setAttribute('contenteditable', true)
|
|
18
|
+
editSet.add(el, true)
|
|
58
19
|
},
|
|
59
|
-
|
|
60
|
-
|
|
20
|
+
editClose: function(el) {
|
|
21
|
+
el.removeAttribute('contenteditable')
|
|
22
|
+
editSet.remove(el)
|
|
23
|
+
},
|
|
24
|
+
editQuit: function() {
|
|
25
|
+
for (let el of editSet.entries()) {
|
|
26
|
+
removeAttribute('contenteditable')
|
|
27
|
+
}
|
|
61
28
|
document.removeEventListener(this.selectionListener)
|
|
62
29
|
}
|
|
63
30
|
},
|
|
64
31
|
keyboard: {
|
|
65
32
|
default: {
|
|
66
33
|
'Control+ ': function() {
|
|
67
|
-
if (this.
|
|
68
|
-
|
|
69
|
-
this.actions.showToolbar(position)
|
|
34
|
+
if (this.state.anchor.visible) {
|
|
35
|
+
this.actions.showToolbar()
|
|
70
36
|
} else {
|
|
71
37
|
this.actions.hideToolbar()
|
|
72
38
|
}
|
|
73
39
|
}
|
|
74
40
|
}
|
|
75
41
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
42
|
+
state: {
|
|
43
|
+
toolbars: {
|
|
44
|
+
mainToolbar: {
|
|
45
|
+
buttons: [
|
|
46
|
+
{
|
|
47
|
+
label: 'Save',
|
|
48
|
+
command: 'save',
|
|
49
|
+
icon: '#save'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Undo',
|
|
53
|
+
command: 'undo',
|
|
54
|
+
icon: '#rotate-ccw'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'Redo',
|
|
58
|
+
command: 'redo',
|
|
59
|
+
icon: '#rotate-cw'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: 'Help',
|
|
63
|
+
command: 'help-main',
|
|
64
|
+
icon: '#help-circle'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'Close',
|
|
68
|
+
command: 'close',
|
|
69
|
+
icon: ''
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
floatToolbarText: {
|
|
74
|
+
buttons: [
|
|
75
|
+
{
|
|
76
|
+
label: 'Text',
|
|
77
|
+
icon: '#type',
|
|
78
|
+
value: 'styleToolbar',
|
|
79
|
+
command: 'expand'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: 'Align',
|
|
83
|
+
icon: '#align-left',
|
|
84
|
+
command: 'expand',
|
|
85
|
+
value: 'alignToolbar'
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
toolbars: {
|
|
89
|
+
styleToolbar,
|
|
90
|
+
alignToolbar
|
|
96
91
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
},
|
|
93
|
+
floatToolbarImg: {
|
|
94
|
+
buttons: [
|
|
95
|
+
{
|
|
96
|
+
label: 'Align',
|
|
97
|
+
icon: '#align-left',
|
|
98
|
+
command: 'expand',
|
|
99
|
+
value: 'alignToolbar'
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
toolbars: {
|
|
103
|
+
alignToolbar
|
|
100
104
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
hooks: {
|
|
109
|
+
start: function() {
|
|
110
|
+
// make sure this.state is a signal before calling start hooks of components
|
|
111
|
+
// and simply.bind - move to default start() hook in app.mjs (when simplyflow is in simplyview)
|
|
112
|
+
this.state = simply.state.signal(this.state),
|
|
113
|
+
simply.bind({
|
|
114
|
+
root: this.state
|
|
104
115
|
})
|
|
116
|
+
// move this code to default start() hook in app.mja, run it before
|
|
117
|
+
// the app.hooks.start function
|
|
118
|
+
for (let component in this.components) {
|
|
119
|
+
if (this.components[component].hooks?.start) {
|
|
120
|
+
this.components[component].hooks.start.apply(this)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
105
123
|
}
|
|
124
|
+
},
|
|
125
|
+
components: {
|
|
126
|
+
toolbars
|
|
106
127
|
}
|
|
107
128
|
})
|
|
108
|
-
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const alignToolbar = {
|
|
132
|
+
buttons: [
|
|
133
|
+
{
|
|
134
|
+
label: 'Left',
|
|
135
|
+
icon: '#align-left',
|
|
136
|
+
command: 'align',
|
|
137
|
+
value: 'left'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: 'Center',
|
|
141
|
+
icon: '#align-center',
|
|
142
|
+
command: 'align',
|
|
143
|
+
value: 'center'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: 'Right',
|
|
147
|
+
icon: '#align-right',
|
|
148
|
+
command: 'align',
|
|
149
|
+
value: 'right'
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: 'Justify',
|
|
153
|
+
icon: '#align-justify',
|
|
154
|
+
command: 'align',
|
|
155
|
+
value: 'justify'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
label: 'None',
|
|
159
|
+
icon: '#x',
|
|
160
|
+
command: 'align',
|
|
161
|
+
value: 'none'
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const styleToolbar = {
|
|
167
|
+
buttons: [
|
|
168
|
+
{
|
|
169
|
+
label: 'Bold',
|
|
170
|
+
icon: '#bold',
|
|
171
|
+
command: 'toggle',
|
|
172
|
+
value: '<strong>'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
label: 'Italic',
|
|
176
|
+
icon: '#italic',
|
|
177
|
+
command: 'toggle',
|
|
178
|
+
value: '<em>'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
label: 'Underline',
|
|
182
|
+
icon: '#underline',
|
|
183
|
+
command: 'toggle',
|
|
184
|
+
value: '<u>'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
label: 'Code',
|
|
188
|
+
icon: '#code',
|
|
189
|
+
command: 'toggle',
|
|
190
|
+
value: '<code>'
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
package/src/model.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {signal, effect, throttledEffect, batch} from './state.mjs'
|
|
2
|
+
import { DEP } from './symbols.mjs'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* This class implements a pluggable data model, where you can
|
|
@@ -46,7 +47,7 @@ class SimplyFlowModel {
|
|
|
46
47
|
}
|
|
47
48
|
const dataSignal = this.effects[this.effects.length-1]
|
|
48
49
|
const connectedSignal = fn.call(this, dataSignal)
|
|
49
|
-
if (!connectedSignal || !connectedSignal[
|
|
50
|
+
if (!connectedSignal || !connectedSignal[DEP.SIGNAL]) {
|
|
50
51
|
throw new Error('addEffect function parameter must return a Signal', { cause: fn })
|
|
51
52
|
}
|
|
52
53
|
this.view = connectedSignal
|