Skip to content

[#74710] show inline page link macros in editing mode#23142

Open
Kharonus wants to merge 5 commits into
devfrom
feature/74710-render-macro-in-ckeditor-editing-mode
Open

[#74710] show inline page link macros in editing mode#23142
Kharonus wants to merge 5 commits into
devfrom
feature/74710-render-macro-in-ckeditor-editing-mode

Conversation

@Kharonus
Copy link
Copy Markdown
Member

@Kharonus Kharonus commented May 8, 2026

Ticket

#74710

What are you trying to accomplish?

  • show inline page link macros while editing with ckeditor

What approach did you choose and why?

  • show page link as turbo frame
  • add route for loading the macro
  • update ckeditor build

Related PR

opf/commonmark-ckeditor-build#115

- https://community.openproject.org/work_packages/74710
- show page link as turbo frame
- add route for loading the macro
- update ckeditor build
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Caution

The provided work package version does not match the core version

Details:

Please make sure that:

  • The work package version OR your pull request target branch is correct

Copy link
Copy Markdown
Contributor

@NobodysNightmare NobodysNightmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR LGTM mostly.

Exclusions:

  • I did not yet look at the underlying CKEditor changes, those still require a review
  • I think we can remove the provider from the InlinePageLinkMacroComponent


alias_method :page_info_result, :model

attr_reader :provider
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Is the provider used anywhere within the component?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is a good question :D I tried to go over my tracks again, and I think, my first iteration tried to resolve the query here instead the outside.

So, no, this is no longer needed and I can drop it.

->(error) { render_error_macro(view_context, error) }
)
end
InlinePageLinkMacroComponent.new(page_info_result, provider:).render_in(view_context)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💚 I like the extraction of the InlinePageLinkMacroComponent

end

def find_provider
@provider = Provider.find(params[:provider_id])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 During manual testing I found a case that's breaking stuff. My test work package contains a link with an invalid provider.

I believe what happens is that this causes a 404 when loading this component (because the find fails), resulting in a Turbo error:

Screencast.from.2026-05-11.09-39-53.webm

I think this is a case that needs to be explicitly handled here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good find

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return `${this.staticBase}/external_redirect?url=${encodeURIComponent(url)}`;
}

public wikiPageLinkMacro(providerId:number, pageIdentifier:string, turboFrameId:string) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providerId:number is a wrong type annotation. In reality we pass the result of a regex match in here.

The regex only matches numbers, though this method can't know this. In fact we will pass a string and should also escape it properly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that same case, but came to a different conslusion.

The path helper service is owned by us and not exposed as a public API. Hence, we can fully control the input.

The provider id is a number in our data model, due to that fact we have both the regex and the method signature here to look for number. This is the causality I'd see here, not that the method signature is following the regex.

Same for the turbo frame id, we "define" this to be a UUID, after that, all methods consume the data as is. The only exception is the page identifier, this is external and not controlled by us. We can see that also in both locations, the regex matches any characters except some control characters, and the path helper escapes it before giving it to the UI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's start with the (hopefully) easier one to discuss:

The provider id is a number in our data model, due to that fact we have both the regex and the method signature here to look for number.

Tell me if I misunderstand TypeScript, but essentially it only works to guarantee types if both sides (caller and callee) use TypeScript, but there are no runtime checks for the types.

I am asking this, because this method is called like this (abbreviated for clarity):

const match = WIKI_PAGE_LINK_RE.exec(word);
const providerId = match[1];
const frameSrc = getOPPath(editor).wikiPageLinkMacro(providerId, "not relevant", "not relevant");

Now match[1] is the result of a regex match and those are always strings. In this case it's a string that only contains the characters 0-9, but it's a string still:

typeof /([0-9]+)/.exec("123")[1]
=> "string" 

The error is not noticable here, because all we do so far is interpolating providerId in a string, but it is in fact not of type number (unless the caller would convert it... which it doesn't).

The path helper service is owned by us and not exposed as a public API. Hence, we can fully control the input.

I see that this is the case, but I am not a fan of the argument. For me it's a matter of separation of concerns. The concern of the wikiPageLinkMacro is to always return a proper URL for the given inputs. I don't care much whether it raises an error for invalid inputs or whether it escapes invalid input, but it should never output wrong URLs that came to life due to escaping errors.

On the argument of "we fully control the input": The question is who guarantees that there will never be a user input involved in any of the two other properties. The next feature we build might use this method again, but this time the provider ID does not come from a controlled regex, but from a select box. And maybe the value of that select box is persisted as a query parameter 🤷 That would pave the way to allow XSS with the capability of generating unintended URLs through this path helper.

To be honest, I want to turn around the question: Why should we care during the implementation of this method, in which ways callers may call it? Why would we add assumptions about callers never exposing one of the parameters, but sometimes exposing others?
Wouldn't it be simpler to treat all parameters equally and escape them all? It's not that there is a negative impact on escaping all parameters.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like said in the live session, I accepted the "plublicness" of the path helper service and escaped everything.

}

public wikiPageLinkMacro(providerId:number, pageIdentifier:string, turboFrameId:string) {
return `${this.staticBase}/wiki_page_link_macro/load?provider_id=${providerId}&page_identifier=${encodeURIComponent(pageIdentifier)}&turbo_frame_id=${turboFrameId}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment above: This method here can't know that turboFrameId comes from a "trusted source". It's an external input that could contain anything. We should also go through encodeURIComponent to add it to the URL.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants