diff --git a/src/components/deck-card-editor.vue b/src/components/deck-card-editor.vue
index fd0928e..1d75551 100644
--- a/src/components/deck-card-editor.vue
+++ b/src/components/deck-card-editor.vue
@@ -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.'
diff --git a/src/editor/content-block.ts b/src/editor/content-block.ts
index c37a5b0..d766cdd 100644
--- a/src/editor/content-block.ts
+++ b/src/editor/content-block.ts
@@ -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: '',
- icon,
+ icon: '',
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
diff --git a/src/editor/contentless-block.ts b/src/editor/contentless-block.ts
index beb7872..d048a01 100644
--- a/src/editor/contentless-block.ts
+++ b/src/editor/contentless-block.ts
@@ -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()
}
diff --git a/src/editor/delimiter.ts b/src/editor/delimiter.ts
index 7bd9db6..324b1da 100644
--- a/src/editor/delimiter.ts
+++ b/src/editor/delimiter.ts
@@ -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
diff --git a/src/editor/heading.ts b/src/editor/heading.ts
index 4f857b2..a7daa70 100644
--- a/src/editor/heading.ts
+++ b/src/editor/heading.ts
@@ -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 = 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
diff --git a/src/editor/heading.ts.bak b/src/editor/heading.ts.bak
new file mode 100644
index 0000000..22d5010
--- /dev/null
+++ b/src/editor/heading.ts.bak
@@ -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 = 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
diff --git a/src/editor/index.ts b/src/editor/index.ts
index 7b1ec63..61f8ae3 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -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'