This commit is contained in:
shaoyongjun 2024-10-31 01:32:52 +08:00
parent 8d6354c239
commit ab00b85c8f
16 changed files with 1659 additions and 161 deletions

View File

@ -9,6 +9,8 @@ import (
"mylomen_server/common/constant"
"mylomen_server/common/utils"
"mylomen_server/static"
"mylomen_server/static/css"
"mylomen_server/static/js"
"net/http"
"os"
"os/signal"
@ -67,6 +69,8 @@ func main() {
//static
static.InitStaticGroup(e.Group("/v1/static/"))
css.InitCssGroup(e.Group("/css"))
js.InitJsGroup(e.Group("/js"))
//ai
apps.InitAiGroup(e.Group("/v1/ai/", func(next echo.HandlerFunc) echo.HandlerFunc {

31
static/css/init.go Normal file
View File

@ -0,0 +1,31 @@
package css
import (
"embed"
"os"
)
import _ "embed"
//go:embed *.css
var cssList embed.FS
var cssMap = initCssMap()
func initCssMap() map[string][]byte {
list, err := cssList.ReadDir(".")
if err != nil {
os.Exit(-1)
return nil
}
var dataMap = make(map[string][]byte, len(list))
for _, file := range list {
//读取配置文件
data, err := cssList.ReadFile(file.Name())
if err == nil {
dataMap[file.Name()] = data
}
}
return dataMap
}

141
static/css/myEdit.css Normal file
View File

@ -0,0 +1,141 @@
/* 字体 */
: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;
}
}
/* style sheet for "letter" printing */
@media print and (width: 8.5in) and (height: 11in) {
@page {
margin: 1in;
}
}
* {
/* 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; */
}
body {
justify-content: center;
/*align-items: center;*/
}
header {
position: relative;
top: 0;
/* height: 8rem; */
z-index: 9999;
left: 0;
right: 0;
width: 100%;
}
#noteshare {
width: 90%;
/* width: 21cm; */
min-height: 10rem;
/* font-size: 1.5rem; */
/*border: 1px red solid;*/
margin: auto auto;
}
#noteshare p {
/*border: 1px rgb(248, 245, 245) solid;*/
margin: 0 0;
padding: 0 0;
/* border: none; */
}
#testInput {
width: 60%;
min-height: 10rem;
border: 1px rgb(0, 140, 255) solid;
margin: 20px auto;
justify-content: center;
}
::selection {
color: antiquewhite;
background-color: cadetblue;
text-shadow: #00a9ff;
}
.my-divider-item {
background-color: lightgray;
width: 1px;
height: 2rem;
margin: 0.2rem 1.6rem;
}
.childStyleStrong {
font-weight: bold
}
.childStyleI {
font-style: italic;
}
.childStyleU {
text-decoration: underline;
/ / 中划线 / / text-decoration: line-through;
}
.childStyleDel {
text-decoration: line-through;
}
.childStyleColor {
color: red;
}
.fixStylePosition {
display: none;
position: fixed;
z-index: 87;
top: 5rem;
left: 10rem;
width: auto;
height: 2.4rem;
padding: 0.8rem 0.8rem;
/*padding: 0.6rem 1rem 0.6rem 1rem;*/
justify-content: center;
align-items: center;
align-content: center;
border-radius: 0.8rem;
border: 1px #dee0e3 solid;
background-color: rgb(255, 255, 255);
/*box-shadow: 0.1rem 0.1rem 0.1rem 0.1rem lightgrey;*/
box-shadow: 0 0.4rem 0.8rem rgba(31, 35, 41, 0.1);
}
.fixStyleOut {
/*border: 1px blue solid;*/
margin: 0 0;
width: auto;
height: 2rem;
padding: 0.5rem 0.5rem;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
}
.fixStyleInnerSpan {
margin: 0 1rem;
}

View File

@ -0,0 +1,16 @@
package css
import (
"github.com/labstack/echo/v4"
"net/http"
)
func InitCssGroup(g *echo.Group) {
g.GET("/:name", func(c echo.Context) error {
cssName := c.Param("name")
c.Response().Header().Set("Cache-Control", "max-age=1")
data, _ := cssMap[cssName]
return c.Blob(http.StatusOK, "text/css; charset=utf-8", data)
})
}

78
static/js/init.go Normal file
View File

@ -0,0 +1,78 @@
package js
import (
"embed"
"io/fs"
"os"
)
import _ "embed"
//go:embed */*/*.js
var jsList embed.FS
//go:embed lib/main.js
var jsMain embed.FS
var jsMap = initJsMap()
func initJsMap() map[string][]byte {
var dataMap = make(map[string][]byte)
btys, _ := jsMain.ReadFile("lib/main.js")
dataMap["lib/main.js"] = btys
list := initJsMapInner("lib/biz")
if list != nil && len(list) > 0 {
for _, file := range list {
//读取配置文件
data, err := jsList.ReadFile("lib/biz/" + file.Name())
if err == nil {
dataMap["lib/biz/"+file.Name()] = data
}
}
}
list = initJsMapInner("lib/common")
if list != nil && len(list) > 0 {
for _, file := range list {
//读取配置文件
data, err := jsList.ReadFile("lib/common/" + file.Name())
if err == nil {
dataMap["lib/common/"+file.Name()] = data
}
}
}
list = initJsMapInner("lib/event")
if list != nil && len(list) > 0 {
for _, file := range list {
//读取配置文件
data, err := jsList.ReadFile("lib/event/" + file.Name())
if err == nil {
dataMap["lib/event/"+file.Name()] = data
}
}
}
list = initJsMapInner("lib/model")
if list != nil && len(list) > 0 {
for _, file := range list {
//读取配置文件
data, err := jsList.ReadFile("lib/model/" + file.Name())
if err == nil {
dataMap["lib/model/"+file.Name()] = data
}
}
}
return dataMap
}
func initJsMapInner(path string) []fs.DirEntry {
list, err := jsList.ReadDir(path)
if err != nil {
os.Exit(-1)
return nil
}
return list
}

481
static/js/lib/biz/MyBiz.js Normal file
View File

@ -0,0 +1,481 @@
"use strict";
import {MyRecovery} from "../model/MyRecovery.js";
import {MyDocItem} from "../model/MyDocItem.js";
import {MyMapItem} from "../model/MyMapItem.js";
export class MyBiz {
constructor() {
/**
* 输入事件
*/
window.myEdit.eventListener.RegisterEventHandle('input', window.myEdit.eventListener.InputListener);
window.myEdit.eventListener.RegisterEventHandle('compositionstart', window.myEdit.eventListener.CompositionstartListener);
window.myEdit.eventListener.RegisterEventHandle('compositionend', window.myEdit.eventListener.CompositionendListener);
// 初始化第一个输入框
let newParagraph = document.createElement("p");
newParagraph.setAttribute("contenteditable", "true");
let uuid = window.myEdit.utils.Uuid()
let curOrder = window.myEdit.ctx.incrementNumThenReturn();
newParagraph.setAttribute("data-id", uuid)
newParagraph.setAttribute("id", uuid)
newParagraph.setAttribute("data-order", curOrder)
newParagraph.onkeydown = window.myEdit.eventListener.KeydownListener
newParagraph.innerHTML = "<br>"
//添加一行
window.myEdit.utils.AddNewParagraph(newParagraph);
}
/**
* 根据类型名称解析 样式的 class 名称
* @param styleName
* @returns {string}
*/
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"
}
}
/**
* 输入事件
* @param e
*/
inputHandle(e) {
let curP = window.myEdit.utils.GetEventTarget(e);
this.updateText(curP)
}
/**
* 中文输入开始事件
* @param e
*/
compositionstartHandle(e) {
// console.log("compositionstart")
window.myEdit.ctx.inCompositionEvent = true
}
/**
* 中文输入结束事件
* @param e
*/
compositionendHandle(e) {
// console.log("compositionend")
let curP = window.myEdit.utils.GetEventTarget(e);
this.updateText(curP)
//中文输入结束
window.myEdit.ctx.inCompositionEvent = false
}
/**
* 撤销事件
* @param event
*/
cancelHandle(event) {
console.log('触发ctrl + Z 事件', event.target)
if (window.myEdit.ctx.latestOpDoc !== undefined && window.myEdit.ctx.latestOpDoc !== null) {
//恢复
window.myEdit.ctx.latestOpDoc.recovery();
//测试展示
window.myEdit.ctx.showTestText()
//阻止事件
window.myEdit.utils.ProhibitDefaultEvent(event);
}
window.myEdit.ctx.latestOpDoc = null
}
/**
* 删除事件
* @param event
*/
deleteHandle(event) {
let curP = window.myEdit.utils.GetEventTarget(event);
let cNo = parseInt(curP.getAttribute("data-order"))
//维护最近一次编辑的内容
if (window.myEdit.ctx.latestOpDoc === undefined
|| window.myEdit.ctx.latestOpDoc === null
|| window.myEdit.ctx.latestOpDoc.getData().getAttribute("data-id") !== curP.getAttribute("data-id")) {
window.myEdit.ctx.latestOpDoc = new MyRecovery(curP.cloneNode(true), function () {
let cNo = parseInt(this.data.getAttribute("data-order"))
console.log("恢复", this.data, cNo, " this: ", this)
if (cNo > 1) {
window.myEdit.utils.InsertAfter(this.data, document.querySelector("#noteshare p[data-order='" + (cNo - 1) + "']"))
} else {
//添加元素到首位 todo_xxx
// window.myEdit.ctx.MyRoot.insertBefore(this.data, window.myEdit.ctx.MyRoot.children[0])
window.myEdit.ctx.MyRoot.insertBefore(this.data, window.myEdit.ctx.MyRoot.children[0]);
}
// 恢复该元素展示
window.myEdit.ctx.MyDocMap.get(cNo).setHidden(false);
})
}
//如果是第一行
let previousSibling = curP.previousSibling
console.log(curP, previousSibling === undefined, previousSibling.id === undefined)
if (previousSibling === undefined || previousSibling.id === undefined) {
//显示用户的输入内容
window.myEdit.ctx.showTestText()
return
}
console.log('触发删除', curP.innerHTML, cNo)
let curS = window.myEdit.utils.GetSelection();
// console.log("当前内容: ", curP.innerHTML, " 当前选区 ", curS)
//处理前面没有内容,后面还有内容需要拼接到上层的场景
if ((curS.isCollapsed && curS.anchorOffset === 0) || curP.innerHTML === '<br>') {
let curNodeRetainHtml = curP.innerHTML
//阻止事件传递
window.myEdit.utils.ProhibitDefaultEvent(event);
//设置该元素隐藏
window.myEdit.ctx.MyDocMap.get(cNo).setHidden(true)
//删除当前元素
// curP.remove()
curP.innerHTML = "<br/>"
let emptyRowNoList = window.myEdit.ctx.MyDocMap.get("emptyRowNoList");
if (emptyRowNoList === undefined || emptyRowNoList === null) {
emptyRowNoList = [];
window.myEdit.ctx.MyDocMap.set("emptyRowNoList", emptyRowNoList);
}
emptyRowNoList.push(cNo);
//拼接
if (curNodeRetainHtml !== '<br>') {
previousSibling.innerHTML = previousSibling.innerHTML + curNodeRetainHtml
}
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(previousSibling, 1);
}
//显示用户的输入内容
window.myEdit.ctx.showTestText();
}
/**
* 回车事件
* @param event
* @param onkeydownHandle
*/
enterHandler(event) {
//阻止事件
window.myEdit.utils.ProhibitDefaultEvent(event);
//uuid
let uuid = window.myEdit.utils.Uuid();
//rowNo
let rowNo = 0;
let emptyRowNoList = window.myEdit.ctx.MyDocMap.get("emptyRowNoList");
console.log(emptyRowNoList);
if (emptyRowNoList === undefined || emptyRowNoList === null) {
rowNo = window.myEdit.ctx.incrementNumThenReturn();
//添加新元素
let newParagraph = document.createElement("p")
newParagraph.setAttribute("contenteditable", "true")
newParagraph.setAttribute("data-id", uuid)
newParagraph.setAttribute("id", uuid)
newParagraph.setAttribute("data-order", rowNo)
newParagraph.onkeydown = window.myEdit.eventListener.KeydownListener
newParagraph.innerHTML = "<br>"
window.myEdit.ctx.MyRoot.appendChild(newParagraph)
window.myEdit.ctx.MyDocMap.set(rowNo, new MyMapItem(uuid))
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(newParagraph, 0);
return
}
let sortArr = emptyRowNoList.sort((a, b) => a - b)
console.log(sortArr);
// 最小值
rowNo = sortArr[0];
let newParagraph = document.querySelector("#noteshare p[data-order='" + rowNo + "']");
newParagraph.innerHTML = "<br>";
window.myEdit.ctx.MyDocMap.get(rowNo).setHidden(false);
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(newParagraph, 0)
}
/**
* 更新文档
* @param {*} e
*/
updateText(curP) {
if (window.myEdit.ctx.inCompositionEvent) {
return;
}
let myP = new MyDocItem(curP);
let cNo = myP.parseOrder();
let mapItem = window.myEdit.ctx.getMapItem(cNo)
//内容不变则不处理
let h5CurLen = curP.innerText.length
let myDocNodeLen = (mapItem && mapItem.getSource()) ? mapItem.getSource().length : 0
if (h5CurLen === myDocNodeLen) {
return
}
mapItem.setSource(curP.innerText)
// console.log("curPTx: ", curP.innerText, "MyDocMap : ", utils.MyDocMap)
//显示用户的输入内容
window.myEdit.ctx.showTestText()
}
/**
* 空格键处理
* @param {*} event
*/
emptyKeyWorkHandler(event) {
let curP = window.myEdit.utils.GetEventTarget(event);
let myDocItem = new MyDocItem(curP);
let inputLength = curP.innerText.length
/**
* h1 ~ h6
*/
if (curP.innerText.startsWith("#") && curP.innerHTML.endsWith("&nbsp;")) {
let curNo = myDocItem.parseOrder();
let mapNode = window.myEdit.ctx.MyDocMap.get(curNo);
// console.log(curP, " - ", curP.innerHTML, curP.innerHTML.startsWith("# "))
if (curP.innerHTML.startsWith("#&nbsp;") || curP.innerHTML.startsWith("# ")) {
mapNode.getStyle().setNodeType("h1")
this.becomeAnotherElement(curP, "h1", onkeydownHandle)
} else if (curP.innerHTML.startsWith("##&nbsp;")) {
mapNode.getStyle().setNodeType("h2")
this.becomeAnotherElement(curP, "h2", onkeydownHandle)
} else if (curP.innerHTML.startsWith("###&nbsp;")) {
mapNode.getStyle().setNodeType("h3")
this.becomeAnotherElement(curP, "h3", onkeydownHandle)
} else if (curP.innerHTML.startsWith("####&nbsp;")) {
mapNode.getStyle().setNodeType("h4")
this.becomeAnotherElement(curP, "h4", onkeydownHandle)
} else if (curP.innerHTML.startsWith("#####&nbsp;")) {
mapNode.getStyle().setNodeType("h5")
this.becomeAnotherElement(curP, "h5", onkeydownHandle)
} else {
mapNode.getStyle().setNodeType("h6")
this.becomeAnotherElement(curP, "h6", onkeydownHandle)
}
}
/**
* 无序列表效果
*/
if (inputLength === 2 && curP.innerText.startsWith("-") && curP.innerHTML.endsWith("&nbsp;")) {
let curNo = myDocItem.parseOrder();
let mapNode = window.myEdit.ctx.MyDocMap.get(curNo);
mapNode.getStyle().setPreStyle("ul", true)
//clean
curP.innerHTML = ""
mapNode.setSource("")
//根据上一层级元素动态选择 todo
curP.setAttribute("style", "padding-left: 1rem;")
//新增元素
let newParagraph = document.createElement("span");
newParagraph.setAttribute("contenteditable", "false")
//∙ vs ∘
newParagraph.innerHTML = "∙ &nbsp;"
curP.append(newParagraph)
//添加一个选区
var selObj = window.myEdit.utils.GetSelection();
var rangeObj = document.createRange()
rangeObj.selectNode(curP)
selObj.addRange(rangeObj)
//收起选区到一个点,光标落在一个可编辑元素上
window.myEdit.utils.GetSelection().collapse(curP, true)
}
/**
* 有序列表效果
*/
if (inputLength > 2 && inputLength <= 5 && isNum(curP.innerText.substring(0, inputLength - 2)) && curP.innerHTML.endsWith(".&nbsp;")) {
let num = curP.innerText.substring(0, inputLength - 2)
console.log(curP.innerText, num)
let curNo = myDocItem.parseOrder();
let mapNode = window.myEdit.ctx.MyDocMap.get(curNo);
mapNode.getStyle().setPreStyle("ol", num)
//clean
curP.innerHTML = ""
mapNode.setSource("")
//todo
curP.setAttribute("style", "padding-left: 1rem;")
//新增元素
let newParagraph = document.createElement("span");
newParagraph.setAttribute("contenteditable", "false");
newParagraph.innerHTML = num + ".&nbsp;"
curP.append(newParagraph);
//收起选区到一个点,光标落在一个可编辑元素上
window.myEdit.utils.GetSelection().collapse(curP, true)
}
}
/**
* 变成另一个元素
* @param {*} elementName
*/
becomeAnotherElement(elementName) {
let newParagraph = document.createElement(elementName)
newParagraph.setAttribute("contenteditable", "true")
newParagraph.setAttribute("data-id", this.self.getAttribute("data-id"))
newParagraph.setAttribute("id", this.self.getAttribute("data-id"))
newParagraph.setAttribute("data-order", this.self.getAttribute("data-order"))
newParagraph.onkeydown = onkeydownHandle
//todo 支持 有数据的行 在行首输入 #
// if()
// switch (elementName){
// case "h1":
// }
newParagraph.innerHTML = "<br>"
window.myEdit.utils.InsertAfter(newParagraph, this.self)
this.self.remove();
this.self = newParagraph;
//matData
let curNo = parseOrder(this.self)
let mapNode = utils.MyDocMap.get(curNo)
mapNode.setSource("")
//收起选区到一个点,光标落在一个可编辑元素上
window.myEdit.utils.GetSelection().setPosition(newParagraph, 0)
}
/**
* 包围样式事件处理
* @param event
*/
surroundContentsByStyleHandler(event) {
let curS = window.myEdit.utils.GetSelection();
let curP = window.myEdit.utils.GetEventTarget(event);
let styleName = curP.getAttribute("data-value");
if (styleName === undefined) {
styleName = curP.parentNode.getAttribute("data-value");
}
let className = this.parseStyleName2ClassName(styleName)
//todo 只对 nodeType = p 执行
console.log("当前光标信息: ", curS, styleName, " className: ", className, curP)
for (let i = 0; i < curS.rangeCount; i++) {
let curSec = curS.getRangeAt(i);
let curPe = curSec.commonAncestorContainer;
//一个元素节点,例如 <p> 和 <div>。
let curPeIsP = curPe.nodeType === 1 && curPe.nodeName === "P";
let curPeParentIsP = curPe.parentNode.nodeType === 1 && curPe.parentNode.nodeName === "P";
let curPeParentIsDIV = curPe.parentNode.nodeType === 1 && curPe.parentNode.nodeName === "DIV";
let curPeParentIsSpan = curPe.parentNode.nodeType === 1 && (curPe.parentNode.nodeName === 'SPAN"' || curPe.parentNode.nodeName === 'SPAN');
let start = curSec.startOffset;
let end = curSec.endOffset;
console.log(" 当前选区信息 : ", curSec,
"\ncurPe: ", curPe,
"\ncurPeP: ", curPe.parentNode, curPe.parentNode.nodeType, curPe.parentNode.nodeName,
"\ncurPeIsP: ", curPeIsP,
"\ncurPeParentIsP : ", curPeParentIsP,
"\ncurPeParentIsDIV", curPeParentIsDIV,
"\ncurPeParentIsSpan", curPeParentIsSpan,
"\nclassName: ", className,
"\ntart: ", start,
"\nend: ", end)
let curPEle = null;
//第一次选择的时候将整行转换成<p><span></span></p>
if (!curPeIsP && curPeParentIsP) {
//没选择,则退出
if (start === end) {
return
} else {
// div
}
let curStartP = curSec.startContainer.parentElement;
console.log("debug1: ", curStartP, curStartP.nodeType, curStartP.nodeName)
//维护最近一次编辑的内容(暂时只支持恢复最近一次编辑)
window.myEdit.ctx.latestOpDoc = new 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
window.myEdit.utils.SyncMapItemChildrenStyle(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 class=' + className + '>' + curStartP.innerText.charAt(j) + '</span>';
} else {
curHtml += '<span>' + curStartP.innerText.charAt(j) + '</span>';
}
}
curStartP.innerHTML = curHtml;
curPEle = curStartP;
//光标保持
// curS.collapseToEnd();
// window.myEdit.utils.GetSelection().setPosition(newParagraph, 0);
} else {
let tmpPNode = curSec.commonAncestorContainer;
if (!curPeIsP && curPeParentIsSpan) {
tmpPNode = curSec.commonAncestorContainer.parentNode.parentNode;
}
console.log("debug2: ", curSec.commonAncestorContainer, tmpPNode, tmpPNode.children, tmpPNode.childNodes)
//维护最近一次编辑的内容(暂时只支持恢复最近一次编辑)
window.myEdit.ctx.latestOpDoc = new MyRecovery(tmpPNode.cloneNode(true), function () {
console.log("恢复上一步样式1", this.data)
let curEl = document.getElementById(this.data.getAttribute("id"));
curEl.innerHTML = this.data.innerHTML;
//文本映射 直接覆盖 map 中的 childrenStyle
window.myEdit.utils.SyncMapItemChildrenStyle(this.data);
})
let myChildren = tmpPNode.childNodes
// let curEleSize = tmpPNode.childNodes.length
for (let j = 0; j < myChildren.length; j++) {
let curEle = myChildren[j]
if (curS.containsNode(curEle, true)) {
curEle.classList.remove(className);
curEle.classList.add(className);
}
}
curPEle = curSec.commonAncestorContainer;
}
//文本映射 直接覆盖 map 中的 childrenStyle
window.myEdit.utils.SyncMapItemChildrenStyle(curPEle);
}
}
}

View File

@ -0,0 +1,398 @@
"use strict";
import {MyDocItem} from "../model/MyDocItem.js";
import {MyMapItem} from "../model/MyMapItem.js";
export class MyUtils {
//todo 判断是哪一种浏览器类型。以及是否是手机
//todo 初始化屏幕宽高比
MyBrowser = null
constructor() {
/**
* 提供浏览器检测的模块
* @unfile
* @module UE.browser
*/
window.browser = ((function () {
var agent = navigator.userAgent.toLowerCase(),
opera = window.opera,
browser = {
/**
* @property {boolean} ie 检测当前浏览器是否为IE
* @example
* ```javascript
* if ( UE.browser.ie ) {
* console.log( '当前浏览器是IE' );
* }
* ```
*/
ie: /(msie\s|trident.*rv:)([\w.]+)/i.test(agent),
/**
* @property {boolean} opera 检测当前浏览器是否为Opera
* @example
* ```javascript
* if ( UE.browser.opera ) {
* console.log( '当前浏览器是Opera' );
* }
* ```
*/
opera: !!opera && opera.version,
/**
* @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
* @example
* ```javascript
* if ( UE.browser.webkit ) {
* console.log( '当前浏览器是webkit内核浏览器' );
* }
* ```
*/
webkit: agent.indexOf(" applewebkit/") > -1,
/**
* @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
* @example
* ```javascript
* if ( UE.browser.mac ) {
* console.log( '当前浏览器运行在mac平台下' );
* }
* ```
*/
mac: agent.indexOf("macintosh") > -1,
/**
* @property {boolean} quirks 检测当前浏览器是否处于怪异模式
* @example
* ```javascript
* if ( UE.browser.quirks ) {
* console.log( '当前浏览器运行处于“怪异模式”' );
* }
* ```
*/
quirks: document.compatMode == "BackCompat"
}
/**
* @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
* @example
* ```javascript
* if ( UE.browser.gecko ) {
* console.log( '当前浏览器内核是gecko内核' );
* }
* ```
*/
browser.gecko =
navigator.product == "Gecko" &&
!browser.webkit &&
!browser.opera &&
!browser.ie
var version = 0
// Internet Explorer 6.0+
if (browser.ie) {
var v1 = agent.match(/(?:msie\s([\w.]+))/)
var v2 = agent.match(/(?:trident.*rv:([\w.]+))/)
if (v1 && v2 && v1[1] && v2[1]) {
version = Math.max(v1[1] * 1, v2[1] * 1)
} else if (v1 && v1[1]) {
version = v1[1] * 1
} else if (v2 && v2[1]) {
version = v2[1] * 1
} else {
version = 0
}
browser.ie11Compat = document.documentMode == 11
/**
* @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
* @warning 如果浏览器不是IE 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.ie9Compat ) {
* console.log( '当前浏览器运行在IE9兼容模式下' );
* }
* ```
*/
browser.ie9Compat = document.documentMode == 9
/**
* @property { boolean } ie8 检测浏览器是否是IE8浏览器
* @warning 如果浏览器不是IE 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.ie8 ) {
* console.log( '当前浏览器是IE8浏览器' );
* }
* ```
*/
browser.ie8 = !!document.documentMode
/**
* @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
* @warning 如果浏览器不是IE 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.ie8Compat ) {
* console.log( '当前浏览器运行在IE8兼容模式下' );
* }
* ```
*/
browser.ie8Compat = document.documentMode == 8
/**
* @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
* @warning 如果浏览器不是IE 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.ie7Compat ) {
* console.log( '当前浏览器运行在IE7兼容模式下' );
* }
* ```
*/
browser.ie7Compat =
(version == 7 && !document.documentMode) || document.documentMode == 7
/**
* @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
* @warning 如果浏览器不是IE 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.ie6Compat ) {
* console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
* }
* ```
*/
browser.ie6Compat = version < 7 || browser.quirks
browser.ie9above = version > 8
browser.ie9below = version < 9
browser.ie11above = version > 10
browser.ie11below = version < 11
}
// Gecko.
if (browser.gecko) {
var geckoRelease = agent.match(/rv:([\d\.]+)/)
if (geckoRelease) {
geckoRelease = geckoRelease[1].split(".")
version =
geckoRelease[0] * 10000 +
(geckoRelease[1] || 0) * 100 +
(geckoRelease[2] || 0) * 1
}
}
/**
* @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是则返回Chrome的大版本号
* @warning 如果浏览器不是chrome 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.chrome ) {
* console.log( '当前浏览器是Chrome' );
* }
* ```
*/
if (/chrome\/(\d+\.\d)/i.test(agent)) {
browser.chrome = +RegExp["\x241"]
}
/**
* @property { Number } safari 检测当前浏览器是否为Safari, 如果是则返回Safari的大版本号
* @warning 如果浏览器不是safari 则该值为undefined
* @example
* ```javascript
* if ( UE.browser.safari ) {
* console.log( '当前浏览器是Safari' );
* }
* ```
*/
if (
/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) &&
!/chrome/i.test(agent)
) {
browser.safari = +(RegExp["\x241"] || RegExp["\x242"])
}
// Opera 9.50+
if (browser.opera) version = parseFloat(opera.version())
// WebKit 522+ (Safari 3+)
if (browser.webkit)
version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1])
/**
* @property { Number } version 检测当前浏览器版本号
* @remind
* <ul>
* <li>IE系列返回值为5,6,7,8,9,10</li>
* <li>gecko系列会返回10900158900</li>
* <li>webkit系列会返回其build号 ( 522)</li>
* </ul>
* @example
* ```javascript
* console.log( '当前浏览器版本号是: ' + UE.browser.version );
* ```
*/
browser.version = version
/**
* @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容
* @example
* ```javascript
* if ( UE.browser.isCompatible ) {
* console.log( '浏览器与UEditor能够良好兼容' );
* }
* ```
*/
browser.isCompatible =
!browser.mobile &&
((browser.ie && version >= 6) ||
(browser.gecko && version >= 10801) ||
(browser.opera && version >= 9.5) ||
(browser.air && version >= 1) ||
(browser.webkit && version >= 522) ||
false)
return browser
})())
//快捷方式
window.ie = window.browser.ie;
window.webkit = window.browser.webkit;
window.gecko = window.browser.gecko;
window.opera = window.browser.opera;
//log
console.log("browser : ", window.browser,
"\nie: ", window.ie,
"\nwindow.webkit: ", window.webkit,
"\nwindow.gecko: ", window.gecko,
"\nwindow.opera: ", window.opera)
}
/**
*
* @returns 生产uuid
*/
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]
}
/**
* 阻止默认事件
* @constructor
*/
ProhibitDefaultEvent(event) {
event.preventDefault()
event.returnValue = false
}
/**
* 获取 触发事件的元素
* @param event
* @constructor
*/
GetEventTarget(event) {
return event.target
}
ParseEvent(e) {
return e || window.event //标准化事件处理
}
GetKeyCode(event) {
return event.keyCode || event.which || event.charCode
}
/**
* 当前选区 兼容不同浏览器
* @returns {Selection|*}
*/
GetSelection() {
return window.getSelection() || document.selection
}
/**
* 是否是数字
* @param value
* @returns {boolean}
*/
IsNum(value) {
return !isNaN(parseFloat(value)) && isFinite(value)
}
/**
* 在节点node后面插入新节点newNode
* @method InsertAfter
* @param { Node } node 目标节点
* @param { Node } newNode 新插入的节点 该节点将置于目标节点之后
* @return { Node } 新插入的节点
*/
InsertAfter(node, newNode) {
return node.nextSibling
? node.parentNode.insertBefore(newNode, node.nextSibling)
: node.parentNode.appendChild(newNode)
}
/**
* 新增一个元素
* @param newParagraph
*/
AddNewParagraph(newParagraph) {
//docRoot
window.myEdit.ctx.MyRoot.appendChild(newParagraph);
//mapRoot
let myP = new MyDocItem(newParagraph);
let curOrder = myP.parseOrder();
let uuid = myP.parseUuid();
window.myEdit.ctx.MyDocMap.set(curOrder, new MyMapItem(uuid))
//收起选区到一个点,光标落在一个可编辑元素上
window.myEdit.utils.GetSelection().setPosition(newParagraph, 0);
}
/**
* 同步某一行数据到对应的 map节点
* @param docP
* @constructor
*/
SyncMapItemChildrenStyle(docP) {
//子元素为空不处理
let items = docP.childNodes;
if (items.length <= 0) {
return
}
//构造参数
let curMyP = new MyDocItem(docP);
let mapItem = window.myEdit.ctx.getMapItem(curMyP.parseOrder());
//清空重置
// console.log(mapItem);
mapItem.getStyle().setChildrenStyleMapNull();
//遍历
for (let i = 0; i < items.length; i++) {
let curItem = items[i];
let tmpClassList = curItem.classList;
if (tmpClassList != null && tmpClassList.length > 0) {
mapItem.getStyle().setChildrenStyle(i, tmpClassList);
}
}
// console.log("sync docP : ", docP, " children: ", docP.children, " childrenMap: ", mapItem.getStyle().getChildrenStyleMap())
}
}

View File

@ -0,0 +1,171 @@
"use strict";
/**
* 解决事件监听 this 问题这里 转接一下
*/
export class MyEventListener {
constructor(styleClass) {
//样式事件
let styleList = document.getElementsByClassName(styleClass);
// console.log(styleList);
if (styleList && styleList.length > 0) {
for (let i = 0; i < styleList.length; i++) {
// console.log(styleList[i]);
styleList[i].addEventListener('click', this.SurroundContentsByStyleListener, true);
}
}
}
SurroundContentsByStyleListener(e) {
const event = window.myEdit.utils.ParseEvent(e);
console.log("SurroundContentsByStyleListener : ", e, event);
//业务
window.myEdit.biz.surroundContentsByStyleHandler(event);
}
/**
* 鼠标按下事件 & 键盘组合事件
* @param {*} e
*/
KeydownListener(e) {
const event = window.myEdit.utils.ParseEvent(e);
// console.log("this: ", this, e, e.target, "\n event: ", event)
const keyCode = window.myEdit.utils.GetKeyCode(event);
const keyCombination = event.ctrlKey
const metaKey = event.metaKey
// console.log("键盘事件 ", event, keyCombination, metaKey, keyCode)
// ctrl + c 复制
if (keyCombination && keyCode === 67) {
// 阻止默认事件
window.myEdit.utils.ProhibitDefaultEvent(event);
console.log('触发ctrl + c 事件', e.target)
}
//撤销
if (metaKey && keyCode === 90) {
window.myEdit.biz.cancelHandle(event)
return;
}
//删除
if (keyCode === 46 || keyCode === 8) {
window.myEdit.biz.deleteHandle(event);
return;
}
//回车事件
if (keyCode === 13 /* && currentNode === key.lastElementChild */) {
window.myEdit.biz.enterHandler(event);
return;
}
//空格键
if (keyCode === 32) {
console.log('触发 空格 事件', e.target)
window.myEdit.biz.emptyKeyWorkHandler(event);
return;
}
}
/**
* 窗口撤销事件
* @param e
* @constructor
*/
WindowsCtrZHandle(e) {
const event = window.myEdit.utils.ParseEvent(e);
const keyCode = window.myEdit.utils.GetKeyCode(event);
const metaKey = event.metaKey;
if (metaKey && keyCode === 90) {
window.myEdit.biz.cancelHandle(event);
}
}
/**
* 输入事件
* @param e
* @constructor
*/
InputListener(e) {
const event = window.myEdit.utils.ParseEvent(e);
window.myEdit.biz.inputHandle(event);
}
/**
* 中文输入开始事件
* @param e
* @constructor
*/
CompositionstartListener(e) {
const event = window.myEdit.utils.ParseEvent(e);
window.myEdit.biz.compositionstartHandle(event);
}
/**
* 中文输入结束事件
* @param e
* @constructor
*/
CompositionendListener(e) {
const event = window.myEdit.utils.ParseEvent(e);
window.myEdit.biz.compositionendHandle(event);
}
/**
* 注册事件处理器
* @param eventName
* @param handler
*/
RegisterEventHandle(eventName, handler) {
window.myEdit.ctx.MyRoot.addEventListener(eventName, handler);
}
/**
* 监听鼠标抬起事件
* @param e
* @constructor
*/
MouseUp(e) {
let styleUtils = document.getElementById("_style_utils");
styleUtils.addEventListener("mousedown", function (e) {
const event = window.myEdit.utils.ParseEvent(e);
window.myEdit.utils.ProhibitDefaultEvent(event);
})
if (window.myEdit.utils.GetSelection().isCollapsed) {
styleUtils.style.display = "none";
return
}
let posX = 0, posY = 0;
const event = window.myEdit.utils.ParseEvent(e);
// if (event.pageX || event.pageY) {
// posX = event.pageX;
// posY = event.pageY;
// } else if (event.clientX || event.clientY) {
// posX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
// posY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
// }
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
var x = event.pageX || event.clientX + scrollX;
var y = event.pageY || event.clientY + scrollY;
console.log("posX: ", x, " posY: ", y, event.pageX, event.pageY, scrollX, scrollY, event.clientX, event.clientY);
//
styleUtils.style.display = "block";
styleUtils.style.left = (event.clientX - 60) + "px";
styleUtils.style.top = (event.clientY - 60) + "px";
// window.myEdit.utils.ProhibitDefaultEvent(event);
}
}

79
static/js/lib/main.js Normal file
View File

@ -0,0 +1,79 @@
"use strict";
import {MyBiz} from './biz/MyBiz.js'
import {MyUtils} from './common/MyUtils.js'
import {MyEventListener} from "./event/MyEventListener.js";
window.onload = function () {
//init
window.myEdit = {
/**
* 优先初始化 工具
*/
utils: new MyUtils(),
/**
* 其次初始化 事件监听
*/
eventListener: new MyEventListener("fixStyleInnerSpan"),
ctx: {
/**
* 文档的根节点
*/
MyRoot: document.getElementById("noteshare"),
/**
* 文档的结构树
*/
MyDocMap: new Map(),
getMapItem: function (orderNo) {
return this.MyDocMap.get(orderNo);
},
/**
* 最新修改的元素. 当前只支持撤销一次
*/
latestOpDoc: null,
/**
* 行增加记录 行号
*/
rowNo: 0,
incrementNumThenReturn: function () {
return this.rowNo++;
},
/**
* 是否开始输入中文
*/
inCompositionEvent: false,
showTestText: function () {
//显示用户的输入内容
let userInput = document.getElementById("testInput");
if (userInput) {
let obj = {}
for (let [k, v] of this.MyDocMap.entries()) {
if (v["data-hidden"] && v["data-hidden"] === true) {
continue
}
obj[k] = v
}
// console.log("userInput : ", userInput)
userInput.innerText = JSON.stringify(obj)
}
}
},
}
/**
* 业务处理
*/
window.myEdit.biz = new MyBiz();
//窗口撤销事件
window.addEventListener('keydown', window.myEdit.eventListener.WindowsCtrZHandle, true);
//监听鼠标抬起事件
document.getElementById("noteshare").addEventListener("mouseup", window.myEdit.eventListener.MouseUp, true);
}

View File

@ -0,0 +1,52 @@
"use strict";
import {MyKV} from './MyKV.js'
export class InnerStyle {
constructor() {
this.nodeType = "p"
//map-> index:classList
this.childrenStyle = null;
//前置包围元素。 如 有序/无序列表
this.preStyle = null;
}
getNodeType() {
return this.nodeType
}
setNodeType(nodeType) {
this.nodeType = nodeType
}
setChildrenStyle(index, classList) {
if (this.childrenStyle === null) {
this.childrenStyle = new Map();
}
this.childrenStyle.set(index, classList)
}
getChildrenStyle(index) {
return this.childrenStyle.get(index);
}
//前置类型 如 ul ol 代码块 等
setPreStyle(k, v) {
if (this.preStyle == null) {
this.preStyle = new MyKV(k, v);
}
}
getPreStyle() {
return this.preStyle
}
getChildrenStyleMap() {
return this.childrenStyle;
}
setChildrenStyleMapNull() {
this.childrenStyle = null;
}
}

View File

@ -0,0 +1,29 @@
"use strict";
/**
* 编辑的每一行 建议不要删除 不要改变
*/
export class MyDocItem {
constructor(p) {
this.self = p;
}
/**
* 解析当前行号
* @returns {number}
*/
parseOrder() {
return parseInt(this.self.getAttribute("data-order"));
}
parseId() {
return parseInt(this.self.getAttribute("id"));
}
parseUuid() {
return parseInt(this.self.getAttribute("data-id"));
}
}

View File

@ -0,0 +1,15 @@
"use strict";
export 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
}
}
}

View File

@ -0,0 +1,31 @@
"use strict";
import {InnerStyle} from './InnerStyle.js'
export class MyMapItem {
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
}
}

View File

@ -0,0 +1,19 @@
"use strict";
export class MyRecovery {
constructor(data, func) {
this.data = data;
this.func = func;
}
getData() {
return this.data;
}
getFun() {
return this.func;
}
recovery() {
this.func();
}
}

View File

@ -0,0 +1,15 @@
package js
import (
"github.com/labstack/echo/v4"
"net/http"
)
func InitJsGroup(g *echo.Group) {
g.GET("/:name", func(c echo.Context) error {
jsName := c.Param("name")
c.Response().Header().Set("Cache-Control", "max-age=1")
return c.Blob(http.StatusOK, "text/javaScript", jsMap[jsName])
})
}

View File

@ -6,187 +6,125 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📒</title>
<link rel="stylesheet" type="text/css" href="/v1/static/css/normalize.css">
<style>
/* 字体 */
: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;
}
}
/* style sheet for "letter" printing */
@media print and (width: 8.5in) and (height: 11in) {
@page {
margin: 1in;
}
}
* {
/* 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; */
}
header {
position: relative;
top: 0;
/* height: 8rem; */
z-index: 9999;
left: 0;
right: 0;
width: 100%;
background: #f7dcbc;
}
#noteshare {
width: 80%;
/* width: 21cm; */
min-height: 80rem;
font-size: 1.6rem;
/*border: 1px red solid;*/
margin: auto 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>
<link rel="stylesheet" type="text/css" href="./css/normalize.css">
<link rel="stylesheet" type="text/css" href="./css/myEdit.css">
</head>
<body style="display: flex; flex-direction:column; ">
<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="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 class="fixStylePosition" id="_style_utils">
<div class="fixStyleOut">
<div style="display: flex;">
<div style="display: flex;">
<div>
<svg width="1.8rem" height="1.8rem" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="TextOutlined">
<path
d="M2 3a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0V4h-7v16h3a1 1 0 1 1 0 2H8a1 1 0 1 1 0-2h3V4H4v3a1 1 0 1 1-2 0V3Z"
fill="currentColor"></path>
</svg>
</div>
<div>
<svg width="1.4rem" height="1.4rem" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="DownBoldOutlined">
<path
d="m3.414 7.086-.707.707a1 1 0 0 0 0 1.414l7.778 7.778a2 2 0 0 0 2.829 0l7.778-7.778a1 1 0 0 0 0-1.414l-.707-.707a1 1 0 0 0-1.415 0l-7.07 7.07-7.072-7.07a1 1 0 0 0-1.414 0Z"
fill="currentColor"></path>
</svg>
</div>
</div>
</div>
</div>
<div class="my-divider-item"></div>
<div style="display: flex;">
<div>
<svg width="1.8rem" height="1.8rem" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="TypographyOutlined">
<path
d="M2 4a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 4a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm1 3a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Zm-1 5a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm1 3a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Z"
fill="currentColor"></path>
</svg>
</div>
<div>
<svg width="1.4rem" height="1.4rem" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="DownBoldOutlined">
<path
d="m3.414 7.086-.707.707a1 1 0 0 0 0 1.414l7.778 7.778a2 2 0 0 0 2.829 0l7.778-7.778a1 1 0 0 0 0-1.414l-.707-.707a1 1 0 0 0-1.415 0l-7.07 7.07-7.072-7.07a1 1 0 0 0-1.414 0Z"
fill="currentColor"></path>
</svg>
</div>
</div>
<div class="head-right">
<button>同步</button>
<div class="my-divider-item"></div>
<div style="display: flex;">
<span class="fixStyleInnerSpan" data-value="b">
<svg data-value="b" width="1.8rem" height="1.8rem" viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="BoldOutlined">
<path data-value="b" d="M5 2.709C5 2.317 5.317 2 5.709 2h6.734a5.317 5.317 0 0 1 3.686 9.148 5.671 5.671 0 0 1-2.623 10.7H5.71a.709.709 0 0 1-.71-.707V2.71Zm2 7.798h5.443a3.19 3.19 0 0 0 3.19-3.19c0-1.762-1.428-3.317-3.19-3.317H7v6.507Zm0 2.126v7.09h6.507a3.544 3.544 0 0 0 0-7.09H7Z"
fill="currentColor"></path>
</svg>
</span>
<span class="fixStyleInnerSpan" data-value="del">
<svg data-value="del" width="1.8rem" height="1.8rem"
viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="HorizontalLineOutlined">
<path
d="M5.49 7.226A5.107 5.107 0 0 1 6.9 3.831C8.017 2.636 9.718 2 11.819 2c2.142 0 3.779.57 4.867 1.689.4.392.869.958 1.26 1.595.443.723-.191 1.53-1.04 1.53-.606 0-1.039-.447-1.326-.981a2.864 2.864 0 0 0-.362-.517c-.735-.93-1.909-1.419-3.386-1.419-2.404 0-4.154 1.395-4.2 3.393-.02.846.337 1.58.995 2.043h-2.75c-.271-.621-.403-1.332-.385-2.107Zm8.906 6.024H4.038c-.518 0-.938-.38-.938-.897 0-.518.42-.978.938-.978h16.125c.518 0 .937.437.937.954 0 .518-.42.921-.937.921h-2.455c.542.806.96 1.954.934 3.055C18.563 19.82 15.87 22 11.572 22c-2.875 0-5.028-.964-6.13-2.745a6.884 6.884 0 0 1-.545-1.191c-.261-.72.318-1.432 1.084-1.432.574 0 1.034.416 1.24.952.17.445.4.794.733 1.142.805.858 2.104 1.305 3.766 1.305 2.845 0 4.696-1.39 4.747-3.61.024-1.072-.256-1.61-.897-2.42-.473-.598-1.174-.751-1.174-.751Z"
fill="currentColor"></path>
</svg>
</span>
<span class="fixStyleInnerSpan" data-value="i">
<svg data-value="i" width="1.8rem" height="1.8rem" viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="ItalicOutlined">
<path
d="M14.825 5.077 11.19 18.923h4.052a1.038 1.038 0 1 1 0 2.077H4.954a1.038 1.038 0 1 1 0-2.077h4.053l3.636-13.846H8.591A1.038 1.038 0 1 1 8.59 3h10.287a1.038 1.038 0 0 1 0 2.077h-4.053Z"
fill="currentColor"></path>
</svg>
</span>
<span class="fixStyleInnerSpan" data-value="u">
<svg data-value="u" width="1.8rem" height="1.8rem" viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg" data-icon="UnderlineOutlined">
<path
d="M7.361 3.052a.99.99 0 0 0-.989-.994.998.998 0 0 0-.999.994v5.765c0 4.205 2.601 7.29 6.627 7.29s6.627-3.085 6.627-7.29V3.052a.996.996 0 0 0-.996-.994.992.992 0 0 0-.992.994v5.765c0 3.003-1.763 5.302-4.639 5.302-2.876 0-4.639-2.299-4.639-5.302V3.052ZM3.054 19.42a.988.988 0 0 0-.994.988 1 1 0 0 0 .994 1h17.892a1 1 0 0 0 .994-1.002.987.987 0 0 0-.994-.986H3.054Z"
fill="currentColor"></path>
</svg>
</span>
</div>
<div class="my-divider-item"></div>
<div>
<span>
<svg width="1.8rem" height="1.8rem" viewBox="0 0 18 18" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.2 10.4A1.19 1.19 0 013 9.2 1.19 1.19 0 014.2 8c.23 0 .43.06.6.17.2.1.33.24.44.43.11.18.17.38.17.6 0 .23-.06.43-.17.6-.1.2-.25.34-.43.45-.18.1-.38.16-.6.16zm4.72 0a1.19 1.19 0 01-1.2-1.2A1.19 1.19 0 018.92 8c.22 0 .42.06.6.17.19.1.33.24.44.43.1.18.16.38.16.6a1.22 1.22 0 01-.6 1.04c-.18.11-.38.17-.6.17zm4.72 0a1.19 1.19 0 01-1.2-1.2 1.19 1.19 0 011.2-1.2c.22 0 .42.06.6.17.18.1.33.24.44.43.11.18.16.38.16.6 0 .23-.05.43-.16.6a1.18 1.18 0 01-1.04.6z"
fill="#535353"></path>
</svg>
</span>
</div>
<!-- <div class="my-divider-item"></div>-->
<!-- <div>-->
<!-- <button onclick="info(this)">info</button>-->
<!-- </div>-->
</div>
</div>
</header>
<main>
<main style="margin-top: 5rem">
<h1>测试编辑</h1>
<div id="noteshare" spellcheck="false" translate="no">
</div>
</main>
<footer>
<!-- <div id="testInput" >测试</div>-->
</footer>
</body>
<script src="/v1/static/js/utils.js"></script>
<script src="/v1/static/js/styleCmd.js"></script>
<script src="/v1/static/js/yanxuelu.js"></script>
<script type="module" src="./js/lib/main.js"></script>
</html>