to:sync
This commit is contained in:
parent
057d853f06
commit
c31252f2c8
329
static/js/styleCmd.js
Normal file
329
static/js/styleCmd.js
Normal file
@ -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 === '<br>') {
|
||||
let curNodeRetainHtml = curP.innerHTML
|
||||
|
||||
//异步修正顺序(第一行删除不掉)
|
||||
// if (curP.innerHTML === '<br>' ) {
|
||||
// console.log("删除当前行。选取信息: ", curS)
|
||||
//阻止事件传递
|
||||
event.preventDefault()
|
||||
event.returnValue = false
|
||||
|
||||
//设置该元素隐藏
|
||||
utils.MyDocMap.get(cNo).setHidden(true)
|
||||
//删除当前元素
|
||||
curP.remove()
|
||||
//拼接
|
||||
if (curNodeRetainHtml !== '<br>') {
|
||||
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 = "<br>"
|
||||
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;
|
||||
//第一次选择的时候将整行转换成<p><span></span></p>
|
||||
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 += '<span contenteditable="true" class=' + className + '>' + curStartP.innerText.charAt(j) + '</span>';
|
||||
} else {
|
||||
curHtml += '<span>' + curStartP.innerText.charAt(j) + '</span>';
|
||||
}
|
||||
}
|
||||
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)
|
419
static/js/utils.js
Normal file
419
static/js/utils.js
Normal file
@ -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 = "<br>"
|
||||
|
||||
insertAfter(newParagraph, curP)
|
||||
curP.remove()
|
||||
|
||||
//matData
|
||||
let curNo = parseOrder(curP)
|
||||
let mapNode = utils.MyDocMap.get(curNo)
|
||||
mapNode.setSource("")
|
||||
|
||||
//收起选区到一个点,光标落在一个可编辑元素上
|
||||
window.getSelection().setPosition(newParagraph, 0)
|
||||
}
|
||||
|
||||
function parseStyleName2ClassName(styleName) {
|
||||
switch (styleName) {
|
||||
case "b":
|
||||
return "childStyleStrong";
|
||||
case "i":
|
||||
return "childStyleI";
|
||||
case "u":
|
||||
return "childStyleU";
|
||||
case "c_red":
|
||||
return "childStyleColor";
|
||||
case "del":
|
||||
return "childStyleDel"
|
||||
}
|
||||
}
|
||||
|
||||
function syncOnePInfo(p) {
|
||||
//子元素为空不处理
|
||||
if (p.children.length <= 0) {
|
||||
return
|
||||
}
|
||||
// let id = p.getAttribute("id");
|
||||
let order = parseInt(p.getAttribute("data-order"));
|
||||
let curMapData = this.MyDocMap.get(order);
|
||||
//清空重置
|
||||
curMapData.getStyle().setChildrenStyleMapNull();
|
||||
for (let i = 0; i < p.children.length; i++) {
|
||||
let curItem = p.children[i];
|
||||
let tmpClassList = curItem.classList;
|
||||
if (tmpClassList != null && tmpClassList.length > 0) {
|
||||
curMapData.getStyle().setChildrenStyle(i, tmpClassList);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("curPEle : ", p, " children: ", p.children, " childrenMap: ", curMapData.getStyle().getChildrenStyleMap())
|
||||
}
|
||||
|
||||
|
||||
window.utils = {
|
||||
isNum,
|
||||
uuid,
|
||||
parseOrder,
|
||||
getSelection,
|
||||
insertAfter,
|
||||
firstCmd,
|
||||
becomeAnotherElement,
|
||||
parseStyleName2ClassName,
|
||||
syncOnePInfo,
|
||||
|
||||
latestOpDoc,
|
||||
num,
|
||||
inCompositionEvent,
|
||||
MyDocMap,
|
||||
MyRoot,
|
||||
|
||||
MyNode,
|
||||
InnerStyle,
|
||||
MyKV,
|
||||
MyQueue,
|
||||
MyRecovery,
|
||||
}
|
||||
|
||||
})()
|
50
static/js/yanxuelu.js
Normal file
50
static/js/yanxuelu.js
Normal file
@ -0,0 +1,50 @@
|
||||
(function (utils, eventHandle) {
|
||||
|
||||
/**
|
||||
* 输入事件
|
||||
*/
|
||||
utils.MyRoot.addEventListener('input', styleCmd.inputHandle);
|
||||
utils.MyRoot.addEventListener('compositionstart', styleCmd.compositionstartHandle);
|
||||
utils.MyRoot.addEventListener('compositionend', styleCmd.compositionendHandle);
|
||||
|
||||
|
||||
//窗口撤销事件
|
||||
window.addEventListener('keydown', styleCmd.windowsCtrZHandle, true);
|
||||
// window.onkeydown = styleCmd.windowsCtrZHandle
|
||||
|
||||
//样式事件
|
||||
document.getElementById("myPlusB").addEventListener('click', styleCmd.surroundContentsByStyle);
|
||||
document.getElementById("myPlusI").addEventListener('click', styleCmd.surroundContentsByStyle);
|
||||
document.getElementById("myPlusU").addEventListener('click', styleCmd.surroundContentsByStyle);
|
||||
document.getElementById("myPlusDel").addEventListener('click', styleCmd.surroundContentsByStyle);
|
||||
document.getElementById("myPlusC").addEventListener('click', styleCmd.surroundContentsByStyle);
|
||||
|
||||
//窗口初始化完成事件
|
||||
window.onload = function () {//do something
|
||||
// yanxuelu.style.outline = 'none'
|
||||
// yanxuelu.onkeydown = fn // 注册keydown事件处理函数,键盘按下
|
||||
// key.onkeyup = fn // 注册keyup事件处理函数,键盘松开
|
||||
// key.onkeypress = fn // 注册keypress事件处理函数,不推荐使用
|
||||
|
||||
var newParagraph = document.createElement("p")
|
||||
newParagraph.setAttribute("contenteditable", "true")
|
||||
let uuid = utils.uuid()
|
||||
let curOrder = ++utils.num
|
||||
newParagraph.setAttribute("data-id", uuid)
|
||||
newParagraph.setAttribute("id", uuid)
|
||||
newParagraph.setAttribute("data-order", curOrder)
|
||||
newParagraph.onkeydown = styleCmd.onkeydownHandle
|
||||
newParagraph.focus()
|
||||
newParagraph.innerHTML = "<br>"
|
||||
utils.MyRoot.appendChild(newParagraph)
|
||||
|
||||
// MyDocMap[curOrder] = new Map()
|
||||
|
||||
utils.MyDocMap.set(curOrder, new utils.MyNode(uuid))
|
||||
|
||||
|
||||
//收起选区到一个点,光标落在一个可编辑元素上
|
||||
window.getSelection().setPosition(newParagraph, 0);
|
||||
}
|
||||
|
||||
})(utils, styleCmd)
|
@ -221,265 +221,182 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section style="min-height: 800px; background-color: #f8f7fa;">
|
||||
<div>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* 字体 */
|
||||
/*:root {*/
|
||||
/* !* font-size: calc(0.5em + 1vw); *!*/
|
||||
/* font-size: 62.5%;*/
|
||||
/*}*/
|
||||
|
||||
/* style sheet for "A4" printing */
|
||||
@media print and (width: 21cm) and (height: 29.7cm) {
|
||||
@page {
|
||||
margin: 3cm;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 90vh;
|
||||
/* style sheet for "letter" printing */
|
||||
@media print and (width: 8.5in) and (height: 11in) {
|
||||
@page {
|
||||
margin: 1in;
|
||||
}
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
flex: 15%;
|
||||
background-color: #f2f2f2;
|
||||
padding: 10px;
|
||||
* {
|
||||
/* margin: 1px 2px;
|
||||
padding: 1px 2px; */
|
||||
font-family: Roboto-Regular, PingFang SC, SF Pro SC, SF Pro Text, SF Pro Icons, Helvetica Neue, Roboto, Helvetica, Arial, sans-serif;
|
||||
outline: none;
|
||||
/* box-sizing: border-box; */
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
flex: 85%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-log {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.user-bubble {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.bubble-content {
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.user-bubble .bubble-content {
|
||||
background-color: #d6eaff;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.ai-bubble .bubble-content {
|
||||
background-color: #e5ece7;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.input-text {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
padding: 1rem 2rem;
|
||||
background-color: #2196f3;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #000;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.language-sql {
|
||||
width: 95%;
|
||||
background-color: #F6F6F6;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
/* overflow-wrap: break-word; */
|
||||
display: block;
|
||||
}
|
||||
|
||||
select {
|
||||
.my-header {
|
||||
position: relative;
|
||||
top: 0;
|
||||
/* height: 8rem; */
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
border: 2px solid #6089a4;
|
||||
font-size: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.recommendation{
|
||||
color: #1c4cf3;
|
||||
margin-top: 1rem;
|
||||
background: #f7dcbc;
|
||||
}
|
||||
|
||||
#noteshare {
|
||||
width: 90%;
|
||||
/* width: 21cm; */
|
||||
min-height: 10rem;
|
||||
/* font-size: 1.5rem; */
|
||||
|
||||
/*border: 1px red solid;*/
|
||||
margin: auto auto 10rem auto;
|
||||
}
|
||||
|
||||
#noteshare p {
|
||||
border: 1px rgb(248, 245, 245) solid;
|
||||
/* border: none; */
|
||||
}
|
||||
|
||||
|
||||
#testInput {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border: 1px rgb(0, 140, 255) solid;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: antiquewhite;
|
||||
background-color: cadetblue;
|
||||
text-shadow: #00a9ff;
|
||||
}
|
||||
|
||||
.my-divider-item {
|
||||
background-color: grey;
|
||||
width: 1px;
|
||||
height: 2rem;
|
||||
margin: 2px 10px 2px 8px;
|
||||
}
|
||||
|
||||
.childStyleStrong {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.childStyleI {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.childStyleU {
|
||||
text-decoration: underline;
|
||||
//中划线
|
||||
//text-decoration: line-through;
|
||||
}
|
||||
|
||||
.childStyleDel {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.childStyleColor {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<!-- <div class="left-panel">-->
|
||||
<!-- <h2>AI文本助手</h2>-->
|
||||
<!-- <h3>常用问题</h3>-->
|
||||
<!-- <div class="recommendation">Java 21有什么新特性</div>-->
|
||||
<!-- <div class="recommendation">红烧肉怎么做</div>-->
|
||||
<!-- </div>-->
|
||||
<div class="right-panel">
|
||||
<div class="chat-log" id="chat-log">
|
||||
|
||||
|
||||
<div class=".my-header">
|
||||
<div style="display: flex;
|
||||
padding: 1rem 4rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #f8f6f4;
|
||||
margin: 0 auto;">
|
||||
|
||||
<div class="head-left">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 11H7.83L13.42 5.41L12 4L4 12L12 20L13.41 18.59L7.83 13H20V11Z" fill="black"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<input type="text" id="user-input" class="input-text" placeholder="请输入您的问题,回车或点击发送确定。">
|
||||
<button id="submit" style="margin-left: 10px;width: 100px" onclick="sendMessage()" class="submit-button">
|
||||
发送
|
||||
</button>
|
||||
<button style="margin-left: 2rem;width: 10rem;background-color: red" onclick="clearChat()"
|
||||
class="submit-button">clear
|
||||
</button>
|
||||
|
||||
<div class="head-center" style="display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 650px;
|
||||
background-color: aqua;">
|
||||
<div style="
|
||||
position: fixed;
|
||||
z-index: 87;
|
||||
/* top: 200px; */
|
||||
/* left: 324px; */
|
||||
">
|
||||
<div>
|
||||
<div style="display: flex;
|
||||
|
||||
">
|
||||
<div>
|
||||
<button id="myPlusB" data-value="b">加粗</button>
|
||||
</div>
|
||||
<div class="my-divider-item"></div>
|
||||
<div>
|
||||
<button id="myPlusI" data-value="i"><i>I</i></button>
|
||||
</div>
|
||||
|
||||
<div class="my-divider-item"></div>
|
||||
<div>
|
||||
<button id="myPlusU" data-value="u"><u>下划线</u></button>
|
||||
</div>
|
||||
<div class="my-divider-item"></div>
|
||||
<div>
|
||||
<button id="myPlusDel" data-value="del">
|
||||
<del>删除线</del>
|
||||
</button>
|
||||
</div>
|
||||
<div class="my-divider-item"></div>
|
||||
<div>
|
||||
<button id="myPlusC" data-value="c_red">红色</button>
|
||||
</div>
|
||||
<!-- <div class="my-divider-item"></div>-->
|
||||
<!-- <div>-->
|
||||
<!-- <button onclick="info(this)">info</button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="head-right">
|
||||
<button>同步</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
|
||||
<script>
|
||||
/*! @license DOMPurify 3.0.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.5/LICENSE */
|
||||
|
||||
// 添加AI信息
|
||||
function addAIMessage(message) {
|
||||
$("#chat-log").append(
|
||||
"<div class=\"chat-bubble ai-bubble\">\n" +
|
||||
" <div class=\"bubble-content\">" + message + "</div>\n" +
|
||||
"</div>"
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<div id="noteshare" spellcheck="false" translate="no">
|
||||
</div>
|
||||
|
||||
// 添加人类信息
|
||||
function addUserMessage(message) {
|
||||
$("#chat-log").append(
|
||||
"<div class=\"chat-bubble user-bubble\">\n" +
|
||||
" <div class=\"bubble-content\">" + message + "</div>\n" +
|
||||
"</div>"
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// 滑动到底部
|
||||
function slideBottom() {
|
||||
let chatlog = document.getElementById("chat-log");
|
||||
chatlog.scrollTop = chatlog.scrollHeight;
|
||||
}
|
||||
|
||||
// 调用api
|
||||
function chatApi(msg) {
|
||||
slideBottom();
|
||||
|
||||
// messagesList.push({role: "user", content: msg});
|
||||
const body = JSON.stringify({ "prompt": msg});
|
||||
// alert(body)
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/v1/ai/completionsTest',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
success: function (res) {
|
||||
// let res1=res?.choices[0]?.message?.content;
|
||||
// res1 = DOMPurify.sanitize(marked.parse(res1));
|
||||
// addAIMessage(res1);
|
||||
|
||||
if (res.code === 0) {
|
||||
let answer = res.data;
|
||||
// answer = marked.parse(answer);
|
||||
answer = DOMPurify.sanitize(marked.parse(answer));
|
||||
addAIMessage(answer);
|
||||
// messageHistory = res.history;
|
||||
} else {
|
||||
addAIMessage("服务接口调用错误。");
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
addAIMessage("服务接口调用异常。");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
function sendMessage() {
|
||||
let userInput = $('#user-input');
|
||||
let userMessage = userInput.val();
|
||||
if (userMessage.trim() === '') {
|
||||
return;
|
||||
}
|
||||
userInput.val("");
|
||||
|
||||
// answer = DOMPurify.sanitize(marked.parse(userMessage));
|
||||
|
||||
addUserMessage(DOMPurify.sanitize(marked.parse(userMessage)));
|
||||
chatApi(userMessage);
|
||||
}
|
||||
|
||||
// 清空聊天记录
|
||||
function clearChat() {
|
||||
$("#chat-log").empty();
|
||||
addAIMessage("你好,请输入你想问的问题。");
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
addAIMessage("你好,请输入你想问的问题。");
|
||||
let submit = $("#submit");
|
||||
let userInput = $("#user-input");
|
||||
let focus = false;
|
||||
// 监听输入框焦点
|
||||
userInput.focus(function () {
|
||||
focus = true;
|
||||
}).blur(function () {
|
||||
focus = false;
|
||||
});
|
||||
// 回车监听事件
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
console.log(focus);
|
||||
if (focus) {
|
||||
submit.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
init();
|
||||
</script>
|
||||
|
||||
</section>
|
||||
|
||||
<footer style="display: flex; justify-content:center; height: 80%; font-size: 1.5rem; background-color:black;">
|
||||
<footer style="display: flex; justify-content:center; height: 80%; font-size: 1.5rem; background-color:black; bottom: 10px;">
|
||||
<p>
|
||||
<a style="color:rgb(201, 201, 206);text-decoration:none;" href="https://beian.miit.gov.cn/" target="_blank"
|
||||
rel="nofollow">
|
||||
@ -586,4 +503,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/v1/static/js/utils.js"></script>
|
||||
<script src="/v1/static/js/styleCmd.js"></script>
|
||||
<script src="/v1/static/js/yanxuelu.js"></script>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user