feat:add codeblock
This commit is contained in:
@@ -154,6 +154,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="menu-item__codeblock">
|
||||||
|
<i></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.11.12",
|
"@types/node": "^16.11.12",
|
||||||
|
"@types/prismjs": "^1.26.0",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.2",
|
||||||
"vite": "^2.4.2"
|
"vite": "^2.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"prismjs": "^1.27.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="14" height="14" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path stroke="#525C6F" stroke-linejoin="round" d="M4 4.5L1.5 7 4 9.5M10 4.5L12.5 7 10 9.5"></path><rect fill="#525C6F" transform="scale(1 -1) rotate(70 16.711 0)" x="2.671" y="6.53" width="8" height="1" rx=".2"></rect></g></svg>
|
||||||
|
After Width: | Height: | Size: 326 B |
@@ -3,10 +3,12 @@ import './dialog.css'
|
|||||||
|
|
||||||
export interface IDialogData {
|
export interface IDialogData {
|
||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label?: string;
|
||||||
name: string;
|
name: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDialogConfirm {
|
export interface IDialogConfirm {
|
||||||
@@ -27,7 +29,7 @@ export class Dialog {
|
|||||||
private options: IDialogOptions
|
private options: IDialogOptions
|
||||||
private mask: HTMLDivElement | null
|
private mask: HTMLDivElement | null
|
||||||
private container: HTMLDivElement | null
|
private container: HTMLDivElement | null
|
||||||
private inputList: HTMLInputElement[]
|
private inputList: (HTMLInputElement | HTMLTextAreaElement)[]
|
||||||
|
|
||||||
constructor(options: IDialogOptions) {
|
constructor(options: IDialogOptions) {
|
||||||
this.options = options
|
this.options = options
|
||||||
@@ -77,12 +79,25 @@ export class Dialog {
|
|||||||
const optionItemContainer = document.createElement('div')
|
const optionItemContainer = document.createElement('div')
|
||||||
optionItemContainer.classList.add('dialog-option__item')
|
optionItemContainer.classList.add('dialog-option__item')
|
||||||
// 选项名称
|
// 选项名称
|
||||||
const optionName = document.createElement('span')
|
if (option.label) {
|
||||||
optionName.append(document.createTextNode(option.label))
|
const optionName = document.createElement('span')
|
||||||
optionItemContainer.append(optionName)
|
optionName.append(document.createTextNode(option.label))
|
||||||
|
optionItemContainer.append(optionName)
|
||||||
|
}
|
||||||
// 选项输入框
|
// 选项输入框
|
||||||
const optionInput = document.createElement('input')
|
let optionInput: HTMLInputElement | HTMLTextAreaElement
|
||||||
optionInput.type = option.type
|
if (option.type === 'textarea') {
|
||||||
|
optionInput = document.createElement('textarea')
|
||||||
|
} else {
|
||||||
|
optionInput = document.createElement('input')
|
||||||
|
optionInput.type = option.type
|
||||||
|
}
|
||||||
|
if (option.width) {
|
||||||
|
optionInput.style.width = `${option.width}px`
|
||||||
|
}
|
||||||
|
if (option.height) {
|
||||||
|
optionInput.style.height = `${option.height}px`
|
||||||
|
}
|
||||||
optionInput.name = option.name
|
optionInput.name = option.name
|
||||||
optionInput.value = option.value || ''
|
optionInput.value = option.value || ''
|
||||||
optionInput.placeholder = option.placeholder || ''
|
optionInput.placeholder = option.placeholder || ''
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
|
|
||||||
.dialog-option__item {
|
.dialog-option__item {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -64,21 +63,23 @@
|
|||||||
color: #3d4757;
|
color: #3d4757;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-option__item input {
|
.dialog-option__item input,
|
||||||
|
.dialog-option__item textarea {
|
||||||
width: 276px;
|
width: 276px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid #d3d3d3;
|
border: 1px solid #d3d3d3;
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
line-height: 17px;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
outline: none;
|
outline: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-option__item input:focus {
|
.dialog-option__item input:focus,
|
||||||
|
.dialog-option__item textarea:focus {
|
||||||
border-color: #4991f2;
|
border-color: #4991f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export class Command {
|
|||||||
private static pageScaleRecovery: Function
|
private static pageScaleRecovery: Function
|
||||||
private static pageScaleMinus: Function
|
private static pageScaleMinus: Function
|
||||||
private static pageScaleAdd: Function
|
private static pageScaleAdd: Function
|
||||||
|
private static insertElementList: Function
|
||||||
|
|
||||||
constructor(adapt: CommandAdapt) {
|
constructor(adapt: CommandAdapt) {
|
||||||
Command.cut = adapt.cut.bind(adapt)
|
Command.cut = adapt.cut.bind(adapt)
|
||||||
@@ -94,6 +95,7 @@ export class Command {
|
|||||||
Command.pageScaleRecovery = adapt.pageScaleRecovery.bind(adapt)
|
Command.pageScaleRecovery = adapt.pageScaleRecovery.bind(adapt)
|
||||||
Command.pageScaleMinus = adapt.pageScaleMinus.bind(adapt)
|
Command.pageScaleMinus = adapt.pageScaleMinus.bind(adapt)
|
||||||
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
|
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
|
||||||
|
Command.insertElementList = adapt.insertElementList.bind(adapt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局命令
|
// 全局命令
|
||||||
@@ -273,4 +275,9 @@ export class Command {
|
|||||||
return Command.pageScaleAdd()
|
return Command.pageScaleAdd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通用
|
||||||
|
public executeInsertElementList(payload: IElement[]) {
|
||||||
|
return Command.insertElementList(payload)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1094,4 +1094,24 @@ export class CommandAdapt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public insertElementList(payload: IElement[]) {
|
||||||
|
if (!payload.length) return
|
||||||
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
|
if (!~startIndex && !~endIndex) return
|
||||||
|
// 格式化element
|
||||||
|
formatElementList(payload, false)
|
||||||
|
const elementList = this.draw.getElementList()
|
||||||
|
const isCollapsed = startIndex === endIndex
|
||||||
|
let start = startIndex + 1
|
||||||
|
if (!isCollapsed) {
|
||||||
|
elementList.splice(start, endIndex - startIndex)
|
||||||
|
}
|
||||||
|
for (let i = 0; i < payload.length; i++) {
|
||||||
|
elementList.splice(start + i, 0, payload[i])
|
||||||
|
}
|
||||||
|
const curIndex = startIndex + payload.length
|
||||||
|
this.range.setRange(curIndex, curIndex)
|
||||||
|
this.draw.render({ curIndex })
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,8 @@ import { ElementType, IElement } from ".."
|
|||||||
import { ZERO } from "../dataset/constant/Common"
|
import { ZERO } from "../dataset/constant/Common"
|
||||||
import { EDITOR_ELEMENT_ZIP_ATTR } from "../dataset/constant/Element"
|
import { EDITOR_ELEMENT_ZIP_ATTR } from "../dataset/constant/Element"
|
||||||
|
|
||||||
export function formatElementList(elementList: IElement[]) {
|
export function formatElementList(elementList: IElement[], isHandleFirstElement = true) {
|
||||||
if (elementList[0]?.value !== ZERO) {
|
if (isHandleFirstElement && elementList[0]?.value !== ZERO) {
|
||||||
elementList.unshift({
|
elementList.unshift({
|
||||||
value: ZERO
|
value: ZERO
|
||||||
})
|
})
|
||||||
|
|||||||
+49
@@ -1,6 +1,8 @@
|
|||||||
import './style.css'
|
import './style.css'
|
||||||
|
import prism from "prismjs"
|
||||||
import Editor, { ElementType, IElement, RowFlex } from './editor'
|
import Editor, { ElementType, IElement, RowFlex } from './editor'
|
||||||
import { Dialog } from './components/dialog/Dialog'
|
import { Dialog } from './components/dialog/Dialog'
|
||||||
|
import { formatPrismToken } from './utils/prism'
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
|
|
||||||
@@ -533,6 +535,53 @@ window.onload = function () {
|
|||||||
instance.command.executeDeleteWatermark()
|
instance.command.executeDeleteWatermark()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeblockDom = document.querySelector<HTMLDivElement>('.menu-item__codeblock')!
|
||||||
|
codeblockDom.onclick = function () {
|
||||||
|
console.log('codeblock')
|
||||||
|
new Dialog({
|
||||||
|
title: '代码块',
|
||||||
|
data: [{
|
||||||
|
type: 'textarea',
|
||||||
|
name: 'codeblock',
|
||||||
|
placeholder: '请输入代码',
|
||||||
|
width: 500,
|
||||||
|
height: 300
|
||||||
|
}],
|
||||||
|
onConfirm: (payload) => {
|
||||||
|
const codeblock = payload.find(p => p.name === 'codeblock')?.value
|
||||||
|
if (!codeblock) return
|
||||||
|
const tokenList = prism.tokenize(codeblock, prism.languages.javascript)
|
||||||
|
const formatTokenList = formatPrismToken(tokenList)
|
||||||
|
const elementList: IElement[] = []
|
||||||
|
for (let i = 0; i < formatTokenList.length; i++) {
|
||||||
|
const formatToken = formatTokenList[i]
|
||||||
|
const tokenStringList = formatToken.content.split('')
|
||||||
|
for (let j = 0; j < tokenStringList.length; j++) {
|
||||||
|
const value = tokenStringList[j]
|
||||||
|
const element: IElement = {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
if (formatToken.color) {
|
||||||
|
element.color = formatToken.color
|
||||||
|
}
|
||||||
|
if (formatToken.bold) {
|
||||||
|
element.bold = true
|
||||||
|
}
|
||||||
|
if (formatToken.italic) {
|
||||||
|
element.italic = true
|
||||||
|
}
|
||||||
|
elementList.push(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementList.unshift({
|
||||||
|
value: '\n'
|
||||||
|
})
|
||||||
|
instance.command.executeInsertElementList(elementList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')
|
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')
|
||||||
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')
|
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')
|
||||||
document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {
|
document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {
|
||||||
|
|||||||
@@ -421,6 +421,10 @@ ul {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item__codeblock i {
|
||||||
|
background-image: url('./assets/images/codeblock.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.menu-item__search {
|
.menu-item__search {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
interface IPrismKindStyle {
|
||||||
|
color?: string;
|
||||||
|
italic?: boolean;
|
||||||
|
opacity?: number;
|
||||||
|
bold?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrismKindStyle(payload: string): IPrismKindStyle | null {
|
||||||
|
switch (payload) {
|
||||||
|
case "comment":
|
||||||
|
case "prolog":
|
||||||
|
case "doctype":
|
||||||
|
case "cdata":
|
||||||
|
return { color: "#008000", italic: true }
|
||||||
|
case "namespace":
|
||||||
|
return { opacity: 0.7 }
|
||||||
|
case "string":
|
||||||
|
return { color: "#A31515" }
|
||||||
|
case "punctuation":
|
||||||
|
case "operator":
|
||||||
|
return { color: "#393A34" }
|
||||||
|
case "url":
|
||||||
|
case "symbol":
|
||||||
|
case "number":
|
||||||
|
case "boolean":
|
||||||
|
case "variable":
|
||||||
|
case "constant":
|
||||||
|
case "inserted":
|
||||||
|
return { color: "#36acaa" }
|
||||||
|
case "atrule":
|
||||||
|
case "keyword":
|
||||||
|
case "attr-value":
|
||||||
|
return { color: "#0000ff" }
|
||||||
|
case "function":
|
||||||
|
return { color: "#b9a40a" }
|
||||||
|
case "deleted":
|
||||||
|
case "tag":
|
||||||
|
return { color: "#9a050f" }
|
||||||
|
case "selector":
|
||||||
|
return { color: "#00009f" }
|
||||||
|
case "important":
|
||||||
|
return { color: "#e90", bold: true }
|
||||||
|
case "italic":
|
||||||
|
return { italic: true }
|
||||||
|
case "class-name":
|
||||||
|
case "property":
|
||||||
|
return { color: "#2B91AF" }
|
||||||
|
case "attr-name":
|
||||||
|
case "regex":
|
||||||
|
case "entity":
|
||||||
|
return { color: "#ff0000" }
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IFormatPrismToken = {
|
||||||
|
type?: string;
|
||||||
|
content: string;
|
||||||
|
} & IPrismKindStyle
|
||||||
|
|
||||||
|
export function formatPrismToken(payload: (Prism.Token | string)[]): IFormatPrismToken[] {
|
||||||
|
const formatTokenList: IFormatPrismToken[] = []
|
||||||
|
function format(tokenList: (Prism.Token | string)[]) {
|
||||||
|
for (let i = 0; i < tokenList.length; i++) {
|
||||||
|
const element = tokenList[i]
|
||||||
|
if (typeof element === 'string') {
|
||||||
|
formatTokenList.push({
|
||||||
|
content: element
|
||||||
|
})
|
||||||
|
} else if (Array.isArray(element.content)) {
|
||||||
|
format(element.content)
|
||||||
|
} else {
|
||||||
|
const { type, content } = element
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
formatTokenList.push({
|
||||||
|
type,
|
||||||
|
content,
|
||||||
|
...getPrismKindStyle(type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format(payload)
|
||||||
|
return formatTokenList
|
||||||
|
}
|
||||||
@@ -7,6 +7,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10"
|
||||||
integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==
|
integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==
|
||||||
|
|
||||||
|
"@types/prismjs@^1.26.0":
|
||||||
|
version "1.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654"
|
||||||
|
integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==
|
||||||
|
|
||||||
esbuild-android-arm64@0.13.13:
|
esbuild-android-arm64@0.13.13:
|
||||||
version "0.13.13"
|
version "0.13.13"
|
||||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.13.tgz#da07b5fb2daf7d83dcd725f7cf58a6758e6e702a"
|
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.13.tgz#da07b5fb2daf7d83dcd725f7cf58a6758e6e702a"
|
||||||
@@ -163,6 +168,11 @@ postcss@^8.3.8:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^0.6.2"
|
source-map-js "^0.6.2"
|
||||||
|
|
||||||
|
prismjs@^1.27.0:
|
||||||
|
version "1.27.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
|
||||||
|
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
|
||||||
|
|
||||||
resolve@^1.20.0:
|
resolve@^1.20.0:
|
||||||
version "1.20.0"
|
version "1.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||||
|
|||||||
Reference in New Issue
Block a user