save card content, add charges component
parent
fb5b5add6e
commit
600af1679a
@ -0,0 +1 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><circle r="7" cx="7" cy="7" stroke="black" stroke-width="2" fill="none" /></svg>
|
After Width: | Height: | Size: 164 B |
@ -0,0 +1 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><rect width="14" height="14" stroke="black" stroke-width="2" fill="none" /></svg>
|
After Width: | Height: | Size: 165 B |
@ -0,0 +1 @@
|
||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"> <path d="M5.625 4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0-4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0 9.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm-4.5-5a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0-4.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0 9.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25z"/></svg>
|
After Width: | Height: | Size: 488 B |
@ -0,0 +1 @@
|
||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"><path d="M5.819 4.607h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 1 1 0-2.138zm0-4.607h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 1 1 0-2.138zm0 9.357h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 0 1 0-2.137zM1.468 4.155V1.33c-.554.404-.926.606-1.118.606a.338.338 0 0 1-.244-.104A.327.327 0 0 1 0 1.59c0-.107.035-.184.105-.234.07-.05.192-.114.369-.192.264-.118.475-.243.633-.373.158-.13.298-.276.42-.438a3.94 3.94 0 0 1 .238-.298C1.802.019 1.872 0 1.975 0c.115 0 .208.042.277.127.07.085.105.202.105.351v3.556c0 .416-.15.624-.448.624a.421.421 0 0 1-.32-.127c-.08-.085-.121-.21-.121-.376zm-.283 6.664h1.572c.156 0 .275.03.358.091a.294.294 0 0 1 .123.25.323.323 0 0 1-.098.238c-.065.065-.164.097-.296.097H.629a.494.494 0 0 1-.353-.119.372.372 0 0 1-.126-.28c0-.068.027-.16.081-.273a.977.977 0 0 1 .178-.268c.267-.264.507-.49.722-.678.215-.188.368-.312.46-.371.165-.11.302-.222.412-.334.109-.112.192-.226.25-.344a.786.786 0 0 0 .085-.345.6.6 0 0 0-.341-.553.75.75 0 0 0-.345-.08c-.263 0-.47.11-.62.329-.02.029-.054.107-.101.235a.966.966 0 0 1-.16.295c-.059.069-.145.103-.26.103a.348.348 0 0 1-.25-.094.34.34 0 0 1-.099-.258c0-.132.031-.27.093-.413.063-.143.155-.273.279-.39.123-.116.28-.21.47-.282.189-.072.411-.107.666-.107.307 0 .569.045.786.137a1.182 1.182 0 0 1 .618.623 1.18 1.18 0 0 1-.096 1.083 2.03 2.03 0 0 1-.378.457c-.128.11-.344.282-.646.517-.302.235-.509.417-.621.547a1.637 1.637 0 0 0-.148.187z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1 @@
|
||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"> <path d="M5.625 4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0-4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0 9.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm-4.5-5a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0-4.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0 9.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25z"/></svg>
|
After Width: | Height: | Size: 488 B |
@ -0,0 +1,137 @@
|
||||
import { ContentlessBlock, BlockToolArgs } from './contentless-block'
|
||||
import icon from '../assets/editor/charges.svg.txt'
|
||||
import iconCircle from '../assets/editor/charges-circle.svg.txt'
|
||||
|
||||
const title = 'Charges'
|
||||
|
||||
interface ChargesData {
|
||||
variant: string;
|
||||
amount: number;
|
||||
size: number;
|
||||
stretch: boolean;
|
||||
}
|
||||
|
||||
class Charges extends ContentlessBlock {
|
||||
static MIN_SIZE = 1
|
||||
static MAX_SIZE = 5
|
||||
private _variant: string
|
||||
private _amount: number
|
||||
private _size: number
|
||||
private _stretch: boolean
|
||||
|
||||
constructor (args: BlockToolArgs) {
|
||||
super(args)
|
||||
console.log('new charges', args)
|
||||
this._settingButtons = [
|
||||
{ name: 'box', icon, action: (name: string) => this.setVariant(name) },
|
||||
{ name: 'more', icon: icon, action: () => this.increaseAmount() },
|
||||
{ name: 'bigger', icon: icon, action: () => this.increaseSize() },
|
||||
{ name: 'circle', icon: iconCircle, action: (name: string) => this.setVariant(name) },
|
||||
{ name: 'less', icon: icon, action: () => this.decreaseAmount() },
|
||||
{ name: 'smaller', icon: icon, action: () => this.decreaseSize() },
|
||||
{ name: 'toggle-stretch', icon: icon, action: () => this.toggleStretch() }
|
||||
]
|
||||
const { variant, amount, size, stretch } = (args.data || {}) as ChargesData
|
||||
|
||||
this._variant = variant || 'box'
|
||||
this._amount = amount || 5
|
||||
this._size = size || 1
|
||||
this._stretch = !(stretch === false)
|
||||
|
||||
this._element = this._render()
|
||||
}
|
||||
|
||||
private setVariant (variant: string) {
|
||||
if (this._variant === variant) return
|
||||
|
||||
const charges = Array.from(this._element.children)
|
||||
|
||||
charges.forEach(charge => {
|
||||
charge.classList.remove(`card-charge-${this._variant}`)
|
||||
charge.classList.add(`card-charge-${variant}`)
|
||||
})
|
||||
|
||||
this._variant = variant
|
||||
}
|
||||
|
||||
private toggleStretch () {
|
||||
if (this._stretch) this._element.classList.remove('card-charges-stretch')
|
||||
else this._element.classList.add('card-charges-stretch')
|
||||
this._stretch = !this._stretch
|
||||
}
|
||||
|
||||
private createCharge (): HTMLElement {
|
||||
const charge = document.createElement('DIV')
|
||||
charge.classList.add('card-charge', `card-charge-${this._variant}`, `card-charge-size-${this._size}`)
|
||||
return charge
|
||||
}
|
||||
|
||||
private increaseAmount () {
|
||||
this._element.appendChild(this.createCharge())
|
||||
this._amount++
|
||||
}
|
||||
|
||||
private decreaseAmount () {
|
||||
const child = this._element.lastElementChild
|
||||
if (child) {
|
||||
this._element.removeChild(child)
|
||||
this._amount--
|
||||
}
|
||||
}
|
||||
|
||||
private increaseSize () {
|
||||
if (this._size >= Charges.MAX_SIZE) return
|
||||
|
||||
const charges = Array.from(this._element.children)
|
||||
|
||||
charges.forEach(charge => {
|
||||
charge.classList.remove(`card-charge-size-${this._size}`)
|
||||
charge.classList.add(`card-charge-size-${this._size + 1}`)
|
||||
})
|
||||
|
||||
this._size++
|
||||
}
|
||||
|
||||
private decreaseSize () {
|
||||
if (this._size <= Charges.MIN_SIZE) return
|
||||
|
||||
const charges = Array.from(this._element.children)
|
||||
|
||||
charges.forEach(charge => {
|
||||
charge.classList.remove(`card-charge-size-${this._size}`)
|
||||
charge.classList.add(`card-charge-size-${this._size - 1}`)
|
||||
})
|
||||
|
||||
this._size--
|
||||
}
|
||||
|
||||
protected _render (): HTMLElement {
|
||||
const el = document.createElement('DIV')
|
||||
el.classList.add('card-charges-wrapper', this._CSS.block)
|
||||
|
||||
if (this._stretch) el.classList.add('card-charges-stretch')
|
||||
|
||||
for (let i = 0; i < this._amount; i++) {
|
||||
el.appendChild(this.createCharge())
|
||||
}
|
||||
|
||||
console.log('rendered', this._amount, 'charges', el)
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
public save (): ChargesData {
|
||||
return {
|
||||
variant: this._variant,
|
||||
amount: this._amount,
|
||||
size: this._size,
|
||||
stretch: this._stretch
|
||||
}
|
||||
}
|
||||
|
||||
static get toolbox () {
|
||||
return { icon, title }
|
||||
}
|
||||
}
|
||||
|
||||
export default Charges
|
@ -1,207 +0,0 @@
|
||||
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,2 +1,4 @@
|
||||
export { default as Delimiter } from './delimiter'
|
||||
export { default as Heading } from './heading'
|
||||
export { default as List } from './list'
|
||||
export { default as Charges } from './charges'
|
||||
|
@ -0,0 +1,254 @@
|
||||
import {
|
||||
ContentBlock,
|
||||
ContentBlockArgs,
|
||||
ContentBlockConfig,
|
||||
ContentBlockData,
|
||||
ConversionConfig
|
||||
} from './content-block'
|
||||
|
||||
import icon from '../assets/editor/list.svg.txt'
|
||||
import iconUL from '../assets/editor/list_unordered.svg.txt'
|
||||
import iconOL from '../assets/editor/list_ordered.svg.txt'
|
||||
|
||||
const title = 'List'
|
||||
|
||||
enum ListStyle {
|
||||
Ordered = 'OL',
|
||||
Unordered = 'UL'
|
||||
}
|
||||
const icons = {
|
||||
[ListStyle.Ordered]: iconOL,
|
||||
[ListStyle.Unordered]: iconUL
|
||||
}
|
||||
|
||||
interface ListConfig extends ContentBlockConfig {
|
||||
placeholder?: string;
|
||||
styles: ListStyle[];
|
||||
defaultStyle: ListStyle;
|
||||
}
|
||||
|
||||
interface ListData extends ContentBlockData {
|
||||
style: ListStyle;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
interface ListConversionConfig extends ConversionConfig {
|
||||
import: (str: string) => ListData;
|
||||
export: (data: ListData) => string;
|
||||
}
|
||||
|
||||
class List extends ContentBlock {
|
||||
static _supportedTags = ['UL', 'OL', 'LI']
|
||||
static _toolboxConfig = { icon, title }
|
||||
|
||||
protected _config: ListConfig
|
||||
private defaultStyle: ListStyle
|
||||
private currentStyle: ListStyle
|
||||
|
||||
constructor (args: ContentBlockArgs) {
|
||||
super(args)
|
||||
this._config = args.config as ListConfig
|
||||
|
||||
if (this._config.styles === undefined) {
|
||||
this._config.styles = [ListStyle.Unordered, ListStyle.Ordered]
|
||||
}
|
||||
|
||||
if (this._config.defaultStyle === undefined) {
|
||||
this._config.defaultStyle = ListStyle.Unordered
|
||||
}
|
||||
if (this._config.styles.indexOf(this._config.defaultStyle) === -1) {
|
||||
console.warn('(ง\'̀-\'́)ง List Tool: the default style specified was not found in available styles')
|
||||
}
|
||||
this.defaultStyle = this._config.defaultStyle
|
||||
this.currentStyle = this.defaultStyle
|
||||
|
||||
this.data = {
|
||||
style: this.currentStyle,
|
||||
items: (args.data as ListData).items
|
||||
}
|
||||
|
||||
this._settingButtons = this._config.styles.map(listStyle => {
|
||||
return {
|
||||
name: listStyle,
|
||||
icon: icons[listStyle] || icon,
|
||||
action: (name: string) => this.setStyle(name as ListStyle),
|
||||
isActive: (name: string): boolean => name === this.currentStyle
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public get data (): ListData {
|
||||
// first render
|
||||
if (!this._element) return this._data as ListData
|
||||
|
||||
const items = this.queryItems()
|
||||
const data = this._data as ListData
|
||||
data.items = Array.from(items).map(item => item.innerText)
|
||||
return data
|
||||
}
|
||||
|
||||
public set data (data: ListData) {
|
||||
const currentData = this._data as ListData
|
||||
|
||||
if (data.style === undefined) data.style = currentData.style || this.defaultStyle
|
||||
if (data.items === undefined) data.items = currentData.items || []
|
||||
|
||||
this._data = data
|
||||
this.currentStyle = data.style
|
||||
|
||||
const newList = this._render()
|
||||
if (this._element.parentNode) {
|
||||
this._element.parentNode.replaceChild(newList, this._element)
|
||||
}
|
||||
this._element = newList
|
||||
}
|
||||
|
||||
private setStyle (style: ListStyle) {
|
||||
this.data = { style, items: this.data.items || [] }
|
||||
}
|
||||
|
||||
protected _render (): HTMLElement {
|
||||
const el = document.createElement(this.currentStyle)
|
||||
el.classList.add('cdx-list', this._CSS.block)
|
||||
el.contentEditable = 'true'
|
||||
el.dataset.placeholder = this._config.placeholder || ''
|
||||
|
||||
const items = this.data.items
|
||||
|
||||
if (!items || !items.length) {
|
||||
const li = document.createElement('LI')
|
||||
li.innerText = ''
|
||||
li.classList.add('cdx-list__item')
|
||||
el.appendChild(li)
|
||||
} else {
|
||||
items.forEach(item => {
|
||||
const li = document.createElement('LI')
|
||||
li.innerText = item
|
||||
li.classList.add('cdx-list__item')
|
||||
el.appendChild(li)
|
||||
})
|
||||
}
|
||||
|
||||
el.addEventListener('keydown', event => {
|
||||
// on pressing Enter
|
||||
if (event.keyCode === 13) return this.getOutOfList(event)
|
||||
// on pressing Backspace
|
||||
if (event.keyCode === 8) return this.backspace(event)
|
||||
}, false)
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
private queryItems (): NodeListOf<HTMLLIElement> {
|
||||
return this._element.querySelectorAll('.cdx-list__item')
|
||||
}
|
||||
|
||||
private get currentItem (): Element | undefined {
|
||||
const selection = window.getSelection()
|
||||
if (selection === null) return
|
||||
|
||||
let currentNode = selection.anchorNode
|
||||
if (currentNode === null) return
|
||||
|
||||
if (currentNode.nodeType !== Node.ELEMENT_NODE) {
|
||||
currentNode = currentNode.parentNode
|
||||
}
|
||||
|
||||
return (currentNode as HTMLElement).closest('.cdx-list__item') || undefined
|
||||
}
|
||||
|
||||
// leave list by pressing enter on an empty list item
|
||||
private getOutOfList (event: KeyboardEvent) {
|
||||
const items = this.queryItems()
|
||||
if (items.length < 2) return
|
||||
|
||||
const lastItem = items[items.length - 1]
|
||||
const currentItem = this.currentItem
|
||||
|
||||
// prevent generation of new li if last li is empty
|
||||
if (currentItem === lastItem && !lastItem.innerText.trim().length) {
|
||||
if (!currentItem.parentElement) return // somethings really wrong
|
||||
currentItem.parentElement.removeChild(currentItem)
|
||||
this.api.blocks.insertNewBlock()
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
public backspace (event: KeyboardEvent) {
|
||||
const items = this.queryItems()
|
||||
const firstItem = items[0]
|
||||
|
||||
if (firstItem === undefined) return
|
||||
if (items.length < 2 && !firstItem.innerText.trim()) event.preventDefault()
|
||||
}
|
||||
|
||||
public selectItem (event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
|
||||
const selection = window.getSelection()
|
||||
if (selection === null) return // no selection, no problem
|
||||
|
||||
const currentNode = selection.anchorNode?.parentNode
|
||||
if (currentNode === null) return
|
||||
|
||||
const currentItem = (currentNode as Element).closest('.cdx-list__item')
|
||||
if (currentItem === null) return
|
||||
|
||||
const range = new Range()
|
||||
|
||||
range.selectNodeContents(currentItem)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
protected pasteHandler (element: HTMLUListElement | HTMLOListElement | HTMLLIElement): ListData {
|
||||
const tag = element.tagName
|
||||
const style = tag === ListStyle.Ordered ? ListStyle.Ordered : ListStyle.Unordered
|
||||
const data: ListData = { style, items: [] }
|
||||
|
||||
if (tag === 'LI') { // does this really happen?
|
||||
data.items.push(element.innerText)
|
||||
} else {
|
||||
const items = Array.from(element.querySelectorAll('LI'))
|
||||
data.items = items.map(item => (item as HTMLElement).innerText).filter(item => !!item.trim())
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// allow to use native enter behavior
|
||||
static get enableLineBreaks (): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
static get sanitize () {
|
||||
return {
|
||||
style: {},
|
||||
items: {
|
||||
br: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get conversionConfig (): ListConversionConfig {
|
||||
return {
|
||||
export: data => {
|
||||
return data.items.join(' * ')
|
||||
},
|
||||
import: str => {
|
||||
return {
|
||||
items: [str],
|
||||
style: ListStyle.Unordered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public save (): ListData {
|
||||
console.log('saving list', this.data)
|
||||
return this.data
|
||||
}
|
||||
}
|
||||
|
||||
export default List
|
Loading…
Reference in New Issue