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 3 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
11 changes: 7 additions & 4 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,16 @@ 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,
readOnly: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is the default value, could be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

tools: {
quote: {
config: {
Expand All @@ -50,4 +54,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 '../src/Stand/StandAPI/StandAPI';
import Extension, { Control } from '../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 {EditorJS} 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
36 changes: 23 additions & 13 deletions src/Stand/Stand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { Plugin } from '../types/editorjs/Plugin.js';
import FileData from '../utils/FileData.js';

// Templates for html and script files
const STAND_TEMPLATE = path.resolve('./src/stand/stand-template.html');
const STAND_SCRIPT_TEMPLATE = path.resolve('./src/stand/stand-template.js');
const STAND_TEMPLATE = path.resolve('./src/stand/templates/stand-template.html');
const STAND_SCRIPT_TEMPLATE = path.resolve('./src/stand/templates/stand-template.js');
const STAND_STYLE_TEMPLATE = path.resolve('./src/stand/templates/stand-template.css');

/**
* Stand is the environment for testing editor.js and its plugins
Expand All @@ -32,6 +33,11 @@ export default class Stand {
*/
private JSData: FileData;

/**
* Data to store into stand style file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description is not clear. What kind of data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it

*/
private CSSData: FileData;

/**
* Initiate stand
*
Expand All @@ -47,6 +53,11 @@ export default class Stand {
*/
this.HTMLFileData = new FileData(STAND_TEMPLATE);

/**
* Get style template file
*/
this.CSSData = new FileData(STAND_STYLE_TEMPLATE);

/**
* Get script template file
*/
Expand All @@ -73,24 +84,26 @@ export default class Stand {
/**
* Add editor.js core initiation to script
*/
this.JSData.insert(`\nconst editor = new Core(editorConfig)`);
this.JSData.insert(`const editor = new Core(editorConfig)`, '{{{ Core }}}');

/**
* File names for stand environment
*/
const bundleName = 'stand.js';
const indexName = 'index.html';
const styleName = 'stand.css';

/**
* Add stand.js script to index.html
*/
this.addScript(bundleName);

/**
* Write file data to index.html and stand.js files
* Write file data to index.html, stand.js and stand.css files
*/
this.HTMLFileData.saveFile(indexName);
this.JSData.saveFile(bundleName);
this.CSSData.saveFile(styleName);
}

/**
Expand All @@ -99,7 +112,7 @@ export default class Stand {
* @param {string} scriptPath - script path
*/
private addScript(scriptPath: string): void {
const script =`\n<script src="${scriptPath}" type="module"></script>`;
const script =`<script src="${scriptPath}" type="module"></script>`;

this.HTMLFileData.insert(script, '<body>');
}
Expand All @@ -120,7 +133,7 @@ export default class Stand {
importSource = tool.packageName;
}

const str = `\nimport ${className} from '${importSource}'`;
const str = `import ${className} from '${importSource}'`;

/**
* Regular comment to insert import after it
Expand All @@ -145,15 +158,12 @@ export default class Stand {
const toolName = this.plugins[i].name;

/**
* If tool object is undefined, create empty object
* Add tool to tools object in editorConfig
*/
this.JSData.insert(`\nif (typeof editorConfig.tools.${toolName} === 'undefined') {`);
this.JSData.insert(`\n\teditorConfig.tools.${toolName} = {}\n}`);
const data = `if (!editorConfig.tools.${toolName}) editorConfig.tools.${toolName} = {}
editorConfig.tools.${toolName}.class = Tool${i}`;

/**
* Create plugin object in tools
*/
this.JSData.insert(`\neditorConfig.tools.${toolName}.class = Tool${i}`);
this.JSData.insert(data, '// {{{ Tools configuration }}}');
}
}
}
18 changes: 18 additions & 0 deletions src/Stand/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 editor.js wrapper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Class for stand API, which is used to interact with editor.js wrapper
* Class for stand API, which is used to interact with Stand

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
export default class StandAPI {
/**
* Editor.js wrapper
*/
public editorWrapper: HTMLDivElement;

/**
* Constructor for stand API
*
* @param {string} holder - editor.js holder name
*/
constructor(holder: string) {
this.editorWrapper = document.getElementById(holder) as HTMLDivElement;
}
}
10 changes: 0 additions & 10 deletions src/Stand/stand-template.html

This file was deleted.

10 changes: 0 additions & 10 deletions src/Stand/stand-template.js

This file was deleted.

70 changes: 70 additions & 0 deletions src/Stand/templates/stand-template.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;
}
16 changes: 16 additions & 0 deletions src/Stand/templates/stand-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!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__content">
<div id="editorjs"></div>
</div>
<div class="dev-stand__extensions"></div>
</div>
</body>
</html>
72 changes: 72 additions & 0 deletions src/Stand/templates/stand-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import config from './editorjs.config';
import StandAPI from './src/stand/StandAPI/StandAPI';

// {{{ Tools }}}

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

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

// {{{ Tools configuration }}}

// {{{ Core }}}

const standAPI = new StandAPI('editorjs');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think editorWrapper should not be hardcoded here, but passed from somewhere as link

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* Create style element to add styles from extensions
*/
const styleElement = document.createElement('style');

/**
* Iterate over all extensions
*/
for (const extensionClass of extensions) {
/**
* Create extension
*/
const extension = new extensionClass(editor, standAPI);

/**
* If extension has styles, add them to the stand
*/
if (extension.styles) {
styleElement.textContent += extension.styles;
}

/**
* Create button to toggle extension
*/
const btn = document.createElement('button');
btn.addEventListener('click', () => {
extension.control.onActivate();
btn.classList.toggle('dev-stand__extensions-button--active');
});
btn.innerHTML = extension.control.icon;
btn.classList.add('dev-stand__extensions-button');

/**
* Create container for extension
*/
const extensionContainer = document.createElement('div');
extensionContainer.classList.add('dev-stand__extensions-item');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code could be organised better with classes or methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* Add title to the extension
*/
const extensionTitle = document.createElement('div');
extensionTitle.innerText = extension.control.title;

/**
* Append extension to the stand
*/
extensionContainer.appendChild(extensionTitle);
extensionContainer.appendChild(btn);
document.querySelector('.dev-stand__extensions').appendChild(extensionContainer);
}
document.head.appendChild(styleElement);


2 changes: 2 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ const Setup = z.object({
*/
export const Config = z.object({
setup: Setup,
editorConfig: z.optional(z.unknown()),
extensions: z.optional(z.array(z.unknown())),
});
Loading