|
|
|
@@ -173,7 +173,8 @@
|
|
|
|
|
<span class="status-menu" v-on:click=" hash=0; save()">
|
|
|
|
|
|
|
|
|
|
<span v-if="diffsApplied > 0">
|
|
|
|
|
+{{ diffsApplied }} Unsaved Changes
|
|
|
|
|
<i class="blue wave square icon"></i>
|
|
|
|
|
+{{ diffsApplied }}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<span v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="left center">
|
|
|
|
@@ -360,6 +361,8 @@
|
|
|
|
|
|
|
|
|
|
import SquireButtonFunctions from '@/mixins/SquireButtonFunctions.js'
|
|
|
|
|
|
|
|
|
|
let rawNoteText = '' // Used for comparing and generating diffs
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'NoteInputPanel',
|
|
|
|
|
props: [ 'noteid', 'position', 'openMenu', 'urlData', 'openNotes'],
|
|
|
|
@@ -390,7 +393,6 @@
|
|
|
|
|
created: '',
|
|
|
|
|
updated: '',
|
|
|
|
|
shareUsername: null,
|
|
|
|
|
// diffNoteText: '',
|
|
|
|
|
statusText: 'saved',
|
|
|
|
|
lastNoteHash: null,
|
|
|
|
|
saveDebounce: null, //Prevent save from being called numerous times quickly
|
|
|
|
@@ -428,6 +430,7 @@
|
|
|
|
|
//Used to restore caret position
|
|
|
|
|
lastRange: null,
|
|
|
|
|
startOffset: 0,
|
|
|
|
|
childIndex: null,
|
|
|
|
|
|
|
|
|
|
//Tag Display
|
|
|
|
|
allTags: [],
|
|
|
|
@@ -570,33 +573,31 @@
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
initSquire(){
|
|
|
|
|
initSquireEvents(){
|
|
|
|
|
|
|
|
|
|
//Set up squire and load note text
|
|
|
|
|
this.setText(this.noteText)
|
|
|
|
|
|
|
|
|
|
// Use squire box HTML for diff/patch changes
|
|
|
|
|
rawNoteText = document.getElementById('squire-id').innerHTML
|
|
|
|
|
|
|
|
|
|
//focus on open, not on mobile, it causes the keyboard to pop up, thats annoying
|
|
|
|
|
if(!this.$store.getters.getIsUserOnMobile){
|
|
|
|
|
this.editor.focus()
|
|
|
|
|
this.editor.moveCursorToEnd()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Set up websockets after squire is set up
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.setupWebSockets()
|
|
|
|
|
}, 500)
|
|
|
|
|
|
|
|
|
|
this.editor.addEventListener('cursor', e => {
|
|
|
|
|
|
|
|
|
|
//Save range to replace cursor if someone else makes an update
|
|
|
|
|
this.lastRange = e.range
|
|
|
|
|
this.startOffset = parseInt(e.range.startOffset)
|
|
|
|
|
return
|
|
|
|
|
this.saveCaretPosition(e)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//Change button states on editor when element is active
|
|
|
|
|
//eg; Bold button turns green when on bold text
|
|
|
|
|
this.editor.addEventListener('pathChange', e => this.pathChangeEvent(e))
|
|
|
|
|
this.editor.addEventListener('pathChange', e => {
|
|
|
|
|
this.pathChangeEvent(e)
|
|
|
|
|
this.diffText(e)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//Click Event - Open links when clicked in editor or toggle checks
|
|
|
|
|
this.editor.addEventListener('click', e => {
|
|
|
|
@@ -681,10 +682,17 @@
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//Bind event handlers
|
|
|
|
|
this.editor.addEventListener('keyup', event => {
|
|
|
|
|
|
|
|
|
|
this.onKeyup(event)
|
|
|
|
|
this.diffText(event)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.editor.addEventListener('focus', e => {
|
|
|
|
|
// this.diffText(e)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.editor.addEventListener('blur', e => {
|
|
|
|
|
this.diffText(e)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// this.editor.addEventListener("dragstart", e => {
|
|
|
|
@@ -692,12 +700,6 @@
|
|
|
|
|
// console.log(e)
|
|
|
|
|
// if(){}
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
//Show and hide additional toolbars
|
|
|
|
|
// this.editor.addEventListener('focus', e => {
|
|
|
|
|
// })
|
|
|
|
|
// this.editor.addEventListener('blur', e => {
|
|
|
|
|
// })
|
|
|
|
|
},
|
|
|
|
|
openEditAttachment(){
|
|
|
|
|
|
|
|
|
@@ -760,13 +762,18 @@
|
|
|
|
|
//Setup all responsive vue data
|
|
|
|
|
this.setupLoadedNoteData(response)
|
|
|
|
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
|
|
|
|
//Adjust note title size after load
|
|
|
|
|
this.titleResize()
|
|
|
|
|
this.initSquire()
|
|
|
|
|
this.initSquireEvents()
|
|
|
|
|
|
|
|
|
|
//Set up websockets after squire is set up
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.initWebsocketEvents()
|
|
|
|
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
}, 500)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
@@ -786,14 +793,11 @@
|
|
|
|
|
this.created = response.data.created
|
|
|
|
|
this.updated = response.data.updated
|
|
|
|
|
this.lastInteractionTimestamp = +new Date
|
|
|
|
|
this.noteTitle = ''
|
|
|
|
|
if(response.data.title){
|
|
|
|
|
this.noteTitle = response.data.title
|
|
|
|
|
}
|
|
|
|
|
this.noteTitle = response.data.title || ''
|
|
|
|
|
|
|
|
|
|
this.noteText = response.data.text
|
|
|
|
|
this.lastNoteHash = this.hashString( response.data.text )
|
|
|
|
|
// this.diffNoteText = response.data.text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Setup note tags
|
|
|
|
|
this.allTags = response.data.tags ? response.data.tags.split(','):[]
|
|
|
|
@@ -811,77 +815,167 @@
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
generateSelector(el){
|
|
|
|
|
|
|
|
|
|
if (!(el instanceof Element))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var path = [];
|
|
|
|
|
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
|
|
|
var selector = el.nodeName.toLowerCase();
|
|
|
|
|
if (el.id) {
|
|
|
|
|
selector += '#' + el.id;
|
|
|
|
|
path.unshift(selector);
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
var sib = el, nth = 1;
|
|
|
|
|
while (sib = sib.previousElementSibling) {
|
|
|
|
|
if (sib.nodeName.toLowerCase() == selector)
|
|
|
|
|
nth++;
|
|
|
|
|
}
|
|
|
|
|
if (nth != 1)
|
|
|
|
|
selector += ":nth-of-type("+nth+")";
|
|
|
|
|
}
|
|
|
|
|
path.unshift(selector);
|
|
|
|
|
el = el.parentNode;
|
|
|
|
|
}
|
|
|
|
|
return path.join(" > ");
|
|
|
|
|
},
|
|
|
|
|
//Called on squire event for keyup
|
|
|
|
|
diffText(event){
|
|
|
|
|
// console.log(event.type)
|
|
|
|
|
|
|
|
|
|
//Diff the changed lines only
|
|
|
|
|
const diffEvents = ['keyup','pathChange', 'click']
|
|
|
|
|
|
|
|
|
|
let oldText = this.noteText
|
|
|
|
|
// let newText = this.getText()
|
|
|
|
|
let newText = document.getElementById('squire-id').innerHTML
|
|
|
|
|
// only process changes on certain events
|
|
|
|
|
if( !diffEvents.includes(event?.type) ){
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diff = dmp.diff_main(oldText, newText)
|
|
|
|
|
// dmp.diff_cleanupSemantic(diff)
|
|
|
|
|
const patch_list = dmp.patch_make(oldText, newText, diff);
|
|
|
|
|
const patch_text = dmp.patch_toText(patch_list);
|
|
|
|
|
clearTimeout(this.diffTextTimeout)
|
|
|
|
|
this.diffTextTimeout = setTimeout(() => {
|
|
|
|
|
|
|
|
|
|
if(patch_text == ''){ return }
|
|
|
|
|
// Current Editor Text
|
|
|
|
|
const liveEditorElm = document.getElementById('squire-id')
|
|
|
|
|
|
|
|
|
|
//Save computed diff text
|
|
|
|
|
this.noteText = newText
|
|
|
|
|
// virtual element for selecting div
|
|
|
|
|
let virtualEditorElm = document.createElement('div')
|
|
|
|
|
virtualEditorElm.innerHTML = rawNoteText
|
|
|
|
|
|
|
|
|
|
// element at cursor
|
|
|
|
|
const elmAtCaret = window.getSelection().getRangeAt(0).startContainer.parentNode
|
|
|
|
|
|
|
|
|
|
// Remove beginngin selector from path, make it more generic
|
|
|
|
|
const path = this.generateSelector(elmAtCaret).replace('div#squire-id > ','')
|
|
|
|
|
let workingPath = ''
|
|
|
|
|
|
|
|
|
|
// default to entire note text, select down if path
|
|
|
|
|
let selectedDivText = virtualEditorElm
|
|
|
|
|
let newSelectedDivText = liveEditorElm
|
|
|
|
|
|
|
|
|
|
if( path != ''){
|
|
|
|
|
|
|
|
|
|
const pathParts = path.split(' > ')
|
|
|
|
|
let testedPathParts = []
|
|
|
|
|
let workingPathParts = []
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < pathParts.length; i++) {
|
|
|
|
|
|
|
|
|
|
testedPathParts.push(pathParts[i])
|
|
|
|
|
let currentTestPath = testedPathParts.join(' > ')
|
|
|
|
|
// console.log('elm test ',i,currentTestPath)
|
|
|
|
|
let elmTest = virtualEditorElm.querySelector(currentTestPath)
|
|
|
|
|
|
|
|
|
|
if(!elmTest){
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workingPathParts.push(pathParts[i])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workingPath = workingPathParts.join(' > ')
|
|
|
|
|
|
|
|
|
|
if(workingPath){
|
|
|
|
|
// Select text from virtual editor text
|
|
|
|
|
selectedDivText = selectedDivText.querySelector(workingPath)
|
|
|
|
|
// select text from current editor text
|
|
|
|
|
newSelectedDivText = newSelectedDivText.querySelector(workingPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const oldDivText = selectedDivText.innerHTML
|
|
|
|
|
const newDivText = newSelectedDivText.innerHTML
|
|
|
|
|
|
|
|
|
|
if(oldDivText == newDivText){ return }
|
|
|
|
|
|
|
|
|
|
const diff = dmp.diff_main(oldDivText, newDivText)
|
|
|
|
|
const patch_list = dmp.patch_make(oldDivText, newDivText, diff)
|
|
|
|
|
const patch_text = dmp.patch_toText(patch_list)
|
|
|
|
|
|
|
|
|
|
// save raw text for future diffs
|
|
|
|
|
rawNoteText = liveEditorElm.innerHTML
|
|
|
|
|
|
|
|
|
|
let newPatch = {
|
|
|
|
|
id: this.rawTextId,
|
|
|
|
|
diff: patch_text,
|
|
|
|
|
path: path,
|
|
|
|
|
// testing metrics
|
|
|
|
|
'old text':oldDivText,
|
|
|
|
|
'new text':newDivText,
|
|
|
|
|
'starting path':path,
|
|
|
|
|
'working path':workingPath,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// console.log('Sending out patch', newPatch)
|
|
|
|
|
|
|
|
|
|
this.$io.emit('note_diff', newPatch)
|
|
|
|
|
}, 100)
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
patchText(incomingPatchs){
|
|
|
|
|
// console.log('incoming patches ', incomingPatchs)
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
|
|
if(incomingPatchs == null){ return resolve(true) }
|
|
|
|
|
if(incomingPatchs.length == 0){ return resolve(true) }
|
|
|
|
|
const editorElement = document.getElementById('squire-id')
|
|
|
|
|
|
|
|
|
|
// let currentText = this.getText()
|
|
|
|
|
let currentText = document.getElementById('squire-id').innerHTML
|
|
|
|
|
|
|
|
|
|
//Convert text of all new patches into patches array
|
|
|
|
|
let patches = []
|
|
|
|
|
// iterate over incoming patches because they apply to specific divs
|
|
|
|
|
incomingPatchs.forEach(patch => {
|
|
|
|
|
|
|
|
|
|
if(patch.time <= this.updated){
|
|
|
|
|
return
|
|
|
|
|
// default to parent element, change to child if set
|
|
|
|
|
let editedElement = editorElement
|
|
|
|
|
if(patch.path){
|
|
|
|
|
editedElement = editorElement.querySelector(patch.path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
patches.push(...dmp.patch_fromText(patch.diff))
|
|
|
|
|
if( !editedElement ){
|
|
|
|
|
editedElement = editorElement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert patch from text and then apply to selected element
|
|
|
|
|
const patches = dmp.patch_fromText(patch.diff)
|
|
|
|
|
const patchResults = dmp.patch_apply(patches, editedElement.innerHTML)
|
|
|
|
|
|
|
|
|
|
// console.log('Patch results')
|
|
|
|
|
// console.log([patch.path, editedElement.innerHTML, patchResults[0]])
|
|
|
|
|
|
|
|
|
|
// patch changed directly into editor
|
|
|
|
|
editedElement.innerHTML = patchResults[0]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if(patches.length == 0){
|
|
|
|
|
return resolve(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var results = dmp.patch_apply(patches, currentText);
|
|
|
|
|
let newText = results[0]
|
|
|
|
|
|
|
|
|
|
this.noteText = newText
|
|
|
|
|
// this.editor.setHTML(newText)
|
|
|
|
|
document.getElementById('squire-id').innerHTML = newText
|
|
|
|
|
|
|
|
|
|
// save editor HTML after change for future comparisons
|
|
|
|
|
rawNoteText = editorElement.innerHTML
|
|
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
return resolve(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
onKeyup(event){
|
|
|
|
|
|
|
|
|
|
this.statusText = 'modified'
|
|
|
|
|
|
|
|
|
|
// Small debounce on diff generation
|
|
|
|
|
clearTimeout(this.diffTextTimeout)
|
|
|
|
|
this.diffTextTimeout = setTimeout(() => {
|
|
|
|
|
this.diffText()
|
|
|
|
|
}, 25)
|
|
|
|
|
|
|
|
|
|
//Save after x seconds
|
|
|
|
|
clearTimeout(this.editDebounce)
|
|
|
|
|
this.editDebounce = setTimeout(() => {
|
|
|
|
@@ -1030,11 +1124,11 @@
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
destroyWebSockets(){
|
|
|
|
|
// this.$io.removeListener('past_diffs')
|
|
|
|
|
// this.$io.removeListener('update_user_count')
|
|
|
|
|
// this.$io.removeListener('incoming_diff')
|
|
|
|
|
this.$io.removeListener('past_diffs')
|
|
|
|
|
this.$io.removeListener('update_user_count')
|
|
|
|
|
this.$io.removeListener('incoming_diff')
|
|
|
|
|
},
|
|
|
|
|
setupWebSockets(){
|
|
|
|
|
initWebsocketEvents(){
|
|
|
|
|
|
|
|
|
|
//Tell server to push this note into a room
|
|
|
|
|
this.$io.emit('join_room', this.rawTextId )
|
|
|
|
@@ -1050,36 +1144,54 @@
|
|
|
|
|
this.diffsApplied = diffSinceLastUpdate.length
|
|
|
|
|
// console.log('Got Diffs Total -> ', diffSinceLastUpdate)
|
|
|
|
|
}
|
|
|
|
|
// console.log(diffSinceLastUpdate)
|
|
|
|
|
this.patchText(diffSinceLastUpdate)
|
|
|
|
|
.then(() => {
|
|
|
|
|
this.restoreCaretPosition()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.$io.on('incoming_diff', incomingDiff => {
|
|
|
|
|
|
|
|
|
|
//Save current caret position
|
|
|
|
|
//Find index of child element based on past range
|
|
|
|
|
const element = window.getSelection().getRangeAt(0).startContainer.parentNode
|
|
|
|
|
const textLines = document.getElementById('squire-id').children
|
|
|
|
|
const childIndex = [...textLines].indexOf(element)
|
|
|
|
|
|
|
|
|
|
this.patchText([incomingDiff])
|
|
|
|
|
.then(() => {
|
|
|
|
|
this.restoreCaretPosition()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
saveCaretPosition(event){
|
|
|
|
|
|
|
|
|
|
//Find index of child element based on past range
|
|
|
|
|
const element = window.getSelection().getRangeAt(0).startContainer.parentNode
|
|
|
|
|
|
|
|
|
|
//Save range to replace cursor if someone else makes an update
|
|
|
|
|
this.lastRange = this.generateSelector(element)
|
|
|
|
|
this.startOffset = parseInt(event.range.startOffset) || 0
|
|
|
|
|
|
|
|
|
|
if(childIndex == -1){
|
|
|
|
|
console.log('Cursor position lost. Div being updated was lost.')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
restoreCaretPosition(){
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
// This code is intended to restore caret position to previous location
|
|
|
|
|
// when a third party updates the note.
|
|
|
|
|
|
|
|
|
|
if(!this.lastRange){ return resolve(true) }
|
|
|
|
|
|
|
|
|
|
const editorElement = document.getElementById('squire-id')
|
|
|
|
|
const lastElement = editorElement.querySelector(this.lastRange)
|
|
|
|
|
|
|
|
|
|
if( !lastElement ){ return resolve(true) }
|
|
|
|
|
|
|
|
|
|
//Reset caret position
|
|
|
|
|
//Find child index of old range and create a new one
|
|
|
|
|
let allChildren = document.getElementById('squire-id').children
|
|
|
|
|
const newLine = allChildren[childIndex].firstChild
|
|
|
|
|
let range = document.createRange()
|
|
|
|
|
range.setStart(newLine, this.startOffset)
|
|
|
|
|
range.setEnd(newLine, this.startOffset)
|
|
|
|
|
this.editor.setSelection(range)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
range.setStart(lastElement.firstChild, this.startOffset)
|
|
|
|
|
range.setEnd(lastElement.firstChild, this.startOffset)
|
|
|
|
|
|
|
|
|
|
// Set range in editor element
|
|
|
|
|
this.editor.setSelection(range)
|
|
|
|
|
|
|
|
|
|
return resolve(true)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
titleResize(){
|
|
|
|
|
//Resize the title field
|
|
|
|
@@ -1102,6 +1214,11 @@
|
|
|
|
|
z-index: 1019;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
.status-menu span + span {
|
|
|
|
|
border-left: 1px solid #ccc;
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
padding-left: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.font-color-bar {
|
|
|
|
|
/*width: calc(100% - 8px);*/
|
|
|
|
|