Skip to content

Commit

Permalink
Bug fixes… see description.
Browse files Browse the repository at this point in the history
- First child element is never be without a parent tag
- Make enter escape blockquote

This closes #89, closes #90
  • Loading branch information
jaredreich committed Mar 12, 2018
1 parent 7ff8695 commit a97ecbd
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 74 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Or create any custom action!

## Browser Support

* IE 9+
* IE 9+ (theoretically, but good luck)
* Chrome 5+
* Firefox 4+
* Safari 5+
Expand Down Expand Up @@ -235,7 +235,8 @@ const editor = init({
classes: {
actionbar: 'pell-actionbar-custom-name',
button: 'pell-button-custom-name',
content: 'pell-content-custom-name'
content: 'pell-content-custom-name',
selected: 'pell-button-selected-custom-name'
}
})

Expand Down
4 changes: 0 additions & 4 deletions demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ <h3>HTML output:</h3>

<script src="dist/pell.js"></script>
<script>
function ensureHTTP (str) {
return /^https?:\/\//.test(str) && str || `http://${str}`
}

var editor = window.pell.init({
element: document.getElementById('pell'),
defaultParagraphSeparator: 'p',
Expand Down
93 changes: 61 additions & 32 deletions dist/pell.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,28 @@ var actions = {
icon: '<b>H<sub>1</sub></b>',
title: 'Heading 1',
result: function result() {
return exec('formatBlock', '<H1>');
return exec(formatBlock, '<h1>');
}
},
heading2: {
icon: '<b>H<sub>2</sub></b>',
title: 'Heading 2',
result: function result() {
return exec('formatBlock', '<H2>');
return exec(formatBlock, '<h2>');
}
},
paragraph: {
icon: '&#182;',
title: 'Paragraph',
result: function result() {
return exec('formatBlock', '<P>');
return exec(formatBlock, '<p>');
}
},
quote: {
icon: '&#8220; &#8221;',
title: 'Quote',
result: function result() {
return exec('formatBlock', '<BLOCKQUOTE>');
return exec(formatBlock, '<blockquote>');
}
},
olist: {
Expand All @@ -93,7 +93,7 @@ var actions = {
icon: '&lt;/&gt;',
title: 'Code',
result: function result() {
return exec('formatBlock', '<PRE>');
return exec(formatBlock, '<pre>');
}
},
line: {
Expand Down Expand Up @@ -128,69 +128,98 @@ var classes = {
selected: 'pell-button-selected'
};

var element = null;
var defaultParagraphSeparator = null;

var formatBlock = 'formatBlock';
var addEventListener = function addEventListener(parent, type, listener) {
return parent.addEventListener(type, listener);
};
var appendChild = function appendChild(parent, child) {
return parent.appendChild(child);
};
var createElement = function createElement(tag) {
return document.createElement(tag);
};
var queryCommandState = function queryCommandState(command) {
return document.queryCommandState(command);
};
var queryCommandValue = function queryCommandValue(command) {
return document.queryCommandValue(command);
};

var preventTab = function preventTab(event) {
if (event.which === 9) event.preventDefault();
var handleKeyDown = function handleKeyDown(event, settings) {
if (event.which === 9) {
event.preventDefault();
} else if (event.which === 13 && queryCommandValue(formatBlock) === 'blockquote') {
setTimeout(function () {
return exec(formatBlock, '<' + defaultParagraphSeparator + '>');
}, 0);
}
};

var exec = function exec(command) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;

document.execCommand(command, false, value);
return document.execCommand(command, false, value);
};

var init = function init(settings) {
settings.actions = settings.actions ? settings.actions.map(function (action) {
element = settings.element;
defaultParagraphSeparator = settings.defaultParagraphSeparator || 'div';

actions = settings.actions ? settings.actions.map(function (action) {
if (typeof action === 'string') return actions[action];else if (actions[action.name]) return _extends({}, actions[action.name], action);
return action;
}) : Object.keys(actions).map(function (action) {
return actions[action];
});

settings.classes = _extends({}, classes, settings.classes);
classes = _extends({}, classes, settings.classes);

var actionbar = document.createElement('div');
actionbar.className = settings.classes.actionbar;
settings.element.appendChild(actionbar);
var actionbar = createElement('div');
actionbar.className = classes.actionbar;
appendChild(element, actionbar);

settings.element.content = document.createElement('div');
settings.element.content.contentEditable = true;
settings.element.content.className = settings.classes.content;
settings.element.content.oninput = function (event) {
return settings.onChange(event.target.innerHTML);
var content = createElement('div');
content.contentEditable = true;
content.className = classes.content;
content.oninput = function (_ref) {
var firstChild = _ref.target.firstChild;

if (firstChild && firstChild.nodeType === 3) exec(formatBlock, '<' + defaultParagraphSeparator + '>');else if (content.innerHTML === '<br>') content.innerHTML = '';
settings.onChange(content.innerHTML);
};
content.onkeydown = function (event) {
return handleKeyDown(event, settings);
};
settings.element.content.onkeydown = preventTab;
settings.element.appendChild(settings.element.content);
appendChild(element, content);

settings.actions.forEach(function (action) {
var button = document.createElement('button');
button.className = settings.classes.button;
actions.forEach(function (action) {
var button = createElement('button');
button.className = classes.button;
button.innerHTML = action.icon;
button.title = action.title;
button.setAttribute('type', 'button');
button.onclick = function () {
return action.result() || settings.element.content.focus();
return action.result() || content.focus();
};

if (action.state) {
var handler = function handler() {
return button.classList[action.state() ? 'add' : 'remove'](settings.classes.selected);
return button.classList[action.state() ? 'add' : 'remove'](classes.selected);
};
settings.element.content.addEventListener('keyup', handler);
settings.element.content.addEventListener('mouseup', handler);
button.addEventListener('click', handler);
addEventListener(content, 'keyup', handler);
addEventListener(content, 'mouseup', handler);
addEventListener(button, 'click', handler);
}

actionbar.appendChild(button);
appendChild(actionbar, button);
});

if (settings.defaultParagraphSeparator) exec('defaultParagraphSeparator', settings.defaultParagraphSeparator);
if (defaultParagraphSeparator) exec('defaultParagraphSeparator', defaultParagraphSeparator);
if (settings.styleWithCSS) exec('styleWithCSS');

return settings.element;
return element;
};

var pell = { exec: exec, init: init };
Expand Down
2 changes: 1 addition & 1 deletion dist/pell.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pell",
"description": "pell - the simplest and smallest WYSIWYG text editor for web, with no dependencies",
"author": "Jared Reich",
"version": "1.0.0",
"version": "1.0.1",
"main": "./dist/pell.min.js",
"scripts": {
"dev": "gulp",
Expand Down
85 changes: 51 additions & 34 deletions src/pell.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const actions = {
let actions = {
bold: {
icon: '<b>B</b>',
title: 'Bold',
Expand Down Expand Up @@ -26,22 +26,22 @@ const actions = {
heading1: {
icon: '<b>H<sub>1</sub></b>',
title: 'Heading 1',
result: () => exec('formatBlock', '<H1>')
result: () => exec(formatBlock, '<h1>')
},
heading2: {
icon: '<b>H<sub>2</sub></b>',
title: 'Heading 2',
result: () => exec('formatBlock', '<H2>')
result: () => exec(formatBlock, '<h2>')
},
paragraph: {
icon: '&#182;',
title: 'Paragraph',
result: () => exec('formatBlock', '<P>')
result: () => exec(formatBlock, '<p>')
},
quote: {
icon: '&#8220; &#8221;',
title: 'Quote',
result: () => exec('formatBlock', '<BLOCKQUOTE>')
result: () => exec(formatBlock, '<blockquote>')
},
olist: {
icon: '&#35;',
Expand All @@ -56,7 +56,7 @@ const actions = {
code: {
icon: '&lt;/&gt;',
title: 'Code',
result: () => exec('formatBlock', '<PRE>')
result: () => exec(formatBlock, '<pre>')
},
line: {
icon: '&#8213;',
Expand All @@ -81,67 +81,84 @@ const actions = {
}
}

const classes = {
let classes = {
actionbar: 'pell-actionbar',
button: 'pell-button',
content: 'pell-content',
selected: 'pell-button-selected'
}

let element = null
let defaultParagraphSeparator = null

const formatBlock = 'formatBlock'
const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener)
const appendChild = (parent, child) => parent.appendChild(child)
const createElement = tag => document.createElement(tag)
const queryCommandState = command => document.queryCommandState(command)
const queryCommandValue = command => document.queryCommandValue(command)

const preventTab = event => {
if (event.which === 9) event.preventDefault()
const handleKeyDown = (event, settings) => {
if (event.which === 9) {
event.preventDefault()
} else if (event.which === 13 && queryCommandValue(formatBlock) === 'blockquote') {
setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0)
}
}

export const exec = (command, value = null) => {
document.execCommand(command, false, value)
}
export const exec = (command, value = null) => document.execCommand(command, false, value)

export const init = settings => {
settings.actions = settings.actions
element = settings.element
defaultParagraphSeparator = settings.defaultParagraphSeparator || 'div'

actions = settings.actions
? settings.actions.map(action => {
if (typeof action === 'string') return actions[action]
else if (actions[action.name]) return { ...actions[action.name], ...action }
return action
})
: Object.keys(actions).map(action => actions[action])

settings.classes = { ...classes, ...settings.classes }
classes = { ...classes, ...settings.classes }

const actionbar = document.createElement('div')
actionbar.className = settings.classes.actionbar
settings.element.appendChild(actionbar)
const actionbar = createElement('div')
actionbar.className = classes.actionbar
appendChild(element, actionbar)

settings.element.content = document.createElement('div')
settings.element.content.contentEditable = true
settings.element.content.className = settings.classes.content
settings.element.content.oninput = event => settings.onChange(event.target.innerHTML)
settings.element.content.onkeydown = preventTab
settings.element.appendChild(settings.element.content)
const content = createElement('div')
content.contentEditable = true
content.className = classes.content
content.oninput = ({ target: { firstChild } }) => {
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`)
else if (content.innerHTML === '<br>') content.innerHTML = ''
settings.onChange(content.innerHTML)
}
content.onkeydown = event => handleKeyDown(event, settings)
appendChild(element, content)

settings.actions.forEach(action => {
const button = document.createElement('button')
button.className = settings.classes.button
actions.forEach(action => {
const button = createElement('button')
button.className = classes.button
button.innerHTML = action.icon
button.title = action.title
button.setAttribute('type', 'button')
button.onclick = () => action.result() || settings.element.content.focus()
button.onclick = () => action.result() || content.focus()

if (action.state) {
const handler = () => button.classList[action.state() ? 'add' : 'remove'](settings.classes.selected)
settings.element.content.addEventListener('keyup', handler)
settings.element.content.addEventListener('mouseup', handler)
button.addEventListener('click', handler)
const handler = () => button.classList[action.state() ? 'add' : 'remove'](classes.selected)
addEventListener(content, 'keyup', handler)
addEventListener(content, 'mouseup', handler)
addEventListener(button, 'click', handler)
}

actionbar.appendChild(button)
appendChild(actionbar, button)
})

if (settings.defaultParagraphSeparator) exec('defaultParagraphSeparator', settings.defaultParagraphSeparator)
if (defaultParagraphSeparator) exec('defaultParagraphSeparator', defaultParagraphSeparator)
if (settings.styleWithCSS) exec('styleWithCSS')

return settings.element
return element
}

export default { exec, init }

0 comments on commit a97ecbd

Please sign in to comment.