Last active
June 25, 2023 14:17
-
-
Save NeuroNexul/2c35d6ecf11d8b7fdd68495e3282f4b7 to your computer and use it in GitHub Desktop.
Integrating CKEditor5 in Next JS: A How-To Guide
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
| :root { | |
| /* Overrides the border radius setting in the theme. */ | |
| --ck-border-radius: 4px !important; | |
| /* Overrides the default font size in the theme. */ | |
| --ck-font-size-base: 1rem !important; | |
| /* Helper variables to avoid duplication in the colors. */ | |
| --ck-custom-background: hsl(270, 1%, 29%) !important; | |
| --ck-custom-foreground: hsl(255, 3%, 18%) !important; | |
| --ck-custom-border: hsl(300, 1%, 22%) !important; | |
| --ck-custom-white: hsl(0, 0%, 100%) !important; | |
| /* -- Overrides generic colors. ------------------------------------------------------------- */ | |
| --ck-color-base-foreground: var(--ck-custom-background) !important; | |
| --ck-color-focus-border: hsl(208, 90%, 62%) !important; | |
| --ck-color-text: hsl(0, 0%, 98%) !important; | |
| --ck-color-shadow-drop: hsla(0, 0%, 0%, 0.2) !important; | |
| --ck-color-shadow-inner: hsla(0, 0%, 0%, 0.1) !important; | |
| /* -- Overrides the default .ck-button class colors. ---------------------------------------- */ | |
| --ck-color-button-default-background: var(--ck-custom-background) !important; | |
| --ck-color-button-default-hover-background: hsl(270, 1%, 22%) !important; | |
| --ck-color-button-default-active-background: hsl(270, 2%, 20%) !important; | |
| --ck-color-button-default-active-shadow: hsl(270, 2%, 23%) !important; | |
| --ck-color-button-default-disabled-background: var(--ck-custom-background) !important; | |
| --ck-color-button-on-background: var(--ck-custom-foreground) !important; | |
| --ck-color-button-on-hover-background: hsl(255, 4%, 16%) !important; | |
| --ck-color-button-on-active-background: hsl(255, 4%, 14%) !important; | |
| --ck-color-button-on-active-shadow: hsl(240, 3%, 19%) !important; | |
| --ck-color-button-on-disabled-background: var(--ck-custom-foreground) !important; | |
| --ck-color-button-action-background: hsl(168, 76%, 42%) !important; | |
| --ck-color-button-action-hover-background: hsl(168, 76%, 38%) !important; | |
| --ck-color-button-action-active-background: hsl(168, 76%, 36%) !important; | |
| --ck-color-button-action-active-shadow: hsl(168, 75%, 34%) !important; | |
| --ck-color-button-action-disabled-background: hsl(168, 76%, 42%) !important; | |
| --ck-color-button-action-text: var(--ck-custom-white) !important; | |
| --ck-color-button-save: hsl(120, 100%, 46%) !important; | |
| --ck-color-button-cancel: hsl(15, 100%, 56%) !important; | |
| /* -- Overrides the default .ck-dropdown class colors. -------------------------------------- */ | |
| --ck-color-dropdown-panel-background: var(--ck-custom-background) !important; | |
| --ck-color-dropdown-panel-border: var(--ck-custom-foreground) !important; | |
| /* -- Overrides the default .ck-splitbutton class colors. ----------------------------------- */ | |
| --ck-color-split-button-hover-background: var(--ck-color-button-default-hover-background) !important; | |
| --ck-color-split-button-hover-border: var(--ck-custom-foreground) !important; | |
| /* -- Overrides the default .ck-input class colors. ----------------------------------------- */ | |
| --ck-color-input-background: var(--ck-custom-background) !important; | |
| --ck-color-input-border: hsl(257, 3%, 43%) !important; | |
| --ck-color-input-text: hsl(0, 0%, 98%) !important; | |
| --ck-color-input-disabled-background: hsl(255, 4%, 21%) !important; | |
| --ck-color-input-disabled-border: hsl(250, 3%, 38%) !important; | |
| --ck-color-input-disabled-text: hsl(0, 0%, 78%) !important; | |
| /* -- Overrides the default .ck-labeled-field-view class colors. ---------------------------- */ | |
| --ck-color-labeled-field-label-background: var(--ck-custom-background) !important; | |
| /* -- Overrides the default .ck-list class colors. ------------------------------------------ */ | |
| --ck-color-list-background: var(--ck-custom-background) !important; | |
| --ck-color-list-button-hover-background: var(--ck-color-base-foreground) !important; | |
| --ck-color-list-button-on-background: var(--ck-color-base-active) !important; | |
| --ck-color-list-button-on-background-focus: var(--ck-color-base-active-focus) !important; | |
| --ck-color-list-button-on-text: var(--ck-color-base-background) !important; | |
| /* -- Overrides the default .ck-balloon-panel class colors. --------------------------------- */ | |
| --ck-color-panel-background: var(--ck-custom-background) !important; | |
| --ck-color-panel-border: var(--ck-custom-border) !important; | |
| /* -- Overrides the default .ck-toolbar class colors. --------------------------------------- */ | |
| --ck-color-toolbar-background: var(--ck-custom-background) !important; | |
| --ck-color-toolbar-border: var(--ck-custom-border) !important; | |
| /* -- Overrides the default .ck-tooltip class colors. --------------------------------------- */ | |
| --ck-color-tooltip-background: hsl(252, 7%, 14%) !important; | |
| --ck-color-tooltip-text: hsl(0, 0%, 93%) !important; | |
| /* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */ | |
| --ck-color-image-caption-background: hsl(0, 0%, 97%) !important; | |
| --ck-color-image-caption-text: hsl(0, 0%, 20%) !important; | |
| /* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */ | |
| --ck-color-widget-blurred-border: hsl(0, 0%, 87%) !important; | |
| --ck-color-widget-hover-border: hsl(43, 100%, 68%) !important; | |
| --ck-color-widget-editable-focus-background: var(--ck-custom-white) !important; | |
| /* -- Overrides the default colors used by the ckeditor5-link package. ---------------------- */ | |
| --ck-color-link-default: hsl(190, 100%, 75%) !important; | |
| } | |
| .customEditor { | |
| color-scheme: dark; | |
| } | |
| .ck-editor__editable { | |
| border: none !important; | |
| box-shadow: none !important; | |
| border-bottom: 1px solid rgb(var(--border-primary-color)) !important; | |
| } | |
| .ck.ck-editor__top.ck-reset_all { | |
| position: sticky !important; | |
| top: 0 !important; | |
| z-index: 1000 !important; | |
| } | |
| .ck-button { | |
| cursor: pointer !important | |
| } | |
| .ck-button.ck-disabled { | |
| cursor: default !important; | |
| } | |
| .ck.ck-button.ck-on, | |
| a.ck.ck-button.ck-on { | |
| color: #35ffb4 !important; | |
| background-color: hsl(270deg 1% 22%) !important; | |
| } | |
| .ck.ck-list__item .ck-button:hover:not(.ck-disabled) { | |
| background-color: var(--ck-color-button-default-hover-background) !important | |
| } |
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
| // @ts-ignore | |
| import DOMPurify from 'dompurify'; | |
| import { customPostItemRenderer, customUserItemRenderer, getPosts, getUsers, MentionLinksPlugin } from './mention'; | |
| var re_weburl = new RegExp( | |
| "^" + | |
| // protocol identifier (optional) | |
| // short syntax // still required | |
| "(?:(?:(?:https?|ftp):)?\\/\\/)" + | |
| // user:pass BasicAuth (optional) | |
| "(?:\\S+(?::\\S*)?@)?" + | |
| "(?:" + | |
| // IP address exclusion | |
| // private & local networks | |
| "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + | |
| "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + | |
| "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" + | |
| // IP address dotted notation octets | |
| // excludes loopback network 0.0.0.0 | |
| // excludes reserved space >= 224.0.0.0 | |
| // excludes network & broadcast addresses | |
| // (first & last IP address of each class) | |
| "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + | |
| "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + | |
| "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + | |
| "|" + | |
| // host & domain names, may end with dot | |
| // can be replaced by a shortest alternative | |
| // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ | |
| "(?:" + | |
| "(?:" + | |
| "[a-z0-9\\u00a1-\\uffff]" + | |
| "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + | |
| ")?" + | |
| "[a-z0-9\\u00a1-\\uffff]\\." + | |
| ")+" + | |
| // TLD identifier name, may end with dot | |
| "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + | |
| ")" + | |
| // port number (optional) | |
| "(?::\\d{2,5})?" + | |
| // resource path (optional) | |
| "(?:[/?#]\\S*)?" + | |
| "$", "i" | |
| ); | |
| /** | |
| * | |
| * @param {*} props | |
| * @returns {import("@types/ckeditor__ckeditor5-core/src/editor/editorconfig").EditorConfig} | |
| */ | |
| export default function EditorToolbarConfig(props: any) { | |
| var _code_languages = props.languages; | |
| return { | |
| language: 'en', | |
| tabSpaces: 4, | |
| extraPlugins: [ | |
| ...(props.extraPlugins || []), | |
| MentionLinksPlugin | |
| ], | |
| toolbar: { | |
| items: [ | |
| 'heading', | |
| 'style', | |
| '|', | |
| 'bold', | |
| 'italic', | |
| 'underline', | |
| 'strikethrough', | |
| 'link', | |
| 'blockQuote', | |
| 'subscript', | |
| 'superscript', | |
| 'removeFormat', | |
| '|', | |
| 'fontBackgroundColor', | |
| 'fontColor', | |
| 'fontFamily', | |
| 'fontSize', | |
| 'highlight', | |
| '|', | |
| 'alignment', | |
| 'indent', | |
| 'outdent', | |
| 'numberedList', | |
| 'bulletedList', | |
| 'todoList', | |
| 'insertTable', | |
| '|', | |
| 'imageInsert', | |
| 'mediaEmbed', | |
| '|', | |
| 'code', | |
| 'codeBlock', | |
| '|', | |
| 'findAndReplace', | |
| 'horizontalLine', | |
| 'htmlEmbed', | |
| 'pageBreak', | |
| 'specialCharacters', | |
| 'restrictedEditingException', | |
| 'selectAll', | |
| 'sourceEditing', | |
| '|', | |
| 'textPartLanguage', | |
| '|', | |
| 'undo', | |
| 'redo' | |
| ] | |
| }, | |
| heading: { | |
| options: [ | |
| { model: 'paragraph', title: 'Paragraph Text', class: 'ck-heading_paragraph' }, | |
| { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' }, | |
| { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }, | |
| { model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }, | |
| { model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }, | |
| { model: 'heading5', view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' }, | |
| { model: 'heading6', view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' }, | |
| ] | |
| }, | |
| fontFamily: { | |
| supportAllValues: true | |
| }, | |
| fontSize: { | |
| // options: ['8rem', '10rem', '12rem', '14rem', 'default', '18rem', '20rem', '22rem', '30rem', '32rem'], | |
| options: ['0.5', '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '2.0'].map(val => ({ | |
| model: val, | |
| title: `x${val}`, | |
| view: { | |
| name: 'span', | |
| styles: { | |
| 'font-size': `${val}rem` | |
| } | |
| } | |
| })) | |
| // supportAllValues: true | |
| }, | |
| fontColor: { | |
| colors, | |
| columns: 10, | |
| documentColors: 20, | |
| }, | |
| fontBackgroundColor: { | |
| colors, | |
| columns: 10, | |
| documentColors: 20, | |
| }, | |
| image: { | |
| toolbar: [ | |
| 'imageStyle:inline', | |
| 'imageStyle:wrapText', | |
| 'imageStyle:alignCenter', | |
| 'imageStyle:breakText', | |
| 'imageStyle:side', | |
| 'imageStyle:sideLeft', | |
| '|', | |
| 'imageResize:25', | |
| 'imageResize:50', | |
| 'imageResize:75', | |
| 'imageResize:original', | |
| '|', | |
| 'imageTextAlternative', | |
| 'toggleImageCaption', | |
| 'linkImage' | |
| ], | |
| resizeUnit: '%', | |
| resizeOptions: [ | |
| { | |
| name: 'imageResize:original', | |
| value: null, | |
| icon: 'original' | |
| }, | |
| { | |
| name: 'imageResize:25', | |
| value: '25', | |
| icon: 'small' | |
| }, | |
| { | |
| name: 'imageResize:50', | |
| value: '50', | |
| icon: 'medium' | |
| }, | |
| { | |
| name: 'imageResize:75', | |
| value: '75', | |
| icon: 'large' | |
| } | |
| ], | |
| }, | |
| link: { | |
| toggleDownloadable: { | |
| mode: 'manual', | |
| label: 'Downloadable', | |
| attributes: { | |
| download: 'file' | |
| } | |
| }, | |
| decorators: { | |
| openInNewTab: { | |
| mode: 'manual', | |
| label: 'Open in a new tab', | |
| defaultValue: true, // This option will be selected by default. | |
| attributes: { | |
| target: '_blank', | |
| rel: 'noopener noreferrer' | |
| } | |
| } | |
| } | |
| }, | |
| table: { | |
| contentToolbar: [ | |
| 'tableColumn', | |
| 'tableRow', | |
| 'mergeTableCells', | |
| 'tableCellProperties', | |
| 'tableProperties', | |
| 'toggleTableCaption' | |
| ] | |
| }, | |
| mediaEmbed: { | |
| toolbar: [ | |
| 'mediaEmbed', | |
| ], | |
| extraProviders: [ | |
| { | |
| name: 'All', | |
| // A URL regexp or an array of URL regexps: | |
| url: re_weburl, | |
| // To be defined only if the media are previewable: | |
| html: (match: any) => { | |
| return ( | |
| '<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' + | |
| `<iframe src="${match[0]}" ` + | |
| 'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' + | |
| 'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' + | |
| '</iframe>' + | |
| '</div>' | |
| ); | |
| } | |
| }, | |
| ], | |
| previewsInData: true | |
| }, | |
| codeBlock: { | |
| languages: _code_languages.map((_language: any) => { | |
| return { | |
| language: _language, | |
| label: _language === "cs" ? "c#" : _language.toUpperCase() | |
| }; | |
| }), | |
| }, | |
| mention: { | |
| feeds: [ | |
| ...props.mentionFeeds || [], | |
| { | |
| marker: '@', | |
| feed: getUsers, | |
| itemRenderer: customUserItemRenderer | |
| }, | |
| { | |
| marker: '#', | |
| feed: getPosts, | |
| itemRenderer: customPostItemRenderer | |
| }, | |
| ], | |
| }, | |
| htmlEmbed: { | |
| showPreviews: true, | |
| sanitizeHtml: (inputHtml: any) => { | |
| // Strip unsafe elements and attributes, e.g.: | |
| // the `<script>` elements and `on*` attributes. | |
| const purify = DOMPurify(window); | |
| const outputHtml = purify.sanitize(inputHtml, { | |
| ADD_TAGS: ["NOTE"] | |
| }); | |
| // const outputHtml = sanitizeHtml(inputHtml); | |
| return { | |
| html: outputHtml, | |
| // true or false depending on whether the sanitizer stripped anything. | |
| hasChanged: true | |
| }; | |
| } | |
| }, | |
| style: { | |
| definitions: [ | |
| { | |
| name: 'Info tag', | |
| element: 'div', | |
| classes: ['info-tag'] | |
| } | |
| ], | |
| }, | |
| } | |
| }; | |
| const colors = [ | |
| // Black | |
| ...colorRow(0, 0, 10, true), | |
| ...colorRow(0, 100, 10), | |
| ...colorRow(30, 100, 10), | |
| ...colorRow(60, 100, 10), | |
| ...colorRow(90, 100, 10), | |
| ...colorRow(120, 100, 10), | |
| ...colorRow(150, 100, 10), | |
| ...colorRow(180, 100, 10), | |
| ...colorRow(210, 100, 10), | |
| ...colorRow(240, 100, 10), | |
| ...colorRow(270, 100, 10), | |
| ...colorRow(300, 100, 10), | |
| ...colorRow(330, 100, 10), | |
| ...colorRow(360, 100, 10), | |
| ]; | |
| function colorRow(h: number, s: number, row: number, isBlack?: boolean) { | |
| const palette = []; | |
| for (let i = 0; i < row; i++) { | |
| let index = isBlack ? i : (i + 1); | |
| let r = isBlack ? (row - 1) : (row + 1); | |
| palette.push({ | |
| color: `hsl(${h}, ${s}%, ${100 / r * index}%)`, | |
| label: `hsl(${h}, ${s}%, ${100 / r * index}%)`, | |
| hasBorder: true | |
| }); | |
| } | |
| return palette; | |
| } |
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 client'; // Necessary for next js to handle this component in client side only. | |
| import React from 'react' | |
| import { CKEditor } from '@ckeditor/ckeditor5-react'; | |
| // @ts-ignore | |
| import CustomEditor from 'ckeditor5-custom-build/build/ckeditor'; | |
| import EditorConfig from '@/components/editor/editor/utils/editor-toolbar.config'; | |
| import prismComponents from "prismjs/components"; | |
| import axios from 'axios'; | |
| // Collect all languages from prismjs | |
| const languages = ["plane", ...Object.keys(prismComponents.languages).filter(e => ![ | |
| "meta", | |
| "django" | |
| ].includes(e)).sort()]; | |
| type Props = { | |
| data: string; | |
| } | |
| /** | |
| * Create Custom Editor Component. | |
| */ | |
| export default function Editor(props: Props) { | |
| // Get editor config from editor-toolbar.config.ts | |
| const editorConfig = EditorConfig({ ...props, languages: (languages) }); | |
| const [isEditorReady, setIsEditorReady] = React.useState(false); | |
| const [isSaving, setIsSaving] = React.useState(false); | |
| const [data, setData] = React.useState(props.data); | |
| async function Save() { | |
| // handle data | |
| } | |
| return ( | |
| <div className={`relative z-50`}> | |
| <div className={`w-full p-2`}> | |
| <button | |
| onClick={Save} | |
| className={`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-70`} | |
| disabled={isSaving}> | |
| {isSaving ? "Saving..." : "Save"} | |
| </button> | |
| </div> | |
| <CKEditor | |
| editor={CustomEditor} | |
| data={props.data} | |
| config={editorConfig} | |
| id={"editor"} | |
| onReady={() => setIsEditorReady(true)} | |
| onChange={(event, editor) => { | |
| const data = editor.getData(); | |
| setData(data); | |
| }} | |
| /> | |
| </div> | |
| ) | |
| } |
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
| import axios from "axios"; | |
| export function getUsers(query: any) { | |
| return new Promise(resolve => { | |
| axios.post("/api/global/find", { query, usersOnly: true }).then(res => { | |
| const newData = res.data.users.map((user: any) => { | |
| return { | |
| ...user, | |
| id: `@${user.name}`, | |
| link: `/u/${user._id}`, | |
| } | |
| }); | |
| resolve(newData); | |
| }); | |
| }); | |
| } | |
| /* | |
| * This plugin customizes the way mentions are handled in the editor model and data. | |
| * Instead of a classic <span class="mention"></span>, | |
| */ | |
| export function MentionLinksPlugin(editor: any) { | |
| editor.conversion.for('upcast').elementToAttribute({ | |
| view: { | |
| name: 'a', | |
| key: 'data-mention', | |
| classes: 'mention', | |
| attributes: { | |
| href: true, | |
| // For user | |
| 'data-user-id': true, | |
| 'data-user-name': true, | |
| 'data-user-email': true, | |
| 'data-user-image': true, | |
| 'data-user-bio': true, | |
| // For posts | |
| 'data-post-id': true, | |
| 'data-post-title': true, | |
| 'data-post-subTitle': true, | |
| 'data-post-banner': true, | |
| } | |
| }, | |
| model: { | |
| key: 'mention', | |
| value: (viewItem: any) => { | |
| const mentionAttribute = editor.plugins.get('Mention').toMentionAttribute(viewItem, { | |
| // Add any other properties that you need. | |
| link: viewItem.getAttribute('href'), | |
| // For user | |
| userId: viewItem.getAttribute('data-user-id'), | |
| name: viewItem.getAttribute('data-user-name'), | |
| email: viewItem.getAttribute('data-user-email'), | |
| image: viewItem.getAttribute('data-user-image'), | |
| bio: viewItem.getAttribute('data-user-bio'), | |
| // For posts | |
| postId: viewItem.getAttribute('data-post-id'), | |
| title: viewItem.getAttribute('data-post-title'), | |
| subTitle: viewItem.getAttribute('data-post-subTitle'), | |
| banner: viewItem.getAttribute('data-post-banner'), | |
| }); | |
| return mentionAttribute; | |
| } | |
| }, | |
| converterPriority: 'high' | |
| }); | |
| editor.conversion.for('downcast').attributeToElement({ | |
| model: 'mention', | |
| view: (modelAttributeValue: any, { writer }: { writer: any }) => { | |
| // Do not convert empty attributes (lack of value means no mention). | |
| if (!modelAttributeValue) { | |
| return; | |
| } | |
| return writer.createAttributeElement('a', { | |
| class: 'mention', | |
| 'data-user-id': modelAttributeValue.id, | |
| 'data-mention': modelAttributeValue.email, | |
| 'data-user-name': modelAttributeValue.name, | |
| 'data-user-email': modelAttributeValue.email, | |
| 'data-user-image': modelAttributeValue.image, | |
| 'data-user-bio': modelAttributeValue.bio, | |
| 'data-post-id': modelAttributeValue.id, | |
| 'data-post-title': modelAttributeValue.title, | |
| 'data-post-subTitle': modelAttributeValue.subTitle, | |
| 'data-post-banner': modelAttributeValue.banner, | |
| href: modelAttributeValue.link | |
| }, { | |
| // Make mention attribute to be wrapped by other attribute elements. | |
| priority: 20, | |
| // Prevent merging mentions together. | |
| id: modelAttributeValue._id | |
| }); | |
| }, | |
| converterPriority: 'high' | |
| }); | |
| } | |
| export function customUserItemRenderer(item: any) { | |
| const parentElement = document.createElement('span'); | |
| parentElement.classList.add('custom-item'); | |
| parentElement.id = `mention-list-item-id-${item.email}`; | |
| // Style | |
| parentElement.style.display = 'block'; | |
| parentElement.style.padding = '5px'; | |
| parentElement.style.position = 'relative'; | |
| const avatarElement = document.createElement('img'); | |
| avatarElement.classList.add('avatar'); | |
| avatarElement.alt = item.name; | |
| avatarElement.referrerPolicy = 'no-referrer'; | |
| // Style | |
| avatarElement.style.width = '40px'; | |
| avatarElement.style.height = '40px'; | |
| avatarElement.style.borderRadius = '50%'; | |
| avatarElement.style.position = 'relative'; | |
| avatarElement.style.marginRight = '5px'; | |
| avatarElement.style.display = 'none'; | |
| // Manage Image | |
| const avatarAltElement = document.createElement('div'); | |
| avatarAltElement.classList.add('avatar'); | |
| avatarAltElement.innerText = String(item.name)[0]; | |
| // Style | |
| avatarAltElement.style.width = '40px'; | |
| avatarAltElement.style.height = '40px'; | |
| avatarAltElement.style.borderRadius = '50%'; | |
| avatarAltElement.style.position = 'relative'; | |
| avatarAltElement.style.marginRight = '5px'; | |
| avatarAltElement.style.display = 'inline-grid'; | |
| avatarAltElement.style.placeItems = 'center'; | |
| avatarAltElement.style.fontSize = '20px'; | |
| avatarAltElement.style.backgroundColor = `#${((1 << 24) * Math.random() | 0).toString(16)}`; | |
| // Conditions | |
| const Img = new Image(); | |
| Img.onload = (e) => { | |
| avatarElement.src = item.image; | |
| avatarAltElement.style.display = 'none'; | |
| avatarElement.style.display = 'inline-grid'; | |
| } | |
| Img.onerror = (err) => { | |
| avatarElement.style.display = 'none'; | |
| avatarAltElement.style.display = 'inline-grid'; | |
| } | |
| Img.referrerPolicy = 'no-referrer'; | |
| Img.src = item.image; | |
| // append | |
| parentElement.appendChild(avatarElement); | |
| parentElement.appendChild(avatarAltElement); | |
| const userElement = document.createElement('span'); | |
| userElement.classList.add('custom-item-username'); | |
| // Style | |
| userElement.style.display = 'inline-block'; | |
| // append | |
| parentElement.appendChild(userElement); | |
| const nameElement = document.createElement('span'); | |
| nameElement.classList.add('custom-item-name'); | |
| nameElement.innerText = item.name; | |
| // Style | |
| nameElement.style.fontSize = '14px'; | |
| nameElement.style.fontWeight = 'bold'; | |
| nameElement.style.lineHeight = '1.5'; | |
| nameElement.style.display = 'block'; | |
| // append | |
| userElement.appendChild(nameElement); | |
| const emailElement = document.createElement('span'); | |
| emailElement.classList.add('custom-item-email'); | |
| emailElement.textContent = item.email; | |
| // Style | |
| emailElement.style.fontSize = '12px'; | |
| emailElement.style.lineHeight = '1'; | |
| emailElement.style.display = 'block'; | |
| // append | |
| userElement.appendChild(emailElement); | |
| return parentElement; | |
| } | |
| export function getPosts(query: any) { | |
| return new Promise(resolve => { | |
| axios.post("/api/global/find", { query, postsOnly: true }).then(res => { | |
| const newData = res.data.posts.map((post: any) => { | |
| return { | |
| ...post, | |
| id: `#${post.title}`, | |
| link: `/post/${post.slug}`, | |
| } | |
| }); | |
| resolve(newData); | |
| }); | |
| }); | |
| } | |
| export function customPostItemRenderer(item: any) { | |
| const parentElement = document.createElement('span'); | |
| parentElement.classList.add('custom-item'); | |
| parentElement.id = `mention-list-item-id-${item.title}`; | |
| // Style | |
| parentElement.style.display = 'block'; | |
| parentElement.style.padding = '5px'; | |
| parentElement.style.position = 'relative'; | |
| const bannerElement = document.createElement('img'); | |
| bannerElement.classList.add('banner'); | |
| bannerElement.alt = item.title; | |
| bannerElement.referrerPolicy = 'no-referrer'; | |
| // Style | |
| bannerElement.style.width = '40px'; | |
| bannerElement.style.height = '40px'; | |
| bannerElement.style.borderRadius = '5px'; | |
| bannerElement.style.position = 'relative'; | |
| bannerElement.style.marginRight = '5px'; | |
| bannerElement.style.display = 'none'; | |
| // Manage Image | |
| const bannerAltElement = document.createElement('div'); | |
| bannerAltElement.classList.add('banner'); | |
| bannerAltElement.innerText = String(item.title)[0]; | |
| // Style | |
| bannerAltElement.style.width = '40px'; | |
| bannerAltElement.style.height = '40px'; | |
| bannerAltElement.style.borderRadius = '5px'; | |
| bannerAltElement.style.position = 'relative'; | |
| bannerAltElement.style.marginRight = '5px'; | |
| bannerAltElement.style.display = 'inline-grid'; | |
| bannerAltElement.style.placeItems = 'center'; | |
| bannerAltElement.style.fontSize = '20px'; | |
| bannerAltElement.style.backgroundColor = `#${((1 << 24) * Math.random() | 0).toString(16)}`; | |
| // Conditions | |
| const Img = new Image(); | |
| Img.onload = (e) => { | |
| bannerElement.src = item.banner; | |
| bannerAltElement.style.display = 'none'; | |
| bannerElement.style.display = 'inline-grid'; | |
| } | |
| Img.onerror = (err) => { | |
| bannerElement.style.display = 'none'; | |
| bannerAltElement.style.display = 'inline-grid'; | |
| } | |
| Img.referrerPolicy = 'no-referrer'; | |
| Img.src = item.banner; | |
| // append | |
| parentElement.appendChild(bannerElement); | |
| parentElement.appendChild(bannerAltElement); | |
| const userElement = document.createElement('span'); | |
| userElement.classList.add('custom-item-username'); | |
| // Style | |
| userElement.style.display = 'inline-block'; | |
| // append | |
| parentElement.appendChild(userElement); | |
| const titleElement = document.createElement('span'); | |
| titleElement.classList.add('custom-item-title'); | |
| titleElement.innerText = item.title; | |
| // Style | |
| titleElement.style.fontSize = '14px'; | |
| titleElement.style.fontWeight = 'bold'; | |
| titleElement.style.lineHeight = '1.5'; | |
| titleElement.style.display = 'block'; | |
| // append | |
| userElement.appendChild(titleElement); | |
| const subTitle = document.createElement('span'); | |
| subTitle.classList.add('custom-item-sub-title'); | |
| subTitle.textContent = item.subTitle; | |
| // Style | |
| subTitle.style.fontSize = '12px'; | |
| subTitle.style.lineHeight = '1'; | |
| subTitle.style.display = 'block'; | |
| // append | |
| userElement.appendChild(subTitle); | |
| return parentElement; | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment