diff --git a/src/rich_text_editor/index.js b/src/rich_text_editor/index.js index 68a44d833..42935d8b3 100644 --- a/src/rich_text_editor/index.js +++ b/src/rich_text_editor/index.js @@ -109,7 +109,9 @@ export default () => { const classes = { actionbar: `${pfx}actionbar`, button: `${pfx}action`, - active: `${pfx}active` + active: `${pfx}active`, + inactive: `${pfx}inactive`, + disabled: `${pfx}disabled` }; const rte = new RichTextEditor({ el, @@ -165,6 +167,32 @@ export default () => { * } * } * }) + * // An example with state + * const isValidAnchor = (rte) => { + * // a utility function to help determine if the selected is a valid anchor node + * const anchor = rte.selection().anchorNode; + * const parentNode = anchor && anchor.parentNode; + * const nextSibling = anchor && anchor.nextSibling; + * return (parentNode && parentNode.nodeName == 'A') || (nextSibling && nextSibling.nodeName == 'A') + * } + * rte.add('toggleAnchor', { + * icon: ``, + * state: (rte, doc) => { + * if (rte && rte.selection()) { + * // `btnState` is a integer, -1 for disabled, 0 for inactive, 1 for active + * return isValidAnchor(rte) ? btnState.ACTIVE : btnState.INACTIVE; + * } else { + * return btnState.INACTIVE; + * } + * }, + * result: (rte, action) => { + * if (isValidAnchor(rte)) { + * rte.exec('unlink'); + * } else { + * rte.insertHTML(`${rte.selection()}`); + * } + * } + * }) */ add(name, action = {}) { action.name = name; diff --git a/src/rich_text_editor/model/RichTextEditor.js b/src/rich_text_editor/model/RichTextEditor.js index d2b076378..597a3f787 100644 --- a/src/rich_text_editor/model/RichTextEditor.js +++ b/src/rich_text_editor/model/RichTextEditor.js @@ -5,6 +5,20 @@ import { on, off } from 'utils/mixins'; const RTE_KEY = '_rte'; +const btnState = { + ACTIVE: 1, + INACTIVE: 0, + DISABLED: -1 +}; +const isValidAnchor = rte => { + const anchor = rte.selection().anchorNode; + const parentNode = anchor && anchor.parentNode; + const nextSibling = anchor && anchor.nextSibling; + return ( + (parentNode && parentNode.nodeName == 'A') || + (nextSibling && nextSibling.nodeName == 'A') + ); +}; const defActions = { bold: { name: 'bold', @@ -37,10 +51,15 @@ const defActions = { style: 'font-size:1.4rem;padding:0 4px 2px;', title: 'Link' }, + state: (rte, doc) => { + if (rte && rte.selection()) { + return isValidAnchor(rte) ? btnState.ACTIVE : btnState.INACTIVE; + } else { + return btnState.INACTIVE; + } + }, result: rte => { - const anchor = rte.selection().anchorNode; - const nextSibling = anchor && anchor.nextSibling; - if (nextSibling && nextSibling.nodeName == 'A') { + if (isValidAnchor(rte)) { rte.exec('unlink'); } else { rte.insertHTML(`${rte.selection()}`); @@ -78,7 +97,9 @@ export default class RichTextEditor { ...{ actionbar: 'actionbar', button: 'action', - active: 'active' + active: 'active', + disabled: 'disabled', + inactive: 'inactive' }, ...settings.classes }; @@ -114,16 +135,34 @@ export default class RichTextEditor { this.getActions().forEach(action => { const btn = action.btn; const update = action.update; - const active = this.classes.active; + const { active, inactive, disabled } = { ...this.classes }; + const state = action.state; const name = action.name; const doc = this.doc; btn.className = btn.className.replace(active, '').trim(); - - // doc.queryCommandValue(name) != 'false' - if (doc.queryCommandSupported(name) && doc.queryCommandState(name)) { - btn.className += ` ${active}`; + btn.className = btn.className.replace(inactive, '').trim(); + btn.className = btn.className.replace(disabled, '').trim(); + + // if there is a state function, which depicts the state, + // i.e. `active`, `disabled`, then call it + if (state) { + switch (state(this, doc)) { + case btnState.ACTIVE: + btn.className += ` ${active}`; + break; + case btnState.INACTIVE: + btn.className += ` ${inactive}`; + break; + case btnState.DISABLED: + btn.className += ` ${disabled}`; + break; + } + } else { + // otherwise default to checking if the name command is supported & enabled + if (doc.queryCommandSupported(name) && doc.queryCommandState(name)) { + btn.className += ` ${active}`; + } } - update && update(this, action); }); } @@ -156,11 +195,18 @@ export default class RichTextEditor { */ syncActions() { this.getActions().forEach(action => { - const event = action.event || 'click'; - action.btn[`on${event}`] = e => { - action.result(this, action); - this.updateActiveActions(); - }; + if (this.settings.actionbar) { + if ( + !action.state || + (action.state && action.state(this, this.doc) >= 0) + ) { + const event = action.event || 'click'; + action.btn[`on${event}`] = e => { + action.result(this, action); + this.updateActiveActions(); + }; + } + } }); } diff --git a/src/styles/scss/_gjs_rte.scss b/src/styles/scss/_gjs_rte.scss index eefd4c183..6654e263a 100644 --- a/src/styles/scss/_gjs_rte.scss +++ b/src/styles/scss/_gjs_rte.scss @@ -33,6 +33,13 @@ } &active { - background-color: $mainDkColor; + background-color: $mainLhColor; + } + &disabled { + color: $mainLhColor; + cursor: not-allowed; + &:hover { + background-color: unset; + } } }