123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- <template>
- <div contenteditable class="url-field" ref="editor" spellcheck="false"></div>
- </template>
- <style lang="scss">
- .highlight-VAR {
- @apply font-bold;
- @apply text-acColor;
- }
- .highlight-TEXT {
- @apply overflow-auto;
- @apply break-all;
- height: 22px;
- }
- .highlight-TEXT::-webkit-scrollbar {
- @apply hidden;
- }
- </style>
- <script>
- export default {
- props: {
- value: { type: String, default: "" },
- },
- data() {
- return {
- cacheValue: null,
- unwatchValue: null,
- }
- },
- mounted() {
- this.$refs.editor.addEventListener("input", this.updateEditor)
- this.$refs.editor.textContent = this.value || ""
- this.cacheValue = this.value || ""
- this.unwatchValue = this.$watch(
- () => this.value,
- (newVal) => {
- if (this.$refs.editor && this.cacheValue !== newVal)
- this.$refs.editor.textContent = newVal || ""
- this.updateEditor()
- }
- )
- this.updateEditor()
- },
- beforeDestroy() {
- this.unwatchValue()
- this.$refs.editor.removeEventListener("input", this.updateEditor)
- },
- methods: {
- renderText(text) {
- const fixedText = text.replace(/(\r\n|\n|\r)/gm, "").trim()
- const parseMap = this.parseURL(fixedText)
- const convertSpan = document.createElement("span")
- const output = parseMap.map(([start, end, protocol]) => {
- convertSpan.textContent = fixedText.substring(start, end + 1)
- return `<span class='highlight-${protocol}'>${convertSpan.innerHTML}</span>`
- })
- return output.join("")
- },
- parseURL(text) {
- const map = []
- const regex = /<<\w+>>/
- let match
- let index = 0
- while ((match = text.substring(index).match(regex))) {
- map.push([index, index + (match.index - 1), "TEXT"])
- map.push([index + match.index, index + match.index + match[0].length - 1, "VAR"])
- index += match.index + match[0].length
- if (index >= text.length - 1) break
- }
- if (text.length > index && !text.substring(index).match(regex)) {
- map.push([index, text.length, "TEXT"])
- }
- return map
- },
- getTextSegments({ childNodes }) {
- const textSegments = []
- Array.from(childNodes).forEach((node) => {
- switch (node.nodeType) {
- case Node.TEXT_NODE:
- textSegments.push({ text: node.nodeValue, node })
- break
- case Node.ELEMENT_NODE:
- textSegments.splice(textSegments.length, 0, ...this.getTextSegments(node))
- break
- }
- })
- return textSegments
- },
- restoreSelection(absoluteAnchorIndex, absoluteFocusIndex) {
- const sel = window.getSelection()
- const textSegments = this.getTextSegments(this.$refs.editor)
- let anchorNode = this.$refs.editor
- let anchorIndex = 0
- let focusNode = this.$refs.editor
- let focusIndex = 0
- let currentIndex = 0
- textSegments.forEach(({ text, node }) => {
- const startIndexOfNode = currentIndex
- const endIndexOfNode = startIndexOfNode + text.length
- if (startIndexOfNode <= absoluteAnchorIndex && absoluteAnchorIndex <= endIndexOfNode) {
- anchorNode = node
- anchorIndex = absoluteAnchorIndex - startIndexOfNode
- }
- if (startIndexOfNode <= absoluteFocusIndex && absoluteFocusIndex <= endIndexOfNode) {
- focusNode = node
- focusIndex = absoluteFocusIndex - startIndexOfNode
- }
- currentIndex += text.length
- })
- sel.setBaseAndExtent(anchorNode, anchorIndex, focusNode, focusIndex)
- },
- updateEditor() {
- this.cacheValue = this.$refs.editor.textContent
- const sel = window.getSelection()
- const textSegments = this.getTextSegments(this.$refs.editor)
- const textContent = textSegments.map(({ text }) => text).join("")
- let anchorIndex = null
- let focusIndex = null
- let currentIndex = 0
- textSegments.forEach(({ text, node }) => {
- if (node === sel.anchorNode) {
- anchorIndex = currentIndex + sel.anchorOffset
- }
- if (node === sel.focusNode) {
- focusIndex = currentIndex + sel.focusOffset
- }
- currentIndex += text.length
- })
- this.$refs.editor.innerHTML = this.renderText(textContent)
- this.restoreSelection(anchorIndex, focusIndex)
- this.$emit("input", this.$refs.editor.textContent)
- },
- },
- }
- </script>
|