made with esnextbin
Last active
November 22, 2016 20:05
-
-
Save oroce/d51cba8c1199c8c62d684ea67fbfb9ef to your computer and use it in GitHub Desktop.
esnextbin sketch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>ESNextbin Sketch</title> | |
<!-- put additional styles and scripts here --> | |
<style> | |
body { | |
padding: 2em; | |
} | |
#editor-container .ql-editor { | |
padding: .5em; | |
border: 1px solid #ccc; | |
max-width: 50em; | |
min-height: 8em; | |
} | |
#editor-container p {margin-top: 0} | |
#editor-container .mention { | |
font-weight: bold; | |
color: blue; | |
} | |
.completions { | |
list-style: none; | |
margin: 0; | |
padding: 0; | |
background: white; | |
border-radius: 2px; | |
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.25); | |
} | |
.completions > li { | |
margin: 0; | |
padding: 0; | |
} | |
.completions > li > button { | |
box-sizing: border-box; | |
height: 2em; | |
padding: .25em .5em; | |
margin: 0; | |
display: block; | |
width: 100%; | |
text-align: left; | |
border: none; | |
background: none; | |
} | |
.completions > li > button:hover { | |
background: #ddd; | |
} | |
.completions > li > button:focus { | |
background: #ddd; | |
outline: none; | |
} | |
.completions > li > button > .matched { | |
font-weight: bold; | |
color: black; | |
} | |
.completions > li > button > * { | |
vertical-align: "middle"; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- put markup and other contents here --> | |
<div style="position: relative"> | |
<div id="editor-container"> | |
im an editor, you can type into me | |
</div> | |
<ul class="completions"> | |
</ul> | |
</div> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// write ES2015 code and import modules from npm | |
// and then press "Execute" to run your program | |
import Quill from 'quill'; | |
let BlockEmbed = Quill.import('blots/block/embed'); | |
class VideoBlot extends BlockEmbed { | |
static create(url) { | |
let node = super.create(); | |
// Set non-format related attributes with static values | |
node.setAttribute('frameborder', '0'); | |
node.setAttribute('allowfullscreen', true); | |
return node; | |
} | |
static formats(node) { | |
// We still need to report unregistered embed formats | |
let format = {}; | |
if (node.hasAttribute('height')) { | |
format.height = node.getAttribute('height'); | |
} | |
if (node.hasAttribute('width')) { | |
format.width = node.getAttribute('width'); | |
} | |
return format; | |
} | |
static value(node) { | |
return node.getAttribute('src'); | |
} | |
format(name, value) { | |
// Handle unregistered embed formats | |
if (name === 'height' || name === 'width') { | |
if (value) { | |
this.domNode.setAttribute(name, value); | |
} else { | |
this.domNode.removeAttribute(name, value); | |
} | |
} else { | |
super.format(name, value); | |
} | |
} | |
} | |
VideoBlot.blotName = 'video'; | |
VideoBlot.tagName = 'iframe'; | |
//Quill.register(VideoBlot); | |
/* Credits go to: http://codepen.io/anon/pen/MjNeVM */ | |
const Inline = Quill.import('blots/inline'); | |
class MentionBlot extends Inline { | |
static create(id) { | |
const node = super.create(); | |
node.dataset.id = id; | |
return node; | |
} | |
static formats(node) { | |
return node.dataset.id; | |
} | |
format(name, value) { | |
if (name === "mention" && value) { | |
this.domNode.dataset.id = value; | |
} else { | |
super.format(name, value); | |
} | |
} | |
formats() { | |
const formats = super.formats(); | |
formats['mention'] = MentionBlot.formats(this.domNode); | |
return formats; | |
} | |
} | |
MentionBlot.blotName = "mention"; | |
MentionBlot.tagName = "SPAN"; | |
MentionBlot.className = "mention"; | |
Quill.register({ | |
'formats/mention': MentionBlot | |
}); | |
const h = (tag, attrs, ...children) => { | |
const elem = document.createElement(tag); | |
Object.keys(attrs).forEach(key => elem[key] = attrs[key]); | |
children.forEach(child => { | |
if (typeof child === "string") | |
child = document.createTextNode(child); | |
elem.appendChild(child); | |
}); | |
return elem; | |
}; | |
class Mentions { | |
constructor(quill, props) { | |
this.quill = quill; | |
this.onClose = props.onClose; | |
this.onOpen = props.onOpen; | |
this.users = props.users; | |
this.container = this.quill.container.parentNode.querySelector(props.container); | |
this.container.style.position = "absolute"; | |
this.container.style.display = "none"; | |
this.onSelectionChange = this.maybeUnfocus.bind(this); | |
this.onTextChange = this.update.bind(this); | |
this.open = false; | |
this.atIndex = null; | |
this.focusedButton = null; | |
quill.keyboard.addBinding({ | |
// TODO: Once Quill supports using event.key (#1091) use that instead of shift-2 | |
key: 50, // 2 | |
shiftKey: true, | |
}, this.onAtKey.bind(this)); | |
quill.keyboard.addBinding({ | |
// TODO: Once Quill supports using event.key (#1091) use that instead of shift-2 | |
key: 81, // 2 | |
altKey: true, | |
}, this.onAtKey.bind(this)); | |
quill.keyboard.addBinding({ | |
key: 40, // ArrowDown | |
collapsed: true, | |
format: ["mention"] | |
}, this.handleArrow.bind(this)); | |
// TODO: Add keybindings for Enter (13) and Tab (9) directly on the quill editor | |
} | |
onAtKey(range, context) { | |
if (this.open) return true; | |
if (range.length > 0) { | |
this.quill.deleteText(range.index, range.length, Quill.sources.USER); | |
} | |
this.quill.insertText(range.index, "@", "mention", "0", Quill.sources.USER); | |
const atSignBounds = this.quill.getBounds(range.index); | |
this.quill.setSelection(range.index + 1, Quill.sources.SILENT); | |
this.atIndex = range.index; | |
this.container.style.left = atSignBounds.left + "px"; | |
this.container.style.top = atSignBounds.top + atSignBounds.height + "px", | |
this.open = true; | |
this.quill.on('text-change', this.onTextChange); | |
this.quill.once('selection-change', this.onSelectionChange); | |
this.update(); | |
this.onOpen && this.onOpen(); | |
} | |
handleArrow() { | |
if (!this.open) return true; | |
this.buttons[0].focus(); | |
} | |
update() { | |
const sel = this.quill.getSelection().index; | |
if (this.atIndex >= sel) { // Deleted the at character | |
return this.close(null); | |
} | |
this.query = this.quill.getText(this.atIndex + 1, sel - this.atIndex - 1); | |
// TODO: Should use fuse.js or similar fuzzy-matcher | |
const users = this.users | |
.filter(u => u.name.startsWith(this.query)) | |
.sort((u1, u2) => u1.name > u2.name); | |
this.renderCompletions(users); | |
} | |
maybeUnfocus() { | |
if (this.container.querySelector("*:focus")) return; | |
this.close(null); | |
} | |
renderCompletions(users) { | |
while (this.container.firstChild) this.container.removeChild(this.container.firstChild); | |
const buttons = Array(users.length); | |
this.buttons = buttons; | |
const handler = (i, user) => event => { | |
if (event.key === "ArrowDown" || event.keyCode === 40) { | |
event.preventDefault(); | |
buttons[Math.min(buttons.length - 1, i + 1)].focus(); | |
} else if (event.key === "ArrowUp" || event.keyCode === 38) { | |
event.preventDefault(); | |
buttons[Math.max(0, i - 1)].focus(); | |
} else if (event.key === "Enter" || event.keyCode === 13 | |
|| event.key === " " || event.keyCode === 32 | |
|| event.key === "Tab" || event.keyCode === 9) { | |
event.preventDefault(); | |
this.close(user); | |
} | |
}; | |
users.forEach((user, i) => { | |
const li = h('li', {}, | |
h('button', {type: "button"}, | |
h('span', {className: "matched"}, "@" + this.query), | |
h('span', {className: "unmatched"}, user.name.slice(this.query.length)))); | |
this.container.appendChild(li); | |
buttons[i] = li.firstChild; | |
// Events will be GC-ed with button on each re-render: | |
buttons[i].addEventListener('keydown', handler(i, user)); | |
buttons[i].addEventListener("mousedown", () => this.close(user)); | |
buttons[i].addEventListener("focus", () => this.focusedButton = i); | |
buttons[i].addEventListener("unfocus", () => this.focusedButton = null); | |
}); | |
this.container.style.display = "block"; | |
} | |
close(value) { | |
this.container.style.display = "none"; | |
while (this.container.firstChild) this.container.removeChild(this.container.firstChild); | |
this.quill.off('selection-change', this.onSelectionChange); | |
this.quill.off('text-change', this.onTextChange); | |
if (value) { | |
const {id, name} = value; | |
this.quill.deleteText(this.atIndex, this.query.length + 1, Quill.sources.USER); | |
this.quill.insertText(this.atIndex, "@" + name, "mention", id, Quill.sources.USER); | |
this.quill.insertText(this.atIndex + name.length + 1, " ", 'mention', false, Quill.sources.USER); | |
this.quill.setSelection(this.atIndex + name.length + 1, 0, Quill.sources.SILENT); | |
} | |
this.quill.focus(); | |
this.open = false; | |
this.onClose && this.onClose(value); | |
} | |
} | |
Quill.register('modules/mentions', Mentions); | |
var quill = new Quill('#editor-container', { | |
modules: { | |
mentions: { | |
container: '.completions', | |
onClose: val => console.log("Closing: ", val), | |
onOpen: () => console.log("Opening"), | |
users: [ | |
{id: 1, name: 'Christy'}, | |
{id: 2, name: 'Micha'}, | |
{id: 3, name: 'Sima'}, | |
{id: 4, name: 'Coreen'}, | |
{id: 5, name: 'Aimee'}, | |
{id: 6, name: 'Brant'}, | |
{id: 7, name: 'Maryetta'}, | |
{id: 8, name: 'Nicol'}, | |
{id: 9, name: 'Thresa'}, | |
{id: 10, name: 'Pura'}, | |
{id: 11, name: 'Audie'}, | |
{id: 12, name: 'Jacob'}, | |
{id: 13, name: 'Mika'}, | |
{id: 14, name: 'Nubia'}, | |
{id: 15, name: 'Ana'}, | |
{id: 16, name: 'Sudie'}, | |
{id: 17, name: 'Raymundo'}, | |
{id: 18, name: 'Carolyne'}, | |
{id: 19, name: 'Doretha'}, | |
{id: 20, name: 'Milo'}, | |
] | |
} | |
} | |
}); | |
quill.on('text-change', (dlt, oldDlt, source) => { | |
console.log('text-change', dlt, oldDlt, source); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "esnextbin-sketch", | |
"version": "0.0.0", | |
"dependencies": { | |
"quill": "1.1.5", | |
"babel-runtime": "6.18.0" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var _keys = require('babel-runtime/core-js/object/keys'); | |
var _keys2 = _interopRequireDefault(_keys); | |
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); | |
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); | |
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | |
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | |
var _createClass2 = require('babel-runtime/helpers/createClass'); | |
var _createClass3 = _interopRequireDefault(_createClass2); | |
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); | |
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); | |
var _get2 = require('babel-runtime/helpers/get'); | |
var _get3 = _interopRequireDefault(_get2); | |
var _inherits2 = require('babel-runtime/helpers/inherits'); | |
var _inherits3 = _interopRequireDefault(_inherits2); | |
var _quill = require('quill'); | |
var _quill2 = _interopRequireDefault(_quill); | |
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | |
var BlockEmbed = _quill2.default.import('blots/block/embed'); // write ES2015 code and import modules from npm | |
// and then press "Execute" to run your program | |
var VideoBlot = function (_BlockEmbed) { | |
(0, _inherits3.default)(VideoBlot, _BlockEmbed); | |
function VideoBlot() { | |
(0, _classCallCheck3.default)(this, VideoBlot); | |
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(VideoBlot).apply(this, arguments)); | |
} | |
(0, _createClass3.default)(VideoBlot, [{ | |
key: 'format', | |
value: function format(name, value) { | |
// Handle unregistered embed formats | |
if (name === 'height' || name === 'width') { | |
if (value) { | |
this.domNode.setAttribute(name, value); | |
} else { | |
this.domNode.removeAttribute(name, value); | |
} | |
} else { | |
(0, _get3.default)((0, _getPrototypeOf2.default)(VideoBlot.prototype), 'format', this).call(this, name, value); | |
} | |
} | |
}], [{ | |
key: 'create', | |
value: function create(url) { | |
var node = (0, _get3.default)((0, _getPrototypeOf2.default)(VideoBlot), 'create', this).call(this); | |
// Set non-format related attributes with static values | |
node.setAttribute('frameborder', '0'); | |
node.setAttribute('allowfullscreen', true); | |
return node; | |
} | |
}, { | |
key: 'formats', | |
value: function formats(node) { | |
// We still need to report unregistered embed formats | |
var format = {}; | |
if (node.hasAttribute('height')) { | |
format.height = node.getAttribute('height'); | |
} | |
if (node.hasAttribute('width')) { | |
format.width = node.getAttribute('width'); | |
} | |
return format; | |
} | |
}, { | |
key: 'value', | |
value: function value(node) { | |
return node.getAttribute('src'); | |
} | |
}]); | |
return VideoBlot; | |
}(BlockEmbed); | |
VideoBlot.blotName = 'video'; | |
VideoBlot.tagName = 'iframe'; | |
//Quill.register(VideoBlot); | |
/* Credits go to: http://codepen.io/anon/pen/MjNeVM */ | |
var Inline = _quill2.default.import('blots/inline'); | |
var MentionBlot = function (_Inline) { | |
(0, _inherits3.default)(MentionBlot, _Inline); | |
function MentionBlot() { | |
(0, _classCallCheck3.default)(this, MentionBlot); | |
return (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(MentionBlot).apply(this, arguments)); | |
} | |
(0, _createClass3.default)(MentionBlot, [{ | |
key: 'format', | |
value: function format(name, value) { | |
if (name === "mention" && value) { | |
this.domNode.dataset.id = value; | |
} else { | |
(0, _get3.default)((0, _getPrototypeOf2.default)(MentionBlot.prototype), 'format', this).call(this, name, value); | |
} | |
} | |
}, { | |
key: 'formats', | |
value: function formats() { | |
var formats = (0, _get3.default)((0, _getPrototypeOf2.default)(MentionBlot.prototype), 'formats', this).call(this); | |
formats['mention'] = MentionBlot.formats(this.domNode); | |
return formats; | |
} | |
}], [{ | |
key: 'create', | |
value: function create(id) { | |
var node = (0, _get3.default)((0, _getPrototypeOf2.default)(MentionBlot), 'create', this).call(this); | |
node.dataset.id = id; | |
return node; | |
} | |
}, { | |
key: 'formats', | |
value: function formats(node) { | |
return node.dataset.id; | |
} | |
}]); | |
return MentionBlot; | |
}(Inline); | |
MentionBlot.blotName = "mention"; | |
MentionBlot.tagName = "SPAN"; | |
MentionBlot.className = "mention"; | |
_quill2.default.register({ | |
'formats/mention': MentionBlot | |
}); | |
var h = function h(tag, attrs) { | |
for (var _len = arguments.length, children = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | |
children[_key - 2] = arguments[_key]; | |
} | |
var elem = document.createElement(tag); | |
(0, _keys2.default)(attrs).forEach(function (key) { | |
return elem[key] = attrs[key]; | |
}); | |
children.forEach(function (child) { | |
if (typeof child === "string") child = document.createTextNode(child); | |
elem.appendChild(child); | |
}); | |
return elem; | |
}; | |
var Mentions = function () { | |
function Mentions(quill, props) { | |
(0, _classCallCheck3.default)(this, Mentions); | |
this.quill = quill; | |
this.onClose = props.onClose; | |
this.onOpen = props.onOpen; | |
this.users = props.users; | |
this.container = this.quill.container.parentNode.querySelector(props.container); | |
this.container.style.position = "absolute"; | |
this.container.style.display = "none"; | |
this.onSelectionChange = this.maybeUnfocus.bind(this); | |
this.onTextChange = this.update.bind(this); | |
this.open = false; | |
this.atIndex = null; | |
this.focusedButton = null; | |
quill.keyboard.addBinding({ | |
// TODO: Once Quill supports using event.key (#1091) use that instead of shift-2 | |
key: 50, // 2 | |
shiftKey: true | |
}, this.onAtKey.bind(this)); | |
quill.keyboard.addBinding({ | |
// TODO: Once Quill supports using event.key (#1091) use that instead of shift-2 | |
key: 81, // 2 | |
altKey: true | |
}, this.onAtKey.bind(this)); | |
quill.keyboard.addBinding({ | |
key: 40, // ArrowDown | |
collapsed: true, | |
format: ["mention"] | |
}, this.handleArrow.bind(this)); | |
// TODO: Add keybindings for Enter (13) and Tab (9) directly on the quill editor | |
} | |
(0, _createClass3.default)(Mentions, [{ | |
key: 'onAtKey', | |
value: function onAtKey(range, context) { | |
if (this.open) return true; | |
if (range.length > 0) { | |
this.quill.deleteText(range.index, range.length, _quill2.default.sources.USER); | |
} | |
this.quill.insertText(range.index, "@", "mention", "0", _quill2.default.sources.USER); | |
var atSignBounds = this.quill.getBounds(range.index); | |
this.quill.setSelection(range.index + 1, _quill2.default.sources.SILENT); | |
this.atIndex = range.index; | |
this.container.style.left = atSignBounds.left + "px"; | |
this.container.style.top = atSignBounds.top + atSignBounds.height + "px", this.open = true; | |
this.quill.on('text-change', this.onTextChange); | |
this.quill.once('selection-change', this.onSelectionChange); | |
this.update(); | |
this.onOpen && this.onOpen(); | |
} | |
}, { | |
key: 'handleArrow', | |
value: function handleArrow() { | |
if (!this.open) return true; | |
this.buttons[0].focus(); | |
} | |
}, { | |
key: 'update', | |
value: function update() { | |
var _this3 = this; | |
var sel = this.quill.getSelection().index; | |
if (this.atIndex >= sel) { | |
// Deleted the at character | |
return this.close(null); | |
} | |
this.query = this.quill.getText(this.atIndex + 1, sel - this.atIndex - 1); | |
// TODO: Should use fuse.js or similar fuzzy-matcher | |
var users = this.users.filter(function (u) { | |
return u.name.startsWith(_this3.query); | |
}).sort(function (u1, u2) { | |
return u1.name > u2.name; | |
}); | |
this.renderCompletions(users); | |
} | |
}, { | |
key: 'maybeUnfocus', | |
value: function maybeUnfocus() { | |
if (this.container.querySelector("*:focus")) return; | |
this.close(null); | |
} | |
}, { | |
key: 'renderCompletions', | |
value: function renderCompletions(users) { | |
var _this4 = this; | |
while (this.container.firstChild) { | |
this.container.removeChild(this.container.firstChild); | |
}var buttons = Array(users.length); | |
this.buttons = buttons; | |
var handler = function handler(i, user) { | |
return function (event) { | |
if (event.key === "ArrowDown" || event.keyCode === 40) { | |
event.preventDefault(); | |
buttons[Math.min(buttons.length - 1, i + 1)].focus(); | |
} else if (event.key === "ArrowUp" || event.keyCode === 38) { | |
event.preventDefault(); | |
buttons[Math.max(0, i - 1)].focus(); | |
} else if (event.key === "Enter" || event.keyCode === 13 || event.key === " " || event.keyCode === 32 || event.key === "Tab" || event.keyCode === 9) { | |
event.preventDefault(); | |
_this4.close(user); | |
} | |
}; | |
}; | |
users.forEach(function (user, i) { | |
var li = h('li', {}, h('button', { type: "button" }, h('span', { className: "matched" }, "@" + _this4.query), h('span', { className: "unmatched" }, user.name.slice(_this4.query.length)))); | |
_this4.container.appendChild(li); | |
buttons[i] = li.firstChild; | |
// Events will be GC-ed with button on each re-render: | |
buttons[i].addEventListener('keydown', handler(i, user)); | |
buttons[i].addEventListener("mousedown", function () { | |
return _this4.close(user); | |
}); | |
buttons[i].addEventListener("focus", function () { | |
return _this4.focusedButton = i; | |
}); | |
buttons[i].addEventListener("unfocus", function () { | |
return _this4.focusedButton = null; | |
}); | |
}); | |
this.container.style.display = "block"; | |
} | |
}, { | |
key: 'close', | |
value: function close(value) { | |
this.container.style.display = "none"; | |
while (this.container.firstChild) { | |
this.container.removeChild(this.container.firstChild); | |
}this.quill.off('selection-change', this.onSelectionChange); | |
this.quill.off('text-change', this.onTextChange); | |
if (value) { | |
var id = value.id; | |
var name = value.name; | |
this.quill.deleteText(this.atIndex, this.query.length + 1, _quill2.default.sources.USER); | |
this.quill.insertText(this.atIndex, "@" + name, "mention", id, _quill2.default.sources.USER); | |
this.quill.insertText(this.atIndex + name.length + 1, " ", 'mention', false, _quill2.default.sources.USER); | |
this.quill.setSelection(this.atIndex + name.length + 1, 0, _quill2.default.sources.SILENT); | |
} | |
this.quill.focus(); | |
this.open = false; | |
this.onClose && this.onClose(value); | |
} | |
}]); | |
return Mentions; | |
}(); | |
_quill2.default.register('modules/mentions', Mentions); | |
var quill = new _quill2.default('#editor-container', { | |
modules: { | |
mentions: { | |
container: '.completions', | |
onClose: function onClose(val) { | |
return console.log("Closing: ", val); | |
}, | |
onOpen: function onOpen() { | |
return console.log("Opening"); | |
}, | |
users: [{ id: 1, name: 'Christy' }, { id: 2, name: 'Micha' }, { id: 3, name: 'Sima' }, { id: 4, name: 'Coreen' }, { id: 5, name: 'Aimee' }, { id: 6, name: 'Brant' }, { id: 7, name: 'Maryetta' }, { id: 8, name: 'Nicol' }, { id: 9, name: 'Thresa' }, { id: 10, name: 'Pura' }, { id: 11, name: 'Audie' }, { id: 12, name: 'Jacob' }, { id: 13, name: 'Mika' }, { id: 14, name: 'Nubia' }, { id: 15, name: 'Ana' }, { id: 16, name: 'Sudie' }, { id: 17, name: 'Raymundo' }, { id: 18, name: 'Carolyne' }, { id: 19, name: 'Doretha' }, { id: 20, name: 'Milo' }] | |
} | |
} | |
}); | |
quill.on('text-change', function (dlt, oldDlt, source) { | |
console.log('text-change', dlt, oldDlt, source); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment