Skip to content

Commit

Permalink
wip: some further work (try) on "styling-tags"
Browse files Browse the repository at this point in the history
based on @Brimstedt's work #54
  • Loading branch information
fritx committed Apr 1, 2018
1 parent 2cd62ce commit 725d7a1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 21 deletions.
10 changes: 6 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
contenteditable></div>
</at>

<at :members="members" name-key="name" v-model="html">
<at :members="members" name-key="name" v-model="html2">
<!-- custom: same as default slot -->
<!-- <template slot="item" scope="s">
<span v-text="s.item"></span>
</template> -->

<span slot="embeddedItem" slot-scope="item">
<span class="tag"><img class="avatar" :src="item.current.avatar"/>{{ item.current.name }}</span>
<span slot="embeddedItem" slot-scope="s">
<span class="tag"><img class="avatar" :src="s.current.avatar">{{ s.current.name }}</span>
</span>

<!-- custom: with avatars -->
Expand Down Expand Up @@ -79,7 +79,7 @@ export default {
components: { At, AtTa },
name: 'app',
data () {
return {
const data = {
members,
text: `
<<< Textarea >>>
Expand Down Expand Up @@ -108,6 +108,8 @@ Playback - Video player.
<div>&lt;&lt;&lt; Content Editable Div &gt;&gt;&gt;</div><div>Awesome Electron&nbsp;<img style="max-width: 50px;" src="static/awesome.svg"></div><div><img style="max-width: 50px;" src="static/electron.svg"></div><div>Useful resources for creating apps with&nbsp;Electron</div><div>Inspired by the&nbsp;awesome&nbsp;list thing. You might also like&nbsp;awesome-nodejs.</div><div>Example apps</div><div>Some good apps written with Electron.</div><div>Open Source</div><div>Atom&nbsp;- Code editor.</div><div>Nuclide&nbsp;- Unified IDE.</div><div>Playback&nbsp;- Video player.</div>
`.trim() // fix trailing abnormal nodes
}
data.html2 = data.html
return data
}
}
</script>
Expand Down
104 changes: 88 additions & 16 deletions src/At.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default {
// at[v-model] mode should be on only when
// initial :value/v-model is present (not nil)
bindsValue: this.value != null,
customsEmbedded: false,
hasComposition: false,
atwho: null
}
Expand Down Expand Up @@ -125,6 +126,9 @@ export default {
}
},
mounted () {
if (this.$scopedSlots.embeddedItem) {
this.customsEmbedded = true
}
if (this.bindsValue) {
this.handleValueUpdate(this.value)
}
Expand Down Expand Up @@ -157,9 +161,45 @@ export default {
handleDelete (e) {
const range = getPrecedingRange()
if (range) {
// fixme: Very bad code from me
if (this.customsEmbedded && range.endOffset >= 1) {
let a = range.endContainer.childNodes[range.endOffset]
|| range.endContainer.childNodes[range.endOffset - 1]
if (!a || a.nodeType === Node.TEXT_NODE && !/^\s?$/.test(a.data)) {
return
} else if (a.nodeType === Node.TEXT_NODE) {
if (a.previousSibling) a = a.previousSibling
} else {
if (a.previousElementSibling) a = a.previousElementSibling
}
let ch = [].slice.call(a.childNodes)
ch = [].reverse.call(ch)
ch.unshift(a)
let last
;[].some.call(ch, c => {
if (c.getAttribute && c.getAttribute('data-at-embedded') != null) {
last = c
return true
}
})
if (last) {
e.preventDefault()
e.stopPropagation()
const r = getRange()
if (r) {
r.setStartBefore(last)
r.deleteContents()
applyRange(r)
this.handleInput()
}
}
return
}
const { atItems, members, suffix, deleteMatch, itemName } = this
const text = range.toString()
const { at, index } = getAtAndIndex(text, atItems)
if (index > -1) {
const chunk = text.slice(index + at.length)
const has = members.some(v => {
Expand Down Expand Up @@ -332,47 +372,79 @@ export default {
},
// todo: 抽离成库并测试
insertText (text, r, suffix) {
insertText (text, r) {
r.deleteContents()
const node = r.endContainer
if (node.nodeType === Node.TEXT_NODE) {
const cut = r.endOffset
node.data = node.data.slice(0, cut) +
text + node.data.slice(cut)
r.setEnd(node, cut + text.length)
} else {
const t = document.createTextNode(text)
r.insertNode(t)
r.setEndAfter(t)
}
r.collapse(false) // 参数在IE下必传
applyRange(r)
},
insertHtml (html, r) {
r.deleteContents()
const node = r.endContainer
var newElement = document.createElement('span')
// Seems `contentediable=false` should includes spaces,
// otherwise, caret can't be placed well across them
newElement.appendChild(document.createTextNode(' '))
newElement.appendChild(this.htmlToElement(html))
newElement.appendChild(document.createTextNode(' '))
newElement.setAttribute('data-at-embedded', '')
newElement.setAttribute("contenteditable", false)
if (node.nodeType === Node.TEXT_NODE) {
const cut = r.endOffset
var secondPart = node.splitText(cut);
var newElement = this.htmlToElement(text);
if (newElement.setAttribute) {
newElement.setAttribute("contenteditable", false);
}
node.parentNode.insertBefore(newElement, secondPart);
secondPart.data = suffix + secondPart.data;
r.setEnd(secondPart, 1);
r.setEndBefore(secondPart)
} else {
const t = document.createTextNode(text)
const t = document.createTextNode(suffix)
r.insertNode(newElement)
r.setEndAfter(newElement)
r.insertNode(t)
r.setEndAfter(t)
}
r.collapse(false) // 参数在IE下必传
applyRange(r)
},
insertItem () {
const { range, offset, list, cur } = this.atwho
const { suffix, atItems, itemName } = this
const { suffix, atItems, itemName, customsEmbedded } = this
const r = range.cloneRange()
const text = range.toString()
const { at, index } = getAtAndIndex(text, atItems)
const start = index + at.length // 从@后第一位开始
// Leading `@` is automatically dropped as `customsEmbedded=true`
// You can fully custom the output inside the embedded slot
const start = customsEmbedded ? index : index + at.length
r.setStart(r.endContainer, start)
// hack: 连续两次 可以确保click后 focus回来 range真正生效
applyRange(r)
applyRange(r)
const curItem = list[cur]
const t = itemName(curItem) + suffix
const { embeddedItem } = this.$refs;
this.insertText(embeddedItem.firstChild.innerHTML, r, suffix);
if (customsEmbedded) {
// `suffix` is ignored as `customsEmbedded=true` has to be
// wrapped around by spaces
const html = this.$refs.embeddedItem.firstChild.innerHTML
this.insertHtml(html, r);
} else {
const t = itemName(curItem) + suffix
this.insertText(t, r);
}
this.$emit('insert', curItem)
this.handleInput()
},
Expand Down
2 changes: 1 addition & 1 deletion src/AtTemplate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</div>
</div>
<span v-show="false" ref="embeddedItem">
<slot name="embeddedItem" :current="currentItem"><span>{{ itemName(currentItem) }}</span></slot>
<slot name="embeddedItem" :current="currentItem"></slot>
</span>
<slot></slot>
</div>
Expand Down

0 comments on commit 725d7a1

Please sign in to comment.