CKEditor – How to add dynamic custom mentions?

my goal is to type anything with # beforehand and CKEditor to highlight it as mention.

It currently works with predefined mentions but that’s not sufficient for me:

Further thing I am trying to achieve it to hyperlink it to a webpage based on the mention.

Example user types: #test

This gets mentioned and hyperlink to https://example.com/test

I have spent hours trying to work it out but can’t come to mind how could I Implement that, so even ideas would be very beneficial.

Below is the code I currently have:

Thank you in advance even if you at least give me an idea how could I move forward..

I would ideally want to use CKEditor, further alternative I thought of is to use JS to look input field change and go from there

ClassicEditor
    .create( document.querySelector( '#snippet-mention-customization' ), {
        plugins: [ Mention, MentionCustomization, /* ... */ ],
        mention: {
            dropdownLimit: 4,
            feeds: [
                {
                            marker: '#',
                            feed: getFeedItems,
                            itemRenderer: customItemRenderer,
                },
            ]
        }
    } )
    .then( editor => {
        window.editor = editor;
    } )
    .catch( err => {
        console.error( err.stack );
    } );

.

const items = [
  {
    id: "@swarley",
    userId: "1",
    name: "Barney Stinson",
    link: "https://www.imdb.com/title/tt0460649/characters/nm0000439",
  },
];

function getFeedItems(queryText) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Items:", items, Array.isArray(items)); // Check if items is an array
      const itemsToDisplay = items
        .filter((item) => isItemMatching(item, queryText))
        .map((item) => ({
          ...item,
          link: "https://example.com/search?q=" + encodeURIComponent(queryText),
        }))
        .slice(0, 10);

      resolve(itemsToDisplay);
    }, 100);
  });
}

function isItemMatching(item, queryText) {
  if (typeof queryText === "string") {
    const searchString = queryText.toLowerCase();
    return (
      item.name.toLowerCase().includes(searchString) ||
      item.id.toLowerCase().includes(searchString)
    );
  } else {
    return true;
  }
}

function customItemRenderer(item) {
  const itemElement = document.createElement("span");
  itemElement.classList.add("custom-item");
  itemElement.id = `mention-list-item-id-${item.userId}`;
  itemElement.textContent = `${item.name} (${item.link}) `;

  const linkElement = document.createElement("span");
  linkElement.classList.add("custom-item-link");
  linkElement.textContent = `[Link]`;

  itemElement.appendChild(linkElement);

  return itemElement;
}

function MentionCustomization(editor) {
  // The upcast converter will convert <a class="mention" href="" data-user-id="">
  // elements to the model 'mention' attribute.
  editor.conversion.for("upcast").elementToAttribute({
    view: {
      name: "a",
      key: "data-mention",
      classes: "mention",
      attributes: {
        href: true,
        "data-user-id": true,
      },
    },
    model: {
      key: "mention",
      value: (viewItem) => {
        // The mention feature expects that the mention attribute value
        // in the model is a plain object with a set of additional attributes.
        // In order to create a proper object, use the toMentionAttribute helper method:
        const mentionAttribute = editor.plugins
          .get("Mention")
          .toMentionAttribute(viewItem, {
            // Add any other properties that you need.
            link: viewItem.getAttribute("href"),
            userId: viewItem.getAttribute("data-user-id"),
          });

        return mentionAttribute;
      },
    },
    converterPriority: "high",
  });

  // Downcast the model 'mention' text attribute to a view <a> element.
  editor.conversion.for("downcast").attributeToElement({
    model: "mention",
    view: (modelAttributeValue, { writer }) => {
      // Do not convert empty attributes (lack of value means no mention).
      if (!modelAttributeValue) {
        return;
      }

      return writer.createAttributeElement(
        "a",
        {
          class: "mention",
          "data-mention": modelAttributeValue.id,
          "data-user-id": modelAttributeValue.userId,
          href: modelAttributeValue.link,
        },
        {
          // Make mention attribute to be wrapped by other attribute elements.
          priority: 20,
          // Prevent merging mentions together.
          id: modelAttributeValue.uid,
        }
      );
    },
    converterPriority: "high",
  });
}