2018-03-28 10:42:46 +08:00
|
|
|
import libraryLoader from "./library_loader.js";
|
2018-11-15 21:51:09 +08:00
|
|
|
import treeService from './tree.js';
|
2019-11-07 05:58:32 +08:00
|
|
|
import noteAutocompleteService from './note_autocomplete.js';
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
class NoteDetailText {
|
|
|
|
/**
|
2019-05-09 01:55:24 +08:00
|
|
|
* @param {TabContext} ctx
|
2019-05-02 04:19:29 +08:00
|
|
|
*/
|
|
|
|
constructor(ctx) {
|
2019-05-02 05:06:18 +08:00
|
|
|
this.ctx = ctx;
|
2019-05-09 01:55:24 +08:00
|
|
|
this.$component = ctx.$tabContent.find('.note-detail-text');
|
2019-07-01 01:41:26 +08:00
|
|
|
this.$editorEl = this.$component.find('.note-detail-text-editor');
|
2019-05-02 04:19:29 +08:00
|
|
|
this.textEditor = null;
|
|
|
|
|
|
|
|
this.$component.on("dblclick", "img", e => {
|
|
|
|
const $img = $(e.target);
|
|
|
|
const src = $img.prop("src");
|
|
|
|
|
|
|
|
const match = src.match(/\/api\/images\/([A-Za-z0-9]+)\//);
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
const noteId = match[1];
|
|
|
|
|
|
|
|
treeService.activateNote(noteId);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
window.open(src, '_blank');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-12 18:58:55 +08:00
|
|
|
async render() {
|
2019-05-02 04:19:29 +08:00
|
|
|
if (!this.textEditor) {
|
|
|
|
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
|
|
|
|
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
|
|
|
|
// display of $component in both branches.
|
|
|
|
this.$component.show();
|
2019-03-05 05:36:46 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
// textEditor might have been initialized during previous await so checking again
|
|
|
|
// looks like double initialization can freeze CKEditor pretty badly
|
|
|
|
if (!this.textEditor) {
|
2019-07-01 01:41:26 +08:00
|
|
|
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
|
2019-11-07 05:58:32 +08:00
|
|
|
placeholder: "Type the content of your note here ...",
|
|
|
|
mention: {
|
|
|
|
feeds: [
|
|
|
|
{
|
|
|
|
marker: '@',
|
|
|
|
feed: queryText => {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
2019-11-08 05:43:01 +08:00
|
|
|
if (rows.length === 1 && rows[0].title === 'No results') {
|
|
|
|
rows = [];
|
|
|
|
}
|
2019-11-07 05:58:32 +08:00
|
|
|
|
|
|
|
for (const row of rows) {
|
2019-11-08 05:43:01 +08:00
|
|
|
row.text = row.name = row.noteTitle;
|
|
|
|
row.id = '@' + row.text;
|
|
|
|
row.link = '#' + row.path;
|
2019-11-07 05:58:32 +08:00
|
|
|
}
|
|
|
|
|
2019-11-08 05:43:01 +08:00
|
|
|
console.log(rows.slice(0, Math.min(5, rows.length)));
|
|
|
|
|
2019-11-07 05:58:32 +08:00
|
|
|
res(rows);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
itemRenderer: item => {
|
2019-11-08 05:43:01 +08:00
|
|
|
const itemElement = document.createElement('span');
|
2019-11-07 05:58:32 +08:00
|
|
|
|
2019-11-08 05:43:01 +08:00
|
|
|
itemElement.classList.add('mentions-item');
|
|
|
|
itemElement.innerHTML = `${item.highlightedTitle} `;
|
2019-11-07 05:58:32 +08:00
|
|
|
|
|
|
|
return itemElement;
|
|
|
|
},
|
|
|
|
minimumCharacters: 0
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2019-05-02 04:19:29 +08:00
|
|
|
});
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-04 20:34:03 +08:00
|
|
|
this.onNoteChange(() => this.ctx.noteChanged());
|
2019-05-02 04:19:29 +08:00
|
|
|
}
|
2018-08-28 21:03:23 +08:00
|
|
|
}
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 05:06:18 +08:00
|
|
|
this.textEditor.isReadOnly = await this.isReadOnly();
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
this.$component.show();
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-04 06:16:41 +08:00
|
|
|
this.textEditor.setData(this.ctx.note.content);
|
2018-03-27 12:22:02 +08:00
|
|
|
}
|
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
getContent() {
|
|
|
|
let content = this.textEditor.getData();
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
// if content is only tags/whitespace (typically <p> </p>), then just make it empty
|
|
|
|
// this is important when setting new note to code
|
|
|
|
if (jQuery(content).text().trim() === '' && !content.includes("<img")) {
|
|
|
|
content = '';
|
|
|
|
}
|
2019-01-27 20:10:03 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
return content;
|
|
|
|
}
|
2019-01-27 20:10:03 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
async isReadOnly() {
|
2019-05-05 04:44:25 +08:00
|
|
|
const attributes = await this.ctx.attributes.getAttributes();
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
return attributes.some(attr => attr.type === 'label' && attr.name === 'readOnly');
|
|
|
|
}
|
2018-03-27 12:22:02 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
focus() {
|
2019-11-10 00:45:22 +08:00
|
|
|
this.$editorEl.trigger('focus');
|
2019-05-02 04:19:29 +08:00
|
|
|
}
|
2018-09-03 22:05:28 +08:00
|
|
|
|
2019-08-26 01:11:42 +08:00
|
|
|
show() {}
|
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
getEditor() {
|
|
|
|
return this.textEditor;
|
|
|
|
}
|
2018-11-15 21:51:09 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
onNoteChange(func) {
|
|
|
|
this.textEditor.model.document.on('change:data', func);
|
|
|
|
}
|
2018-11-15 21:51:09 +08:00
|
|
|
|
2019-05-02 04:19:29 +08:00
|
|
|
cleanup() {
|
|
|
|
if (this.textEditor) {
|
|
|
|
this.textEditor.setData('');
|
|
|
|
}
|
2018-11-15 21:51:09 +08:00
|
|
|
}
|
2019-05-02 04:19:29 +08:00
|
|
|
|
|
|
|
scrollToTop() {
|
|
|
|
this.$component.scrollTop(0);
|
2018-11-15 21:51:09 +08:00
|
|
|
}
|
2019-05-02 04:19:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export default NoteDetailText
|