Skip to content

Commit

Permalink
Merge pull request #13 from marcelinombb/dev
Browse files Browse the repository at this point in the history
Reestruturações na criação de plugin e correções
  • Loading branch information
marcelinombb authored Jul 8, 2024
2 parents db08307 + 8671f81 commit b91261b
Show file tree
Hide file tree
Showing 50 changed files with 1,437 additions and 1,099 deletions.
26 changes: 6 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"@tiptap/extension-heading": "^2.4.0",
"@tiptap/extension-history": "^2.4.0",
"@tiptap/extension-horizontal-rule": "^2.4.0",
"@tiptap/extension-image": "^2.4.0",
"@tiptap/extension-italic": "^2.4.0",
"@tiptap/extension-list-item": "^2.4.0",
"@tiptap/extension-ordered-list": "^2.4.0",
Expand Down Expand Up @@ -67,6 +66,6 @@
"@simonwep/pickr": "^1.9.1",
"@tiptap/pm": "^2.4.0",
"@wiris/mathtype-html-integration-devkit": "^1.17.3",
"katex": "^0.16.10"
"katex": "^0.16.11"
}
}
46 changes: 27 additions & 19 deletions src/ExitusEditor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type Plugin } from '@editor/Plugin'
import { Toolbar } from '@editor/toolbar'
import { createHTMLElement } from '@editor/utils'
import { type AnyExtension, Editor, type EditorOptions } from '@tiptap/core'

interface Config {
[key: string]: any
}
Expand All @@ -12,6 +12,8 @@ export interface ExitusEditorOptions extends EditorOptions {
config: Config
}

export type PluginClassConstructor = typeof Plugin

function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0,
Expand All @@ -25,39 +27,39 @@ class ExitusEditor extends Editor {
toolbar: Toolbar
toolbarItemsDiv!: HTMLDivElement
editorMainDiv!: HTMLDivElement

private pluginsInstances = new Map<string, Plugin>()
static extensions: AnyExtension[]
static plugins: PluginClassConstructor[]
static toolbarOrder: string[]

constructor(options: Partial<ExitusEditorOptions> = {}) {
let ext = ExitusEditor.extensions
if (options.config !== undefined) {
ext = ExitusEditor.extensions.map(ext => {
const conf = options!.config![ext.name]
if (conf) {
return ext.configure(conf)
}

return ext
})
}
const ext = ExitusEditor.plugins.reduce<AnyExtension[]>((acc, plugin) => {
return [...acc, ...plugin.requires]
}, [])

super({ ...options, extensions: ext })
this.editorInstance = generateUUID()

const toolbarOrder: string[] = [...ExitusEditor.toolbarOrder, ...(options.toolbarOrder ?? [])]

this.toolbar = new Toolbar(this, {
toolbarOrder: toolbarOrder,
configStorage: this.extensionStorage
this.toolbar = new Toolbar(this, toolbarOrder)

ExitusEditor.plugins.forEach(plugin => {
const pluginInstance = new plugin(this)
pluginInstance.init()
this.pluginsInstances.set(plugin.pluginName, pluginInstance)
})

this._createUI(options.container as Element)
}

getPluginInstance(name: string) {
return this.pluginsInstances.get(name)
}

private _generateEditorUI() {
const toolbarItems = this.toolbar.createToolbar()
const toolbarEditor = createHTMLElement('div', { class: 'ex-toolbar-editor' }, [toolbarItems])
const toolbarItemsDiv = this.toolbar.render()
const toolbarEditor = createHTMLElement('div', { class: 'ex-toolbar-editor' }, [toolbarItemsDiv])

const editorMain = this.options.element
editorMain.className = 'editor-main'
Expand All @@ -69,7 +71,7 @@ class ExitusEditor extends Editor {

const editorShell = createHTMLElement('div', { class: 'editor-shell' }, [toolbarEditor, editorScroller])

this.toolbarItemsDiv = toolbarItems
this.toolbarItemsDiv = toolbarItemsDiv

return editorShell
}
Expand All @@ -78,6 +80,12 @@ class ExitusEditor extends Editor {
const editorUI = this._generateEditorUI()
container.appendChild(editorUI)
}

destroy(): void {
this.pluginsInstances.forEach(plugin => plugin.destroy())
this.pluginsInstances.clear()
super.destroy()
}
}

export default ExitusEditor
21 changes: 21 additions & 0 deletions src/editor/Plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type ExitusEditor from '@src/ExitusEditor'
import { type AnyExtension } from '@tiptap/core'

export interface PluginInterface {
init(): void
destroy(): void
}

export class Plugin implements PluginInterface {
constructor(readonly editor: ExitusEditor) {}
init(): void {
throw new Error('init must be implemented in the derived class')
}
destroy(): void {}
static get pluginName(): string {
throw new Error('pluginName must be implemented in the derived class')
}
static get requires(): AnyExtension[] {
return []
}
}
4 changes: 4 additions & 0 deletions src/editor/toolbar/Tool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { type Toolbar } from './Toolbar'

export interface Tool {
name: string
on(): void
off(): void
update(toolbar: Toolbar): void
render(): HTMLElement
}
99 changes: 56 additions & 43 deletions src/editor/toolbar/Toolbar.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { Button, type ButtonConfig, type Dropdown } from '@editor/ui'
import { Button, type ButtonConfig, type ButtonEventProps, Dropdown, type DropdownConfig, type DropDownEventProps } from '@editor/ui'
import type ExitusEditor from '@src/ExitusEditor'

import { type Tool } from './Tool'

function getExtensionStorage(configStorage: ConfigStorage, name: string) {
const storage = configStorage[name]
return storage
}

function isEmptyObject(obj: object) {
return Object.keys(obj).length === 0
}

type ConfigStorage = {
[key: string]: { toolbarButtonConfig: object | object[] }
}
Expand All @@ -23,57 +14,79 @@ export interface ToolbarConfig {
}
}
export class Toolbar {
editor: ExitusEditor
toolbarConfig: ToolbarConfig
toolbarItemsDiv!: HTMLDivElement
tools: Tool[] = []
private tools = new Map<string, Tool>()
currentActive!: string
constructor(
public editor: ExitusEditor,
public toolbarOrder: string[]
) {}

constructor(exitusEditor: ExitusEditor, toolbarConfig: ToolbarConfig) {
this.editor = exitusEditor
this.toolbarConfig = toolbarConfig
setTool(name: string, tool: Tool) {
if (this.tools.has(name)) {
throw new Error('Duplicated Tool')
}
this.tools.set(name, tool)
}

setToolbarConfig(toolbarConfig: ToolbarConfig) {
this.toolbarConfig = toolbarConfig
notifyAllTools() {
this.tools.forEach((tool, _key) => {
tool.update(this)
})
}

setButton(name: string, buttonConfig: Partial<ButtonConfig>) {
const button = new Button(this.editor, buttonConfig, name)

button.bind('click', props => {
buttonConfig.click!(props)
this.currentActive = props.button.name
this.notifyAllTools()
})

this.setTool(name, button)
}

setDropDown(name: string, dropdownConfig: DropdownConfig, dropdownContent: (dropdown: Dropdown) => HTMLElement) {
const newDropdownConfig = {
...dropdownConfig,
click: (props: DropDownEventProps) => {
dropdownConfig.click(props)
this.currentActive = props.dropdown.name
this.notifyAllTools()
}
}

const dropdown = new Dropdown(this.editor, newDropdownConfig, name)

dropdown.setDropDownContent(dropdownContent(dropdown))

this.setTool(name, dropdown)
}

getTool(name: string) {
return this.tools.get(name)
}

closeAllTools() {
this.tools.forEach(tool => tool.off())
}

createToolbar() {
render() {
this.toolbarItemsDiv = document.createElement('div')
this.toolbarItemsDiv.className = 'ex-toolbar-items'

const { configStorage, toolbarOrder } = this.toolbarConfig
toolbarOrder.forEach(item => {
const tool = getExtensionStorage(configStorage, item)
this.toolbarOrder.forEach(item => {
if (item == '|') {
const separator = document.createElement('span')
separator.className = 'ex-toolbar-separator'
this.toolbarItemsDiv?.append(separator)
} else if (!isEmptyObject(tool)) {
const toolbarButtonConfig = Array.isArray(tool.toolbarButtonConfig) ? tool.toolbarButtonConfig : [tool.toolbarButtonConfig]

toolbarButtonConfig.forEach((config: any) => {
if (config?.dropdown) {
const dropdown = config?.dropdown({ editor: this.editor }) as Dropdown
const button = new Button(this.editor, config)
dropdown.setParentToolbar(this)
dropdown.setButton(button)
this.tools.push(dropdown)
this.toolbarItemsDiv?.append(dropdown.render())
} else {
const button = new Button(this.editor, config)
button.setParentToolbar(this)
this.tools.push(button)
button.bind('click', config.click)
this.toolbarItemsDiv?.append(button.render())
}
})
} else {
const tool = this.getTool(item)
if (tool) {
this.toolbarItemsDiv?.append(tool.render())
}
}
})

return this.toolbarItemsDiv
}
}
16 changes: 12 additions & 4 deletions src/editor/ui/Button.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { type Toolbar } from '@editor/toolbar'
import { createHTMLElement } from '@editor/utils'
import type ExitusEditor from '@src/ExitusEditor'
import { type Editor } from '@tiptap/core'

import { type Tool } from '../toolbar/Tool'

import { type Dropdown } from '.'

export type ButtonEventProps = {
editor: Editor
editor: ExitusEditor
button: Button
event: Event
}
Expand Down Expand Up @@ -36,15 +35,24 @@ const defaultConfig: Partial<ButtonConfig> = {
export class Button implements Tool {
config: Partial<ButtonConfig>
button: HTMLButtonElement
editor: Editor
editor: ExitusEditor
dropdown!: Dropdown
parentToolbar!: Toolbar
constructor(editor: Editor, config: Partial<ButtonConfig>) {
constructor(
editor: ExitusEditor,
config: Partial<ButtonConfig>,
public name: string = ''
) {
this.config = { ...defaultConfig, ...config }
this.editor = editor
this.button = this.createButton()
}

update(toolbar: Toolbar): void {
if (toolbar.currentActive === this.name) return
this.off()
}

setEditor(editor: ExitusEditor) {
this.editor = editor
}
Expand Down
Loading

0 comments on commit b91261b

Please sign in to comment.