feat:add codeblock

pr675
黄云飞 4 years ago
parent 76637eb863
commit 28c818a8fb

@ -154,6 +154,9 @@
</ul>
</div>
</div>
<div class="menu-item__codeblock">
<i></i>
</div>
</div>
<div class="menu-divider"></div>
<div class="menu-item">

@ -8,7 +8,11 @@
},
"devDependencies": {
"@types/node": "^16.11.12",
"@types/prismjs": "^1.26.0",
"typescript": "^4.3.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 {
type: string;
label: string;
label?: string;
name: string;
value?: string;
placeholder?: string;
width?: number;
height?: number;
}
export interface IDialogConfirm {
@ -27,7 +29,7 @@ export class Dialog {
private options: IDialogOptions
private mask: HTMLDivElement | null
private container: HTMLDivElement | null
private inputList: HTMLInputElement[]
private inputList: (HTMLInputElement | HTMLTextAreaElement)[]
constructor(options: IDialogOptions) {
this.options = options
@ -77,12 +79,25 @@ export class Dialog {
const optionItemContainer = document.createElement('div')
optionItemContainer.classList.add('dialog-option__item')
// 选项名称
const optionName = document.createElement('span')
optionName.append(document.createTextNode(option.label))
optionItemContainer.append(optionName)
if (option.label) {
const optionName = document.createElement('span')
optionName.append(document.createTextNode(option.label))
optionItemContainer.append(optionName)
}
// 选项输入框
const optionInput = document.createElement('input')
optionInput.type = option.type
let optionInput: HTMLInputElement | HTMLTextAreaElement
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.value = option.value || ''
optionInput.placeholder = option.placeholder || ''

@ -52,7 +52,6 @@
.dialog-option__item {
margin-bottom: 18px;
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
@ -64,21 +63,23 @@
color: #3d4757;
}
.dialog-option__item input {
.dialog-option__item input,
.dialog-option__item textarea {
width: 276px;
height: 30px;
border-radius: 2px;
border: 1px solid #d3d3d3;
min-height: 30px;
line-height: 17px;
padding: 5px;
box-sizing: border-box;
outline: none;
appearance: 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;
}

@ -49,6 +49,7 @@ export class Command {
private static pageScaleRecovery: Function
private static pageScaleMinus: Function
private static pageScaleAdd: Function
private static insertElementList: Function
constructor(adapt: CommandAdapt) {
Command.cut = adapt.cut.bind(adapt)
@ -94,6 +95,7 @@ export class Command {
Command.pageScaleRecovery = adapt.pageScaleRecovery.bind(adapt)
Command.pageScaleMinus = adapt.pageScaleMinus.bind(adapt)
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
Command.insertElementList = adapt.insertElementList.bind(adapt)
}
// 全局命令
@ -273,4 +275,9 @@ export class Command {
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 { EDITOR_ELEMENT_ZIP_ATTR } from "../dataset/constant/Element"
export function formatElementList(elementList: IElement[]) {
if (elementList[0]?.value !== ZERO) {
export function formatElementList(elementList: IElement[], isHandleFirstElement = true) {
if (isHandleFirstElement && elementList[0]?.value !== ZERO) {
elementList.unshift({
value: ZERO
})

@ -1,6 +1,8 @@
import './style.css'
import prism from "prismjs"
import Editor, { ElementType, IElement, RowFlex } from './editor'
import { Dialog } from './components/dialog/Dialog'
import { formatPrismToken } from './utils/prism'
window.onload = function () {
@ -533,6 +535,53 @@ window.onload = function () {
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 searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')
document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {

@ -421,6 +421,10 @@ ul {
position: relative;
}
.menu-item__codeblock i {
background-image: url('./assets/images/codeblock.svg');
}
.menu-item__search {
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"
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:
version "0.13.13"
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"
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:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"

Loading…
Cancel
Save