diff --git a/public/js/index.js b/public/js/index.js index 698fe987..80070d1f 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -206,10 +206,12 @@ const fileTypes = { */ // --------------------------------------------------------------------------------------------------- +let applicationState = null + let editorInstance = null let editor = null -function getEditorInstance () { return editorInstance} +function getEditorInstance () { return editorInstance } function setEditorInstance (e) { editorInstance = e editor = editorInstance.editor @@ -224,6 +226,13 @@ function getEditor () { return null } +/** + * @returns ApplicationState + */ +function getApplicationState () { + return applicationState +} + // --------------------------------------------------------------------------------------------------- /** @@ -233,9 +242,9 @@ function getEditor () { $(document).ready(function () { - console.log('-- first phase --') + applicationState = new ApplicationState() initCodeMirrorEditor() - registerCodeMirrorEvent() + // registerCodeMirrorEvent() registerSocketIO() // var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor) defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) @@ -308,7 +317,7 @@ $(document).ready(function () { }) }) -function initCodeMirrorEditor() { +function initCodeMirrorEditor () { const textit = document.getElementById('textit') if (!textit) { throw new Error('There was no textit area!') @@ -446,248 +455,345 @@ function registerCodeMirrorEvent () { }) } +class ApplicationState { + constructor () { + this.connectionStatus = statusType.connected + } + + setClientId (id) { + this.id = id + } + + setConnectionStatus (status) { + this.connectionStatus = status + // notify status changed + + setTimeout(() => { + notifyConnectionStatusChanged(this.connectionStatus) + }, 2000) + } +} + +function notifyConnectionStatusChanged (newStatus) { + showStatus(newStatus) +} + +function onSocketConnectHandler () { + // used for reconnection socket io server if user disconnect or has network problem + clearInterval(retryTimer) + retryTimer = null + + console.log(`connect to server, client id is: ${socket.id}`) + personalInfo['id'] = socket.id + // set changed + getApplicationState().setConnectionStatus(statusType.connected) + + // emit version to check this client is valid + socket.emit('version') +} + +function onDocHandler (event) { + console.log(event) + var noteContent = event.str + var isNoteContentMismatch = editor.getValue() !== noteContent + var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== event.revision && !havePendingOperation()))) || event.force + + saveInfo() + + if (setDoc && isNoteContentMismatch) { + if (cmClient) cmClient.editorAdapter.ignoreNextChange = true + if (noteContent) editor.setValue(noteContent) + else editor.setValue('') + } + + if (!window.loaded) { + editor.clearHistory() + ui.spinner.hide() + ui.content.fadeIn() + } else { + // if current doc is equal to the doc before disconnect + if (setDoc && isNoteContentMismatch) editor.clearHistory() + else if (lastInfo.history) editor.setHistory(lastInfo.history) + lastInfo.history = null + } + + if (!cmClient) { + cmClient = window.cmClient = new EditorClient( + event.revision, event.clients, + new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) + ) + synchronized_ = cmClient.state + } else if (setDoc) { + if (isNoteContentMismatch) { + cmClient.undoManager.undoStack.length = 0 + cmClient.undoManager.redoStack.length = 0 + } + cmClient.revision = event.revision + cmClient.setState(synchronized_) + cmClient.initializeClientList() + cmClient.initializeClients(event.clients) + } else if (havePendingOperation()) { + cmClient.serverReconnect() + } + + if (setDoc && isNoteContentMismatch) { + isDirty = true + updateView() + } + + restoreInfo() +} + function registerSocketIO () { const realtimeClient = new RealtimeClient() socket = realtimeClient.socket - socket.on('info', function (data) { - console.error(data) - switch (data.code) { - case 403: - location.href = serverurl + '/403' - break - case 404: - location.href = serverurl + '/404' - break - case 500: - location.href = serverurl + '/500' - break - } - }) - socket.on('error', function (data) { - console.error(data) - if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } - }) - socket.on('delete', function () { - if (personalInfo.login) { - deleteServerHistory(noteid, function (err, data) { - if (!err) location.href = serverurl - }) - } else { - getHistory(function (notehistory) { - var newnotehistory = removeHistory(noteid, notehistory) - saveHistory(newnotehistory) - location.href = serverurl - }) - } - }) - socket.on('maintenance', function () { - cmClient.revision = -1 - }) - socket.on('disconnect', function (data) { - showStatus(statusType.offline) - if (window.loaded) { - saveInfo() - lastInfo.history = editor.getHistory() - } - if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } - if (!retryTimer) { - retryTimer = setInterval(function () { - if (!needRefresh) socket.connect() - }, 1000) - } - }) - socket.on('reconnect', function (data) { - // sync back any change in offline - emitUserStatus(true) - cursorActivity(editor) - socket.emit('online users') - }) - socket.on('connect', function (data) { - clearInterval(retryTimer) - retryTimer = null - personalInfo['id'] = socket.id - showStatus(statusType.connected) - socket.emit('version') - }) - socket.on('version', function (data) { - if (version !== data.version) { - if (version < data.minimumCompatibleVersion) { - setRefreshModal('incompatible-version') - setNeedRefresh() - } else { - setRefreshModal('new-version') - } - } - }) - socket.on('check', function (data) { - // console.log(data); - updateInfo(data) - }) - socket.on('permission', function (data) { - updatePermission(data.permission) - }) - socket.on('refresh', function (data) { - // console.log(data); - editorInstance.config.docmaxlength = data.docmaxlength - editor.setOption('maxLength', editorInstance.config.docmaxlength) - updateInfo(data) - updatePermission(data.permission) - if (!window.loaded) { - // auto change mode if no content detected - var nocontent = editor.getValue().length <= 0 - if (nocontent) { - if (visibleXS) { appState.currentMode = modeType.edit } else { appState.currentMode = modeType.both } - } - // parse mode from url - if (window.location.search.length > 0) { - var urlMode = modeType[window.location.search.substr(1)] - if (urlMode) appState.currentMode = urlMode - } - changeMode(appState.currentMode) - if (nocontent && !visibleXS) { - editor.focus() - editor.refresh() - } - updateViewInner() // bring up view rendering earlier - updateHistory() // update history whether have content or not - window.loaded = true - emitUserStatus() // send first user status - updateOnlineStatus() // update first online status - setTimeout(function () { - // work around editor not refresh or doc not fully loaded - windowResizeInner() - // work around might not scroll to hash - scrollToHash() - }, 1) - } - if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) } - }) - socket.on('doc', function (obj) { - console.log(obj) - var body = obj.str - var bodyMismatch = editor.getValue() !== body - var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force + console.log(`before connect to server, client id is: ${socket.id}`) - saveInfo() - if (setDoc && bodyMismatch) { - if (cmClient) cmClient.editorAdapter.ignoreNextChange = true - if (body) editor.setValue(body) - else editor.setValue('') - } + realtimeClient.on('connect', onSocketConnectHandler) // event 1 + realtimeClient.on('doc', onDocHandler) // event 2 - if (!window.loaded) { - editor.clearHistory() - ui.spinner.hide() - ui.content.fadeIn() - } else { - // if current doc is equal to the doc before disconnect - if (setDoc && bodyMismatch) editor.clearHistory() - else if (lastInfo.history) editor.setHistory(lastInfo.history) - lastInfo.history = null - } + // socket.on('connect', function (data) { + // clearInterval(retryTimer) + // retryTimer = null + // personalInfo['id'] = socket.id + // showStatus(statusType.connected) + // socket.emit('version') + // }) - if (!cmClient) { - cmClient = window.cmClient = new EditorClient( - obj.revision, obj.clients, - new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) - ) - synchronized_ = cmClient.state - } else if (setDoc) { - if (bodyMismatch) { - cmClient.undoManager.undoStack.length = 0 - cmClient.undoManager.redoStack.length = 0 - } - cmClient.revision = obj.revision - cmClient.setState(synchronized_) - cmClient.initializeClientList() - cmClient.initializeClients(obj.clients) - } else if (havePendingOperation()) { - cmClient.serverReconnect() - } + // socket.on('reconnect', function (data) { + // // sync back any change in offline + // emitUserStatus(true) + // cursorActivity(editor) + // socket.emit('online users') + // }) - if (setDoc && bodyMismatch) { - isDirty = true - updateView() - } + // socket.on('info', function (data) { + // console.error(data) + // switch (data.code) { + // case 403: + // location.href = serverurl + '/403' + // break + // case 404: + // location.href = serverurl + '/404' + // break + // case 500: + // location.href = serverurl + '/500' + // break + // } + // }) + // socket.on('error', function (data) { + // console.error(data) + // if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } + // }) + // socket.on('delete', function () { + // if (personalInfo.login) { + // deleteServerHistory(noteid, function (err, data) { + // if (!err) location.href = serverurl + // }) + // } else { + // getHistory(function (notehistory) { + // var newnotehistory = removeHistory(noteid, notehistory) + // saveHistory(newnotehistory) + // location.href = serverurl + // }) + // } + // }) + // socket.on('maintenance', function () { + // cmClient.revision = -1 + // }) + // socket.on('disconnect', function (data) { + // showStatus(statusType.offline) + // if (window.loaded) { + // saveInfo() + // lastInfo.history = editor.getHistory() + // } + // if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } + // if (!retryTimer) { + // retryTimer = setInterval(function () { + // if (!needRefresh) socket.connect() + // }, 1000) + // } + // }) - restoreInfo() - }) - socket.on('ack', function () { - isDirty = true - updateView() - }) - socket.on('operation', function () { - isDirty = true - updateView() - }) - socket.on('online users', function (data) { - if (debug) { console.debug(data) } - onlineUsers = data.users - updateOnlineStatus() - $('.CodeMirror-other-cursors').children().each(function (key, value) { - var found = false - for (var i = 0; i < data.users.length; i++) { - var user = data.users[i] - if ($(this).attr('id') === user.id) { found = true } - } - if (!found) { - $(this).stop(true).fadeOut('normal', function () { - $(this).remove() - }) - } - }) - for (var i = 0; i < data.users.length; i++) { - var user = data.users[i] - if (user.id !== socket.id) { buildCursor(user) } else { personalInfo = user } - } - }) - socket.on('user status', function (data) { - if (debug) { console.debug(data) } - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id === data.id) { - onlineUsers[i] = data - } - } - updateOnlineStatus() - if (data.id !== socket.id) { buildCursor(data) } - }) - socket.on('cursor focus', function (data) { - if (debug) { console.debug(data) } - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id === data.id) { - onlineUsers[i].cursor = data.cursor - } - } - if (data.id !== socket.id) { buildCursor(data) } - // force show - var cursor = $('div[data-clientid="' + data.id + '"]') - if (cursor.length > 0) { - cursor.stop(true).fadeIn() - } - }) - socket.on('cursor activity', function (data) { - if (debug) { console.debug(data) } - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id === data.id) { - onlineUsers[i].cursor = data.cursor - } - } - if (data.id !== socket.id) { buildCursor(data) } - }) - socket.on('cursor blur', function (data) { - if (debug) { console.debug(data) } - for (var i = 0; i < onlineUsers.length; i++) { - if (onlineUsers[i].id === data.id) { - onlineUsers[i].cursor = null - } - } - if (data.id !== socket.id) { buildCursor(data) } - // force hide - var cursor = $('div[data-clientid="' + data.id + '"]') - if (cursor.length > 0) { - cursor.stop(true).fadeOut() - } - }) + // socket.on('version', function (data) { + // if (version !== data.version) { + // if (version < data.minimumCompatibleVersion) { + // setRefreshModal('incompatible-version') + // setNeedRefresh() + // } else { + // setRefreshModal('new-version') + // } + // } + // }) + // socket.on('check', function (data) { + // // console.log(data); + // updateInfo(data) + // }) + // socket.on('permission', function (data) { + // updatePermission(data.permission) + // }) + // socket.on('refresh', function (data) { + // // console.log(data); + // editorInstance.config.docmaxlength = data.docmaxlength + // editor.setOption('maxLength', editorInstance.config.docmaxlength) + // updateInfo(data) + // updatePermission(data.permission) + // if (!window.loaded) { + // // auto change mode if no content detected + // var nocontent = editor.getValue().length <= 0 + // if (nocontent) { + // if (visibleXS) { appState.currentMode = modeType.edit } else { appState.currentMode = modeType.both } + // } + // // parse mode from url + // if (window.location.search.length > 0) { + // var urlMode = modeType[window.location.search.substr(1)] + // if (urlMode) appState.currentMode = urlMode + // } + // changeMode(appState.currentMode) + // if (nocontent && !visibleXS) { + // editor.focus() + // editor.refresh() + // } + // updateViewInner() // bring up view rendering earlier + // updateHistory() // update history whether have content or not + // window.loaded = true + // emitUserStatus() // send first user status + // updateOnlineStatus() // update first online status + // setTimeout(function () { + // // work around editor not refresh or doc not fully loaded + // windowResizeInner() + // // work around might not scroll to hash + // scrollToHash() + // }, 1) + // } + // if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) } + // }) + // socket.on('doc', function (obj) { + // console.log(obj) + // var body = obj.str + // var bodyMismatch = editor.getValue() !== body + // var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force + // + // saveInfo() + // if (setDoc && bodyMismatch) { + // if (cmClient) cmClient.editorAdapter.ignoreNextChange = true + // if (body) editor.setValue(body) + // else editor.setValue('') + // } + // + // if (!window.loaded) { + // editor.clearHistory() + // ui.spinner.hide() + // ui.content.fadeIn() + // } else { + // // if current doc is equal to the doc before disconnect + // if (setDoc && bodyMismatch) editor.clearHistory() + // else if (lastInfo.history) editor.setHistory(lastInfo.history) + // lastInfo.history = null + // } + // + // if (!cmClient) { + // cmClient = window.cmClient = new EditorClient( + // obj.revision, obj.clients, + // new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) + // ) + // synchronized_ = cmClient.state + // } else if (setDoc) { + // if (bodyMismatch) { + // cmClient.undoManager.undoStack.length = 0 + // cmClient.undoManager.redoStack.length = 0 + // } + // cmClient.revision = obj.revision + // cmClient.setState(synchronized_) + // cmClient.initializeClientList() + // cmClient.initializeClients(obj.clients) + // } else if (havePendingOperation()) { + // cmClient.serverReconnect() + // } + // + // if (setDoc && bodyMismatch) { + // isDirty = true + // updateView() + // } + // + // restoreInfo() + // }) + // socket.on('ack', function () { + // isDirty = true + // updateView() + // }) + // socket.on('operation', function () { + // isDirty = true + // updateView() + // }) + // socket.on('online users', function (data) { + // if (debug) { console.debug(data) } + // onlineUsers = data.users + // updateOnlineStatus() + // $('.CodeMirror-other-cursors').children().each(function (key, value) { + // var found = false + // for (var i = 0; i < data.users.length; i++) { + // var user = data.users[i] + // if ($(this).attr('id') === user.id) { found = true } + // } + // if (!found) { + // $(this).stop(true).fadeOut('normal', function () { + // $(this).remove() + // }) + // } + // }) + // for (var i = 0; i < data.users.length; i++) { + // var user = data.users[i] + // if (user.id !== socket.id) { buildCursor(user) } else { personalInfo = user } + // } + // }) + // socket.on('user status', function (data) { + // if (debug) { console.debug(data) } + // for (var i = 0; i < onlineUsers.length; i++) { + // if (onlineUsers[i].id === data.id) { + // onlineUsers[i] = data + // } + // } + // updateOnlineStatus() + // if (data.id !== socket.id) { buildCursor(data) } + // }) + // socket.on('cursor focus', function (data) { + // if (debug) { console.debug(data) } + // for (var i = 0; i < onlineUsers.length; i++) { + // if (onlineUsers[i].id === data.id) { + // onlineUsers[i].cursor = data.cursor + // } + // } + // if (data.id !== socket.id) { buildCursor(data) } + // // force show + // var cursor = $('div[data-clientid="' + data.id + '"]') + // if (cursor.length > 0) { + // cursor.stop(true).fadeIn() + // } + // }) + // socket.on('cursor activity', function (data) { + // if (debug) { console.debug(data) } + // for (var i = 0; i < onlineUsers.length; i++) { + // if (onlineUsers[i].id === data.id) { + // onlineUsers[i].cursor = data.cursor + // } + // } + // if (data.id !== socket.id) { buildCursor(data) } + // }) + // socket.on('cursor blur', function (data) { + // if (debug) { console.debug(data) } + // for (var i = 0; i < onlineUsers.length; i++) { + // if (onlineUsers[i].id === data.id) { + // onlineUsers[i].cursor = null + // } + // } + // if (data.id !== socket.id) { buildCursor(data) } + // // force hide + // var cursor = $('div[data-clientid="' + data.id + '"]') + // if (cursor.length > 0) { + // cursor.stop(true).fadeOut() + // } + // }) } // --------------------------------------------------------------------------------------------------- @@ -807,7 +913,7 @@ var wasFocus = false // updateHistoryInner(); // }) // $(window).on('error', function () { - // setNeedRefresh(); +// setNeedRefresh(); // }) // setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown, editor) @@ -2034,13 +2140,28 @@ class RealtimeClient { * overwrite the original event for checking login state */ patchSocket () { + const onevent = this.socket.onevent + this.socket.onevent = function (packet) { + console.log(`received ${packet.data[0]} event -------- on event`) + var args = packet.data || [] + onevent.call(this, packet) // original call + packet.data = ['*'].concat(args) + onevent.call(this, packet) // additional call to catch-all + } + + // patch on / emit event const on = this.socket.on const emit = this.socket.emit const skipEvent = () => needRefresh || checkLoginStateChanged() - + const socket = this.socket this.socket.on = function () { if (skipEvent()) return - on.apply(socket, arguments) + const eventName = arguments[0] + const func = arguments[1] + on.call(socket, eventName, function () { + console.log(`received ${eventName} event.`) + func.apply(this, arguments) + }) } this.socket.emit = function () { @@ -2049,6 +2170,10 @@ class RealtimeClient { } } + on (eventName, handler) { + this.socket.on(eventName, handler) + } + registerEvent () { // socket.io connection event this.socket.on('connect')