glib-web 2.1.1 → 2.2.1

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.
@@ -1,10 +1,15 @@
1
1
  <template>
2
+ <!-- In the current Vuetify, there seems to be a bug with validate-on-blur where once a radioGroup has a validation error,
3
+ the error can only be removed by submitting the form. But this is better than the alternative, which is radioGroup showing
4
+ validation error on page load.
5
+ -->
2
6
  <v-radio-group
3
7
  v-model="fieldModel"
4
8
  :name="fieldName"
5
9
  :disabled="spec.readOnly"
6
10
  :rules="$validation()"
7
11
  :row="spec.row"
12
+ validate-on-blur
8
13
  @change="onChange"
9
14
  >
10
15
  <div v-for="(childView, index) in spec.childViews" :key="index">
@@ -1,34 +1,53 @@
1
1
  <template>
2
2
  <div :style="$styles()" :class="$classes()">
3
+ <v-tabs v-model="mode" fixed-tabs>
4
+ <v-tab>Editor</v-tab>
5
+ <v-tab>Code</v-tab>
6
+ </v-tabs>
7
+
3
8
  <v-progress-linear v-if="showProgress" v-model="progress.value" />
9
+
10
+ <!-- Remove the editor to avoid circular updating between this editor and the raw field. -->
4
11
  <VueEditor
5
- v-model="rawValue"
12
+ v-if="!rawMode"
13
+ id="rich-editor"
14
+ v-model="htmlValue"
6
15
  :editor-toolbar="customToolbar"
7
16
  use-custom-image-handler
8
- @text-change="onChange"
17
+ @text-change="onEditorChange"
9
18
  @image-added="uploadImage"
10
19
  />
11
- <input type="hidden" :name="spec.name" :value="produceValue" />
12
- <input
13
- v-for="(imageKey, index) in imageKeys"
14
- :key="index"
15
- type="hidden"
16
- :name="imageUploader.name"
17
- :value="images[imageKey]"
18
- />
20
+ <!-- Hide these fields but don't remove them because these are the values that will get submitted. -->
21
+ <div :style="{ display: rawMode ? 'block' : 'none' }">
22
+ <v-textarea
23
+ id="raw-editor"
24
+ v-model="producedValue"
25
+ :name="spec.name"
26
+ :style="$styles()"
27
+ :class="$classes()"
28
+ :outlined="$classes().includes('outlined')"
29
+ @input="onCodeChange"
30
+ ></v-textarea>
31
+ <v-text-field
32
+ v-for="(imageKey, index) in imageKeys"
33
+ :key="index"
34
+ :label="`Image ${index + 1}`"
35
+ :style="$styles()"
36
+ :outlined="$classes().includes('outlined')"
37
+ type="text"
38
+ :name="imageUploader.name"
39
+ :value="images[imageKey]"
40
+ />
41
+ </div>
19
42
  </div>
20
43
  </template>
21
44
 
22
45
  <script>
23
46
  import Uploader from "../../utils/uploader";
24
- import { VueEditor } from "vue2-editor";
47
+ import { VueEditor, Quill } from "vue2-editor";
25
48
  import TurndownService from "turndown";
26
49
  import { gfm } from "turndown-plugin-gfm";
27
50
 
28
- // import EventController, {
29
- // DISABLE_SUBMIT_BUTTON
30
- // } from "../../utils/global-event-controller";
31
-
32
51
  export default {
33
52
  components: { VueEditor },
34
53
  props: {
@@ -41,26 +60,52 @@ export default {
41
60
  [{ list: "ordered" }, { list: "bullet" }],
42
61
  ["image", "link"]
43
62
  ],
44
- rawValue: "",
45
- cleanValue: "",
63
+ htmlValue: "",
64
+ cleanValue: null,
65
+ producedValue: "",
46
66
  images: {},
47
67
  imageKeys: [],
48
68
  progress: { value: -1 },
49
69
  imageUploader: {},
50
- produce: "markdown",
70
+ produce: null,
71
+ mode: null,
51
72
  turndownService: new TurndownService({ headingStyle: "atx" })
52
73
  }),
53
74
  computed: {
54
- showProgress: function() {
75
+ showProgress() {
55
76
  return this.progress.value >= 0;
56
77
  },
57
- produceValue: function() {
58
- if (this.produce == "html") {
59
- return this.cleanValue;
60
- } else if (this.produce == "markdown") {
61
- return this.turndownService.turndown(this.cleanValue);
62
- } else {
63
- return null;
78
+ rawMode() {
79
+ return this.mode == 1;
80
+ }
81
+ },
82
+ watch: {
83
+ cleanValue(val, oldVal) {
84
+ if (oldVal == null) {
85
+ // Don't update `producedValue` if this is first-time initialization to preserve the original value.
86
+ return;
87
+ }
88
+ switch (this.produce) {
89
+ case "html":
90
+ this.producedValue = val;
91
+ break;
92
+ case "markdown":
93
+ this.producedValue = this.turndownService.turndown(val);
94
+ break;
95
+ default:
96
+ console.log(`Unsupported format: ${this.produce}`);
97
+ }
98
+ },
99
+ producedValue(val) {
100
+ switch (this.produce) {
101
+ case "html":
102
+ this.htmlValue = val;
103
+ break;
104
+ case "markdown":
105
+ this.htmlValue = this.toHtmlValue(val);
106
+ break;
107
+ default:
108
+ console.log(`Unsupported format: ${this.produce}`);
64
109
  }
65
110
  }
66
111
  },
@@ -69,6 +114,8 @@ export default {
69
114
  },
70
115
  methods: {
71
116
  $ready() {
117
+ this.produce = this.spec.produce || "markdown";
118
+
72
119
  this.turndownService.use(gfm);
73
120
  this.turndownService.addRule("strikethrough", {
74
121
  filter: ["del", "s", "strike"],
@@ -77,29 +124,39 @@ export default {
77
124
  }
78
125
  });
79
126
 
80
- const vm = this;
81
- this.rawValue = (this.spec.value || "").replace(
82
- /\{\{image([0-9]+)\}\}/g,
83
- function(_, index) {
84
- const image = vm.spec.images[index - 1];
85
- if (
86
- image &&
87
- vm.$type.isString(image.value) &&
88
- vm.$type.isString(image.fileUrl)
89
- ) {
90
- const url = image.fileUrl;
91
- const key = url.hashCode().toString();
92
- vm.images[key] = image.value;
93
- return url;
94
- }
95
- return "{{IMAGE_NOT_FOUND}}";
127
+ this.turndownService.addRule("codeblock", {
128
+ filter: ["pre"],
129
+ replacement: function(content) {
130
+ return "```\n" + content + "```";
96
131
  }
97
- );
132
+ });
98
133
 
99
134
  this.imageUploader = this.spec.imageUploader;
100
- if (this.spec.produce) {
101
- this.produce = this.spec.produce;
102
- }
135
+
136
+ // Convert initial markdown value to html for displaying.
137
+ this.producedValue = this.spec.value || "";
138
+ this.htmlValue = this.toHtmlValue(this.producedValue);
139
+ },
140
+ toHtmlValue(producedValue) {
141
+ const vm = this;
142
+ var value = producedValue.replace(/\{\{image([0-9]+)\}\}/g, function(
143
+ _,
144
+ index
145
+ ) {
146
+ const image = vm.spec.images[index - 1];
147
+ if (
148
+ image &&
149
+ vm.$type.isString(image.value) &&
150
+ vm.$type.isString(image.fileUrl)
151
+ ) {
152
+ const url = image.fileUrl;
153
+ const key = url.hashCode().toString();
154
+ vm.images[key] = image.value;
155
+ return url;
156
+ }
157
+ return "{{IMAGE_NOT_FOUND}}";
158
+ });
159
+ return this.produce == "markdown" ? Utils.format.markdown(value) : value;
103
160
  },
104
161
  uploadImage: function(file, editor, cursorLocation) {
105
162
  let vm = this;
@@ -127,9 +184,11 @@ export default {
127
184
  });
128
185
  }
129
186
  },
130
- onChange() {
187
+ onEditorChange() {
131
188
  this.separateOutImages();
132
-
189
+ this.onCodeChange();
190
+ },
191
+ onCodeChange() {
133
192
  Utils.type.ifObject(this.spec.onChange, onChange => {
134
193
  this.$nextTick(() => {
135
194
  const params = {
@@ -148,7 +207,7 @@ export default {
148
207
  var index = 0;
149
208
  vm.imageKeys.clear();
150
209
  // TODO: Fix to avoid replacing <video src="">
151
- this.cleanValue = this.rawValue.replace(/src="([^"]+)"/g, function(
210
+ this.cleanValue = this.htmlValue.replace(/src="([^"]+)"/g, function(
152
211
  _,
153
212
  imageValue
154
213
  ) {
@@ -220,9 +279,13 @@ export default {
220
279
  height: 375px;
221
280
  }
222
281
  */
223
- .ql-toolbar.sticky {
282
+ /* .ql-toolbar.sticky {
224
283
  position: fixed;
225
284
  z-index: 99;
226
285
  background-color: white;
286
+ } */
287
+ #rich-editor,
288
+ #raw-editor {
289
+ height: 350px;
227
290
  }
228
291
  </style>
@@ -61,6 +61,15 @@ export default {
61
61
  & > span > :last-child {
62
62
  margin-bottom: 0;
63
63
  }
64
+ pre {
65
+ background-color: #f0f0f0;
66
+ padding: 8px 10px;
67
+
68
+ & > code {
69
+ background-color: transparent;
70
+ padding: 0;
71
+ }
72
+ }
64
73
  }
65
74
  .line-clamp {
66
75
  display: -webkit-box;
@@ -20,6 +20,16 @@ export default {
20
20
  v => (v ? true : spec.message)
21
21
  ]);
22
22
  });
23
+ Utils.type.ifObject(val.format, spec => {
24
+ augmentedRules = augmentedRules.concat([
25
+ v => {
26
+ if (v && !v.match(spec.regex)) {
27
+ return spec.message;
28
+ }
29
+ return true;
30
+ }
31
+ ]);
32
+ });
23
33
  });
24
34
  return augmentedRules;
25
35
  },
@@ -202,10 +202,7 @@ export default {
202
202
  // Has to be executed before $ready(). This executes regardless of whether a form is found because fields
203
203
  // may be used without a form.
204
204
  this.fieldName = this.spec.name;
205
- // Don't initialize fieldModel unnecessarily because this will trigger validations on page load
206
- if (this.spec.value) {
207
- this.fieldModel = this._sanitizeValue(this.spec.value);
208
- }
205
+ this.fieldModel = this._sanitizeValue(this.spec.value);
209
206
  }
210
207
  },
211
208
  $classes(spec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {