Skip to content
Open
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
Merge pull request #36 from editor-js/new/codex-icon
Standardize the `marker` plugin toolbox icon with `codexteam/icons`
  • Loading branch information
BensenHsu committed Jan 6, 2023
commit 0261541b68d728692cd5a2fe58c8db2133b97e6d
232 changes: 80 additions & 152 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,186 +1,114 @@
/**
* Build styles
*/
import './index.css';
import { IconMarker } from '@codexteam/icons'

/**
* Marker Tool for the Editor.js
*
* Allows to wrap inline fragment and style it somehow.
*/
export default class Marker {
/**
* Class name for term-tag
*
* @type {string}
*/
static get CSS() {
return 'cdx-marker';
};

/**
* @param {{api: object}} - Editor.js API
*/
constructor({api}) {
this.api = api;
export default class Marker {

/**
* Toolbar Button
*
* @type {HTMLElement|null}
*/
this.button = null;
static get isInline() {
return true;
}

/**
* Tag represented the term
*
* @type {string}
*/
this.tag = 'MARK';
get state() {
return this._state;
}

/**
* CSS classes
*/
this.iconClasses = {
base: this.api.styles.inlineToolButton,
active: this.api.styles.inlineToolButtonActive
};
set state(state) {
this._state = state;

this.button.classList.toggle(this.api.styles.inlineToolButtonActive, state);
}

/**
* Specifies Tool as Inline Toolbar Tool
*
* @return {boolean}
*/
static get isInline() {
return true;
constructor({api}) {
this.api = api;
this.button = null;
this._state = false;

this.tag = 'MARK';
this.class = 'cdx-marker';
}

/**
* Create button element for Toolbar
*
* @return {HTMLElement}
*/
render() {
this.button = document.createElement('button');
this.button.type = 'button';
this.button.classList.add(this.iconClasses.base);
this.button.innerHTML = this.toolboxIcon;
this.button.innerHTML = '<svg width="20" height="18"><path d="M10.458 12.04l2.919 1.686-.781 1.417-.984-.03-.974 1.687H8.674l1.49-2.583-.508-.775.802-1.401zm.546-.952l3.624-6.327a1.597 1.597 0 0 1 2.182-.59 1.632 1.632 0 0 1 .615 2.201l-3.519 6.391-2.902-1.675zm-7.73 3.467h3.465a1.123 1.123 0 1 1 0 2.247H3.273a1.123 1.123 0 1 1 0-2.247z"/></svg>';
this.button.classList.add(this.api.styles.inlineToolButton);

return this.button;
}

/**
* Wrap/Unwrap selected fragment
*
* @param {Range} range - selected fragment
*/
surround(range) {
if (!range) {
if (this.state) {
this.unwrap(range);
return;
}

let termWrapper = this.api.selection.findParentTag(this.tag, Marker.CSS);

/**
* If start or end of selection is in the highlighted block
*/
if (termWrapper) {
this.unwrap(termWrapper);
} else {
this.wrap(range);
}
this.wrap(range);
}

/**
* Wrap selection with term-tag
*
* @param {Range} range - selected fragment
*/
wrap(range) {
/**
* Create a wrapper for highlighting
*/
let marker = document.createElement(this.tag);

marker.classList.add(Marker.CSS);

/**
* SurroundContent throws an error if the Range splits a non-Text node with only one of its boundary points
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents}
*
* // range.surroundContents(span);
*/
marker.appendChild(range.extractContents());
range.insertNode(marker);

/**
* Expand (add) selection to highlighted block
*/
this.api.selection.expandToTag(marker);
const selectedText = range.extractContents();
const mark = document.createElement(this.tag);

mark.classList.add(this.class);
mark.appendChild(selectedText);
range.insertNode(mark);

this.api.selection.expandToTag(mark);
}

/**
* Unwrap term-tag
*
* @param {HTMLElement} termWrapper - term wrapper tag
*/
unwrap(termWrapper) {
/**
* Expand selection to all term-tag
*/
this.api.selection.expandToTag(termWrapper);

let sel = window.getSelection();
let range = sel.getRangeAt(0);

let unwrappedContent = range.extractContents();

/**
* Remove empty term-tag
*/
termWrapper.parentNode.removeChild(termWrapper);

/**
* Insert extracted content
*/
range.insertNode(unwrappedContent);

/**
* Restore selection
*/
sel.removeAllRanges();
sel.addRange(range);
unwrap(range) {
const mark = this.api.selection.findParentTag(this.tag, this.class);
const text = range.extractContents();

mark.remove();

range.insertNode(text);
}

/**
* Check and change Term's state for current selection
*/

checkState() {
const termTag = this.api.selection.findParentTag(this.tag, Marker.CSS);
const mark = this.api.selection.findParentTag(this.tag);

this.button.classList.toggle(this.iconClasses.active, !!termTag);
this.state = !!mark;

if (this.state) {
this.showActions(mark);
} else {
this.hideActions();
}
}

/**
* Get Tool icon's SVG
* @return {string}
*/
get toolboxIcon() {
return IconMarker;
renderActions() {
this.colorPicker = document.createElement('input');
this.colorPicker.type = 'color';
this.colorPicker.value = '#f5f1cc';
this.colorPicker.hidden = true;

return this.colorPicker;
}

/**
* Sanitizer rule
* @return {{mark: {class: string}}}
*/
static get sanitize() {
return {
mark: {
class: Marker.CSS
}
showActions(mark) {
const {backgroundColor} = mark.style;
this.colorPicker.value = backgroundColor ? this.convertToHex(backgroundColor) : '#f5f1cc';

this.colorPicker.onchange = () => {
mark.style.backgroundColor = this.colorPicker.value;
};
this.colorPicker.hidden = false;
}

hideActions() {
this.colorPicker.onchange = null;
this.colorPicker.hidden = true;
}
}

convertToHex(color) {
const rgb = color.match(/(\d+)/g);

let hexr = parseInt(rgb[0]).toString(16);
let hexg = parseInt(rgb[1]).toString(16);
let hexb = parseInt(rgb[2]).toString(16);

hexr = hexr.length === 1 ? '0' + hexr : hexr;
hexg = hexg.length === 1 ? '0' + hexg : hexg;
hexb = hexb.length === 1 ? '0' + hexb : hexb;

return '#' + hexr + hexg + hexb;
}
}