mirror of
https://github.com/bonfire-networks/bonfire-app.git
synced 2024-05-18 00:52:40 +00:00
541 lines
17 KiB
JavaScript
541 lines
17 KiB
JavaScript
/*
|
|
This file was generated by the Surface compiler.
|
|
*/
|
|
|
|
import { defaultValueCtx, editorViewOptionsCtx, Editor, editorViewCtx, commandsCtx, rootCtx } from '@milkdown/core';
|
|
import { $prose, replaceAll, insert} from '@milkdown/utils';
|
|
import { commonmark, wrapInHeadingCommand, toggleStrongCommand, toggleEmphasisCommand, remarkInlineLinkPlugin } from '@milkdown/preset-commonmark';
|
|
import {gfm} from "@milkdown/preset-gfm";
|
|
import { emoji } from '@milkdown/plugin-emoji';
|
|
import { listener, listenerCtx } from '@milkdown/plugin-listener';
|
|
import { SlashProvider } from '@milkdown/plugin-slash'
|
|
import { slashFactory } from '@milkdown/plugin-slash';
|
|
import { gemoji } from "gemoji";
|
|
import { clipboard } from '@milkdown/plugin-clipboard';
|
|
import { createPopup } from '@picmo/popup-picker';
|
|
|
|
import { Plugin, PluginKey } from '@milkdown/prose/state';
|
|
import { Decoration, DecorationSet } from '@milkdown/prose/view';
|
|
|
|
const PlaceholderPlugin = new Plugin({
|
|
key: new PluginKey('milkdown-placeholder'),
|
|
props: {
|
|
decorations: (state) => {
|
|
const element = document.createElement('span')
|
|
|
|
element.classList.add('milkdown-placeholder')
|
|
element.style.position = "absolute";
|
|
element.style.opacity = "0.5";
|
|
element.innerText = "Write something...";
|
|
|
|
const placeholderDecoration = Decoration.widget(0, element, { key: 'milkdown-placeholder', side: 0 });
|
|
if (state.doc.textContent.trim().length === 0) {
|
|
return DecorationSet.create(state.doc, [placeholderDecoration])
|
|
}
|
|
}
|
|
}
|
|
});
|
|
const placeholder = $prose(() => PlaceholderPlugin);
|
|
|
|
|
|
const MIN_PREFIX_LENGTH = 2
|
|
const VALID_CHARS = '[\\w\\+_\\-:]'
|
|
const MENTION_PREFIX = '(?:@)'
|
|
const EMOJI_PREFIX = '(?::)'
|
|
const MENTION_REGEX = new RegExp(`(?:\\s|^)(${MENTION_PREFIX}${VALID_CHARS}{${MIN_PREFIX_LENGTH},})$`)
|
|
const EMOJI_REGEX = new RegExp(`(?:\\s|^)(${EMOJI_PREFIX}${VALID_CHARS}{${MIN_PREFIX_LENGTH},})$`)
|
|
|
|
|
|
import '@milkdown/theme-nord/style.css';
|
|
|
|
const markdown = ``
|
|
|
|
|
|
function mentionsPluginView(view) {
|
|
const content = document.createElement('ul');
|
|
content.tabIndex = 1;
|
|
|
|
content.className = 'm-0 p-0 menu w-72 bg-base-100 shadow-lg ring-2';
|
|
let list = ''
|
|
|
|
const provider = new SlashProvider({
|
|
content,
|
|
shouldShow: (view, prevState) => {
|
|
// get the current content of the editor
|
|
const { state } = view;
|
|
const { doc } = state;
|
|
const currentText = doc.textContent;
|
|
|
|
if (currentText === '') {
|
|
return false;
|
|
}
|
|
|
|
|
|
const mentions = currentText.match(MENTION_REGEX)
|
|
|
|
// Display the menu if the last character is `@` followed by 2 chars.
|
|
if (mentions) {
|
|
// get the characters that follows the `@` in currentText
|
|
const text = mentions[1].split('@').pop()
|
|
|
|
return getFeedItems(text, '@').then(res => {
|
|
list = ''
|
|
if (res.length > 0) {
|
|
// Add max 4 items to the menu
|
|
let maxItems = 4
|
|
for (let i = 0; i < res.length && i < maxItems; i++) {
|
|
list += mentionItemRenderer(res[i], text);
|
|
}
|
|
content.innerHTML = list
|
|
return true
|
|
} else {
|
|
content.innerHTML = ''
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
return false;
|
|
},
|
|
trigger: '@',
|
|
});
|
|
|
|
return {
|
|
update: (updatedView, prevState) => {
|
|
provider.update(updatedView, prevState);
|
|
},
|
|
destroy: () => {
|
|
provider.destroy();
|
|
content.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function emojisPluginView() {
|
|
const content = document.createElement('ul');
|
|
content.tabIndex = 1;
|
|
|
|
content.className = 'm-0 p-0 menu w-72 bg-base-100 shadow-lg ring-2';
|
|
let list = ''
|
|
|
|
const provider = new SlashProvider({
|
|
content,
|
|
shouldShow: (view, prevState) => {
|
|
// get the current content of the editor
|
|
const { state } = view;
|
|
const { doc } = state;
|
|
const currentText = doc.textContent;
|
|
|
|
if (currentText === '') {
|
|
return false;
|
|
}
|
|
|
|
|
|
const emojis = currentText.match(EMOJI_REGEX)
|
|
// Display the menu if the last character is `@` followed by 2 chars.
|
|
if (emojis) {
|
|
// get the characters that follows the `@` in currentText
|
|
const text = emojis[1].split(':').pop()
|
|
const index = gemoji.findIndex((emoji) => {
|
|
return emoji.names.some((name) => name.includes(text));
|
|
});
|
|
list = ''
|
|
if (index > 0) {
|
|
// Add max 4 items to the menu
|
|
gemoji
|
|
.filter((emoji) => {
|
|
return emoji.names.some((name) => name.includes(text));
|
|
})
|
|
.slice(0, 6)
|
|
.map((emoji) => {
|
|
list += emojiItemRenderer(emoji, text);
|
|
})
|
|
|
|
content.innerHTML = list
|
|
return true
|
|
} else {
|
|
content.innerHTML = ''
|
|
return false
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
trigger: ':',
|
|
});
|
|
|
|
|
|
return {
|
|
update: (updatedView, prevState) => {
|
|
provider.update(updatedView, prevState);
|
|
},
|
|
destroy: () => {
|
|
provider.destroy();
|
|
content.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// function slashPluginView(view) {
|
|
// const content = document.createElement('ul');
|
|
// content.tabIndex = 1;
|
|
|
|
// content.className = 'm-0 p-0 menu w-72 bg-base-100 shadow-lg ring-2';
|
|
// let list = slashItemRenderer()
|
|
// content.innerHTML = list
|
|
|
|
|
|
// const provider = new SlashProvider({
|
|
// content,
|
|
// trigger: '/',
|
|
// });
|
|
|
|
|
|
|
|
// return {
|
|
// update: (updatedView, prevState) => {
|
|
// provider.update(updatedView, prevState);
|
|
// },
|
|
// destroy: () => {
|
|
// provider.destroy();
|
|
// content.remove();
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
|
|
|
|
function getFeedItems(queryText, prefix) {
|
|
// console.log(prefix)
|
|
if (queryText && queryText.length > 0) {
|
|
return new Promise((resolve) => {
|
|
// this requires the bonfire_tag extension
|
|
fetch("/api/tag/autocomplete/ck5/" + prefix + "/" + queryText)
|
|
.then((response) => response.json())
|
|
.then((data) => {
|
|
console.log("data")
|
|
console.log(data)
|
|
let values = data.map((item) => ({
|
|
id: item.id,
|
|
value: item.name,
|
|
icon: item.icon
|
|
}));
|
|
resolve(values);
|
|
})
|
|
.catch((error) => {
|
|
console.error("There has been a problem with the tag search:", error);
|
|
resolve([]);
|
|
});
|
|
});
|
|
} else return [];
|
|
}
|
|
|
|
const mentionItemRenderer = (item, text) => {
|
|
return `
|
|
<li class="rounded-none">
|
|
<button type="button" data-mention="${item.id}" data-text="${text}" class="mention_btn rounded-none w-full flex items-center">
|
|
<div class="flex items-center gap-3 w-full pointer-events-none">
|
|
<div class="flex-shrink-0">
|
|
<img class="h-6 w-6 rounded-full" src="${item.icon}" alt="">
|
|
</div>
|
|
<div class="gap-0 items-start flex flex-col" data-id="${item.id}" data-input="${text}">
|
|
<div class="text-sm truncate max-w-[240px] text-base-content font-semibold">${item.value}</div>
|
|
<div class="text-xs truncate max-w-[240px] text-base-content/70 font-regular">${item.id}</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</li>`
|
|
}
|
|
|
|
const emojiItemRenderer = (item, text) => {
|
|
|
|
return `
|
|
<li class="rounded-none">
|
|
<button type="button" data-text="${text}" data-emoji=${item.emoji} class="emoji_btn gap-3 rounded-none w-full flex items-center">
|
|
<div class="pointer-events-none flex items-baseline w-full gap-2">
|
|
<span>${item.emoji}</span> <span class="truncate max-w-[220px]">:${item.names[0]}:</span>
|
|
</div>
|
|
</button>
|
|
</li>`
|
|
}
|
|
|
|
// const slashItemRenderer = () => {
|
|
// return `
|
|
// <li class="rounded-none">
|
|
// <button type="button" class="heading_btn rounded-none flex items-center gap-2">
|
|
// <span class="pointer-events-none material-symbols-outlined text-nord-10 dark:text-nord-9">
|
|
// <svg class="w-5 h-5 shrink-0 flex-1 text-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6 17q-.425 0-.713-.288T5 16V8q0-.425.288-.713T6 7q.425 0 .713.288T7 8v3h4V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v8q0 .425-.288.713T12 17q-.425 0-.713-.288T11 16v-3H7v3q0 .425-.288.713T6 17Zm12 0q-.425 0-.713-.288T17 16V9h-1q-.425 0-.713-.288T15 8q0-.425.288-.713T16 7h2q.425 0 .713.288T19 8v8q0 .425-.288.713T18 17Z"/></svg>
|
|
// </span>
|
|
// Heading
|
|
// </button>
|
|
// </li>
|
|
// <li class="rounded-none">
|
|
// <button type="button" class="bold_btn rounded-none flex items-center gap-2">
|
|
// <span class="material-symbols-outlined text-nord-10 dark:text-nord-9">
|
|
// <svg class="w-5 h-5 shrink-0 flex-1 text-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6 17q-.425 0-.713-.288T5 16V8q0-.425.288-.713T6 7q.425 0 .713.288T7 8v3h4V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v8q0 .425-.288.713T12 17q-.425 0-.713-.288T11 16v-3H7v3q0 .425-.288.713T6 17Zm12 0q-.425 0-.713-.288T17 16V9h-1q-.425 0-.713-.288T15 8q0-.425.288-.713T16 7h2q.425 0 .713.288T19 8v8q0 .425-.288.713T18 17Z"/></svg>
|
|
// </span>
|
|
// Bold
|
|
// </button>
|
|
// </li>
|
|
// <li class="rounded-none">
|
|
// <button type="button" class="italic_btn rounded-none flex items-center gap-2">
|
|
// <span class="material-symbols-outlined text-nord-10 dark:text-nord-9">
|
|
// <svg class="w-5 h-5 shrink-0 flex-1 text-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6 17q-.425 0-.713-.288T5 16V8q0-.425.288-.713T6 7q.425 0 .713.288T7 8v3h4V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v8q0 .425-.288.713T12 17q-.425 0-.713-.288T11 16v-3H7v3q0 .425-.288.713T6 17Zm12 0q-.425 0-.713-.288T17 16V9h-1q-.425 0-.713-.288T15 8q0-.425.288-.713T16 7h2q.425 0 .713.288T19 8v8q0 .425-.288.713T18 17Z"/></svg>
|
|
// </span>
|
|
// Italic
|
|
// </button>
|
|
// </li>
|
|
// <li class="rounded-none">
|
|
// <button type="button" class="link_btn rounded-none flex items-center gap-2">
|
|
// <span class="material-symbols-outlined text-nord-10 dark:text-nord-9">
|
|
// <svg class="w-5 h-5 shrink-0 flex-1 text-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6 17q-.425 0-.713-.288T5 16V8q0-.425.288-.713T6 7q.425 0 .713.288T7 8v3h4V8q0-.425.288-.713T12 7q.425 0 .713.288T13 8v8q0 .425-.288.713T12 17q-.425 0-.713-.288T11 16v-3H7v3q0 .425-.288.713T6 17Zm12 0q-.425 0-.713-.288T17 16V9h-1q-.425 0-.713-.288T15 8q0-.425.288-.713T16 7h2q.425 0 .713.288T19 8v8q0 .425-.288.713T18 17Z"/></svg>
|
|
// </span>
|
|
// Link
|
|
// </button>
|
|
// </li>
|
|
|
|
// <li class="rounded-none">
|
|
// <button type="button" class="divider_btn rounded-none flex items-center gap-2">
|
|
// <span class="material-symbols-outlined text-nord-10 dark:text-nord-9">
|
|
// <svg class="w-5 h-5 shrink-0 flex-1 text-info" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><path fill="currentColor" fill-rule="evenodd" d="M2 7.5a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1h-10a.5.5 0 0 1-.5-.5Z" clip-rule="evenodd"/></svg>
|
|
// </span>
|
|
// Divider
|
|
// </button>
|
|
// </li>
|
|
// `
|
|
|
|
// }
|
|
|
|
|
|
|
|
const mentionSlash = slashFactory('mentions-slash');
|
|
const emojisSlash = slashFactory('emojis-slash');
|
|
// const slash = slashFactory('slash');
|
|
let isUpdatingMarkdown = false;
|
|
|
|
const createEditor = async (_this, hidden_input, composer$) => {
|
|
const editor = await Editor
|
|
.make()
|
|
.config(ctx => {
|
|
ctx.set(rootCtx, '#editor')
|
|
ctx.set(defaultValueCtx, markdown)
|
|
ctx.set(mentionSlash.key, {
|
|
view: mentionsPluginView
|
|
})
|
|
ctx.set(emojisSlash.key, {
|
|
|
|
view: emojisPluginView
|
|
})
|
|
ctx.get(listenerCtx)
|
|
.markdownUpdated((ctx, markdown, prevMarkdown) => {
|
|
const transformedMarkdown = markdown
|
|
.replace(/!\[(.*?)\]\(.*?\)/g, '$1');
|
|
hidden_input.value = transformedMarkdown;
|
|
console.log(hidden_input.value);
|
|
const inputEvent = new Event('input', {
|
|
bubbles: true,
|
|
});
|
|
hidden_input.dispatchEvent(inputEvent);
|
|
})
|
|
ctx.update(editorViewOptionsCtx, (prev) => ({
|
|
...prev,
|
|
attributes: {
|
|
placeholder: "Type your text here...",
|
|
class: 'editor prose prose-sm h-full p-2 focus:outline-none composer w-full max-w-full',
|
|
spellcheck: 'false' },
|
|
}))
|
|
})
|
|
// .config(nord)
|
|
.use(commonmark)
|
|
.use(remarkInlineLinkPlugin)
|
|
.use(gfm)
|
|
.use(emoji)
|
|
.use(listener)
|
|
.use(mentionSlash)
|
|
.use(emojisSlash)
|
|
.use(clipboard)
|
|
.use(placeholder)
|
|
// .use(slash)
|
|
.create()
|
|
|
|
|
|
const trigger = document.querySelector('.emoji-button');
|
|
|
|
trigger.addEventListener('click', () => {
|
|
picker.toggle();
|
|
});
|
|
|
|
const picker = createPopup({}, {
|
|
referenceElement: trigger,
|
|
triggerElement: trigger,
|
|
emojiSize: '1.75rem',
|
|
className: 'z-[99999999999999999999]',
|
|
});
|
|
|
|
|
|
picker.addEventListener('emoji:select', event => {
|
|
console.log(event.emoji)
|
|
editor.action((ctx) => {
|
|
const view = ctx.get(editorViewCtx);
|
|
const { state } = view;
|
|
const { selection } = state;
|
|
view.dispatch(
|
|
view.state.tr
|
|
.insertText(event.emoji + ' ')
|
|
);
|
|
view.focus()
|
|
})
|
|
});
|
|
|
|
|
|
const submit_btn = document.getElementById('submit_btn');
|
|
const heading_btn = document.getElementById('heading_btn');
|
|
const bold_btn = document.getElementById('bold_btn');
|
|
const italic_btn = document.getElementById('italic_btn');
|
|
|
|
// const quote_btn = document.getElementById('quote_btn');
|
|
// const strike_btn = document.getElementById('strike_btn');
|
|
// const table_btn = document.getElementById('table_btn');
|
|
_this.handleEvent("smart_input:reset", ({text}) => {
|
|
editor.action(replaceAll(''))
|
|
})
|
|
|
|
// submit_btn.addEventListener('click', (e) => {
|
|
// editor.action(replaceAll(''))
|
|
// })
|
|
|
|
_this.handleEvent("mention_suggestions", ({text}) => {
|
|
// replace the current text with the text from the event
|
|
editor.action(replaceAll(''))
|
|
if (text != null) {
|
|
editor.action((ctx) => {
|
|
const view = ctx.get(editorViewCtx);
|
|
view.dispatch(
|
|
view.state.tr
|
|
.insertText(text + ' ')
|
|
);
|
|
view.focus()
|
|
})
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
heading_btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
editor.action((ctx) => {
|
|
const commandManager = ctx.get(commandsCtx);
|
|
const view = ctx.get(editorViewCtx);
|
|
commandManager.call(wrapInHeadingCommand.key, 3);
|
|
view.focus()
|
|
});
|
|
})
|
|
|
|
bold_btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
editor.action((ctx) => {
|
|
const commandManager = ctx.get(commandsCtx);
|
|
const view = ctx.get(editorViewCtx);
|
|
commandManager.call(toggleStrongCommand.key);
|
|
view.focus()
|
|
});
|
|
})
|
|
|
|
italic_btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
editor.action((ctx) => {
|
|
const commandManager = ctx.get(commandsCtx);
|
|
const view = ctx.get(editorViewCtx);
|
|
commandManager.call(toggleEmphasisCommand.key);
|
|
view.focus()
|
|
});
|
|
})
|
|
|
|
// quote_btn.addEventListener('click', (e) => {
|
|
// e.preventDefault();
|
|
// editor.action((ctx) => {
|
|
// const commandManager = ctx.get(commandsCtx);
|
|
// const view = ctx.get(editorViewCtx);
|
|
// commandManager.call(wrapInBlockquoteCommand.key);
|
|
// view.focus()
|
|
// });
|
|
// })
|
|
|
|
// strike_btn.addEventListener('click', (e) => {
|
|
// e.preventDefault();
|
|
// editor.action((ctx) => {
|
|
// const commandManager = ctx.get(commandsCtx);
|
|
// const view = ctx.get(editorViewCtx);
|
|
// commandManager.call(toggleStrikethroughCommand.key);
|
|
// view.focus()
|
|
// });
|
|
// })
|
|
|
|
// table_btn.addEventListener('click', (e) => {
|
|
// e.preventDefault();
|
|
// editor.action((ctx) => {
|
|
// const commandManager = ctx.get(commandsCtx);
|
|
// const view = ctx.get(editorViewCtx);
|
|
// commandManager.call(insertTableCommand.key);
|
|
// view.focus()
|
|
// });
|
|
// })
|
|
|
|
|
|
|
|
|
|
composer$.addEventListener('click', (e) => {
|
|
if (e.target.matches('.emoji_btn')) {
|
|
e.preventDefault();
|
|
const emoji = e.target.dataset.emoji;
|
|
const text = e.target.dataset.text
|
|
editor.action((ctx) => {
|
|
const view = ctx.get(editorViewCtx);
|
|
const { state } = view;
|
|
const { selection } = state;
|
|
view.dispatch(
|
|
view.state.tr
|
|
.delete(selection.from - text.length - 1, selection.from)
|
|
.insertText(emoji + ' ')
|
|
);
|
|
view.focus()
|
|
})
|
|
}
|
|
|
|
if (e.target.matches('.mention_btn')) {
|
|
e.preventDefault();
|
|
const mention = e.target.dataset.mention;
|
|
const text = e.target.dataset.text
|
|
editor.action((ctx) => {
|
|
const view = ctx.get(editorViewCtx);
|
|
const { state } = view;
|
|
const { selection } = state;
|
|
|
|
// Calculate the start position for deletion
|
|
const startPos = selection.from - text.length -1;
|
|
|
|
view.dispatch(
|
|
view.state.tr
|
|
.delete(startPos, selection.from)
|
|
.insertText(`${mention} ` + `\u200B ` ) // add a space character after the mention variable
|
|
);
|
|
view.focus()
|
|
})
|
|
}
|
|
})
|
|
|
|
return editor;
|
|
}
|
|
|
|
export default {
|
|
mounted() {
|
|
const hidden_input = document.getElementById('editor_hidden_input');
|
|
const composer$ = this.el.querySelector('#editor')
|
|
createEditor(this, hidden_input, composer$)
|
|
}
|
|
}
|
|
|