From b115eeefa060c70de136284da6cb529273cf2202 Mon Sep 17 00:00:00 2001 From: Piotr Skorupa Date: Tue, 12 Aug 2025 11:18:25 +0200 Subject: [PATCH] Add cross-project wiki references --- doc/user/markdown.md | 4 +- lib/banzai/filter/wiki_link_gollum_filter.rb | 31 +++++++++++-- .../filter/wiki_link_gollum_filter_spec.rb | 46 +++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 133a49a9ae53c4..c3a1ec0b953ded 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -793,8 +793,8 @@ GitLab Flavored Markdown recognizes the following: | Repository file reference (specific line) | `[README](doc/README.md#L13)` | | | | [Alert](../operations/incident_management/alerts.md) | `^alert#123` | `namespace/project^alert#123` | `project^alert#123` | | [Contact](crm/_index.md#contacts) | `[contact:test@example.com]` | | | -| [Wiki page](project/wiki/_index.md) (if the page slug is the same as the title) | `[[Home]]` | | | -| [Wiki page](project/wiki/_index.md) (if the page slug is different from the title) | `[[How to use GitLab\|how-to-use-gitlab]]` | | | +| [Wiki page](project/wiki/_index.md) (if the page slug is the same as the title) | `[[Home]]` | `[[namespace/project:Home]]` | `[[namespace:Home]]` | +| [Wiki page](project/wiki/_index.md) (if the page slug is different from the title) | `[[How to use GitLab\|how-to-use-gitlab]]` | `[[How to use GitLab\|namespace/project:how-to-use-gitlab]]` | `[[How to use GitLab\|namespace:how-to-use-gitlab]]` | **Footnotes**: diff --git a/lib/banzai/filter/wiki_link_gollum_filter.rb b/lib/banzai/filter/wiki_link_gollum_filter.rb index 3c7e20ed299c6a..b2031609c7580e 100644 --- a/lib/banzai/filter/wiki_link_gollum_filter.rb +++ b/lib/banzai/filter/wiki_link_gollum_filter.rb @@ -18,6 +18,10 @@ module Filter # * [[http://en.wikipedia.org/wiki/Git_(software)]] # * [[Git|http://en.wikipedia.org/wiki/Git_(software)]] # + # - Link to wiki pages in another namespace or project: + # + # * [[flightjs/Flight:External Page]] + # # - Link internal images, gollum special attributes not supported # # * [[images/logo.png]] @@ -61,9 +65,9 @@ def process_image_link(node) path = if url?(node[:href]) node[:href] - elsif wiki + elsif wiki_from_context @image_link_count += 1 - wiki.find_file(node[:href], load_content: false)&.path + wiki_from_context.find_file(node[:href], load_content: false)&.path end return unless path @@ -79,18 +83,35 @@ def process_image_link(node) def process_page_link(node) return if node[:href].casecmp?('_toc_') && node.text.casecmp?('_toc_') + parts = node[:href].split(':') + + if parts.size == 1 + reference = parts[0].strip + else + namespace, reference = *parts.compact.map(&:strip) + end + + group = Namespace.find_by_full_path(namespace) + project = Project.find_by_full_path(namespace) if group.nil? + + wiki = project&.wiki + wiki ||= group&.wiki + wiki ||= wiki_from_context + if url?(node[:href]) set_common_attributes(node) elsif wiki set_common_attributes(node) - node[:href] = ::File.join(wiki_base_path, node[:href]) + node[:href] = ::File.join(wiki_base_path(wiki), reference) node.add_class('gfm') node.add_class('gfm-gollum-wiki-page') node['data-reference-type'] = 'wiki_page' node['data-project'] = context[:project].id if context[:project] + node['data-project'] ||= project.id if project node['data-group'] = context[:group]&.id if context[:group] + node['data-group'] ||= group.id if group end end @@ -101,11 +122,11 @@ def set_common_attributes(node) node['data-gollum'] = true end - def wiki + def wiki_from_context context[:wiki] || context[:project]&.wiki || context[:group]&.wiki end - def wiki_base_path + def wiki_base_path(wiki) wiki&.wiki_base_path end diff --git a/spec/lib/banzai/filter/wiki_link_gollum_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_gollum_filter_spec.rb index 283e6ac035fd4d..6b81eb04fbb032 100644 --- a/spec/lib/banzai/filter/wiki_link_gollum_filter_spec.rb +++ b/spec/lib/banzai/filter/wiki_link_gollum_filter_spec.rb @@ -141,6 +141,52 @@ end end + context 'when linking external namespace wikis' do + let_it_be(:other_project) { create(:project) } + let_it_be(:other_wiki) { create(:project_wiki, project: other_project) } + let_it_be(:other_group) { create(:group) } + let_it_be(:other_group_wiki) { create(:group_wiki, group: other_group) } + + it 'created link text includes the resource text and wiki base path' do + tag = "[[#{other_project.full_path}:wiki-slug]]" + doc = pipeline_filter("See #{tag}", wiki: wiki) + expected_path = ::File.join(other_wiki.wiki_base_path, 'wiki-slug') + + expect(doc.at_css('a').text).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path + expect(doc.at_css('a')['data-reference-type']).to eq 'wiki_page' + expect(doc.at_css('a')['data-canonical-src']).to eq 'wiki-slug' + expect(doc.at_css('a')['data-gollum']).to eq 'true' + expect(doc.at_css('a')['data-project']).to eq other_project.id.to_s + expect(doc.at_css('a')['data-group']).to be_nil + expect(doc.at_css('a')['class']).to eq 'gfm gfm-gollum-wiki-page' + end + + it 'created link text will be link-text' do + tag = "[[link-text|#{other_project.full_path}:wiki-slug]]" + doc = pipeline_filter("See #{tag}", wiki: wiki) + expected_path = ::File.join(other_wiki.wiki_base_path, 'wiki-slug') + + expect(doc.at_css('a').text).to eq 'link-text' + expect(doc.at_css('a')['href']).to eq expected_path + end + + it 'handles group wiki links' do + tag = "[[#{other_group.full_path}:wiki-slug]]" + doc = pipeline_filter("See #{tag}", project: nil, group: group, wiki: wiki) + expected_path = ::File.join(other_group_wiki.wiki_base_path, 'wiki-slug') + + expect(doc.at_css('a').text).to eq 'wiki-slug' + expect(doc.at_css('a')['href']).to eq expected_path + expect(doc.at_css('a')['data-reference-type']).to eq 'wiki_page' + expect(doc.at_css('a')['data-canonical-src']).to eq 'wiki-slug' + expect(doc.at_css('a')['data-gollum']).to eq 'true' + expect(doc.at_css('a')['data-project']).to be_nil + expect(doc.at_css('a')['data-group']).to eq other_group.id.to_s + expect(doc.at_css('a')['class']).to eq 'gfm gfm-gollum-wiki-page' + end + end + it 'leaves other text content untouched' do doc = pipeline_filter('This is [[a link|link]]', wiki: wiki) -- GitLab