Skip to content

Commit

Permalink
feat(plainText): suppress formatting shortcuts for plain text
Browse files Browse the repository at this point in the history
  • Loading branch information
dfreier committed Sep 19, 2023
1 parent 04fddac commit ed139eb
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 15 deletions.
20 changes: 20 additions & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ <h3>HTML</h3>
</div>
</section>

<!-- Plain Text -->
<section class="example-section space-after">
<div class="section-content">
<h2 class="example-title">Plain Text</h2>

<div class="plain-text-example example-sheet">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.</p>
</div>

<div class="code-example">
<p>Plain text blocks don't allow any markup. Newlines are replaced with spaces.</p>
<h3>Example:</h3>
<pre><code class="language-javascript">
editable.add('.example', {plainText: true})
</code></pre>
</div>

</div>
</section>


<!-- Styling -->
<section class="example-section space-after">
Expand Down
9 changes: 6 additions & 3 deletions examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ const editable = new Editable({browserSpellcheck: false})

// Paragraph
// ---------
editable.enable('.paragraph-example p', true)
editable.enable('.paragraph-example p', {normalize: true})
eventList(editable)

// Text formatting toolbar
editable.enable('.formatting-example p', true)
editable.enable('.formatting-example p', {normalize: true})
setupTooltip()

editable.enable('.styling-example p', true)
// Plain Text
editable.enable('.plain-text-example.example-sheet', {plainText: true})

editable.enable('.styling-example p', {normalize: true})
const secondExample = document.querySelector('.formatting-example p')
updateCode(secondExample)

Expand Down
42 changes: 42 additions & 0 deletions spec/selection.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expect} from 'chai'
import rangy from 'rangy'

import {Editable} from '../src/core'
import Selection from '../src/selection'
import Cursor from '../src/cursor'
import config from '../src/config'
Expand Down Expand Up @@ -467,6 +468,47 @@ describe('Selection', function () {
expect('isAtEnd' in Selection.prototype).to.equal(true)
})
})

describe('plain text host', function () {
beforeEach(function () {
this.editable = new Editable()
})

describe('with regular text', function () {
beforeEach(function () {
this.div = createElement('<div>regular text</div>')
const range = rangy.createRange()
range.selectNodeContents(this.div)
this.selection = new Selection(this.div, range)

this.editable.enable(this.div, {plainText: true})
})

it('should not make regular text bold on toggle', function () {
this.selection.toggleBold()
expect(this.div.innerHTML).to.equal('regular text')
})

it('should not make regular text bold on forceWrap', function () {
this.selection.makeBold()
expect(this.div.innerHTML).to.equal('regular text')
})

it('should not make regular text italic on toggle', function () {
this.selection.toggleEmphasis()
expect(this.div.innerHTML).to.equal('regular text')
})

it('should not make regular text italic on forceWrap', function () {
this.selection.giveEmphasis()
expect(this.div.innerHTML).to.equal('regular text')
})
})

afterEach(function () {
this.editable.disable(this.div)
})
})
})

const getHtml = function (tag) {
Expand Down
7 changes: 6 additions & 1 deletion src/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ const state = {}
export const next = getSibling('nextElementSibling')
export const previous = getSibling('previousElementSibling')

export function init (elem, {normalize, shouldSpellcheck}) {
export function init (elem, {normalize, plainText, shouldSpellcheck}) {
setBlockId(elem)

elem.setAttribute('contenteditable', true)
elem.setAttribute('spellcheck', Boolean(shouldSpellcheck))
elem.setAttribute('data-plaintext', Boolean(plainText))

elem.classList.remove(config.editableDisabledClass)
elem.classList.add(config.editableClass)
Expand All @@ -23,13 +24,17 @@ export function init (elem, {normalize, shouldSpellcheck}) {
export function disable (elem) {
elem.removeAttribute('contenteditable')
elem.removeAttribute('spellcheck')
elem.removeAttribute('data-plaintext')

setState(elem, undefined)

elem.classList.remove(config.editableClass)
elem.classList.add(config.editableDisabledClass)
}

export function isPlainTextBlock (elem) {
return elem.getAttribute('data-plaintext') === 'true'
}

export function setBlockId (elem) {
if (!elem.hasAttribute('data-editable')) {
Expand Down
30 changes: 19 additions & 11 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ export class Editable {
* @param {HTMLElement|Array(HTMLElement)|String} target A HTMLElement, an
* array of HTMLElement or a query selector representing the target where
* the API should be added on.
* @param {normalize, plainText} options Block specific configuration
* @chainable
*/
add (target) {
this.enable(target)
add (target, options) {
this.enable(target, options)
// TODO check css whitespace settings
return this
}
Expand Down Expand Up @@ -143,19 +144,26 @@ export class Editable {
}

/**
* Adds the Editable.JS API to the given target elements.
*
* @method enable
* @param { HTMLElement | undefined } target editable root element(s)
* If no param is specified all editables marked as disabled are enabled.
* @chainable
*/
enable (target, normalize) {
* Adds the Editable.JS API to the given target elements.
*
* @method enable
* @param { HTMLElement | undefined } target editable root element(s)
* If no param is specified all editables marked as disabled are enabled.
* @param {boolean} normalize normalizes target content (legacy param)
* @param {boolean} options.normalize normalizes target content
* @param {boolean} options.plainText prevents text formatting for block
* @chainable
*/
enable (target, options) {
const {
normalize = typeof options === 'boolean' ? options : false,
plainText = false
} = options ?? {}
const shouldSpellcheck = this.config.browserSpellcheck
const targets = domArray(target || `.${config.editableDisabledClass}`, this.win.document)

for (const element of targets) {
block.init(element, {normalize, shouldSpellcheck})
block.init(element, {normalize, plainText, shouldSpellcheck})
this.dispatcher.notify('init', element)
}

Expand Down
3 changes: 3 additions & 0 deletions src/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'rangy/lib/rangy-textrange'
import Cursor from './cursor'
import * as content from './content'
import * as parser from './parser'
import * as block from './block'
import config from './config'
import highlightSupport from './highlight-support'
import highlightText from './highlight-text'
Expand Down Expand Up @@ -144,6 +145,7 @@ export default class Selection extends Cursor {

// toggle('<em>')
toggle (elem) {
if (block.isPlainTextBlock(this.host)) return
elem = this.adoptElement(elem)
this.range = content.toggleTag(this.host, this.range, elem)
this.setSelection()
Expand Down Expand Up @@ -307,6 +309,7 @@ export default class Selection extends Cursor {
// the same tagName is affecting the selection this tag will be
// remove first.
forceWrap (elem) {
if (block.isPlainTextBlock(this.host)) return
elem = this.adoptElement(elem)
this.range = content.forceWrap(this.host, this.range, elem)
this.setSelection()
Expand Down

0 comments on commit ed139eb

Please sign in to comment.