161 lines
4.4 KiB
TypeScript
161 lines
4.4 KiB
TypeScript
import {
|
|
ContentBlock,
|
|
ContentBlockArgs,
|
|
ContentBlockConfig,
|
|
ContentBlockData,
|
|
HTMLPasteEvent
|
|
} from './content-block'
|
|
|
|
import icon from '../assets/editor/header.svg.txt'
|
|
import icon1 from '../assets/editor/header1.svg.txt'
|
|
import icon2 from '../assets/editor/header2.svg.txt'
|
|
import icon3 from '../assets/editor/header3.svg.txt'
|
|
import icon4 from '../assets/editor/header4.svg.txt'
|
|
import icon5 from '../assets/editor/header5.svg.txt'
|
|
import icon6 from '../assets/editor/header6.svg.txt'
|
|
|
|
const title = 'Heading'
|
|
|
|
enum HeadingLevel {
|
|
One = 1,
|
|
Two = 2,
|
|
Three = 3,
|
|
Four = 4,
|
|
Five = 5,
|
|
Six = 6
|
|
}
|
|
|
|
const icons = [null, icon1, icon2, icon3, icon4, icon5, icon6]
|
|
|
|
interface HeadingConfig extends ContentBlockConfig {
|
|
placeholder?: string;
|
|
levels?: HeadingLevel[];
|
|
defaultLevel?: HeadingLevel;
|
|
}
|
|
|
|
interface HeaderData extends ContentBlockData {
|
|
text: string;
|
|
level?: HeadingLevel;
|
|
}
|
|
|
|
class Heading extends ContentBlock {
|
|
static _supportedTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
|
|
static _toolboxConfig = { icon, title }
|
|
|
|
protected _config: HeadingConfig
|
|
private defaultLevel: HeadingLevel
|
|
private currentLevel: HeadingLevel
|
|
|
|
constructor (args: ContentBlockArgs) {
|
|
super(args)
|
|
this._config = args.config as HeadingConfig
|
|
|
|
if (this._config.levels === undefined) {
|
|
this._config.levels = [HeadingLevel.Two, HeadingLevel.Three]
|
|
}
|
|
if (this._config.defaultLevel === undefined) {
|
|
this._config.defaultLevel = HeadingLevel.Two
|
|
}
|
|
if (this._config.levels.indexOf(this._config.defaultLevel) === -1) {
|
|
console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels')
|
|
}
|
|
this.defaultLevel = this._config.defaultLevel
|
|
this.currentLevel = this.defaultLevel
|
|
|
|
// setting data will rerender the element with the right settings
|
|
this.data = {
|
|
level: this.currentLevel,
|
|
text: (args.data as HeaderData).text || ''
|
|
}
|
|
|
|
this._settingButtons = this._config.levels.map(level => {
|
|
return {
|
|
name: `H${level}`,
|
|
icon: icons[level] || icon,
|
|
action: (name: string) => this.setLevel(name),
|
|
isActive: (name: string): boolean => this.isCurrentLevel(name)
|
|
}
|
|
})
|
|
}
|
|
|
|
public get data () {
|
|
return this._data
|
|
}
|
|
|
|
public set data (data: HeaderData) {
|
|
const currentData = this._data as HeaderData
|
|
|
|
if (data.level === undefined) data.level = currentData.level || this.defaultLevel
|
|
if (data.text === undefined) data.text = currentData.text || ''
|
|
|
|
this._data = data
|
|
this.currentLevel = data.level
|
|
|
|
const newHeader = this._render()
|
|
if (this._element.parentNode) {
|
|
this._element.parentNode.replaceChild(newHeader, this._element)
|
|
}
|
|
this._element = newHeader
|
|
}
|
|
|
|
private isCurrentLevel (name: string): boolean {
|
|
const currentLevel = `H${this.currentLevel}`
|
|
return name === currentLevel
|
|
}
|
|
|
|
private setLevel (name: string) {
|
|
const level = parseInt(name[1], 10)
|
|
this.data = { level, text: this._element.innerHTML }
|
|
}
|
|
|
|
protected _render (): HTMLElement {
|
|
const el = document.createElement(`H${this.currentLevel}`)
|
|
el.innerHTML = this.data.text || ''
|
|
el.classList.add(this._CSS.block)
|
|
el.contentEditable = 'true'
|
|
el.dataset.placeholder = this._config.placeholder || ''
|
|
return el
|
|
}
|
|
|
|
// Handle pasted H1-H6 tags to substitute with header tool
|
|
public onPaste (event: HTMLPasteEvent) {
|
|
const content = event.detail.data
|
|
const text = content.innerHTML
|
|
let level = this.defaultLevel
|
|
|
|
const tagMatch = content.tagName.match(/H(\d)/)
|
|
if (tagMatch) level = parseInt(tagMatch[1], 10)
|
|
|
|
// Fallback to nearest level when specified not available
|
|
if (this._config.levels) {
|
|
level = this._config.levels.reduce((prevLevel, currLevel) => {
|
|
return Math.abs(currLevel - level) < Math.abs(prevLevel - level) ? currLevel : prevLevel
|
|
})
|
|
}
|
|
|
|
this.data = { level, text }
|
|
}
|
|
|
|
// Method that specified how to merge two Text blocks.
|
|
// Called by Editor.js by backspace at the beginning of the Block
|
|
public merge (data: HeaderData) {
|
|
this.data = {
|
|
text: this.data.text + (data.text || ''),
|
|
level: this.data.level
|
|
}
|
|
}
|
|
|
|
// extract tools data from view
|
|
public save (toolsContent: HTMLElement): HeaderData {
|
|
return {
|
|
text: toolsContent.innerHTML,
|
|
level: this.currentLevel
|
|
}
|
|
}
|
|
|
|
static get sanitize () {
|
|
return { level: {} }
|
|
}
|
|
}
|
|
|
|
export default Heading
|