Skip to content

Commit 91acffd

Browse files
committed
feat: Further QEditor work
1 parent ca65e26 commit 91acffd

File tree

7 files changed

+167
-101
lines changed

7 files changed

+167
-101
lines changed

dev/components/form/editor.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
<q-editor
44
v-model="model"
55
:toolbar="[
6-
['bold', 'italic', 'underline', 'strike', 'h1', 'paragraph'],
7-
['subscript', 'superscript'],
6+
['bold', 'italic', 'underline', 'strike', 'h1', 'p'],
7+
['link', 'print', 'subscript', 'superscript'],
88
['quote', 'bullet', 'number', 'outdent', 'indent'],
9-
[['paragraph', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']], ['hr', 'removeFormat'],
9+
[['size-1', 'size-2', 'size-3', 'size-4', 'size-5', 'size-6', 'size-7']],
10+
[['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code']], ['hr', 'removeFormat'],
1011
['undo', 'redo']
1112
]"
1213
>

src/components/btn/QBtnDropdown.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export default {
4949
staticClass: 'transition-generic',
5050
'class': {
5151
'rotate-180': this.opened,
52-
'on-right': !this.split
52+
'on-right': !this.split,
53+
'q-btn-dropdown-arrow': !this.split
5354
}
5455
}
5556
),
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
.q-btn-dropdown-arrow
2-
padding 0 4px
1+
.q-btn-dropdown-simple
2+
padding-right 0
3+
.q-btn-dropdown-arrow
4+
margin-right 8px
35
.q-btn-dropdown-split .q-btn-dropdown-arrow
6+
padding 0 4px
47
border-left 1px solid rgba(255, 255, 255, .25)

src/components/editor/QEditor.js

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { getToolbar } from './editor-utils'
33
import { buttons } from './editor-definitions'
44
import { Caret } from './editor-caret'
55

6+
document.execCommand('defaultParagraphSeparator', false, 'div')
7+
68
export default {
79
name: 'q-editor',
810
provide () {
@@ -49,55 +51,39 @@ export default {
4951
return !this.readonly && !this.disable
5052
},
5153
buttons () {
52-
const getBtn = name => {
53-
const
54-
btn = this.definitions[name] || buttons[name],
55-
state = this.attrib[btn.test || btn.cmd]
56-
57-
if (state === void 0 && (btn.type === 'toggle' || btn.disable)) {
58-
this.attrib[btn.test || btn.cmd] = false
59-
}
60-
return btn
61-
}
62-
54+
const getBtn = name => this.definitions[name] || buttons[name]
6355
return this.toolbar.map(
64-
group => group.map(name => {
65-
if (Array.isArray(name)) {
66-
return name.map(item => getBtn(item))
67-
}
68-
return getBtn(name)
69-
})
56+
group => group.map(name => Array.isArray(name) ? name.map(item => getBtn(item)) : getBtn(name))
7057
)
7158
},
7259
keys () {
73-
const k = {}
74-
this.buttons.forEach(group => {
75-
group.forEach(btn => {
76-
if (Array.isArray(btn)) {
77-
btn.forEach(item => {
78-
if (item.key) {
79-
k[item.key] = {
80-
cmd: btn.cmd,
81-
param: btn.param
82-
}
83-
}
84-
})
85-
}
86-
else if (btn.key) {
60+
const
61+
k = {},
62+
add = btn => {
63+
if (btn.key) {
8764
k[btn.key] = {
8865
cmd: btn.cmd,
8966
param: btn.param
9067
}
9168
}
69+
}
70+
71+
this.buttons.forEach(group => {
72+
group.forEach(btn => {
73+
if (Array.isArray(btn)) {
74+
btn.forEach(add)
75+
}
76+
else {
77+
add(btn)
78+
}
9279
})
9380
})
9481
return k
9582
}
9683
},
9784
data () {
9885
return {
99-
editWatcher: true,
100-
attrib: {}
86+
editWatcher: true
10187
}
10288
},
10389
watch: {
@@ -119,7 +105,7 @@ export default {
119105
},
120106
onKeydown (e) {
121107
const key = getEventKey(e)
122-
this.updateAttributes()
108+
this.refreshToolbar()
123109

124110
if (!e.ctrlKey || [17, 65, 67, 86].includes(key)) {
125111
return
@@ -135,34 +121,26 @@ export default {
135121
},
136122
runCmd (cmd, param, update = true) {
137123
console.log('applying', cmd, param)
138-
this.caret.apply(cmd, param)
139-
this.$refs.content.focus()
124+
this.caret.apply(cmd, param, () => {
125+
this.$refs.content.focus()
140126

141-
if (update) {
142-
this.updateAttributes()
143-
}
127+
if (update) {
128+
this.refreshToolbar()
129+
}
130+
})
144131
},
145-
updateAttributes () {
132+
refreshToolbar () {
146133
setTimeout(() => {
147-
let change = false
148-
Object.keys(this.attrib).forEach(cmd => {
149-
const state = this.caret.is(cmd)
150-
if (this.attrib[cmd] !== state) {
151-
this.attrib[cmd] = state
152-
change = true
153-
}
154-
})
155-
if (change) {
156-
console.log('forceUpdate')
157-
this.$forceUpdate()
158-
}
134+
this.$forceUpdate()
159135
}, 1)
160136
}
161137
},
162138
mounted () {
163-
this.$refs.content.innerHTML = this.value
164-
this.caret = new Caret(this.$refs.content)
165-
document.execCommand('defaultParagraphSeparator', false, 'div')
139+
this.$nextTick(() => {
140+
this.caret = new Caret(this.$refs.content)
141+
this.$refs.content.innerHTML = this.value
142+
this.$nextTick(this.refreshToolbar)
143+
})
166144
},
167145
render (h) {
168146
const attr = this.attrib
@@ -184,7 +162,7 @@ export default {
184162
on: {
185163
input: this.onInput,
186164
keydown: this.onKeydown,
187-
click: this.updateAttributes,
165+
click: this.refreshToolbar,
188166
blur: () => {
189167
this.caret.save()
190168
}

src/components/editor/editor-caret.js

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Dialog from '../dialog'
2+
13
function getBlockElement (el, parent) {
24
if (parent && el === parent) {
35
return null
@@ -37,10 +39,10 @@ export class Caret {
3739
}
3840

3941
get selection () {
40-
var sel = document.getSelection()
4142
if (!this.el) {
42-
return sel
43+
return
4344
}
45+
const sel = document.getSelection()
4446
// only when the selection in element
4547
if (isChildOf(sel.anchorNode, this.el) && isChildOf(sel.focusNode, this.el)) {
4648
return sel
@@ -51,7 +53,7 @@ export class Caret {
5153
const sel = this.selection
5254

5355
if (!sel) {
54-
return null
56+
return
5557
}
5658

5759
return sel.rangeCount
@@ -62,7 +64,7 @@ export class Caret {
6264
get parent () {
6365
const range = this.range
6466
if (!range) {
65-
return null
67+
return
6668
}
6769

6870
const node = range.startContainer
@@ -74,7 +76,7 @@ export class Caret {
7476
get blockParent () {
7577
const parent = this.parent
7678
if (!parent) {
77-
return null
79+
return
7880
}
7981
return getBlockElement(parent, this.el)
8082
}
@@ -117,11 +119,27 @@ export class Caret {
117119
: false
118120
}
119121

120-
is (name) {
121-
if (['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'A', 'IMG'].includes(name)) {
122-
return this.hasParent(name)
122+
is (cmd, param) {
123+
switch (cmd) {
124+
case 'formatBlock':
125+
if (param === 'DIV' && this.parent === this.el) {
126+
return true
127+
}
128+
return this.hasParent(param, param === 'PRE')
129+
case 'link':
130+
return this.hasParent('A', true)
131+
case 'fontSize':
132+
return document.queryCommandValue(cmd) === param
133+
default:
134+
const state = document.queryCommandState(cmd)
135+
return param ? state === param : state
136+
}
137+
}
138+
139+
getParentAttribute (attrib) {
140+
if (this.parent) {
141+
return this.parent.getAttribute(attrib)
123142
}
124-
return document.queryCommandState(name)
125143
}
126144

127145
can (name) {
@@ -141,13 +159,62 @@ export class Caret {
141159
}
142160
}
143161

144-
apply (cmd, param) {
145-
if (cmd === 'formatBlock' && ['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'A', 'IMG'].includes(param)) {
146-
if (this.is(param)) {
162+
apply (cmd, param, done = () => {}) {
163+
if (cmd === 'formatBlock') {
164+
if (['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'].includes(param) && this.is(param)) {
147165
this.apply('outdent')
166+
done()
148167
return
149168
}
150169
}
170+
else if (cmd === 'print') {
171+
const win = window.open('', '_blank', 'width=600,height=700,left=200,top=100,menubar=no,toolbar=no,location=no,scrollbars=yes')
172+
win.document.open()
173+
win.document.write(`<!doctype html><html><head><title>Print</title></head><body onload="print();"><div>${this.el.innerHTML}</div></body></html>`)
174+
win.document.close()
175+
done()
176+
return
177+
}
178+
else if (cmd === 'link') {
179+
const link = this.getParentAttribute('href')
180+
if (link && this.range) {
181+
this.range.selectNodeContents(this.parent)
182+
}
183+
this.save()
184+
Dialog.create({
185+
title: 'Link',
186+
message: this.selection ? this.selection.toString() : null,
187+
form: {
188+
url: {
189+
type: 'text',
190+
label: 'URL',
191+
model: link || 'http://'
192+
}
193+
},
194+
buttons: [
195+
{
196+
label: link ? 'Remove' : 'Cancel',
197+
handler: () => {
198+
if (link) {
199+
this.restore()
200+
document.execCommand('unlink')
201+
done()
202+
}
203+
}
204+
},
205+
{
206+
label: link ? 'Update' : 'Create',
207+
handler: data => {
208+
this.restore()
209+
document.execCommand('createLink', false, data.url)
210+
done()
211+
}
212+
}
213+
]
214+
})
215+
return
216+
}
151217
document.execCommand(cmd, false, param)
218+
done()
152219
}
153220
}

src/components/editor/editor-definitions.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,40 @@ export const buttons = {
88
number: {type: 'toggle', cmd: 'insertOrderedList', icon: 'format_list_numbered', tip: 'Numbered style list'},
99
subscript: {type: 'toggle', cmd: 'subscript', icon: 'vertical_align_bottom', tip: 'Subscript'},
1010
superscript: {type: 'toggle', cmd: 'superscript', icon: 'vertical_align_top', tip: 'Superscript'},
11+
link: {type: 'toggle', cmd: 'link', icon: 'link', tip: 'Link', key: 76},
1112

1213
quote: {type: 'toggle', test: 'BLOCKQUOTE', cmd: 'formatBlock', param: 'BLOCKQUOTE', icon: 'format_quote', tip: 'Quote', key: 81},
1314
left: {type: 'toggle', cmd: 'justifyLeft', icon: 'format_align_left', tip: 'Align to left'},
1415
center: {type: 'toggle', cmd: 'justifyCenter', icon: 'format_align_center', tip: 'Align to center'},
1516
right: {type: 'toggle', cmd: 'justifyRight', icon: 'format_align_right', tip: 'Align to right'},
1617
justify: {type: 'toggle', cmd: 'justifyFull', icon: 'format_align_justify', tip: 'Justify'},
1718

18-
// execute
19-
outdent: {type: 'execute', disable: vm => vm.caret && !vm.caret.can('outdent'), cmd: 'outdent', icon: 'format_indent_decrease', tip: 'Decrease indentation'},
20-
indent: {type: 'execute', disable: vm => vm.caret && !vm.caret.can('indent'), cmd: 'indent', icon: 'format_indent_increase', tip: 'Increase indentation'},
21-
highlight: {type: 'execute', cmd: 'hiliteColor', param: '#D4FF00', icon: 'format_color_text', tip: 'Highlight'}, // no IE
22-
removeFormat: {type: 'execute', cmd: 'removeFormat', icon: 'format_clear', tip: 'Remove formatting'},
23-
hr: {type: 'execute', cmd: 'insertHorizontalRule', icon: 'remove', tip: 'Horizontal line'},
24-
undo: {type: 'execute', cmd: 'undo', icon: 'undo', tip: 'Undo', key: 90},
25-
redo: {type: 'execute', cmd: 'redo', icon: 'redo', tip: 'Redo', key: 89},
26-
27-
h1: {type: 'toggle', test: 'H1', cmd: 'formatBlock', param: 'H1', icon: 'format_size', tip: 'Heading H1'},
28-
h2: {type: 'toggle', test: 'H2', cmd: 'formatBlock', param: 'H2', icon: 'format_size', tip: 'Heading H2'},
29-
h3: {type: 'toggle', test: 'H3', cmd: 'formatBlock', param: 'H3', icon: 'format_size', tip: 'Heading H3'},
30-
h4: {type: 'toggle', test: 'H4', cmd: 'formatBlock', param: 'H4', icon: 'format_size', tip: 'Heading H4'},
31-
h5: {type: 'toggle', test: 'H5', cmd: 'formatBlock', param: 'H5', icon: 'format_size', tip: 'Heading H5'},
32-
h6: {type: 'toggle', test: 'H6', cmd: 'formatBlock', param: 'H6', icon: 'format_size', tip: 'Heading H6'},
33-
paragraph: {type: 'toggle', test: 'DIV', cmd: 'formatBlock', param: 'DIV', icon: 'format_size', tip: 'Paragraph'}
19+
// run
20+
print: {type: 'run', cmd: 'print', icon: 'print', tip: 'Print'},
21+
outdent: {type: 'run', disable: vm => vm.caret && !vm.caret.can('outdent'), cmd: 'outdent', icon: 'format_indent_decrease', tip: 'Decrease indentation'},
22+
indent: {type: 'run', disable: vm => vm.caret && !vm.caret.can('indent'), cmd: 'indent', icon: 'format_indent_increase', tip: 'Increase indentation'},
23+
highlight: {type: 'run', cmd: 'hiliteColor', param: '#D4FF00', icon: 'format_color_text', tip: 'Highlight'}, // no IE
24+
removeFormat: {type: 'run', cmd: 'removeFormat', icon: 'format_clear', tip: 'Remove formatting'},
25+
hr: {type: 'run', cmd: 'insertHorizontalRule', icon: 'remove', tip: 'Horizontal line'},
26+
undo: {type: 'run', cmd: 'undo', icon: 'undo', tip: 'Undo', key: 90},
27+
redo: {type: 'run', cmd: 'redo', icon: 'redo', tip: 'Redo', key: 89},
28+
29+
h1: {type: 'toggle', cmd: 'formatBlock', param: 'H1', icon: 'format_size', tip: 'Title H1'},
30+
h2: {type: 'toggle', cmd: 'formatBlock', param: 'H2', icon: 'format_size', tip: 'Title H2'},
31+
h3: {type: 'toggle', cmd: 'formatBlock', param: 'H3', icon: 'format_size', tip: 'Title H3'},
32+
h4: {type: 'toggle', cmd: 'formatBlock', param: 'H4', icon: 'format_size', tip: 'Title H4'},
33+
h5: {type: 'toggle', cmd: 'formatBlock', param: 'H5', icon: 'format_size', tip: 'Title H5'},
34+
h6: {type: 'toggle', cmd: 'formatBlock', param: 'H6', icon: 'format_size', tip: 'Title H6'},
35+
p: {type: 'toggle', cmd: 'formatBlock', param: 'DIV', icon: 'format_size', tip: 'Paragraph'},
36+
code: {type: 'toggle', cmd: 'formatBlock', param: 'PRE', icon: 'code', tip: 'Code'},
37+
38+
'size-1': {type: 'toggle', cmd: 'fontSize', param: '1', icon: 'filter_1', tip: 'Very small'},
39+
'size-2': {type: 'toggle', cmd: 'fontSize', param: '2', icon: 'filter_2', tip: 'A bit small'},
40+
'size-3': {type: 'toggle', cmd: 'fontSize', param: '3', icon: 'filter_3', tip: 'Normal'},
41+
'size-4': {type: 'toggle', cmd: 'fontSize', param: '4', icon: 'filter_4', tip: 'Medium-large'},
42+
'size-5': {type: 'toggle', cmd: 'fontSize', param: '5', icon: 'filter_5', tip: 'Big'},
43+
'size-6': {type: 'toggle', cmd: 'fontSize', param: '6', icon: 'filter_6', tip: 'Very big'},
44+
'size-7': {type: 'toggle', cmd: 'fontSize', param: '7', icon: 'filter_7', tip: 'Maximum'}
3445

3546
/*
3647
link: {cmd: 'link', icon: 'link', tip: 'Link', key: 76},

0 commit comments

Comments
 (0)