-
-
Save kaliop-lblois/7bdf1bea5100f777ed9de5a8c69060ad to your computer and use it in GitHub Desktop.
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 { | |
| Box, | |
| Button, | |
| Combobox, | |
| ComboboxOption, | |
| Dialog, | |
| DialogBody, | |
| DialogFooter, | |
| SingleSelect, | |
| SingleSelectOption | |
| } from "@strapi/design-system" | |
| import { useEffect, useMemo, useState } from "react" | |
| import pluginId from "../pluginId" | |
| import { useContentSearch } from "../hooks/useContentSearch" | |
| import { useContentTypes } from "../hooks/useContentTypes" | |
| export function ContentModal() { | |
| const [editor, setEditor] = useState<any>() | |
| const visible = useMemo(() => editor !== undefined, [editor]) | |
| const closeModal = () => setEditor(undefined) | |
| const contentTypes = useContentTypes() | |
| const { contentType, setContentType, search, setSearch, searchResults, setContentId, contentId } = useContentSearch() | |
| useEffect(() => { | |
| document.addEventListener(pluginId, (event) => { | |
| // @ts-ignore | |
| setEditor(event.detail.editor) | |
| }) | |
| }, []) | |
| const onApply = () => { | |
| editor.commands.get(pluginId).execute({ | |
| "data-plugin": pluginId, | |
| "data-content-type": contentType, | |
| "data-content-id": contentId, | |
| style: "color: #006096" | |
| }) | |
| setEditor(undefined) | |
| } | |
| return ( | |
| <Dialog onClose={closeModal} title="Pick your content" isOpen={visible}> | |
| <DialogBody> | |
| <Box> | |
| <Box marginBottom={2}> | |
| <SingleSelect value={contentType} onChange={setContentType} label="Content type" placeholder="Article..."> | |
| {contentTypes.map((contentType) => ( | |
| <SingleSelectOption value={contentType.uid}>{contentType.info.displayName}</SingleSelectOption> | |
| ))} | |
| </SingleSelect> | |
| </Box> | |
| <Box marginBottom={2}> | |
| <Combobox | |
| label="Content title" | |
| disabled={!contentType} | |
| value={search} | |
| onInputChange={(event: any) => { | |
| const value = event.target.value | |
| if (value) { | |
| setSearch(value) | |
| } | |
| }} | |
| onChange={setContentId} | |
| > | |
| {(searchResults.hits || []).map((hit: any) => ( | |
| // @ts-ignore | |
| <ComboboxOption value={hit.id}>{hit[searchResults.attribute]}</ComboboxOption> | |
| ))} | |
| </Combobox> | |
| </Box> | |
| <Button disabled={!contentType && !contentId} onClick={onApply}> | |
| Apply | |
| </Button> | |
| </Box> | |
| </DialogBody> | |
| <DialogFooter | |
| startAction={ | |
| <Button onClick={closeModal} variant="tertiary"> | |
| Close | |
| </Button> | |
| } | |
| /> | |
| </Dialog> | |
| ) | |
| } |
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 pluginId from "../pluginId" | |
| const STRAPI_SVG = `<svg width="600" height="600" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M0 208C0 109.948 0 60.9218 30.4609 30.4609C60.9218 0 109.948 0 208 0H392C490.052 0 539.078 0 569.539 30.4609C600 60.9218 600 109.948 600 208V392C600 490.052 600 539.078 569.539 569.539C539.078 600 490.052 600 392 600H208C109.948 600 60.9218 600 30.4609 569.539C0 539.078 0 490.052 0 392V208Z" fill="#4945FF"/> | |
| <path fill-rule="evenodd" clip-rule="evenodd" d="M414 182H212V285H315V388H418V186C418 183.791 416.209 182 414 182Z" fill="white"/> | |
| <rect x="311" y="285" width="4" height="4" fill="white"/> | |
| <path d="M212 285H311C313.209 285 315 286.791 315 289V388H216C213.791 388 212 386.209 212 384V285Z" fill="#9593FF"/> | |
| <path d="M315 388H418L318.414 487.586C317.154 488.846 315 487.953 315 486.172V388Z" fill="#9593FF"/> | |
| <path d="M212 285H113.828C112.046 285 111.154 282.846 112.414 281.586L212 182V285Z" fill="#9593FF"/> | |
| </svg> | |
| ` | |
| export function EntryLink(editor: any) { | |
| // @ts-ignore | |
| const toolbarButton = new window.CKEditor5.ui.ButtonView() | |
| toolbarButton.set({ label: "Strapi entry link", icon: STRAPI_SVG }) | |
| editor.conversion.for("upcast").elementToAttribute({ | |
| view: { | |
| name: "span", | |
| attributes: { | |
| "data-plugin": true, | |
| "data-content-type": true, | |
| "data-content-id": true | |
| }, | |
| styles: { color: true } | |
| }, | |
| model: { | |
| key: pluginId, | |
| consumeName: true, | |
| value: (viewElement: any) => { | |
| const plugin = viewElement.getAttribute("data-plugin") | |
| if (plugin !== pluginId) return null | |
| const contentType = viewElement.getAttribute("data-content-type") | |
| const contentId = viewElement.getAttribute("data-content-id") | |
| const style = viewElement.getAttribute("style") | |
| return { | |
| "data-plugin": plugin, | |
| "data-content-type": contentType, | |
| "data-content-id": contentId, | |
| style | |
| } | |
| } | |
| }, | |
| converterPriority: "high" | |
| }) | |
| editor.conversion.for("downcast").attributeToElement({ | |
| model: pluginId, | |
| view: (modelAttributeValue: any, { writer }: any) => { | |
| if (!modelAttributeValue) return | |
| return writer.createAttributeElement("span", modelAttributeValue, { priority: 7 }) | |
| } | |
| }) | |
| editor.model.schema.extend("$text", { allowAttributes: pluginId }) | |
| editor.commands.add(pluginId, generateCommand(editor)) | |
| editor.ui.componentFactory.add(pluginId, () => toolbarButton) | |
| editor.on("ready", () => { | |
| if (!toolbarButton.element) return | |
| toolbarButton.element.addEventListener("click", () => { | |
| const { selection } = editor.model.document | |
| if (selection.hasAttribute(pluginId)) { | |
| return editor.model.change((writer: any) => { | |
| const ranges = selection.getRanges() | |
| for (const range of ranges) { | |
| writer.removeAttribute(pluginId, range) | |
| } | |
| }) | |
| } | |
| const event = new CustomEvent(pluginId, { detail: { editor } }) | |
| document.dispatchEvent(event) | |
| }) | |
| }) | |
| } | |
| function generateCommand(editor: any) { | |
| return { | |
| execute: (attributes: any) => { | |
| const { selection } = editor.model.document | |
| editor.model.change((writer: any) => { | |
| if (selection.hasAttribute(pluginId)) { | |
| const ranges = selection.getRanges() | |
| for (const range of ranges) { | |
| writer.removeAttribute(pluginId, range) | |
| } | |
| } else { | |
| const range = selection.getFirstRange() | |
| writer.setAttribute(pluginId, attributes, range) | |
| } | |
| }) | |
| }, | |
| destroy: () => {} | |
| } | |
| } |
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 { Strapi } from "@strapi/strapi" | |
| import { StrapiContentType } from "../../interfaces" | |
| export default ({ strapi }: { strapi: Strapi }) => ({ | |
| getContentTypesDefinition() { | |
| return Object.values(strapi.contentTypes).filter((contentType) => | |
| // @ts-ignore | |
| contentType.uid.startsWith("api::") | |
| ) as StrapiContentType[] | |
| }, | |
| async searchContent(contentTypeUid: string, search: string, locale: string) { | |
| const contentTypes = this.getContentTypesDefinition() as StrapiContentType[] | |
| const contentType = contentTypes.find((contentType) => contentType.uid === contentTypeUid) | |
| if (!contentType) return { attribute: null, hits: [] } | |
| const searchableAttribute = getSearchableAttribute(contentType) | |
| if (!searchableAttribute) return { attribute: null, hits: [] } | |
| // @ts-ignore | |
| const rawHits = await strapi.entityService.findMany(contentTypeUid, { | |
| filters: { | |
| [searchableAttribute]: { | |
| $containsi: search | |
| } | |
| }, | |
| locale | |
| }) | |
| // @ts-ignore | |
| const isDraftAndPublish = contentType.pluginOptions.draftAndPublish | |
| const hits = isDraftAndPublish ? rawHits?.filter((hit) => hit.publishDate !== null) : rawHits | |
| return { attribute: searchableAttribute, hits } | |
| }, | |
| async getContents(contents: { contentType: string; contentId: string }[]) { | |
| const items: any[] = [] | |
| for (const { contentType, contentId } of contents) { | |
| // @ts-ignore | |
| const hit = await strapi.entityService.findOne(contentType, contentId) | |
| items.push({ contentType, contentId, entry: hit }) | |
| } | |
| return items | |
| } | |
| }) | |
| function getSearchableAttribute(contentType: StrapiContentType): string | undefined { | |
| for (const attribute in contentType.attributes) { | |
| const definition = contentType.attributes[attribute] as any | |
| if (definition.type !== "string") continue | |
| return attribute | |
| } | |
| } |
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
| export function useCmsInternalLink(text: string) { | |
| const spanRegex = /<span[^>]*>(?<text>[^<]+)<\/span>/g | |
| const linkedContents = computed(() => { | |
| const definitions = (text.match(spanRegex) || []) | |
| .map((item) => ({ | |
| contentType: extractContentType(item), | |
| contentId: extractContentId(item) | |
| })) | |
| .filter((item) => item.contentId && item.contentType) as ContentDefinition[] | |
| return definitions | |
| }) | |
| const variables = computed(() => ({ contentDefinitions: linkedContents.value })) | |
| const { result } = useStrapiContentsQuery(variables) | |
| const contents = computed(() => result.value?.getStrapiContents || []) | |
| const mappedText = computed(() => { | |
| return text.replace(spanRegex, (fullMatch: string) => { | |
| const { text } = spanRegex.exec(fullMatch)?.groups || {} | |
| if (!text) return fullMatch | |
| const pluginResult = /<span[^>]*(?<attribute>data-plugin)="(?<value>\S+)"[^>]*>/g.exec(fullMatch) | |
| const contentType = extractContentType(fullMatch) | |
| const contentId = extractContentId(fullMatch) | |
| if (!pluginResult?.groups || !contentType || !contentId) return text | |
| const content = contents.value.find((item) => item?.contentId === contentId && item.contentType === contentType) | |
| if (!content) { | |
| console.warn("Issue computing internal link", fullMatch, { contentType, contentId, content }) | |
| return fullMatch | |
| } | |
| const url = useEntryLink(content.entry as MrctContent) | |
| if (!url) { | |
| console.warn("Issue computing internal link", fullMatch, { contentType, contentId, content, url }) | |
| return fullMatch | |
| } | |
| return `<a href="${url}" data-plugin="ckeditor-internal-link">${text}</a>` | |
| }) | |
| }) | |
| return mappedText | |
| } | |
| function extractContentType(item: string) { | |
| const match = /<span[^>]*(?<attribute>data-content-type)="(?<value>\S+)"[^>]*>/g.exec(item) | |
| return match?.groups?.value | |
| } | |
| function extractContentId(item: string) { | |
| const match = /<span[^>]*(?<attribute>data-content-id)="(?<value>\S+)"[^>]*>/g.exec(item) | |
| return match?.groups?.value | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment