API for adding buttons to the new composer

This commit is contained in:
Robin Ward 2015-11-02 16:05:40 -05:00
parent 290708ca53
commit 5cd6308850
4 changed files with 160 additions and 73 deletions

View File

@ -12,6 +12,96 @@ function getHead(head, prev) {
}
}
const _createCallbacks = [];
function Toolbar() {
this.groups = [
{group: 'fontStyles', buttons: []},
{group: 'insertions', buttons: []},
{group: 'extras', buttons: [], lastGroup: true}
];
this.addButton({
id: 'bold',
group: 'fontStyles',
perform: e => e.applySurround('**', '**', 'bold_text')
});
this.addButton({
id: 'italic',
group: 'fontStyles',
perform: e => e.applySurround('*', '*', 'italic_text')
});
this.addButton({group: 'insertions', id: 'link', action: 'showLinkModal'});
this.addButton({
id: 'quote',
group: 'insertions',
icon: 'quote-right',
perform: e => e.applySurround('> ', '', 'code_text')
});
this.addButton({
id: 'code',
group: 'insertions',
perform(e) {
if (e.selected.value.indexOf("\n") !== -1) {
e.applySurround(' ', '', 'code_text');
} else {
e.applySurround('`', '`', 'code_text');
}
},
});
this.addButton({
id: 'bullet',
group: 'extras',
icon: 'list-ul',
perform: e => e.applyList('* ', 'list_item')
});
this.addButton({
id: 'list',
group: 'extras',
icon: 'list-ol',
perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item')
});
this.addButton({
id: 'heading',
group: 'extras',
icon: 'font',
perform: e => e.applyList('## ', 'heading_text')
});
this.addButton({
id: 'rule',
group: 'extras',
icon: 'minus',
perform: e => e.addText("\n\n----------\n")
});
};
Toolbar.prototype.addButton = function(button) {
const g = this.groups.findProperty('group', button.group);
if (!g) {
throw `Couldn't find toolbar group ${button.group}`;
}
g.buttons.push({
id: button.id,
className: button.className || button.id,
icon: button.icon || button.id,
action: button.action || 'toolbarButton',
perform: button.perform || Ember.k
});
};
export function onToolbarCreate(func) {
_createCallbacks.push(func);
};
export default Ember.Component.extend({
classNames: ['d-editor'],
ready: false,
@ -25,6 +115,13 @@ export default Ember.Component.extend({
loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true));
},
@property
toolbar() {
const toolbar = new Toolbar();
_createCallbacks.forEach(cb => cb(toolbar));
return toolbar;
},
@property('ready', 'value')
preview(ready, value) {
if (!ready) { return; }
@ -51,7 +148,7 @@ export default Ember.Component.extend({
showSelector({
appendTo: self.$(),
container,
onSelect: title => self._addText(`${title}:`)
onSelect: title => self._addText(this._getSelected(), `${title}:`)
});
return "";
}
@ -112,8 +209,7 @@ export default Ember.Component.extend({
});
},
_applySurround(head, tail, exampleKey) {
const sel = this._getSelected();
_applySurround(sel, head, tail, exampleKey) {
const pre = sel.pre;
const post = sel.post;
@ -162,10 +258,9 @@ export default Ember.Component.extend({
}
},
_applyList(head, exampleKey) {
const sel = this._getSelected();
_applyList(sel, head, exampleKey) {
if (sel.value.indexOf("\n") !== -1) {
this._applySurround(head, '', exampleKey);
this._applySurround(sel, head, '', exampleKey);
} else {
const [hval, hlen] = getHead(head);
@ -185,20 +280,22 @@ export default Ember.Component.extend({
}
},
_addText(text, sel) {
sel = sel || this._getSelected();
_addText(sel, text) {
const insert = `${sel.pre}${text}`;
this.set('value', `${insert}${sel.post}`);
this._selectText(insert.length, 0);
},
actions: {
bold() {
this._applySurround('**', '**', 'bold_text');
},
toolbarButton(button) {
italic() {
this._applySurround('*', '*', 'italic_text');
const selected = this._getSelected();
button.perform({
selected,
applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey),
applyList: (head, exampleKey) => this._applyList(selected, head, exampleKey),
addText: text => this._addText(selected, text)
});
},
showLinkModal() {
@ -214,48 +311,19 @@ export default Ember.Component.extend({
if (m && m.length === 2) {
const description = m[1];
const remaining = link.replace(m[0], '');
this._addText(`[${description}](${remaining})`, this._lastSel);
this._addText(this._lastSel, `[${description}](${remaining})`);
} else {
this._addText(`[${link}](${link})`, this._lastSel);
this._addText(this._lastSel, `[${link}](${link})`);
}
this.set('link', '');
},
code() {
const sel = this._getSelected();
if (sel.value.indexOf("\n") !== -1) {
this._applySurround(' ', '', 'code_text');
} else {
this._applySurround('`', '`', 'code_text');
}
},
quote() {
this._applySurround('> ', "", 'code_text');
},
bullet() {
this._applyList('* ', 'list_item');
},
list() {
this._applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item');
},
heading() {
this._applyList('## ', 'heading_text');
},
rule() {
this._addText("\n\n----------\n");
},
emoji() {
showSelector({
appendTo: this.$(),
container: this.container,
onSelect: title => this._addText(`:${title}:`)
onSelect: title => this._addText(this._getSelected(), `:${title}:`)
});
}
}

View File

@ -1,4 +1,5 @@
import { showSelector } from "discourse/lib/emoji/emoji-toolbar";
import { onToolbarCreate } from 'discourse/components/d-editor';
export default {
name: 'enable-emoji',
@ -6,6 +7,16 @@ export default {
initialize(container) {
const siteSettings = container.lookup('site-settings:main');
if (siteSettings.enable_emoji) {
onToolbarCreate(toolbar => {
toolbar.addButton({
id: 'emoji',
group: 'extras',
icon: 'smile-o',
action: 'emoji'
});
});
window.PagedownCustom.appendButtons.push({
id: 'wmd-emoji-button',
description: I18n.t("composer.emoji"),

View File

@ -8,20 +8,14 @@
<div class='d-editor-container'>
<div class='d-editor-button-bar'>
{{d-button action="bold" icon="bold" class="bold"}}
{{d-button action="italic" icon="italic" class="italic"}}
<div class='d-editor-spacer'></div>
{{d-button action="showLinkModal" icon="link" class="link"}}
{{d-button action="quote" icon="quote-right" class="quote"}}
{{d-button action="code" icon="code" class="code"}}
<div class='d-editor-spacer'></div>
{{d-button action="bullet" icon="list-ul" class="bullet"}}
{{d-button action="list" icon="list-ol" class="list"}}
{{d-button action="heading" icon="font" class="heading"}}
{{d-button action="rule" icon="minus" class="rule"}}
{{#if siteSettings.enable_emoji}}
{{d-button action="emoji" icon="smile-o" class="emoji"}}
{{/if}}
{{#each toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{d-button action=b.action actionParam=b icon=b.icon class=b.className title=t.title}}
{{/each}}
{{#unless group.lastGroup}}
<div class='d-editor-spacer'></div>
{{/unless}}
{{/each}}
</div>
{{textarea value=value class="d-editor-input"}}

View File

@ -1,4 +1,5 @@
import componentTest from 'helpers/component-test';
import { onToolbarCreate } from 'discourse/components/d-editor';
moduleForComponent('d-editor', {integration: true});
@ -441,21 +442,34 @@ testCase(`rule with a selection`, function(assert, textarea) {
});
});
testCase(`emoji`, function(assert) {
assert.equal($('.emoji-modal').length, 0);
componentTest('emoji', {
template: '{{d-editor value=value}}',
setup() {
// Test adding a custom button
onToolbarCreate(toolbar => {
toolbar.addButton({
id: 'emoji',
group: 'extras',
icon: 'smile-o',
action: 'emoji'
});
});
this.set('value', 'hello world.');
},
test(assert) {
assert.equal($('.emoji-modal').length, 0);
click('button.emoji');
andThen(() => {
assert.equal($('.emoji-modal').length, 1);
});
click('button.emoji');
andThen(() => {
assert.equal($('.emoji-modal').length, 1);
});
click('a[data-group-id=0]');
click('a[title=grinning]');
click('a[data-group-id=0]');
click('a[title=grinning]');
andThen(() => {
assert.ok($('.emoji-modal').length === 0);
assert.equal(this.get('value'), 'hello world.:grinning:');
});
andThen(() => {
assert.ok($('.emoji-modal').length === 0);
assert.equal(this.get('value'), 'hello world.:grinning:');
});
}
});