finishes generalized contentless and content block classes
This commit is contained in:
parent
4e91649066
commit
ef8dacd89e
7 changed files with 312 additions and 107 deletions
|
@ -7,7 +7,7 @@ import { Component, Prop, Vue } from 'vue-property-decorator'
|
|||
|
||||
import Editor from '@editorjs/editorjs'
|
||||
import List from '@editorjs/list'
|
||||
import { /* Heading, */ Delimiter, Boop } from '@/editor'
|
||||
import { Heading, Delimiter } from '@/editor'
|
||||
|
||||
@Component
|
||||
export default class DeckCardEditor extends Vue {
|
||||
|
@ -28,8 +28,8 @@ export default class DeckCardEditor extends Vue {
|
|||
tools: {
|
||||
// header: Heading,
|
||||
list: { class: List, inlineToolbar: true },
|
||||
delimiter: { class: Delimiter, inlineToolbar: true },
|
||||
boop: { class: Boop, inlineToolbar: true }
|
||||
delimiter: { class: Delimiter, inlineToolbar: false },
|
||||
heading: { class: Heading, inlineToolbar: true }
|
||||
},
|
||||
// data: {},
|
||||
placeholder: 'Click here to write your card.'
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
import { BlockTool, BlockToolData, ToolboxConfig, API, HTMLPasteEvent, ToolConstructable, ToolSettings } from '@editorjs/editorjs'
|
||||
import icon from '@/assets/editor/text.svg.txt'
|
||||
import {
|
||||
BlockTool,
|
||||
BlockToolData,
|
||||
ToolboxConfig,
|
||||
API,
|
||||
HTMLPasteEvent,
|
||||
ToolSettings,
|
||||
SanitizerConfig
|
||||
} from '@editorjs/editorjs'
|
||||
|
||||
export { HTMLPasteEvent } from '@editorjs/editorjs'
|
||||
|
||||
interface PasteConfig {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface ContentBlockConfig extends ToolSettings {
|
||||
export interface ContentBlockConfig extends ToolSettings {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
interface ContentBlockArgs {
|
||||
export interface ContentBlockSettingButton {
|
||||
name: string;
|
||||
icon: string;
|
||||
action: (name: string, event?: MouseEvent) => void; // action triggered by button
|
||||
isActive?: (name: string) => boolean; // determine if current button is active
|
||||
}
|
||||
|
||||
export type ContentBlockSettings = ContentBlockSettingButton[]
|
||||
|
||||
export interface ContentBlockArgs {
|
||||
api: API;
|
||||
config?: ContentBlockConfig;
|
||||
data?: BlockToolData;
|
||||
|
@ -19,7 +37,7 @@ interface CSSClasses {
|
|||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface ContentBlockData extends BlockToolData {
|
||||
export interface ContentBlockData extends BlockToolData {
|
||||
text: string;
|
||||
}
|
||||
|
||||
|
@ -32,8 +50,7 @@ export class ContentBlock implements BlockTool {
|
|||
static _supportedTags: string[] = []
|
||||
|
||||
static _toolboxConfig: ToolboxConfig = {
|
||||
// icon: '<svg></svg>',
|
||||
icon,
|
||||
icon: '<svg></svg>',
|
||||
title: 'UnnamedContentPlugin'
|
||||
}
|
||||
|
||||
|
@ -48,6 +65,7 @@ export class ContentBlock implements BlockTool {
|
|||
protected _placeholder: string
|
||||
protected _CSS: CSSClasses
|
||||
protected onKeyUp: (event: KeyboardEvent) => void
|
||||
protected _settingButtons: ContentBlockSettings = []
|
||||
|
||||
constructor ({ data, config, api }: ContentBlockArgs) {
|
||||
this.api = api
|
||||
|
@ -133,7 +151,7 @@ export class ContentBlock implements BlockTool {
|
|||
}
|
||||
|
||||
// Sanitizer rules
|
||||
static get sanitize () {
|
||||
static get sanitize (): SanitizerConfig {
|
||||
return {
|
||||
text: { br: true }
|
||||
}
|
||||
|
@ -151,6 +169,30 @@ export class ContentBlock implements BlockTool {
|
|||
this._element.innerHTML = this._data.text || ''
|
||||
}
|
||||
|
||||
public renderSettings (): HTMLElement {
|
||||
const wrapper = document.createElement('DIV')
|
||||
|
||||
this._settingButtons.forEach(tune => {
|
||||
// make sure the settings button does something
|
||||
if (!tune.icon || typeof tune.action !== 'function') return
|
||||
|
||||
const { name, icon, action, isActive } = tune
|
||||
|
||||
const btn = document.createElement('SPAN')
|
||||
btn.classList.add(this.api.styles.settingsButton)
|
||||
|
||||
if (typeof isActive === 'function' && isActive(name)) {
|
||||
btn.classList.add(this.api.styles.settingsButtonActive)
|
||||
}
|
||||
btn.innerHTML = icon
|
||||
btn.addEventListener('click', event => action(name, event))
|
||||
|
||||
wrapper.appendChild(btn)
|
||||
})
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// Used by Editor.js paste handling API.
|
||||
// Provides configuration to handle the tools tags.
|
||||
static get pasteConfig (): PasteConfig {
|
||||
|
@ -165,4 +207,4 @@ export class ContentBlock implements BlockTool {
|
|||
}
|
||||
}
|
||||
|
||||
export default ContentBlock as ToolConstructable
|
||||
export default ContentBlock
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { BlockTool, BlockToolData, ToolConfig, ToolboxConfig, API } from '@editorjs/editorjs'
|
||||
|
||||
interface BlockToolConfig extends ToolConfig {
|
||||
[key: string]: string;
|
||||
}
|
||||
import { BlockTool, BlockToolData, ToolSettings, ToolboxConfig, API } from '@editorjs/editorjs'
|
||||
|
||||
export interface BlockToolArgs {
|
||||
api: API;
|
||||
config: BlockToolConfig;
|
||||
config?: ToolSettings;
|
||||
data?: BlockToolData;
|
||||
}
|
||||
|
||||
export class ContentlessBlock implements BlockTool {
|
||||
static get contentless () {
|
||||
return true
|
||||
}
|
||||
|
||||
protected api: API
|
||||
protected _element: HTMLElement
|
||||
protected _data: object
|
||||
protected _config: ToolConfig
|
||||
protected _config: ToolSettings
|
||||
|
||||
constructor ({ data, config, api }: BlockToolArgs) {
|
||||
this.api = api
|
||||
this._config = config
|
||||
this._config = config as ToolSettings
|
||||
this._data = data || {}
|
||||
this._element = this._render()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { ToolConstructable } from '@editorjs/editorjs'
|
||||
import ContentlessBlock from './contentless-block'
|
||||
import icon from '../assets/editor/delimiter.svg.txt'
|
||||
const title = 'Delimiter'
|
||||
|
@ -22,4 +21,4 @@ export class Delimiter extends ContentlessBlock {
|
|||
}
|
||||
}
|
||||
|
||||
export default Delimiter as ToolConstructable
|
||||
export default Delimiter
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { ToolConstructable, ToolConfig, HTMLPasteEvent } from '@editorjs/editorjs'
|
||||
import { BlockToolExt, BlockToolArgs } from './block-tool'
|
||||
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'
|
||||
|
@ -7,11 +13,8 @@ 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'
|
||||
|
||||
interface PasteConfig {
|
||||
tags: string[];
|
||||
}
|
||||
const title = 'Heading'
|
||||
|
||||
enum HeadingLevel {
|
||||
One = 1,
|
||||
|
@ -22,35 +25,31 @@ enum HeadingLevel {
|
|||
Six = 6
|
||||
}
|
||||
|
||||
interface HeaderConfig extends ToolConfig {
|
||||
const icons = [null, icon1, icon2, icon3, icon4, icon5, icon6]
|
||||
|
||||
interface HeadingConfig extends ContentBlockConfig {
|
||||
placeholder?: string;
|
||||
levels?: HeadingLevel[];
|
||||
defaultLevel?: HeadingLevel;
|
||||
}
|
||||
|
||||
interface HeaderData {
|
||||
text?: string;
|
||||
interface HeaderData extends ContentBlockData {
|
||||
text: string;
|
||||
level?: HeadingLevel;
|
||||
}
|
||||
|
||||
class Heading extends BlockToolExt {
|
||||
protected _config: HeaderConfig
|
||||
protected settingsButtons: HTMLElement[] = []
|
||||
private levels: 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
|
||||
private icons: Map<HeadingLevel, string> = new Map([
|
||||
[HeadingLevel.One, icon1],
|
||||
[HeadingLevel.Two, icon2],
|
||||
[HeadingLevel.Three, icon3],
|
||||
[HeadingLevel.Four, icon4],
|
||||
[HeadingLevel.Five, icon5],
|
||||
[HeadingLevel.Six, icon6]
|
||||
])
|
||||
|
||||
constructor (args: BlockToolArgs) {
|
||||
constructor (args: ContentBlockArgs) {
|
||||
super(args)
|
||||
this._config = args.config as HeaderConfig
|
||||
this._config = args.config as HeadingConfig
|
||||
this._CSS.wrapper = 'card-content-block'
|
||||
|
||||
if (this._config.levels === undefined) {
|
||||
this._config.levels = [HeadingLevel.Two, HeadingLevel.Three]
|
||||
|
@ -61,7 +60,6 @@ class Heading extends BlockToolExt {
|
|||
if (this._config.levels.indexOf(this._config.defaultLevel) === -1) {
|
||||
console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels')
|
||||
}
|
||||
this.levels = this._config.levels
|
||||
this.defaultLevel = this._config.defaultLevel
|
||||
this.currentLevel = this.defaultLevel
|
||||
|
||||
|
@ -70,30 +68,19 @@ class Heading extends BlockToolExt {
|
|||
level: this.currentLevel,
|
||||
text: (args.data as HeaderData).text || ''
|
||||
}
|
||||
}
|
||||
|
||||
public renderSettings (): HTMLElement {
|
||||
const wrapper = document.createElement('DIV')
|
||||
|
||||
this.levels.forEach(level => {
|
||||
const { settingsButton, settingsButtonActive } = this._CSS
|
||||
const btn = document.createElement('SPAN')
|
||||
btn.classList.add(settingsButton)
|
||||
btn.dataset.level = `${level}`
|
||||
btn.innerHTML = this.icons.get(level) || icon
|
||||
|
||||
if (this.currentLevel === level) btn.classList.add(settingsButtonActive)
|
||||
|
||||
btn.addEventListener('click', () => this.setLevel(level))
|
||||
wrapper.appendChild(btn)
|
||||
this.settingsButtons.push(btn)
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
public get data (): HeaderData {
|
||||
return this._data as HeaderData
|
||||
public get data () {
|
||||
return this._data
|
||||
}
|
||||
|
||||
public set data (data: HeaderData) {
|
||||
|
@ -112,17 +99,14 @@ class Heading extends BlockToolExt {
|
|||
this._element = newHeader
|
||||
}
|
||||
|
||||
private setLevel (level: HeadingLevel) {
|
||||
this.data = { level, text: this._element.innerHTML }
|
||||
private isCurrentLevel (name: string): boolean {
|
||||
const currentLevel = `H${this.currentLevel}`
|
||||
return name === currentLevel
|
||||
}
|
||||
|
||||
protected get _CSS () {
|
||||
return {
|
||||
block: this.api.styles.block,
|
||||
settingsButton: this.api.styles.settingsButton,
|
||||
settingsButtonActive: this.api.styles.settingsButtonActive,
|
||||
wrapper: 'card-heading'
|
||||
}
|
||||
private setLevel (name: string) {
|
||||
const level = parseInt(name[1], 10)
|
||||
this.data = { level, text: this._element.innerHTML }
|
||||
}
|
||||
|
||||
protected _render (): HTMLElement {
|
||||
|
@ -137,7 +121,7 @@ class Heading extends BlockToolExt {
|
|||
}
|
||||
|
||||
// Handle pasted H1-H6 tags to substitute with header tool
|
||||
onPaste (event: HTMLPasteEvent) {
|
||||
public onPaste (event: HTMLPasteEvent) {
|
||||
const content = event.detail.data
|
||||
const text = content.innerHTML
|
||||
let level = this.defaultLevel
|
||||
|
@ -155,14 +139,6 @@ class Heading extends BlockToolExt {
|
|||
this.data = { level, text }
|
||||
}
|
||||
|
||||
// Used by Editor.js paste handling API.
|
||||
// Provides configuration to handle H1-H6 tags.
|
||||
static get pasteConfig (): PasteConfig {
|
||||
return {
|
||||
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -172,35 +148,17 @@ class Heading extends BlockToolExt {
|
|||
}
|
||||
}
|
||||
|
||||
// validate text block data
|
||||
validate (blockData: HeaderData): boolean {
|
||||
if (!blockData.text) return false
|
||||
return blockData.text.trim() !== ''
|
||||
}
|
||||
|
||||
// extract tools data from view
|
||||
save (toolsContent: HTMLElement): HeaderData {
|
||||
public save (toolsContent: HTMLElement): HeaderData {
|
||||
return {
|
||||
text: toolsContent.innerHTML,
|
||||
level: this.currentLevel
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Heading to be converted to/from other blocks
|
||||
static get conversionConfig () {
|
||||
return {
|
||||
export: 'text', // use 'text' property for other blocks
|
||||
import: 'text' // fill 'text' property from other block's export string
|
||||
}
|
||||
}
|
||||
|
||||
static get sanitize (): object {
|
||||
static get sanitize () {
|
||||
return { level: {} }
|
||||
}
|
||||
|
||||
static get toolbox () {
|
||||
return { icon, title }
|
||||
}
|
||||
}
|
||||
|
||||
export default Heading as ToolConstructable
|
||||
export default Heading
|
||||
|
|
207
src/editor/heading.ts.bak
Normal file
207
src/editor/heading.ts.bak
Normal file
|
@ -0,0 +1,207 @@
|
|||
import { ToolConstructable, ToolConfig, HTMLPasteEvent } from '@editorjs/editorjs'
|
||||
import { BlockToolExt, BlockToolArgs } from './block-tool'
|
||||
|
||||
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'
|
||||
|
||||
interface PasteConfig {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
enum HeadingLevel {
|
||||
One = 1,
|
||||
Two = 2,
|
||||
Three = 3,
|
||||
Four = 4,
|
||||
Five = 5,
|
||||
Six = 6
|
||||
}
|
||||
|
||||
interface HeaderConfig extends ToolConfig {
|
||||
placeholder?: string;
|
||||
levels?: HeadingLevel[];
|
||||
defaultLevel?: HeadingLevel;
|
||||
}
|
||||
|
||||
interface HeaderData {
|
||||
text?: string;
|
||||
level?: HeadingLevel;
|
||||
}
|
||||
|
||||
class Heading extends BlockToolExt {
|
||||
protected _config: HeaderConfig
|
||||
protected settingsButtons: HTMLElement[] = []
|
||||
private levels: HeadingLevel[]
|
||||
private defaultLevel: HeadingLevel
|
||||
private currentLevel: HeadingLevel
|
||||
private icons: Map<HeadingLevel, string> = new Map([
|
||||
[HeadingLevel.One, icon1],
|
||||
[HeadingLevel.Two, icon2],
|
||||
[HeadingLevel.Three, icon3],
|
||||
[HeadingLevel.Four, icon4],
|
||||
[HeadingLevel.Five, icon5],
|
||||
[HeadingLevel.Six, icon6]
|
||||
])
|
||||
|
||||
constructor (args: BlockToolArgs) {
|
||||
super(args)
|
||||
this._config = args.config as HeaderConfig
|
||||
|
||||
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.levels = this._config.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 || ''
|
||||
}
|
||||
}
|
||||
|
||||
public renderSettings (): HTMLElement {
|
||||
const wrapper = document.createElement('DIV')
|
||||
|
||||
this.levels.forEach(level => {
|
||||
const { settingsButton, settingsButtonActive } = this._CSS
|
||||
const btn = document.createElement('SPAN')
|
||||
btn.classList.add(settingsButton)
|
||||
btn.dataset.level = `${level}`
|
||||
btn.innerHTML = this.icons.get(level) || icon
|
||||
|
||||
if (this.currentLevel === level) btn.classList.add(settingsButtonActive)
|
||||
|
||||
btn.addEventListener('click', () => this.setLevel(level))
|
||||
wrapper.appendChild(btn)
|
||||
this.settingsButtons.push(btn)
|
||||
})
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
public get data (): HeaderData {
|
||||
return this._data as HeaderData
|
||||
}
|
||||
|
||||
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 setLevel (level: HeadingLevel) {
|
||||
this.data = { level, text: this._element.innerHTML }
|
||||
}
|
||||
|
||||
protected get _CSS () {
|
||||
return {
|
||||
block: this.api.styles.block,
|
||||
settingsButton: this.api.styles.settingsButton,
|
||||
settingsButtonActive: this.api.styles.settingsButtonActive,
|
||||
wrapper: 'card-heading'
|
||||
}
|
||||
}
|
||||
|
||||
protected _render (): HTMLElement {
|
||||
console.log('render', `H${this.currentLevel}`, this.data)
|
||||
|
||||
const el = document.createElement(`H${this.currentLevel}`)
|
||||
el.innerHTML = this.data.text || ''
|
||||
el.classList.add(this._CSS.wrapper, this._CSS.block)
|
||||
el.contentEditable = 'true'
|
||||
el.dataset.placeholder = this._config.placeholder || ''
|
||||
return el
|
||||
}
|
||||
|
||||
// Handle pasted H1-H6 tags to substitute with header tool
|
||||
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 }
|
||||
}
|
||||
|
||||
// Used by Editor.js paste handling API.
|
||||
// Provides configuration to handle H1-H6 tags.
|
||||
static get pasteConfig (): PasteConfig {
|
||||
return {
|
||||
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// validate text block data
|
||||
validate (blockData: HeaderData): boolean {
|
||||
if (!blockData.text) return false
|
||||
return blockData.text.trim() !== ''
|
||||
}
|
||||
|
||||
// extract tools data from view
|
||||
save (toolsContent: HTMLElement): HeaderData {
|
||||
return {
|
||||
text: toolsContent.innerHTML,
|
||||
level: this.currentLevel
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Heading to be converted to/from other blocks
|
||||
static get conversionConfig () {
|
||||
return {
|
||||
export: 'text', // use 'text' property for other blocks
|
||||
import: 'text' // fill 'text' property from other block's export string
|
||||
}
|
||||
}
|
||||
|
||||
static get sanitize (): object {
|
||||
return { level: {} }
|
||||
}
|
||||
|
||||
static get toolbox () {
|
||||
return { icon, title }
|
||||
}
|
||||
}
|
||||
|
||||
export default Heading as ToolConstructable
|
|
@ -1,3 +1,2 @@
|
|||
export { default as Delimiter } from './delimiter'
|
||||
// export { default as Heading } from './heading'
|
||||
export { default as Boop } from './content-block'
|
||||
export { default as Heading } from './heading'
|
||||
|
|
Loading…
Reference in a new issue