This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/components/d-editor.js.es6

264 lines
7.2 KiB
JavaScript

import loadScript from 'discourse/lib/load-script';
import { default as property, on } from 'ember-addons/ember-computed-decorators';
import { showSelector } from "discourse/lib/emoji/emoji-toolbar";
// Our head can be a static string or a function that returns a string
// based on input (like for numbered lists).
function getHead(head, prev) {
if (typeof head === "string") {
return [head, head.length];
} else {
return getHead(head(prev));
}
}
export default Ember.Component.extend({
classNames: ['d-editor'],
ready: false,
insertLinkHidden: true,
link: '',
lastSel: null,
@on('didInsertElement')
_loadSanitizer() {
this._applyEmojiAutocomplete();
loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true));
},
@property('ready', 'value')
preview(ready, value) {
if (!ready) { return; }
const text = Discourse.Dialect.cook(value || "", {sanitize: true});
return text ? text : "";
},
_applyEmojiAutocomplete() {
if (!this.siteSettings.enable_emoji) { return; }
const container = this.container;
const template = container.lookup('template:emoji-selector-autocomplete.raw');
const self = this;
this.$('.d-editor-input').autocomplete({
template: template,
key: ":",
transformComplete(v) {
if (v.code) {
return `${v.code}:`;
} else {
showSelector({
appendTo: self.$(),
container,
onSelect: title => self._addText(`${title}:`)
});
return "";
}
},
dataSource(term) {
return new Ember.RSVP.Promise(resolve => {
const full = `:${term}`;
term = term.toLowerCase();
if (term === "") {
return resolve(["smile", "smiley", "wink", "sunny", "blush"]);
}
if (Discourse.Emoji.translations[full]) {
return resolve([Discourse.Emoji.translations[full]]);
}
const options = Discourse.Emoji.search(term, {maxResults: 5});
return resolve(options);
}).then(list => list.map(code => {
return {code, src: Discourse.Emoji.urlFor(code)};
})).then(list => {
if (list.length) {
list.push({ label: I18n.t("composer.more_emoji") });
}
return list;
});
}
});
},
_getSelected() {
if (!this.get('ready')) { return; }
const textarea = this.$('textarea.d-editor-input')[0];
let start = textarea.selectionStart;
let end = textarea.selectionEnd;
if (start === end) {
start = end = textarea.value.length;
}
const value = textarea.value.substring(start, end);
const pre = textarea.value.slice(0, start);
const post = textarea.value.slice(end);
return { start, end, value, pre, post };
},
_selectText(from, length) {
Ember.run.scheduleOnce('afterRender', () => {
const textarea = this.$('textarea.d-editor-input')[0];
textarea.focus();
textarea.selectionStart = from;
textarea.selectionEnd = textarea.selectionStart + length;
});
},
_applySurround(head, tail, exampleKey) {
const sel = this._getSelected();
const pre = sel.pre;
const post = sel.post;
const tlen = tail.length;
if (sel.start === sel.end) {
if (tlen === 0) { return; }
const [hval, hlen] = getHead(head);
const example = I18n.t(`composer.${exampleKey}`);
this.set('value', `${pre}${hval}${example}${tail}${post}`);
this._selectText(pre.length + hlen, example.length);
} else {
const lines = sel.value.split("\n");
let [hval, hlen] = getHead(head);
if (lines.length === 1 && pre.slice(-tlen) === tail && post.slice(0, hlen) === hval) {
this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`);
this._selectText(sel.start - hlen, sel.value.length);
} else {
const contents = lines.map(l => {
if (l.length === 0) { return l; }
if (l.slice(0, hlen) === hval && tlen === 0 || l.slice(-tlen) === tail) {
if (tlen === 0) {
const result = l.slice(hlen);
[hval, hlen] = getHead(head, hval);
return result;
} else if (l.slice(-tlen) === tail) {
const result = l.slice(hlen, -tlen);
[hval, hlen] = getHead(head, hval);
return result;
}
}
const result = `${hval}${l}${tail}`;
[hval, hlen] = getHead(head, hval);
return result;
}).join("\n");
this.set('value', `${pre}${contents}${post}`);
if (lines.length === 1 && tlen > 0) {
this._selectText(sel.start + hlen, contents.length - hlen - hlen);
} else {
this._selectText(sel.start, contents.length);
}
}
}
},
_applyList(head, exampleKey) {
const sel = this._getSelected();
if (sel.value.indexOf("\n") !== -1) {
this._applySurround(head, '', exampleKey);
} else {
const [hval, hlen] = getHead(head);
if (sel.start === sel.end) {
sel.value = I18n.t(`composer.${exampleKey}`);
}
const trimmedPre = sel.pre.trim();
const number = (sel.value.indexOf(hval) === 0) ? sel.value.slice(hlen) : `${hval}${sel.value}`;
const preLines = trimmedPre.length ? `${trimmedPre}\n\n` : "";
const trimmedPost = sel.post.trim();
const post = trimmedPost.length ? `\n\n${trimmedPost}` : trimmedPost;
this.set('value', `${preLines}${number}${post}`);
this._selectText(preLines.length, number.length);
}
},
_addText(text, sel) {
sel = sel || this._getSelected();
const insert = `${sel.pre}${text}`;
this.set('value', `${insert}${sel.post}`);
this._selectText(insert.length, 0);
},
actions: {
bold() {
this._applySurround('**', '**', 'bold_text');
},
italic() {
this._applySurround('*', '*', 'italic_text');
},
showLinkModal() {
this._lastSel = this._getSelected();
this.set('insertLinkHidden', false);
},
insertLink() {
const link = this.get('link');
if (Ember.isEmpty(link)) { return; }
const m = / "([^"]+)"/.exec(link);
if (m && m.length === 2) {
const description = m[1];
const remaining = link.replace(m[0], '');
this._addText(`[${description}](${remaining})`, this._lastSel);
} else {
this._addText(`[${link}](${link})`, this._lastSel);
}
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}:`)
});
}
}
});