Skip to content

Added ability to create extensions #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dev-stand/src/StandAPI/StandAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Class for stand API, which is used to interact with Stand
*/
export default class StandAPI {
/**
* Editor.js wrapper
*/
public editorWrapper: HTMLDivElement;

/**
* Constructor for stand API
*
* @param {string} editorHolder - editor.js holder element
*/
constructor(editorHolder: HTMLDivElement) {
this.editorWrapper = editorHolder;
}
}
113 changes: 113 additions & 0 deletions dev-stand/src/StandExtension/StandExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Extension from '../types/extension';

/**
* Class for adding extension to stand
*/
export default class StandExtension {
/**
* Extension instance
*/
public readonly extension: Extension;

/**
* Constructor for stand extension
*
* @param extension - extension instance
*/
constructor(extension: Extension) {
this.extension = extension;
}

/**
* Get CSS classes for stand extension
*/
private static getCSS(): {[key: string]: string} {
return {
devStandExtensionButton: `dev-stand__extensions-button`,
devStandExtensionButtonActive: `dev-stand__extensions-button--active`,
devStandExtensionItem: `dev-stand__extensions-item`,
devStandExtensions: `dev-stand__extensions`,

};
}

/**
* Add extension to stand
*/
public add(): void {
this.addExtensionStyles();

/**
* Create elements for extension
*/
const btn = this.createExtensionButton();
const title = this.createExtensionTitle();
const container = this.createExtensionContainer();

/**
* Append button and title to extension container
*/
container.appendChild(title);
container.appendChild(btn);

/**
* Append extension container to extensions
*/
const extensionsContainer = document.getElementsByClassName(StandExtension.getCSS().devStandExtensions)[0];

if (extensionsContainer) {
extensionsContainer.appendChild(container);
}
}

/**
* Add extension styles to stand
*/
private addExtensionStyles(): void {
if (!this.extension.styles) {
return;
}
const styleElement = document.createElement('style');

styleElement.textContent += this.extension.styles;
document.head.appendChild(styleElement);
}

/**
* Create extension toggle button
*/
private createExtensionButton(): HTMLButtonElement {
const button = document.createElement('button');

button.innerHTML = this.extension.control.icon;
button.addEventListener('click', () => {
this.extension.control.onActivate();
button.classList.toggle(StandExtension.getCSS().devStandExtensionButtonActive);
});
button.classList.add(StandExtension.getCSS().devStandExtensionButton);

return button;
}

/**
* Create container for extension
*/
private createExtensionContainer(): HTMLDivElement {
const container = document.createElement('div');

container.classList.add(StandExtension.getCSS().devStandExtensionItem);

return container;
}

/**
* Create extension title
*/
private createExtensionTitle(): HTMLDivElement {
const title = document.createElement('div');

title.innerHTML = this.extension.control.title;

return title;
}
}
14 changes: 14 additions & 0 deletions dev-stand/src/templates/stand-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editor.js Dev Stand</title>
<link rel="stylesheet" href="stand.css">
</head>
<body>
<div class="dev-stand">
<div class="dev-stand__content"></div>
<div class="dev-stand__extensions"></div>
</div>
</body>
</html>
48 changes: 48 additions & 0 deletions dev-stand/src/templates/stand-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import config from '../editorjs.config';
import StandAPI from './src/StandAPI/StandAPI';
import StandExtension from './src/StandExtension/StandExtension';

// {{{ Tools }}}

const editorConfig = config().editorConfig;
const extensions = config().extensions;

if (typeof editorConfig.tools === 'undefined') {
editorConfig.tools = {}
}

const devStandContentClass = 'dev-stand__content';

/**
* Check if holder is set in config
*/
const editorHolderId = editorConfig.holder ? editorConfig.holder : 'editorjs';

/**
* Create holder for editor
*/
const editorHolder = document.createElement('div');
editorHolder.id = editorHolderId;

/**
* Append holder to dev-stand
*/
const devStandContent = document.querySelector(`.${devStandContentClass}`);
devStandContent.appendChild(editorHolder);

// {{{ Tools configuration }}}

// {{{ Core }}}

const standAPI = new StandAPI(editorHolder);

/**
* Iterate over all extensions
*/
for (const extensionClass of extensions) {
const extension = new extensionClass(editor, standAPI);
const standExtension = new StandExtension(extension);
standExtension.add();
}


42 changes: 42 additions & 0 deletions dev-stand/src/types/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import StandAPI from '../StandAPI/StandAPI';
import EditorJS from '@editorjs/editorjs';

/**
* Extension control interface
*/
export interface Control {
/**
* Icon for extension
*/
icon: string;
/**
* Title for extension
*/
title: string;
/**
* Function to activate extension
*/
onActivate: () => void;
}

/**
* Extension interface
*/
export default interface Extension {
/**
* Editor.js instance
*/
editor: EditorJS;
/**
* Stand API instance
*/
stand: StandAPI;
/**
* Getter for extension control
*/
readonly control: Control;
/**
* Getter for extension styles for editor.js wrapper
*/
readonly styles?: string;
}
70 changes: 70 additions & 0 deletions dev-stand/stand.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
:root {
--color-bg-main: #fff;
--color-border-light: #E8E8EB;
--color-text-main: #000;
}

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5em;
margin: 0;
background: var(--color-bg-main);
color: var(--color-text-main);
}

.dev-stand {
font-size: 16.2px;
}

.dev-stand__extensions {
display: flex;
align-items: center;
position: fixed;
bottom: 0;
right: 0;
left: 0;
background: var(--color-bg-main);
border-radius: 8px 8px 0 0;
border-top: 1px solid var(--color-border-light);
box-shadow: 0 2px 6px var(--color-border-light);
font-size: 13px;
padding: 8px 15px;
z-index: 1;
user-select: none;
}

.dev-stand__extensions-item:not(:last-of-type)::after {
content: '|';
color: #ddd;
margin: 0 15px 0 12px;
}

.dev-stand__extensions-item {
display: flex;
align-items: center;
gap: 10px;
}

.dev-stand__extensions-button {
display: inline-block;
padding: 3px 12px;
transition: all 150ms ease;
cursor: pointer;
border-radius: 31px;
background: #eff1f4;
text-align: center;
user-select: none;
border: 1px solid #e1e3e8;
}

.dev-stand__extensions-button--active {
background: gray;
}

.dev-stand__content {
max-width: 1100px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
11 changes: 6 additions & 5 deletions editorjs.config.sample.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import ReadOnlyExtension from './extensions/ReadOnly.js';

/**
* Create editor.js dev-tools config
*
Expand All @@ -18,14 +20,14 @@ export default function config(): unknown {
},
code: '@editorjs/code',
quote: '@editorjs/quote',
warning: {
path: './warning/dist/bundle.js',
},
header: '@editorjs/header',
checklist: {
path: './checklist/dist/bundle.js',
},
},
},
extensions: [ ReadOnlyExtension ],
editorConfig: {
inlineToolbar: true,
tools: {
quote: {
config: {
Expand All @@ -50,4 +52,3 @@ export default function config(): unknown {
},
};
}

41 changes: 41 additions & 0 deletions extensions/ReadOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import EditorJS from '@editorjs/editorjs';
import StandAPI from '../dev-stand/src/StandAPI/StandAPI';
import Extension, { Control } from '../dev-stand/src/types/extension';

/**
* Extension for toggle read only mode
*/
export default class ReadOnlyExtension implements Extension {
/**
* Editor.js instance
*/
public readonly editor: EditorJS;
/**
* Stand API with editor.js wrapper
*/
public readonly stand: StandAPI;

/**
* Constructor for read only extension
*
* @param editor - editor.js instance
* @param {StandAPI} stand - stand API instance
*/
constructor(editor: EditorJS, stand: StandAPI) {
this.editor = editor;
this.stand = stand;
}

/**
* Get extension control
*/
public get control(): Control {
return {
icon: '🔒',
title: 'Read Only',
onActivate: () => {
this.editor.readOnly.toggle();
},
};
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"yarn-or-npm": "^3.0.1"
},
"dependencies": {
"@editorjs/editorjs": "2.26.5",
"esbuild": "^0.17.16",
"vite": "^4.2.1",
"zod": "^3.20.6"
Expand Down
Loading