neo.mjs 6.9.8 → 6.9.10
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/apps/ServiceWorker.mjs +2 -2
- package/apps/learnneo/index.html +5 -0
- package/apps/learnneo/view/home/ContentComponent.mjs +24 -0
- package/apps/learnneo/view/home/ContentTreeList.mjs +55 -28
- package/apps/learnneo/view/home/MainContainer.mjs +36 -17
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/form/field/textarea/MainContainer.mjs +1 -1
- package/package.json +2 -2
- package/resources/scss/src/apps/learnneo/Viewport.scss +0 -63
- package/resources/scss/src/apps/learnneo/home/ContentComponent.scss +61 -0
- package/resources/scss/src/apps/learnneo/home/ContentTreeList.scss +37 -0
- package/resources/scss/src/form/field/FileUpload.scss +4 -4
- package/resources/scss/src/form/field/TextArea.scss +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/form/field/FileUpload.mjs +9 -2
- package/src/form/field/Text.mjs +30 -2
- package/src/form/field/TextArea.mjs +19 -34
- package/src/main/DomAccess.mjs +34 -0
- package/src/main/addon/HighlightJS.mjs +0 -12
- package/src/util/String.mjs +5 -3
- package/test/components/files/form/field/Select.mjs +1 -1
- package/resources/data/learnneo/content.json +0 -27
package/apps/ServiceWorker.mjs
CHANGED
package/apps/learnneo/index.html
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
<!DOCTYPE HTML>
|
2
2
|
<html>
|
3
|
+
|
3
4
|
<head>
|
4
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
5
6
|
<meta charset="UTF-8">
|
6
7
|
<title>LearnNeo</title>
|
8
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans|Inconsolata">
|
9
|
+
</link>
|
7
10
|
</head>
|
11
|
+
|
8
12
|
<body>
|
9
13
|
<script src="../../src/MicroLoader.mjs" type="module"></script>
|
10
14
|
</body>
|
15
|
+
|
11
16
|
</html>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import Base from '../../../../src/component/Base.mjs';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @class LearnNeo.view.home.ContentComponent
|
5
|
+
* @extends Neo.component.Base
|
6
|
+
*/
|
7
|
+
class ContentComponent extends Base {
|
8
|
+
static config = {
|
9
|
+
/**
|
10
|
+
* @member {String} className='LearnNeo.view.home.ContentComponent'
|
11
|
+
* @protected
|
12
|
+
*/
|
13
|
+
className: 'LearnNeo.view.home.ContentComponent',
|
14
|
+
/**
|
15
|
+
* @member {String[]} baseCls=['learn-content']
|
16
|
+
* @protected
|
17
|
+
*/
|
18
|
+
baseCls: ['learn-content']
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
Neo.applyClassConfig(ContentComponent);
|
23
|
+
|
24
|
+
export default ContentComponent;
|
@@ -12,30 +12,46 @@ class ContentTreeList extends TreeList {
|
|
12
12
|
* @protected
|
13
13
|
*/
|
14
14
|
className: 'LearnNeo.view.home.ContentTreeList',
|
15
|
+
/**
|
16
|
+
* @member {String[]} cls=['topics-tree']
|
17
|
+
*/
|
18
|
+
cls: ['topics-tree'],
|
15
19
|
/**
|
16
20
|
* @member {Neo.data.Store} store=ContentStore
|
17
21
|
*/
|
18
|
-
store: ContentStore
|
19
|
-
}
|
22
|
+
store: ContentStore,
|
20
23
|
|
21
|
-
|
22
|
-
* todo: createItems() should get triggered onStoreLoad()
|
23
|
-
*/
|
24
|
-
onConstructed() {
|
25
|
-
super.onConstructed();
|
26
|
-
let me = this;
|
27
|
-
Neo.Main.getByPath({path: 'location.search'})
|
28
|
-
.then(data => {
|
29
|
-
const searchString = data?.substr(1) || '';
|
30
|
-
const search = searchString ? JSON.parse(`{"${decodeURI(searchString.replace(/&/g, "\",\"").replace(/=/g, "\":\""))}"}`) : {};
|
31
|
-
me.deck = search.deck || 'learnneo';
|
32
|
-
me.doLoadStore();
|
33
|
-
console.log(search);
|
34
|
-
});
|
24
|
+
cls: 'topics-tree'
|
35
25
|
}
|
26
|
+
|
36
27
|
get contentPath() {
|
37
28
|
return `../../../resources/data/${this.deck}`;
|
38
29
|
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @param {Object} record
|
33
|
+
* @returns {Promise<void>}
|
34
|
+
*/
|
35
|
+
async doFetchContent(record) {
|
36
|
+
let me = this,
|
37
|
+
path = `${me.contentPath}`;
|
38
|
+
|
39
|
+
path += record.path ? `/pages/${record.path}` : `/p/${record.id}.md`;
|
40
|
+
|
41
|
+
if (record.isLeaf && path) {
|
42
|
+
const data = await fetch(path);
|
43
|
+
const content = await data.text();
|
44
|
+
|
45
|
+
await Neo.main.addon.Markdown.markdownToHtml(content)
|
46
|
+
.then(
|
47
|
+
html => me.fire('contentChange', {component: me, html}),
|
48
|
+
() => me.fire('contentChange', {component: me}));
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
*
|
54
|
+
*/
|
39
55
|
doLoadStore() {
|
40
56
|
const me = this;
|
41
57
|
Neo.Xhr.promiseJson({
|
@@ -48,19 +64,30 @@ class ContentTreeList extends TreeList {
|
|
48
64
|
})
|
49
65
|
}
|
50
66
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
67
|
+
/**
|
68
|
+
* todo: createItems() should get triggered onStoreLoad()
|
69
|
+
*/
|
70
|
+
onConstructed() {
|
71
|
+
super.onConstructed();
|
55
72
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
73
|
+
let me = this;
|
74
|
+
|
75
|
+
Neo.Main.getByPath({path: 'location.search'})
|
76
|
+
.then(data => {
|
77
|
+
const searchString = data?.substr(1) || '';
|
78
|
+
const search = searchString ? JSON.parse(`{"${decodeURI(searchString.replace(/&/g, "\",\"").replace(/=/g, "\":\""))}"}`) : {};
|
79
|
+
me.deck = search.deck || 'learnneo';
|
80
|
+
me.doLoadStore();
|
81
|
+
console.log(search);
|
82
|
+
});
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* @param {Object} record
|
87
|
+
*/
|
88
|
+
onLeafItemClick(record) {
|
89
|
+
super.onLeafItemClick(record);
|
90
|
+
this.doFetchContent(record);
|
64
91
|
}
|
65
92
|
}
|
66
93
|
|
@@ -1,36 +1,55 @@
|
|
1
|
-
import Container
|
2
|
-
import
|
3
|
-
import ContentTreeList
|
1
|
+
import Container from '../../../../src/container/Base.mjs';
|
2
|
+
import ContentComponent from './ContentComponent.mjs';
|
3
|
+
import ContentTreeList from './ContentTreeList.mjs';
|
4
4
|
import MainContainerController from './MainContainerController.mjs';
|
5
|
-
import MainContainerModel
|
6
|
-
import Splitter
|
5
|
+
import MainContainerModel from './MainContainerModel.mjs';
|
6
|
+
import Splitter from '../../../../src/component/Splitter.mjs';
|
7
7
|
|
8
|
+
/**
|
9
|
+
* @class LearnNeo.view.home.MainContainer
|
10
|
+
* @extends Neo.container.Base
|
11
|
+
*/
|
8
12
|
class MainContainer extends Container {
|
9
13
|
static config = {
|
14
|
+
/**
|
15
|
+
* @member {String} className='LearnNeo.view.home.MainContainer'
|
16
|
+
* @protected
|
17
|
+
*/
|
10
18
|
className: 'LearnNeo.view.home.MainContainer',
|
11
|
-
|
19
|
+
/**
|
20
|
+
* @member {Neo.controller.Component} controller=MainContainerController
|
21
|
+
*/
|
12
22
|
controller: MainContainerController,
|
23
|
+
/**
|
24
|
+
* @member {Object[]} items
|
25
|
+
*/
|
13
26
|
items: [{
|
14
|
-
module: Container,
|
15
|
-
layout: 'fit',
|
27
|
+
module : Container,
|
28
|
+
layout : 'fit',
|
16
29
|
minWidth: 350,
|
17
|
-
width: 350,
|
30
|
+
width : 350,
|
18
31
|
|
19
32
|
items: [{
|
20
|
-
module: ContentTreeList,
|
33
|
+
module : ContentTreeList,
|
21
34
|
reference: 'tree',
|
22
|
-
listeners: {contentChange: 'onContentListLeafClick'}
|
35
|
+
listeners: {contentChange: 'onContentListLeafClick'}
|
23
36
|
}]
|
24
37
|
}, {
|
25
|
-
module: Splitter,
|
26
|
-
resizeTarget: 'previous'
|
38
|
+
module : Splitter,
|
39
|
+
resizeTarget: 'previous',
|
40
|
+
size : 5
|
27
41
|
}, {
|
28
|
-
module:
|
29
|
-
layout: {ntype: 'card', activeIndex: null},
|
30
|
-
cls: 'learn-content',
|
42
|
+
module : ContentComponent,
|
31
43
|
reference: 'content'
|
32
44
|
}],
|
33
|
-
|
45
|
+
/**
|
46
|
+
* @member {Object} layout={ntype:'hbox',align:'stretch'}
|
47
|
+
*/
|
48
|
+
layout: {ntype: 'hbox', align: 'stretch'},
|
49
|
+
/**
|
50
|
+
* @member {Neo.model.Component} model=MainContainerModel
|
51
|
+
*/
|
52
|
+
model: MainContainerModel
|
34
53
|
}
|
35
54
|
}
|
36
55
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "neo.mjs",
|
3
|
-
"version": "6.9.
|
3
|
+
"version": "6.9.10",
|
4
4
|
"description": "The webworkers driven UI framework",
|
5
5
|
"type": "module",
|
6
6
|
"repository": {
|
@@ -49,7 +49,7 @@
|
|
49
49
|
"clean-webpack-plugin": "^4.0.0",
|
50
50
|
"commander": "^11.1.0",
|
51
51
|
"cssnano": "^6.0.1",
|
52
|
-
"envinfo": "^7.
|
52
|
+
"envinfo": "^7.11.0",
|
53
53
|
"fs-extra": "^11.1.1",
|
54
54
|
"highlightjs-line-numbers.js": "^2.8.0",
|
55
55
|
"inquirer": "^9.2.11",
|
@@ -1,63 +0,0 @@
|
|
1
|
-
.learn-content {
|
2
|
-
|
3
|
-
font-size: 13pt;
|
4
|
-
letter-spacing: 1px;
|
5
|
-
|
6
|
-
em,
|
7
|
-
i {
|
8
|
-
font-family: Palatino, "Times New Roman", serif;
|
9
|
-
font-size: 1.07em;
|
10
|
-
}
|
11
|
-
|
12
|
-
p {
|
13
|
-
margin: 0.5em 0em 0.7em 0em ;
|
14
|
-
}
|
15
|
-
|
16
|
-
mark {
|
17
|
-
background-color: #B8FCE3;
|
18
|
-
color: black;
|
19
|
-
padding: 0.1em 0.2em;
|
20
|
-
}
|
21
|
-
|
22
|
-
padding: 1em;
|
23
|
-
|
24
|
-
overflow: scroll;
|
25
|
-
|
26
|
-
details summary {
|
27
|
-
cursor: pointer;
|
28
|
-
transition: margin 300ms ease-out;
|
29
|
-
}
|
30
|
-
|
31
|
-
details[open] {
|
32
|
-
margin-bottom: 2em;
|
33
|
-
}
|
34
|
-
|
35
|
-
details summary {
|
36
|
-
list-style: none;
|
37
|
-
color: #555;
|
38
|
-
display: flex;
|
39
|
-
/* also removes the list marker */
|
40
|
-
align-items: center;
|
41
|
-
font-weight: bold;
|
42
|
-
}
|
43
|
-
|
44
|
-
details summary::before {
|
45
|
-
content: "\f055";
|
46
|
-
}
|
47
|
-
|
48
|
-
details[open] summary::before {
|
49
|
-
content: "\f056";
|
50
|
-
}
|
51
|
-
|
52
|
-
details summary::before {
|
53
|
-
font-family: var(--fa-style-family, "Font Awesome 6 Free");
|
54
|
-
font-weight: var(--fa-style, 900);
|
55
|
-
font-size: 1.3em;
|
56
|
-
margin: 0.4em 0.4em 0.4em 0;
|
57
|
-
color: #c4c4c4;
|
58
|
-
}
|
59
|
-
|
60
|
-
summary::-webkit-details-marker {
|
61
|
-
display: none;
|
62
|
-
}
|
63
|
-
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
.learn-content {
|
2
|
+
font-family : 'Open Sans';
|
3
|
+
font-size : 13pt;
|
4
|
+
letter-spacing: 1px;
|
5
|
+
overflow : scroll;
|
6
|
+
padding : 1em;
|
7
|
+
|
8
|
+
em,
|
9
|
+
i {
|
10
|
+
font-family: Palatino, "Times New Roman", serif;
|
11
|
+
font-size : 1.07em;
|
12
|
+
}
|
13
|
+
|
14
|
+
p {
|
15
|
+
margin: 0.5em 0em 0.7em 0em;
|
16
|
+
}
|
17
|
+
|
18
|
+
mark {
|
19
|
+
background-color: #B8FCE3;
|
20
|
+
color : black;
|
21
|
+
padding : 0.1em 0.2em;
|
22
|
+
}
|
23
|
+
|
24
|
+
details summary {
|
25
|
+
cursor: pointer;
|
26
|
+
transition: margin 300ms ease-out;
|
27
|
+
}
|
28
|
+
|
29
|
+
details[open] {
|
30
|
+
margin-bottom: 2em;
|
31
|
+
}
|
32
|
+
|
33
|
+
details summary {
|
34
|
+
list-style: none;
|
35
|
+
color: #555;
|
36
|
+
display: flex;
|
37
|
+
/* also removes the list marker */
|
38
|
+
align-items: center;
|
39
|
+
font-weight: bold;
|
40
|
+
}
|
41
|
+
|
42
|
+
details summary::before {
|
43
|
+
content: "\f055";
|
44
|
+
}
|
45
|
+
|
46
|
+
details[open] summary::before {
|
47
|
+
content: "\f056";
|
48
|
+
}
|
49
|
+
|
50
|
+
details summary::before {
|
51
|
+
color : #c4c4c4;
|
52
|
+
font-family: var(--fa-style-family, "Font Awesome 6 Free");
|
53
|
+
font-size : 1.3em;
|
54
|
+
font-weight: var(--fa-style, 900);
|
55
|
+
margin : 0.4em 0.4em 0.4em 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
summary::-webkit-details-marker {
|
59
|
+
display: none;
|
60
|
+
}
|
61
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
.topics-tree {
|
2
|
+
&.neo-tree-list {
|
3
|
+
.neo-treelist-collapse-all-icon {
|
4
|
+
.neo-treelist-menu-item-content:before {
|
5
|
+
content: "\f102" !important;
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
.neo-treelist-expand-all-icon {
|
10
|
+
.neo-treelist-menu-item-content:before {
|
11
|
+
content: "\f103" !important;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
.neo-list-container {
|
16
|
+
.neo-list-item {
|
17
|
+
&.neo-list-item-leaf {
|
18
|
+
.neo-list-item-content:before {
|
19
|
+
content: none !important;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
&.neo-list-folder {
|
24
|
+
.neo-list-item-content:before {
|
25
|
+
content: "\f107" !important;
|
26
|
+
}
|
27
|
+
|
28
|
+
&.neo-folder-open {
|
29
|
+
.neo-list-item-content:before {
|
30
|
+
content: "\f106" !important;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -136,8 +136,8 @@
|
|
136
136
|
}
|
137
137
|
|
138
138
|
// During upload, it's a progress circle.
|
139
|
-
// Of the upload fails, the process circle stops but remains visible
|
140
|
-
.neo-file-upload-state-uploading, .neo-file-upload-state-upload-failed {
|
139
|
+
// Of the upload fails, the process circle stops but remains visible //todo
|
140
|
+
.neo-file-upload-state-uploading, .neo-file-upload-state-upload-failed, .neo-file-upload-state-error {
|
141
141
|
.neo-file-upload-state-icon {
|
142
142
|
background-image : conic-gradient(
|
143
143
|
var(--fileuploadfield-progress-color) 0 var(--upload-progress),
|
@@ -184,8 +184,8 @@
|
|
184
184
|
}
|
185
185
|
}
|
186
186
|
|
187
|
-
// If the upload or scan failed, we show an error UI and the action button cancels
|
188
|
-
.neo-file-upload-state-upload-failed, .neo-file-upload-state-scan-failed, .neo-file-upload-field.neo-invalid {
|
187
|
+
// If the upload or scan failed, we show an error UI and the action button cancels //todo error
|
188
|
+
.neo-file-upload-state-upload-failed, .neo-file-upload-state-scan-failed, .neo-file-upload-field.neo-invalid, .neo-file-upload-state-error {
|
189
189
|
--fileuploadfield-progress-color : var(--fileuploadfield-error-color);
|
190
190
|
border-color : var(--fileuploadfield-error-color);
|
191
191
|
|
package/src/DefaultConfig.mjs
CHANGED
@@ -236,12 +236,12 @@ const DefaultConfig = {
|
|
236
236
|
useVdomWorker: true,
|
237
237
|
/**
|
238
238
|
* buildScripts/injectPackageVersion.mjs will update this value
|
239
|
-
* @default '6.9.
|
239
|
+
* @default '6.9.10'
|
240
240
|
* @memberOf! module:Neo
|
241
241
|
* @name config.version
|
242
242
|
* @type String
|
243
243
|
*/
|
244
|
-
version: '6.9.
|
244
|
+
version: '6.9.10'
|
245
245
|
};
|
246
246
|
|
247
247
|
Object.assign(DefaultConfig, {
|
@@ -322,7 +322,8 @@ class FileUpload extends Base {
|
|
322
322
|
malwareFoundInFile : 'Malware found in file',
|
323
323
|
pleaseCheck : 'Please check the file and try again',
|
324
324
|
successfullyUploaded : 'Successfully uploaded',
|
325
|
-
fileWasDeleted : 'File was deleted'
|
325
|
+
fileWasDeleted : 'File was deleted',
|
326
|
+
fileIsInAnErrorState : 'File is in an error state'
|
326
327
|
}
|
327
328
|
|
328
329
|
/**
|
@@ -565,6 +566,7 @@ class FileUpload extends Base {
|
|
565
566
|
// saved, so we just go back to ready state
|
566
567
|
case 'upload-failed':
|
567
568
|
case 'scan-failed':
|
569
|
+
case 'error':
|
568
570
|
me.clear();
|
569
571
|
me.state = 'ready';
|
570
572
|
break;
|
@@ -729,7 +731,12 @@ class FileUpload extends Base {
|
|
729
731
|
case 'deleted':
|
730
732
|
status.innerHTML = me.fileWasDeleted;
|
731
733
|
isChangeEventNeeded = true;
|
732
|
-
|
734
|
+
break;
|
735
|
+
case 'error':
|
736
|
+
status.innerHTML = me.fileIsInAnErrorState;
|
737
|
+
me.error = me.pleaseCheck;
|
738
|
+
isChangeEventNeeded = true;
|
739
|
+
}
|
733
740
|
|
734
741
|
if (isChangeEventNeeded && oldValue !== undefined) {
|
735
742
|
me.fireChangeEvent(me.file)
|
package/src/form/field/Text.mjs
CHANGED
@@ -2,6 +2,7 @@ import Base from './Base.mjs';
|
|
2
2
|
import BaseTrigger from './trigger/Base.mjs';
|
3
3
|
import ClearTrigger from './trigger/Clear.mjs';
|
4
4
|
import NeoArray from '../../util/Array.mjs';
|
5
|
+
import StringUtil from '../../util/String.mjs';
|
5
6
|
import VDomUtil from '../../util/VDom.mjs';
|
6
7
|
import VNodeUtil from '../../util/VNode.mjs';
|
7
8
|
|
@@ -234,6 +235,14 @@ class Text extends Base {
|
|
234
235
|
* @member {Function|String|null} validator=null
|
235
236
|
*/
|
236
237
|
validator: null,
|
238
|
+
/**
|
239
|
+
* getVlue can be xssProtected and values are escaped
|
240
|
+
* @member {Boolean} xssProtected=false
|
241
|
+
*/
|
242
|
+
xssProtected_: false,
|
243
|
+
/**
|
244
|
+
* @member {Object} _vdom
|
245
|
+
*/
|
237
246
|
/**
|
238
247
|
* @member {Object} _vdom
|
239
248
|
*/
|
@@ -1144,6 +1153,17 @@ class Text extends Base {
|
|
1144
1153
|
return this.id + '-trigger-' + type
|
1145
1154
|
}
|
1146
1155
|
|
1156
|
+
/**
|
1157
|
+
* @returns {*}
|
1158
|
+
*/
|
1159
|
+
getValue() {
|
1160
|
+
if (this.xssProtected) {
|
1161
|
+
return StringUtil.escapeHtml(super.getValue())
|
1162
|
+
} else {
|
1163
|
+
return super.getValue()
|
1164
|
+
}
|
1165
|
+
}
|
1166
|
+
|
1147
1167
|
/**
|
1148
1168
|
* @returns {Boolean}
|
1149
1169
|
*/
|
@@ -1269,10 +1289,14 @@ class Text extends Base {
|
|
1269
1289
|
if (centerBorderEl && me.isEmpty()) {
|
1270
1290
|
delete centerBorderEl.width;
|
1271
1291
|
}
|
1292
|
+
}
|
1272
1293
|
|
1273
|
-
|
1294
|
+
if (Neo.isString(me.value)) {
|
1295
|
+
me.value = me.value.trim()
|
1274
1296
|
}
|
1275
1297
|
|
1298
|
+
me.update();
|
1299
|
+
|
1276
1300
|
super.onFocusLeave(data)
|
1277
1301
|
}
|
1278
1302
|
|
@@ -1288,7 +1312,11 @@ class Text extends Base {
|
|
1288
1312
|
|
1289
1313
|
if (vnode) {
|
1290
1314
|
// required for validation -> revert a wrong user input
|
1291
|
-
vnode.vnode.attributes.value = value
|
1315
|
+
vnode.vnode.attributes.value = value
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
if (Neo.isString(value)) {
|
1319
|
+
value = value.trim()
|
1292
1320
|
}
|
1293
1321
|
|
1294
1322
|
me.clean = false;
|
@@ -32,7 +32,7 @@ class TextArea extends Text {
|
|
32
32
|
* any height of text. Bounds can be set using the `minHeight` and `maxHeight` settings.
|
33
33
|
* @member {Boolean} autoGrow=false
|
34
34
|
*/
|
35
|
-
|
35
|
+
autoGrow_ : false,
|
36
36
|
/**
|
37
37
|
* @member {String[]} baseCls=['neo-textarea','neo-textfield']
|
38
38
|
*/
|
@@ -74,6 +74,15 @@ class TextArea extends Text {
|
|
74
74
|
wrap_: null
|
75
75
|
}
|
76
76
|
|
77
|
+
afterSetAutoGrow(autoGrow) {
|
78
|
+
this.syncAutoGrowMonitor();
|
79
|
+
|
80
|
+
// Restore any configured height if autoGrow turned off
|
81
|
+
if (!autoGrow) {
|
82
|
+
this.afterSetHeight(this._height);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
77
86
|
/**
|
78
87
|
* Triggered after the cols config got changed
|
79
88
|
* @param {Number|null} value
|
@@ -102,7 +111,7 @@ class TextArea extends Text {
|
|
102
111
|
*/
|
103
112
|
afterSetMounted(value, oldValue) {
|
104
113
|
super.afterSetMounted(value, oldValue);
|
105
|
-
this.
|
114
|
+
this.syncAutoGrowMonitor();
|
106
115
|
}
|
107
116
|
|
108
117
|
/**
|
@@ -143,7 +152,6 @@ class TextArea extends Text {
|
|
143
152
|
}
|
144
153
|
|
145
154
|
super.afterSetValue(value, oldValue);
|
146
|
-
this.syncAutoGrowHeight();
|
147
155
|
}
|
148
156
|
|
149
157
|
/**
|
@@ -167,37 +175,14 @@ class TextArea extends Text {
|
|
167
175
|
return this.beforeSetEnumValue(value, oldValue, 'wrap', 'wrapValues');
|
168
176
|
}
|
169
177
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
/**
|
180
|
-
* @protected
|
181
|
-
*/
|
182
|
-
async syncAutoGrowHeight() {
|
183
|
-
let me = this;
|
184
|
-
|
185
|
-
if (me.mounted && me.autoGrow) {
|
186
|
-
const
|
187
|
-
inputEl = me.getInputEl(),
|
188
|
-
dims = await Neo.main.DomAccess.getScrollingDimensions({
|
189
|
-
appName : me.appName,
|
190
|
-
id : me.getInputElId()
|
191
|
-
});
|
192
|
-
|
193
|
-
// We must not show the scrollbar when autoGrowing
|
194
|
-
inputEl.style.overflowY = 'hidden';
|
195
|
-
|
196
|
-
if (dims.scrollHeight > dims.clientHeight - 5) {
|
197
|
-
inputEl.height = dims.scrollHeight;
|
198
|
-
}
|
199
|
-
|
200
|
-
me.update();
|
178
|
+
async syncAutoGrowMonitor() {
|
179
|
+
if (this.mounted) {
|
180
|
+
// Delegate monitoring of sizes to the VDOM thread.
|
181
|
+
Neo.main.DomAccess.monitorAutoGrow({
|
182
|
+
appName : this.appName,
|
183
|
+
id : this.getInputElId(),
|
184
|
+
autoGrow : this.autoGrow
|
185
|
+
});
|
201
186
|
}
|
202
187
|
}
|
203
188
|
}
|
package/src/main/DomAccess.mjs
CHANGED
@@ -102,6 +102,7 @@ class DomAccess extends Base {
|
|
102
102
|
'getBoundingClientRect',
|
103
103
|
'getScrollingDimensions',
|
104
104
|
'measure',
|
105
|
+
'monitorAutoGrow',
|
105
106
|
'scrollBy',
|
106
107
|
'scrollIntoView',
|
107
108
|
'scrollTo',
|
@@ -582,6 +583,39 @@ class DomAccess extends Base {
|
|
582
583
|
return value;
|
583
584
|
}
|
584
585
|
|
586
|
+
/**
|
587
|
+
* Checks the overflow status of a TextAreaField's <textarea> element and updates the
|
588
|
+
* height so that there is never a vertical scrollbar.
|
589
|
+
* @param {Object} data
|
590
|
+
*/
|
591
|
+
async monitorAutoGrow(data) {
|
592
|
+
const
|
593
|
+
me = this,
|
594
|
+
target = data.subject = me.getElement(data.id);
|
595
|
+
|
596
|
+
// We need to update the height on every input event is autoGrow is truthy.
|
597
|
+
target[data.autoGrow ? 'addEventListener' : 'removeEventListener']('input', me.monitorAutoGrowHandler);
|
598
|
+
|
599
|
+
// Fix the height up immediately too
|
600
|
+
if (data.autoGrow) {
|
601
|
+
me.monitorAutoGrowHandler({
|
602
|
+
target
|
603
|
+
});
|
604
|
+
}
|
605
|
+
}
|
606
|
+
|
607
|
+
monitorAutoGrowHandler({ target }) {
|
608
|
+
const
|
609
|
+
{ style } = target,
|
610
|
+
{ style : inputStyle } = target.closest('.neo-textarea');
|
611
|
+
|
612
|
+
// Measure the scrollHeight when forced to overflow, then set height to encompass the scrollHeight
|
613
|
+
style.height = style.minHeight = 0;
|
614
|
+
inputStyle.setProperty('--textfield-input-height', `${target.scrollHeight + 5}px`);
|
615
|
+
inputStyle.setProperty('height', '');
|
616
|
+
style.height = style.minHeight = '';
|
617
|
+
}
|
618
|
+
|
585
619
|
/**
|
586
620
|
* @param {Array} mutations
|
587
621
|
*/
|
@@ -96,18 +96,6 @@ class HighlightJS extends Base {
|
|
96
96
|
})
|
97
97
|
}
|
98
98
|
|
99
|
-
/**
|
100
|
-
* @param {Object} data
|
101
|
-
* @returns {Boolean}
|
102
|
-
*/
|
103
|
-
setConfigs(data) {
|
104
|
-
delete data.appName;
|
105
|
-
|
106
|
-
this.set(data);
|
107
|
-
|
108
|
-
return true
|
109
|
-
}
|
110
|
-
|
111
99
|
/**
|
112
100
|
* You can pass in 'light', 'dark', or a path for a custom theme
|
113
101
|
* @param {String} theme
|
package/src/util/String.mjs
CHANGED
@@ -14,18 +14,20 @@ class StringUtil extends Base {
|
|
14
14
|
'<' : '<',
|
15
15
|
'>' : '>',
|
16
16
|
'"' : '"',
|
17
|
-
'\'': '
|
17
|
+
'\'': ''',
|
18
|
+
'$' : '$',
|
19
|
+
'\\': '\'
|
18
20
|
}
|
19
21
|
/**
|
20
22
|
* @member {RegExp} charPattern
|
21
23
|
* @static
|
22
24
|
*/
|
23
|
-
static charPattern = /[&<>"']/g
|
25
|
+
static charPattern = /[&<>"'$\\]/g
|
24
26
|
/**
|
25
27
|
* @member {RegExp} entityPattern
|
26
28
|
* @static
|
27
29
|
*/
|
28
|
-
static entityPattern = /(&)|(<)|(>)|(")|(
|
30
|
+
static entityPattern = /(&)|(<)|(>)|(")|(')|($)|(\)/g
|
29
31
|
|
30
32
|
static config = {
|
31
33
|
/**
|
@@ -1,27 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"data": [{
|
3
|
-
"id" : 1,
|
4
|
-
"isLeaf" : true,
|
5
|
-
"name" : "Why neo.mjs",
|
6
|
-
"parentId": null,
|
7
|
-
"path" : "whyneo.md"
|
8
|
-
}, {
|
9
|
-
"id" : 2,
|
10
|
-
"isLeaf" : false,
|
11
|
-
"name" : "Class Definitions and Config",
|
12
|
-
"parentId": null,
|
13
|
-
"path" : null
|
14
|
-
}, {
|
15
|
-
"id" : 3,
|
16
|
-
"isLeaf" : false,
|
17
|
-
"name" : "Class Basics",
|
18
|
-
"parentId": 2,
|
19
|
-
"path" : null
|
20
|
-
}, {
|
21
|
-
"id" : 4,
|
22
|
-
"isLeaf" : true,
|
23
|
-
"name" : "Classes, Properties, and Methods",
|
24
|
-
"parentId": 3,
|
25
|
-
"path" : null
|
26
|
-
}]
|
27
|
-
}
|