diff --git a/static/js/styleCmd.js b/static/js/styleCmd.js
new file mode 100644
index 0000000..ec2f9ff
--- /dev/null
+++ b/static/js/styleCmd.js
@@ -0,0 +1,329 @@
+/** * 样式 */
+(function (utils) {
+ function inputHandle(e) {
+ if (!utils.inCompositionEvent) {
+ updateText(e)
+ }
+ }
+
+ function compositionstartHandle(e) {
+ // console.log("compositionstart")
+ utils.inCompositionEvent = true
+ }
+
+ function compositionendHandle(e) {
+ // console.log("compositionend")
+ updateText(e)
+ utils.inCompositionEvent = false
+ }
+
+
+ /**
+ * 鼠标按下事件
+ */
+ function windowsCtrZHandle(e) {
+ const event = e || window.event //标准化事件处理
+ const keyCode = event.keyCode || event.which || event.charCode
+ const metaKey = event.metaKey
+ // console.log("windows键盘事件 ", event, keyCombination, metaKey, keyCode)
+
+ //撤销
+ if (metaKey && keyCode === 90) {
+ console.log('触发ctrl + Z 事件', e.target)
+ if (utils.latestOpDoc !== undefined && utils.latestOpDoc !== null) {
+ // //
+ // let cNo = parseInt(utils.latestOpDoc.getAttribute("data-order"))
+ // console.log("恢复", utils.latestOpDoc, cNo)
+ // if (cNo > 1) {
+ // let qq = "#noteshare p[data-order='" + (cNo - 1) + "']"
+ // // console.log("qq: ", qq)
+ // utils.insertAfter(utils.latestOpDoc, document.querySelector(qq))
+ // } else {
+ // //添加元素到首位
+ // yanxuelu.insertBefore(utils.latestOpDoc, utils.MyRoot.children[0])
+ // }
+ // utils.MyDocMap.get(cNo).setHidden(false)
+
+ //恢复
+ utils.latestOpDoc.recovery();
+
+ // utils.MyDocMap.get(cNo).setSource(latestOpDoc.innerText)
+ //
+ showTestText()
+
+ event.preventDefault()
+ event.returnValue = false
+ }
+
+ utils.latestOpDoc = null
+ }
+
+ }
+
+ /**
+ * 鼠标按下事件 & 键盘组合事件
+ * @param {*} e
+ */
+ function onkeydownHandle(e) {
+ const event = e || window.event //标准化事件处理
+ // window.event 等价于 event参数
+ // 有些浏览器除了通过keyCode获取输入键code,还可以通过which,charCode获取,这么写是出于浏览器兼容性考虑
+ const keyCode = event.keyCode || event.which || event.charCode
+ const keyCombination = event.ctrlKey
+ const metaKey = event.metaKey
+ // console.log("键盘事件 ", event, keyCombination, metaKey, keyCode)
+
+ // ctrl + c 复制
+ if (keyCombination && keyCode === 67) {
+ // 阻止默认事件
+ event.preventDefault()
+ event.returnValue = false
+ console.log('触发ctrl + c 事件', e.target)
+ }
+
+ //撤销
+ if (metaKey && keyCode === 90) {
+ // console.log('触发ctrl + Z 事件', e.target)
+ if (utils.latestOpDoc !== undefined && utils.latestOpDoc !== null) {
+ // console.log(utils.latestOpDoc)
+ //恢复
+ utils.latestOpDoc.recovery();
+ //
+ // console.log(MyDocMap.get(cNo))
+ let cNo = parseInt(utils.latestOpDoc.getData().getAttribute("data-order"))
+ utils.MyDocMap.get(cNo).setHidden(false)
+ // utils.MyDocMap.get(cNo).setSource(latestOpDoc.innerText)
+ //
+ showTestText()
+
+ //阻止事件
+ event.preventDefault()
+ event.returnValue = false
+ }
+
+ utils.latestOpDoc = null
+ }
+
+ //删除
+ if (keyCode === 46 || keyCode === 8) {
+ let curP = event.target
+ let cNo = parseInt(curP.getAttribute("data-order"))
+
+ //维护最近一次编辑的内容
+ if (utils.latestOpDoc === undefined || utils.latestOpDoc === null || utils.latestOpDoc.getData().getAttribute("data-id") !== curP.getAttribute("data-id")) {
+ utils.latestOpDoc = new utils.MyRecovery(curP.cloneNode(true), function () {
+ let cNo = parseInt(this.data.getAttribute("data-order"))
+ console.log("恢复", this.data, cNo)
+ if (cNo > 1) {
+ utils.insertAfter(this.data, document.querySelector("#noteshare p[data-order='" + (cNo - 1) + "']"))
+ } else {
+ //添加元素到首位
+ utils.MyRoot.insertBefore(this.data, utils.MyRoot.children[0])
+ }
+
+ // 恢复该元素展示
+ utils.MyDocMap.get(cNo).setHidden(false)
+ })
+ }
+
+
+ //如果是第一行
+ let previousSibling = curP.previousSibling
+ // console.log(curP, previousSibling == undefined ,previousSibling.id == undefined)
+ if (previousSibling === undefined || previousSibling.id === undefined) {
+ //显示用户的输入内容
+ showTestText()
+ return
+ }
+
+
+ // console.log('触发删除', curP.innerHTML, cNo, utils.num, utils.MyDocMap.get(cNo + 1))
+ let curS = window.getSelection()
+ // console.log("当前内容: ", curP.innerHTML, " 当前选区 :", curS)
+ //处理前面没有内容,后面还有内容需要拼接到上层的场景
+ if ((curS.isCollapsed && curS.anchorOffset === 0) || curP.innerHTML === '
') {
+ let curNodeRetainHtml = curP.innerHTML
+
+ //异步修正顺序(第一行删除不掉)
+ // if (curP.innerHTML === '
' ) {
+ // console.log("删除当前行。选取信息: ", curS)
+ //阻止事件传递
+ event.preventDefault()
+ event.returnValue = false
+
+ //设置该元素隐藏
+ utils.MyDocMap.get(cNo).setHidden(true)
+ //删除当前元素
+ curP.remove()
+ //拼接
+ if (curNodeRetainHtml !== '
') {
+ previousSibling.innerHTML = previousSibling.innerHTML + curNodeRetainHtml
+ }
+
+ //收起选区到一个点,光标落在一个可编辑元素上
+ window.getSelection().setPosition(previousSibling, 1);
+ }
+
+ //显示用户的输入内容
+ showTestText();
+ }
+
+ //回车事件
+ if (keyCode === 13 /* && currentNode === key.lastElementChild */) {
+ event.preventDefault()
+ let uuid = utils.uuid()
+ let curOrder = ++(utils.num)
+ var newParagraph = document.createElement("p")
+ newParagraph.setAttribute("contenteditable", "true")
+ newParagraph.setAttribute("data-id", uuid)
+ newParagraph.setAttribute("id", uuid)
+ newParagraph.setAttribute("data-order", curOrder)
+ newParagraph.onkeydown = onkeydownHandle
+ newParagraph.focus()
+ newParagraph.innerHTML = "
"
+ utils.MyRoot.appendChild(newParagraph)
+ utils.MyDocMap.set(curOrder, new utils.MyNode(uuid))
+
+ //收起选区到一个点,光标落在一个可编辑元素上
+ window.getSelection().setPosition(newParagraph, 0)
+ }
+ }
+
+
+ /**
+ *
+ */
+ function showTestText() {
+ //显示用户的输入内容
+ const obj = {};
+ for ([k, v] of utils.MyDocMap.entries()) {
+ if (v.getHidden() && v.getHidden() === true) {
+ continue
+ }
+ obj[k] = v
+ }
+
+ // console.log("当前文档结构数: ",obj);
+
+ // var userInput = document.getElementById("testInput")
+ // userInput.value = JSON.stringify(obj)
+ }
+
+ /**
+ * 更新文档
+ * @param {*} e
+ */
+ function updateText(e) {
+ let curP = e.target
+
+ //是否需要变更元素类型
+ utils.firstCmd(curP, onkeydownHandle)
+
+
+ let cNo = parseInt(curP.getAttribute("data-order"))
+ let cp = utils.MyDocMap.get(cNo)
+
+ //内容不变则不处理
+ let h5CurLen = curP.innerText.length
+ let myDocNodeLen = (cp && cp.getSource()) ? cp.getSource().length : 0
+ if (h5CurLen === myDocNodeLen) {
+ return
+ }
+
+ cp.setSource(curP.innerText)
+ // console.log("curPTx: ", curP.innerText, "MyDocMap : ", utils.MyDocMap)
+
+ //显示用户的输入内容
+ showTestText()
+ }
+
+
+ function surroundContentsByStyle() {
+ let curS = utils.getSelection()
+ let curSec = curS.getRangeAt(0)
+ let styleName = this.getAttribute("data-value");
+ let className = utils.parseStyleName2ClassName(styleName)
+ //todo 只对 nodeType = p 执行
+ // console.log("当前光标信息: ", curS, " 当前选区信息 : ", curSec)
+
+ for (let i = 0; i < curS.rangeCount; i++) {
+ let curSec = curS.getRangeAt(i)
+
+ let curEleSize = curSec.commonAncestorContainer.childNodes.length
+
+ let curStartP = curSec.startContainer.parentElement
+ //todo 先支持 一行。
+ let start = curSec.startOffset;
+ let end = curSec.endOffset;
+ // console.log(curStartP.cloneNode(true), styleName, start, end, " curEleSize: ", curEleSize)
+
+ let curPEle = null;
+ //第一次选择的时候将整行转换成
+ if (curEleSize === 0) { + //没选择,则退出 + if (start === end) { + return + } + // console.log("debug1: ", curSec.commonAncestorContainer) + //维护最近一次编辑的内容(暂时只支持恢复最近一次编辑) + utils.latestOpDoc = new utils.MyRecovery(curStartP.cloneNode(true), function () { + console.log("恢复上一步样式1", this.innerHTML) + let curEl = document.getElementById(this.data.getAttribute("id")); + curEl.innerHTML = this.data.innerHTML; + + //文本映射 直接覆盖 map 中的 childrenStyle + utils.syncOnePInfo(this.data); + }) + + let curHtml = "" + for (let j = 0; j < curStartP.innerText.length; j++) { + // console.log(curStartP.innerText.charAt(j)) + if (j >= start && j < end) { + curHtml += '' + curStartP.innerText.charAt(j) + ''; + } else { + curHtml += '' + curStartP.innerText.charAt(j) + ''; + } + } + curStartP.innerHTML = curHtml; + + curPEle = curStartP; + } else { + // console.log("debug2: ", curSec.commonAncestorContainer) + //维护最近一次编辑的内容(暂时只支持恢复最近一次编辑) + utils.latestOpDoc = new utils.MyRecovery(curSec.commonAncestorContainer.cloneNode(true), function () { + console.log("恢复上一步样式1", this.data) + let curEl = document.getElementById(this.data.getAttribute("id")); + curEl.innerHTML = this.data.innerHTML; + //文本映射 直接覆盖 map 中的 childrenStyle + utils.syncOnePInfo(this.data); + }) + + let myChildren = curSec.commonAncestorContainer.children + for (let j = 0; j < curEleSize; j++) { + let curEle = myChildren[j] + if (curS.containsNode(curEle, true)) { + curEle.classList.remove(className); + curEle.classList.add(className); + } + } + + curPEle = curSec.commonAncestorContainer; + } + + + //文本映射 直接覆盖 map 中的 childrenStyle + utils.syncOnePInfo(curPEle) + } + } + + window.styleCmd = { + inputHandle, compositionstartHandle, compositionendHandle, + onkeydownHandle, windowsCtrZHandle, + surroundContentsByStyle, + + showTestText, + updateText, + + } + +})(utils) \ No newline at end of file diff --git a/static/js/utils.js b/static/js/utils.js new file mode 100644 index 0000000..b57b42c --- /dev/null +++ b/static/js/utils.js @@ -0,0 +1,419 @@ +/** * + * 工具类 + */ +(function () { + + /** + * 最新修改的元素 + */ + const latestOpDoc = null + + class MyRecovery { + constructor(data, func) { + this.data = data; + this.func = func; + } + + getData() { + return this.data; + } + + getFun() { + return this.func; + } + + recovery() { + this.func(); + } + } + + /** + * 行数增加记录 + */ + const num = 0 + + /** + * 是否开始输入中文 + */ + const inCompositionEvent = false; + + /** + * 我的文档数据 + */ + const MyDocMap = new Map() + + + // push,推一个在后面,就是 append 的意思 + // pop,从后面拉走,配合 push 就是栈 + // unshift,从前面加一个数据,就是 insert(it, 0) 的意思 + // shift,前面拉个数据走,配合 shift 就是一个反向栈,配合 push 就是队列 + class MyQueue { + constructor(size) { + this.insertIndex = 0 + this.delIndex = 0 + this.capacity = 0; + this.size = size; + this.arr = new Array(size); + } + + push(handle) { + if (this.capacity === this.arr.length) { + console.log("满了"); + this.capacity--; + this.arr[this.capacity] = null; + + //todo 替代 + // this.end++; + } + this.arr[this.capacity] = handle; + this.capacity++; + } + + pull() { + if (this.capacity <= 0) { + return null + } + this.capacity--; + + //到底回退 + if (this.shift > this.size) { + this.shift = 0; + } + + return this.arr[this.shift]; + } + } + + + class MyNode { + constructor(id) { + this.id = id + this.hidden = false + this.style = new InnerStyle() + } + + getHidden() { + return this.hidden + } + + setHidden(val) { + this.hidden = val + } + + setSource(source) { + this.source = source + } + + getSource() { + return this.source + } + + getStyle() { + return this.style + } + } + + class InnerStyle { + constructor() { + this.nodeType = "p" + //map-> index:classList + this.childrenStyle = null; + this.preStyle = null; + + this.getNodeType = function getNodeType() { + return this.nodeType + } + this.setNodeType = function setNodeType(nodeType) { + this.nodeType = nodeType + } + + //前置类型 如 ul ol 代码块 等 + this.setPreStyle = function setPreStyle(k, v) { + if (this.preStyle == null) { + this.preStyle = new MyKV(k, v); + } + } + this.getPreStyle = function getPreStyle() { + return this.preStyle + } + } + + setChildrenStyle(index, classList) { + if (this.childrenStyle === null) { + this.childrenStyle = new Map(); + } + this.childrenStyle.set(index, classList) + } + + getChildrenStyle(index) { + return this.childrenStyle.get(index); + } + + getChildrenStyleMap() { + return this.childrenStyle; + } + + setChildrenStyleMapNull() { + this.childrenStyle = null; + } + + } + + class MyKV { + constructor(k, v) { + this.k = k + this.v = v + + this.getK = function getK() { + return this.k + } + this.getV = function getV() { + return this.v + } + + } + } + + /** + * 主节点元素 + */ + const MyRoot = document.getElementById("noteshare") + + + function isNum(value) { + return !isNaN(parseFloat(value)) && isFinite(value) + } + + function getSelection() { + return window.getSelection() || document.selection + } + + /** + * + * @returns 生产uuid + */ + function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + .replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }).split("-")[0] + } + + /** + * 在目标元素后面插叙 新元素 + * @param {*} newElement + * @param {*} targetElement + */ + function insertAfter(newElement, targetElement) { + // console.log("开始: ",newElement, targetElement) + var parent = targetElement.parentNode + if (parent.lastChild == targetElement) { + // 如果最后的节点是目标元素,则直接添加。因为默认是最后 + parent.appendChild(newElement) + } else { + parent.insertBefore(newElement, targetElement.nextSibling) + //如果不是,则插入在目标元素的下一个兄弟节点 的前面。也就是目标元素的后面 + } + } + + function parseOrder(curP) { + return parseInt(curP.getAttribute("data-order")) + } + + /** + * + * @param {*} curP + * @param onkeydownHandle + */ + function firstCmd(curP, onkeydownHandle) { + let newParagraph; + let inputLength = curP.innerText.length + /** + * h1 ~ h6 + */ + if (curP.innerText.startsWith("#") && curP.innerHTML.endsWith(" ")) { + let curNo = parseOrder(curP) + let mapNode = utils.MyDocMap.get(curNo) + + // console.log(curP, " - ", curP.innerHTML, curP.innerHTML.startsWith("# ")) + if (curP.innerHTML.startsWith("# ") || curP.innerHTML.startsWith("# ")) { + mapNode.getStyle().setNodeType("h1") + becomeAnotherElement(curP, "h1", onkeydownHandle) + } else if (curP.innerHTML.startsWith("## ")) { + mapNode.getStyle().setNodeType("h2") + becomeAnotherElement(curP, "h2", onkeydownHandle) + } else if (curP.innerHTML.startsWith("### ")) { + mapNode.getStyle().setNodeType("h3") + becomeAnotherElement(curP, "h3", onkeydownHandle) + } else if (curP.innerHTML.startsWith("#### ")) { + mapNode.getStyle().setNodeType("h4") + becomeAnotherElement(curP, "h4", onkeydownHandle) + } else if (curP.innerHTML.startsWith("##### ")) { + mapNode.getStyle().setNodeType("h5") + becomeAnotherElement(curP, "h5", onkeydownHandle) + } else { + mapNode.getStyle().setNodeType("h6") + becomeAnotherElement(curP, "h6", onkeydownHandle) + } + } + + /** + * 无序列表效果 + */ + if (inputLength === 2 && curP.innerText.startsWith("-") && curP.innerHTML.endsWith(" ")) { + let curNo = parseOrder(curP) + let mapNode = utils.MyDocMap.get(curNo) + mapNode.getStyle().setPreStyle("ul", true) + + //clean + curP.innerHTML = "" + mapNode.setSource("") + + //根据上一层级元素动态选择 todo + curP.setAttribute("style", "padding-left: 1rem;") + //新增元素 + newParagraph = document.createElement("span"); + newParagraph.setAttribute("contenteditable", "false") + //∙ vs ∘ + newParagraph.innerHTML = "∙ " + curP.append(newParagraph) + + //添加一个选区 + var selObj = window.getSelection() + var rangeObj = document.createRange() + rangeObj.selectNode(curP) + selObj.addRange(rangeObj) + + //收起选区到一个点,光标落在一个可编辑元素上 + window.getSelection().collapse(curP, true) + } + + /** + * 有序列表效果 + */ + if (inputLength > 2 && inputLength <= 5 && isNum(curP.innerText.substring(0, inputLength - 2)) && curP.innerHTML.endsWith(". ")) { + let num = curP.innerText.substring(0, inputLength - 2) + console.log(curP.innerText, num) + + let curNo = parseOrder(curP) + let mapNode = utils.MyDocMap.get(curNo) + mapNode.getStyle().setPreStyle("ol", num) + + //clean + curP.innerHTML = "" + mapNode.setSource("") + //todo + curP.setAttribute("style", "padding-left: 1rem;") + //新增元素 + newParagraph = document.createElement("span"); + newParagraph.setAttribute("contenteditable", "false") + newParagraph.innerHTML = num + ". " + curP.append(newParagraph) + + //添加一个选区 + var selObj = window.getSelection() + var rangeObj = document.createRange() + rangeObj.selectNode(curP) + selObj.addRange(rangeObj) + + //收起选区到一个点,光标落在一个可编辑元素上 + window.getSelection().collapse(curP, true) + } + + } + + /** + * 变成另一个元素 + * @param {*} curP + * @param {*} elementName + */ + function becomeAnotherElement(curP, elementName, onkeydownHandle) { + var newParagraph = document.createElement(elementName) + newParagraph.setAttribute("contenteditable", "true") + newParagraph.setAttribute("data-id", curP.getAttribute("data-id")) + newParagraph.setAttribute("id", curP.getAttribute("data-id")) + newParagraph.setAttribute("data-order", curP.getAttribute("data-order")) + newParagraph.onkeydown = onkeydownHandle + + //todo 支持 有数据的行 在行首输入 # + // if() + // switch (elementName){ + // case "h1": + // } + newParagraph.innerHTML = "