@afeefa/vue-app 0.0.62 → 0.0.65
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/.afeefa/package/release/version.txt +1 -1
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/components/ABreadcrumbs.vue +3 -2
- package/src/components/AModal.vue +21 -3
- package/src/components/ARichTextArea.vue +95 -85
- package/src/components/ATableRow.vue +4 -0
- package/src/components/flying-context/FlyingContextEvent.js +5 -0
- package/src/components/form/EditForm.vue +13 -3
- package/src/components/form/EditModal.vue +52 -35
- package/src/components/form/NestedEditForm.vue +4 -0
- package/src/components/form/fields/FormFieldRichTextArea.vue +5 -3
- package/src/components/list/ListViewMixin.js +14 -2
- package/src/components/search-select/SearchSelectList.vue +0 -1
- package/src/components/vue/Component.js +9 -2
- package/src/events.js +2 -0
- package/src-admin/bootstrap.js +1 -0
- package/src-admin/components/App.vue +55 -6
- package/src-admin/components/FlyingContext.vue +77 -0
- package/src-admin/components/FlyingContextContainer.vue +85 -0
- package/src-admin/components/StickyFooter.vue +42 -0
- package/src-admin/components/StickyFooterContainer.vue +66 -0
- package/src-admin/components/form/EditFormButtons.vue +8 -3
- package/src-admin/components/index.js +4 -7
- package/src-admin/components/list/ListView.vue +22 -9
- package/src-admin/components/pages/EditPage.vue +23 -9
- package/src-admin/components/routes/DataRouteMixin.js +24 -15
- package/src-admin/config/routing.js +0 -11
- package/src-admin/config/vuetify.js +7 -1
- package/src-admin/directives/index.js +26 -0
- package/src-admin/styles.scss +1 -4
- package/src-admin/components/pages/CreatePage.vue +0 -114
- package/src-admin/components/pages/DetailPage.vue +0 -143
- package/src-admin/components/pages/EditPageMixin.js +0 -96
- package/src-admin/components/pages/ListPage.vue +0 -55
- package/src-admin/components/routes/CreateRoute.vue +0 -15
- package/src-admin/components/routes/DetailRoute.vue +0 -85
- package/src-admin/components/routes/EditRoute.vue +0 -78
- package/src-admin/components/routes/ListRoute.vue +0 -110
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.65
|
package/README.md
CHANGED
package/package.json
CHANGED
@@ -106,9 +106,10 @@ export default class ABreadcrumbs extends Vue {
|
|
106
106
|
getItemTitle (title) {
|
107
107
|
// title = title.concat(title).concat(title)
|
108
108
|
if (title.length > 20) {
|
109
|
-
title = title.slice(0, 10).trim() + '...' + title.slice(-10).trim()
|
109
|
+
// title = title.slice(0, 10).trim() + '...' + title.slice(-10).trim()
|
110
110
|
}
|
111
|
-
return title
|
111
|
+
return title
|
112
|
+
// return title.toUpperCase()
|
112
113
|
}
|
113
114
|
}
|
114
115
|
</script>
|
@@ -7,6 +7,8 @@
|
|
7
7
|
v-bind="$attrs"
|
8
8
|
:contentClass="modalId"
|
9
9
|
transition="v-fade-transition"
|
10
|
+
:persistent="true"
|
11
|
+
:no-click-animation="true"
|
10
12
|
@click:outside="cancel"
|
11
13
|
@keydown.esc="cancel"
|
12
14
|
>
|
@@ -41,7 +43,7 @@ import { getZIndex } from 'vuetify/lib/util/helpers'
|
|
41
43
|
import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
|
42
44
|
|
43
45
|
@Component({
|
44
|
-
props: ['show', 'title', 'triggerClass', 'anchorPosition']
|
46
|
+
props: ['show', 'title', 'beforeClose', 'triggerClass', 'anchorPosition']
|
45
47
|
})
|
46
48
|
export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentWidthMixin) {
|
47
49
|
modalId = randomCssClass(10)
|
@@ -83,7 +85,15 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
|
|
83
85
|
* and emit a visibility event
|
84
86
|
*/
|
85
87
|
@Watch('show')
|
86
|
-
showChanged () {
|
88
|
+
async showChanged () {
|
89
|
+
// check if a modal is allowed to be closed
|
90
|
+
if (this.modal && !this.show && this.beforeClose) {
|
91
|
+
const result = await this.beforeClose()
|
92
|
+
if (!result) {
|
93
|
+
return
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
87
97
|
this.modal = this.show
|
88
98
|
}
|
89
99
|
|
@@ -121,7 +131,15 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
|
|
121
131
|
this.urp_registerPositionWatcher(this.position)
|
122
132
|
}
|
123
133
|
|
124
|
-
cancel () {
|
134
|
+
async cancel () {
|
135
|
+
// check if a modal is allowed to be closed
|
136
|
+
if (this.modal && this.beforeClose) {
|
137
|
+
const result = await this.beforeClose()
|
138
|
+
if (!result) {
|
139
|
+
return
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
125
143
|
this.modal = false
|
126
144
|
}
|
127
145
|
|
@@ -4,85 +4,84 @@
|
|
4
4
|
v-if="editor"
|
5
5
|
class="menu-bar"
|
6
6
|
>
|
7
|
-
<
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
</v-btn>
|
7
|
+
<div>
|
8
|
+
<v-btn
|
9
|
+
small
|
10
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('bold')}]"
|
11
|
+
@click="editor.chain().focus().toggleBold().run()"
|
12
|
+
>
|
13
|
+
<v-icon>{{ boldIcon }}</v-icon>
|
14
|
+
</v-btn>
|
15
|
+
|
16
|
+
<v-btn
|
17
|
+
small
|
18
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('italic')}]"
|
19
|
+
@click="editor.chain().focus().toggleItalic().run()"
|
20
|
+
>
|
21
|
+
<v-icon>{{ italicIcon }}</v-icon>
|
22
|
+
</v-btn>
|
23
|
+
|
24
|
+
<v-btn
|
25
|
+
small
|
26
|
+
:class="['menu-button', 'strike', {'is-active': focus && editor.isActive('strike')}]"
|
27
|
+
@click="editor.chain().focus().toggleStrike().run()"
|
28
|
+
>
|
29
|
+
<v-icon>{{ strikeIcon }}</v-icon>
|
30
|
+
</v-btn>
|
31
|
+
|
32
|
+
<v-btn
|
33
|
+
small
|
34
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 1})}]"
|
35
|
+
@click="editor.chain().focus().toggleHeading({level: 1}).run()"
|
36
|
+
>
|
37
|
+
<v-icon>{{ h1Icon }}</v-icon>
|
38
|
+
</v-btn>
|
39
|
+
|
40
|
+
<v-btn
|
41
|
+
small
|
42
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 2})}]"
|
43
|
+
@click="editor.chain().focus().toggleHeading({level: 2}).run()"
|
44
|
+
>
|
45
|
+
<v-icon>{{ h2Icon }}</v-icon>
|
46
|
+
</v-btn>
|
47
|
+
|
48
|
+
<v-btn
|
49
|
+
small
|
50
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('bulletList')}]"
|
51
|
+
@click="editor.chain().focus().toggleBulletList().run()"
|
52
|
+
>
|
53
|
+
<v-icon>{{ ulIcon }}</v-icon>
|
54
|
+
</v-btn>
|
55
|
+
|
56
|
+
<v-btn
|
57
|
+
small
|
58
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('orderedList')}]"
|
59
|
+
@click="editor.chain().focus().toggleOrderedList().run()"
|
60
|
+
>
|
61
|
+
<v-icon>{{ olIcon }}</v-icon>
|
62
|
+
</v-btn>
|
63
|
+
|
64
|
+
<v-btn
|
65
|
+
small
|
66
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('blockquote')}]"
|
67
|
+
@click="editor.chain().focus().toggleBlockquote().run()"
|
68
|
+
>
|
69
|
+
<v-icon>{{ commentIcon }}</v-icon>
|
70
|
+
</v-btn>
|
71
|
+
|
72
|
+
<v-btn
|
73
|
+
small
|
74
|
+
class="menu-button"
|
75
|
+
:disabled="initialValue === editor.getHTML()"
|
76
|
+
@click="editor.chain().focus().undo().run()"
|
77
|
+
>
|
78
|
+
<v-icon>{{ undoIcon }}</v-icon>
|
79
|
+
</v-btn>
|
80
|
+
</div>
|
81
|
+
|
82
|
+
<div>
|
83
|
+
<slot name="buttons" />
|
84
|
+
</div>
|
86
85
|
</div>
|
87
86
|
|
88
87
|
<editor-content
|
@@ -119,6 +118,7 @@ import {
|
|
119
118
|
export default class ARichTextArea extends Vue {
|
120
119
|
editor = null
|
121
120
|
internalValue = null
|
121
|
+
initialValue = null
|
122
122
|
focus = false
|
123
123
|
|
124
124
|
boldIcon = mdiFormatBold
|
@@ -133,6 +133,7 @@ export default class ARichTextArea extends Vue {
|
|
133
133
|
redoIcon = mdiRotateRight
|
134
134
|
|
135
135
|
created () {
|
136
|
+
this.initialValue = this.value
|
136
137
|
this.internalValue = this.value
|
137
138
|
}
|
138
139
|
|
@@ -158,14 +159,20 @@ export default class ARichTextArea extends Vue {
|
|
158
159
|
this.$emit('blur')
|
159
160
|
}
|
160
161
|
})
|
161
|
-
|
162
|
-
this.editor.commands.setContent(this.internalValue, false)
|
163
162
|
}
|
164
163
|
|
165
164
|
beforeDestroy () {
|
166
165
|
this.editor.destroy()
|
167
166
|
}
|
168
167
|
|
168
|
+
/**
|
169
|
+
* reset the text area to disable the undo button
|
170
|
+
* e.g. after saving the form while keeping it open
|
171
|
+
*/
|
172
|
+
reset () {
|
173
|
+
this.initialValue = this.value
|
174
|
+
}
|
175
|
+
|
169
176
|
@Watch('value')
|
170
177
|
valueChanged () {
|
171
178
|
this.internalValue = this.value
|
@@ -192,10 +199,6 @@ export default class ARichTextArea extends Vue {
|
|
192
199
|
|
193
200
|
|
194
201
|
<style lang="scss" scoped>
|
195
|
-
.v-input:not(.v-input--is-focused) ::v-deep .v-counter {
|
196
|
-
display: none;
|
197
|
-
}
|
198
|
-
|
199
202
|
.a-rich-text-editor {
|
200
203
|
::v-deep .ProseMirror {
|
201
204
|
&-focused {
|
@@ -205,9 +208,12 @@ export default class ARichTextArea extends Vue {
|
|
205
208
|
}
|
206
209
|
|
207
210
|
.menu-bar {
|
211
|
+
display: flex;
|
212
|
+
justify-content: space-between;
|
208
213
|
margin: -.2rem 0 .5rem -.2rem;
|
209
214
|
}
|
210
215
|
|
216
|
+
.menu-bar .v-btn,
|
211
217
|
.menu-button {
|
212
218
|
padding: 0 !important;
|
213
219
|
width: 30px !important;
|
@@ -239,6 +245,10 @@ export default class ARichTextArea extends Vue {
|
|
239
245
|
&.is-active {
|
240
246
|
background: #ECECEC !important;
|
241
247
|
}
|
248
|
+
|
249
|
+
&[disabled] {
|
250
|
+
background: none !important;
|
251
|
+
}
|
242
252
|
}
|
243
253
|
|
244
254
|
::v-deep .ProseMirror {
|
@@ -4,9 +4,10 @@
|
|
4
4
|
autocomplete="off"
|
5
5
|
>
|
6
6
|
<slot
|
7
|
+
name="form"
|
7
8
|
:changed="changed"
|
8
9
|
:valid="valid"
|
9
|
-
:
|
10
|
+
:modelToEdit="modelToEdit"
|
10
11
|
/>
|
11
12
|
</v-form>
|
12
13
|
</template>
|
@@ -27,16 +28,22 @@ export default class EditForm extends Vue {
|
|
27
28
|
modelToEdit = null
|
28
29
|
valid = false
|
29
30
|
lastJson = null
|
31
|
+
forcedUnchange = false
|
30
32
|
|
31
33
|
created () {
|
32
34
|
this.reset()
|
33
35
|
}
|
34
36
|
|
37
|
+
forceUnchanged () {
|
38
|
+
this.forcedUnchange = true
|
39
|
+
this.$emit('update:changed', false)
|
40
|
+
}
|
41
|
+
|
35
42
|
reset () {
|
36
43
|
if (this.createModelToEdit) {
|
37
44
|
this.modelToEdit = this.createModelToEdit(this.model)
|
38
|
-
} else {
|
39
|
-
this.modelToEdit = this.model
|
45
|
+
} else if (this.model) {
|
46
|
+
this.modelToEdit = this.model.cloneForEdit()
|
40
47
|
}
|
41
48
|
this.lastJson = this.json
|
42
49
|
}
|
@@ -65,6 +72,9 @@ export default class EditForm extends Vue {
|
|
65
72
|
}
|
66
73
|
|
67
74
|
get changed () {
|
75
|
+
if (this.forcedUnchange) {
|
76
|
+
return false
|
77
|
+
}
|
68
78
|
// console.log(this.json)
|
69
79
|
// console.log(this.lastJson)
|
70
80
|
return this.json !== this.lastJson
|
@@ -1,6 +1,7 @@
|
|
1
1
|
<template>
|
2
2
|
<a-modal
|
3
3
|
:title="title"
|
4
|
+
:beforeClose="beforeClose"
|
4
5
|
:show.sync="show_"
|
5
6
|
v-bind="$attrs"
|
6
7
|
>
|
@@ -10,16 +11,18 @@
|
|
10
11
|
|
11
12
|
<edit-form
|
12
13
|
v-if="show_"
|
14
|
+
ref="form"
|
13
15
|
:model="model"
|
16
|
+
:createModelToEdit="createModelToEdit"
|
14
17
|
>
|
15
|
-
<template #
|
18
|
+
<template #form="{modelToEdit, changed, valid}">
|
16
19
|
<slot
|
17
|
-
name="
|
18
|
-
:
|
20
|
+
name="form"
|
21
|
+
:modelToEdit="modelToEdit"
|
22
|
+
:changed="changed"
|
23
|
+
:valid="valid"
|
19
24
|
/>
|
20
|
-
</template>
|
21
25
|
|
22
|
-
<template #default="{changed, valid}">
|
23
26
|
<a-row
|
24
27
|
class="mt-8 mb-1 pb-1 gap-4"
|
25
28
|
right
|
@@ -31,25 +34,14 @@
|
|
31
34
|
Schließen
|
32
35
|
</v-btn>
|
33
36
|
|
34
|
-
<
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
</v-btn>
|
43
|
-
|
44
|
-
<v-icon
|
45
|
-
v-if="changed"
|
46
|
-
small
|
47
|
-
text
|
48
|
-
@click="reset"
|
49
|
-
>
|
50
|
-
{{ undoIcon }}
|
51
|
-
</v-icon>
|
52
|
-
</a-row>
|
37
|
+
<edit-form-buttons
|
38
|
+
:changed="changed"
|
39
|
+
:valid="valid"
|
40
|
+
small
|
41
|
+
:has="{reset: !!modelToEdit.id}"
|
42
|
+
@save="$emit('save', modelToEdit, ignoreChangesOnClose)"
|
43
|
+
@reset="$refs.form.reset()"
|
44
|
+
/>
|
53
45
|
</a-row>
|
54
46
|
</template>
|
55
47
|
</edit-form>
|
@@ -59,21 +51,26 @@
|
|
59
51
|
|
60
52
|
<script>
|
61
53
|
import { Component, Vue, Watch } from '@a-vue'
|
62
|
-
import {
|
54
|
+
import { DialogEvent } from '@a-vue/events'
|
63
55
|
|
64
56
|
@Component({
|
65
|
-
props: ['model', 'title', 'show']
|
57
|
+
props: ['model', 'createModelToEdit', 'title', 'show']
|
66
58
|
})
|
67
59
|
export default class EditModal extends Vue {
|
68
60
|
show_ = false
|
61
|
+
ignoreChangesOnClose_ = false
|
69
62
|
|
70
|
-
|
63
|
+
created () {
|
64
|
+
if (this.show) { // open on create with v-show
|
65
|
+
this.open()
|
66
|
+
}
|
67
|
+
}
|
71
68
|
|
72
69
|
/**
|
73
70
|
* visiblility changes from outside
|
74
71
|
* this will trigger the show_ watcher,
|
75
72
|
* forward the change to the modal and
|
76
|
-
* later emit
|
73
|
+
* later emit an open/close event
|
77
74
|
*/
|
78
75
|
@Watch('show')
|
79
76
|
showChanged () {
|
@@ -90,7 +87,6 @@ export default class EditModal extends Vue {
|
|
90
87
|
@Watch('show_')
|
91
88
|
show_Changed () {
|
92
89
|
if (this.show_) {
|
93
|
-
this.reset()
|
94
90
|
this.$emit('open')
|
95
91
|
} else {
|
96
92
|
this.$emit('close')
|
@@ -101,16 +97,37 @@ export default class EditModal extends Vue {
|
|
101
97
|
this.show_ = true
|
102
98
|
}
|
103
99
|
|
104
|
-
|
105
|
-
|
100
|
+
async beforeClose () {
|
101
|
+
// run only if show_ is true to prevent double checks with a-modal
|
102
|
+
if (this.show_ && this.$refs.form.changed && !this.ignoreChangesOnClose_) {
|
103
|
+
const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
|
104
|
+
title: 'Änderungen verwerfen?',
|
105
|
+
message: 'Im Formular sind nicht gespeicherte Änderungen. Sollen diese verworfen werden?',
|
106
|
+
yesButton: 'Verwerfen'
|
107
|
+
}))
|
108
|
+
if (result !== DialogEvent.RESULT_YES) {
|
109
|
+
return false
|
110
|
+
}
|
111
|
+
}
|
112
|
+
return true
|
106
113
|
}
|
107
114
|
|
108
|
-
|
109
|
-
this
|
115
|
+
async close () {
|
116
|
+
const result = await this.beforeClose()
|
117
|
+
if (!result) {
|
118
|
+
return
|
119
|
+
}
|
120
|
+
|
121
|
+
this.show_ = false
|
110
122
|
}
|
111
123
|
|
112
|
-
|
113
|
-
|
124
|
+
/**
|
125
|
+
* hook to allow to leave a just created (saved) model
|
126
|
+
*/
|
127
|
+
ignoreChangesOnClose () {
|
128
|
+
// this.$refs.form.forceUnchanged()
|
129
|
+
console.info('TODO switch form to forceUnchanged')
|
130
|
+
this.ignoreChangesOnClose_ = true
|
114
131
|
}
|
115
132
|
}
|
116
133
|
</script>
|
@@ -64,9 +64,16 @@ export class ListViewMixin extends Vue {
|
|
64
64
|
.on('change', this.filtersChanged) // listen to change
|
65
65
|
|
66
66
|
this._filtersInitialized()
|
67
|
+
this.$emit('update:filters', this.filters)
|
68
|
+
this.$emit('update:listViewModel', this.listViewModel)
|
67
69
|
|
68
70
|
if (this.models) {
|
69
71
|
this.$emit('update:count', this.meta_.count_search)
|
72
|
+
|
73
|
+
this.$emit('onLoad', {
|
74
|
+
models: this.models_,
|
75
|
+
meta: this.meta_
|
76
|
+
})
|
70
77
|
} else {
|
71
78
|
this.load()
|
72
79
|
}
|
@@ -132,7 +139,7 @@ export class ListViewMixin extends Vue {
|
|
132
139
|
|
133
140
|
if (!models) { // error happened
|
134
141
|
this.isLoading = false
|
135
|
-
this.$emit('update:isLoading',
|
142
|
+
this.$emit('update:isLoading', false)
|
136
143
|
return
|
137
144
|
}
|
138
145
|
|
@@ -144,8 +151,13 @@ export class ListViewMixin extends Vue {
|
|
144
151
|
}
|
145
152
|
|
146
153
|
this.isLoading = false
|
147
|
-
this.$emit('update:isLoading',
|
154
|
+
this.$emit('update:isLoading', false)
|
148
155
|
|
149
156
|
this.$emit('update:count', this.meta_.count_search)
|
157
|
+
|
158
|
+
this.$emit('onLoad', {
|
159
|
+
models,
|
160
|
+
meta
|
161
|
+
})
|
150
162
|
}
|
151
163
|
}
|
@@ -32,9 +32,16 @@ function propsWithDefaults (props) {
|
|
32
32
|
// property: { some object }, should be a normal vue props object
|
33
33
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
34
34
|
normalizedProps[subProp] = value
|
35
|
-
// property: true, null, ...
|
35
|
+
// property: true, false, null, ...
|
36
36
|
} else {
|
37
|
-
|
37
|
+
if (typeof value === 'boolean') {
|
38
|
+
normalizedProps[subProp] = {
|
39
|
+
type: Boolean,
|
40
|
+
default: value
|
41
|
+
}
|
42
|
+
} else {
|
43
|
+
normalizedProps[subProp] = { default: value }
|
44
|
+
}
|
38
45
|
}
|
39
46
|
})
|
40
47
|
} else {
|
package/src/events.js
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
export { BaseEvent } from './plugins/event-bus/BaseEvent'
|
1
2
|
export { LoadingEvent } from './components/loading-indicator/LoadingEvent'
|
2
3
|
export { SaveEvent } from './components/save-indicator/SaveEvent'
|
3
4
|
export { AlertEvent } from './components/alert/AlertEvent'
|
4
5
|
export { DialogEvent } from './components/dialog/DialogEvent'
|
6
|
+
export { FlyingContextEvent } from './components/flying-context/FlyingContextEvent'
|
package/src-admin/bootstrap.js
CHANGED