[MGNLUI-4419] Allow RichTextFieldFactory to link to any workspace Created: 17/Apr/18  Updated: 08/Mar/21

Status: Open
Project: Magnolia UI
Component/s: forms
Affects Version/s: 5.6.4, 6.2
Fix Version/s: None

Type: Improvement Priority: Neutral
Reporter: Adrien Manzoni Assignee: Unassigned
Resolution: Unresolved Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
relation
Template:
Acceptance criteria:
Empty
Task DoD:
[ ]* Doc/release notes changes? Comment present?
[ ]* Downstream builds green?
[ ]* Solution information and context easily available?
[ ]* Tests
[ ]* FixVersion filled and not yet released
[ ]  Architecture Decision Record (ADR)
Date of First Response:

 Description   

In a RichTextField, the users have the possibility to add links to a website node or to a DAM node.

If we want to create links to another workspaces, we need to create a custom field type and duplicate half of the methods.

It would be great to have the mapping workspace -> app name available in the definition of the form field, so we can configure it in yaml.



 Comments   
Comment by Chris Jennings [ 07/Oct/19 ]

Another client has raised this and has suggested this replacement method with an accompanying addAcceptedWorkspaceToApp method.

 

private String mapWorkSpaceToApp(String workspace) {
  for (final var workspaceToAppMapping : workspaceToAppMappings) {
    if (workspace.equalsIgnoreCase(workspaceToAppMapping.workspace)) {
      return workspaceToAppMapping.app;
    }
  }
  throw new IllegalArgumentException(workspace + " is not a supported workspace by rich text field");
}

 

Comment by Michael Fosgerau [ 07/Oct/19 ]

Here's a workaround implementation until this gets into the official magnolia code:

public class SgRichTextFieldFactory extends RichTextFieldFactory {
    private static final Logger log = LoggerFactory.getLogger(SgRichTextFieldFactory.class);

    private final SimpleTranslator i18n;
    private final ChooserController chooserController;
    private final AppDescriptorRegistry appDescriptorRegistry;

    private MagnoliaCKEditorTextField richTextEditor;

    private List<WorkspaceToAppMapping> workspaceToAppMappings;

    public SgRichTextFieldFactory(RichTextFieldDefinition definition, ComponentProvider componentProvider, SimpleTranslator i18n, ChooserController chooserController, AppDescriptorRegistry appDescriptorRegistry) {
        super(definition, componentProvider, i18n, chooserController, appDescriptorRegistry);
        this.i18n = i18n;
        this.chooserController = chooserController;
        this.appDescriptorRegistry = appDescriptorRegistry;
        workspaceToAppMappings = new ArrayList<>();

        addAcceptedWorkspaceToApp(RepositoryConstants.WEBSITE,"pages");
        addAcceptedWorkspaceToApp("dam","assets");
    }

    @Override
    protected Component createFieldComponent() {
        MagnoliaCKEditorConfig config = initializeCKEditorConfig();
        richTextEditor = new MagnoliaCKEditorTextField(config);

        if (getDefinition().getHeight() > 0) {
            richTextEditor.setHeight(getDefinition().getHeight(), Sizeable.Unit.PIXELS);
        }

        richTextEditor.addListener((eventName, value) -> {
            if (eventName.equals(EVENT_GET_MAGNOLIA_LINK)) {
                try {
                    Gson gson = new Gson();
                    RichTextFieldFactory.PluginData pluginData = gson.fromJson(value, RichTextFieldFactory.PluginData.class);
                    openLinkDialog(pluginData.path, pluginData.workspace);
                } catch (Exception e) {
                    log.error("openLinkDialog failed", e);
                    richTextEditor.firePluginEvent(EVENT_CANCEL_LINK, i18n.translate("ui-framework.richtexteditorexception.opentargetappfailure"));
                }
            }
        });

        return richTextEditor;
    }

    protected MagnoliaCKEditorConfig initializeCKEditorConfig() {

        MagnoliaCKEditorConfig config = new MagnoliaCKEditorConfig();
        String path = VaadinService.getCurrentRequest().getContextPath();

        // MAGNOLIA LINK PLUGIN — may be used with/without customConfig
        config.addExternalPlugin(PLUGIN_NAME_MAGNOLIALINK, path + PLUGIN_PATH_MAGNOLIALINK);
        config.addListenedEvent(EVENT_GET_MAGNOLIA_LINK);

        // CUSTOM CONFIG.JS — bypass further config because it can't be overridden otherwise
        if (StringUtils.isNotBlank(getDefinition().getConfigJsFile())) {
            config.addExtraConfig("customConfig", "'" + path + getDefinition().getConfigJsFile() + "'");
            return config;
        }

        // DEFINITION
        if (!getDefinition().isAlignment()) {
            config.addToRemovePlugins("justify");
        }
        if (!getDefinition().isImages()) {
            config.addToRemovePlugins("image");
        }
        if (!getDefinition().isLists()) {
            // In CKEditor 4.1.1 enterkey depends on indent which itself depends on list
            config.addToRemovePlugins("enterkey");
            config.addToRemovePlugins("indent");
            config.addToRemovePlugins("list");
        }
        if (!getDefinition().isSource()) {
            config.addToRemovePlugins("sourcearea");
        }
        if (!getDefinition().isTables()) {
            config.addToRemovePlugins("table");
            config.addToRemovePlugins("tabletools");
            config.addToRemovePlugins("tableselection");
        }

        if (getDefinition().getColors() != null) {
            config.addExtraConfig("colorButton_colors", "'" + getDefinition().getColors() + "'");
            config.addExtraConfig("colorButton_enableMore", "false");
            config.addToRemovePlugins("colordialog");
        } else {
            config.addToRemovePlugins("colorbutton");
            config.addToRemovePlugins("colordialog");
        }
        if (getDefinition().getFonts() != null) {
            config.addExtraConfig("font_names", "'" + getDefinition().getFonts() + "'");
        } else {
            config.addExtraConfig("removeButtons", "'Font'");
        }
        if (getDefinition().getFontSizes() != null) {
            config.addExtraConfig("fontSize_sizes", "'" + getDefinition().getFontSizes() + "'");
        } else {
            config.addExtraConfig("removeButtons", "'FontSize'");
        }
        if (getDefinition().getFonts() == null && getDefinition().getFontSizes() == null) {
            config.addToRemovePlugins("font");
            config.addToRemovePlugins("fontSize");
        }

        // MAGNOLIA EXTRA CONFIG
        List<MagnoliaCKEditorConfig.ToolbarGroup> toolbars = initializeToolbarConfig();
        config.addToolbarLine(toolbars);

        config.addToExtraPlugins(PLUGIN_NAME_MAGNOLIALINK);
        config.addToRemovePlugins("elementspath");
        config.setBaseFloatZIndex(10000);
        config.setResizeEnabled(false);

        return config;
    }

    protected List<MagnoliaCKEditorConfig.ToolbarGroup> initializeToolbarConfig() {
        return defaultToolbar();
    }

    public void addAcceptedWorkspaceToApp(final String workspace, final String app) {
        try {
            sgMapWorkspaceToApp(workspace);
            log.debug("Mapping was already added from {} to {}", workspace, app);
        } catch (final IllegalArgumentException ex) {
            final var mapping = new WorkspaceToAppMapping();
            mapping.setWorkspace(workspace);
            mapping.setApp(app);
            workspaceToAppMappings.add(mapping);
        }
    }

    private String mapWorkSpaceToApp(final String workspace) {
        for (final var workspaceToAppMapping : workspaceToAppMappings) {
            if (workspace.equalsIgnoreCase(workspaceToAppMapping.workspace)) {
                return workspaceToAppMapping.app;
            }
        }
        throw new IllegalArgumentException(workspace + " is not a supported workspace by rich text field");
    }

    private void openLinkDialog(String path, String workspace) {
        chooserController.openChooser(createChooserDefinition(workspace), SessionUtil.getNode(path, workspace))
                .whenComplete((ChooserController.ChooseResult<Node> result, Throwable e) -> {

                    if (!result.isChosen()) {
                        richTextEditor.firePluginEvent(EVENT_CANCEL_LINK);
                        return;
                    }
                    if (e != null) {
                        String error = i18n.translate("ui-framework.richtexteditorexception.cannotaccessselecteditem");
                        log.error(error, e);
                        richTextEditor.firePluginEvent(EVENT_CANCEL_LINK, error);
                    }
                    result.getChoice().ifPresent(node -> {
                        Gson gson = new Gson();
                        RichTextFieldFactory.MagnoliaLink mlink = createMagnoliaLink(node);
                        richTextEditor.firePluginEvent(EVENT_SEND_MAGNOLIA_LINK, gson.toJson(mlink));
                    });
                });
    }

    private RichTextFieldFactory.MagnoliaLink createMagnoliaLink(Node node) {
        RichTextFieldFactory.MagnoliaLink mlink = new RichTextFieldFactory.MagnoliaLink();
        try {
            mlink.identifier = node.getIdentifier();
            mlink.repository = node.getSession().getWorkspace().getName();
            mlink.path = node.getPath();
            mlink.caption = PropertyUtil.getString(node, "title", node.getName());
        }
        catch (final RepositoryException ex) {
            log.error("Failed to get linked node {}", node, ex);
        }
        return mlink;
    }

    private ChooserDefinition<Node, SingleItemWorkbenchChooser<Node>> createChooserDefinition(String workspace) {
        SingleItemWorkbenchChooserDefinition<Node> definition = new SingleItemWorkbenchChooserDefinition<>();
        AppAwareWorkbenchChooserDefinition<Node> workbenchChooserDefinition = new AppAwareWorkbenchChooserDefinition<>(appDescriptorRegistry);
        workbenchChooserDefinition.setAppName(mapWorkSpaceToApp(workspace));
        definition.setWorkbenchChooser(workbenchChooserDefinition);
        return definition;
    }

    /**
     * Link info wrapper.
     */
    public static class MagnoliaLink {

        public String identifier;

        public String repository;

        public String path;

        public String caption;

    }

    /**
     * Plugin data wrapper.
     */
    public static class PluginData {
        public String workspace;
        public String path;
    }

    /**
     * Mapping class between a workspace and an app.
     */
    public static class WorkspaceToAppMapping {
        public String workspace;
        public String app;
    }
}
Comment by Samuli Saarinen [ 05/Feb/21 ]

This is still an issue with Magnolia 6.2.5+ RichTextFieldFactory. Even making RichTextFieldFactory#mapWorkspaceToChooserDialog from private -> protected would allow easier extensibility.

Generated at Mon Feb 12 09:16:25 CET 2024 using Jira 9.4.2#940002-sha1:46d1a51de284217efdcb32434eab47a99af2938b.