From fcf3a958c9fa1da5c5d7bac27afd52f27c9bff5f Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
- Inline reference to WP:
- Inline reference to WP by ID:
- Inline reference to WP by ID with CF with a dot:
- Inline reference to WP by subject:
Inline reference to project:
- Inline reference to WP:
- Inline reference to WP by ID:
- Inline reference to WP by subject:
Inline reference to project: #{link_to(message1.subject,
project_forum_topic_path(project, forum, message1),
class: 'message op-uc-link',
+ aria: { label: "A dynamic link to a message placed using a macro" },
target: '_top')} #{link} #{link_to(subproject.name, project_url,
target: '_top',
+ aria: { label: "A dynamic link to a project placed using a macro" },
class: 'project op-uc-link')} #{link_to(subproject.name, project_url,
target: '_top',
+ aria: { label: "A dynamic link to a project placed using a macro" },
class: 'project op-uc-link')} #{link_to(subproject.name, project_url,
target: '_top',
+ aria: { label: "A dynamic link to a project placed using a macro" },
class: 'project op-uc-link')}
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -184,7 +184,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -212,7 +212,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -241,7 +241,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -273,7 +273,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -302,7 +302,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- title: "User #{linked_project_member.name}",
+ aria: { label: "A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -373,7 +373,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="/users/#{user.id}/hover_card"
href="/users/#{user.id}"
- title="User Foo Barrit">Foo Barrit
+ aria-label="A dynamic link to a user placed using a macro">Foo Barrit
[[CookBook documentation]]
From 0e0ef73ce1b4a25b2d8df462828be643c2d24f06 Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
#{link_to(message1.subject, project_forum_topic_path(project, forum, message1), class: 'message op-uc-link', - aria: { label: "A dynamic link to a message placed using a macro" }, + aria: { + label: 'A dynamic link to a message placed using a macro' + }, target: '_top')}
") } end @@ -396,7 +398,9 @@ it { expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', - aria: { label: "A dynamic link to a project placed using a macro" }, + aria: { + label: 'A dynamic link to a project placed using a macro' + }, class: 'project op-uc-link')}
") } end @@ -407,7 +411,9 @@ it { expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', - aria: { label: "A dynamic link to a project placed using a macro" }, + aria: { + label: 'A dynamic link to a project placed using a macro' + }, class: 'project op-uc-link')}
") } end @@ -418,7 +424,9 @@ it { expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', - aria: { label: "A dynamic link to a project placed using a macro" }, + aria: { + label: 'A dynamic link to a project placed using a macro' + }, class: 'project op-uc-link')}
") } end @@ -615,10 +623,10 @@ def source_url_with_ext(**) entry_revision_project_repository_path(project_id: identifier, repo_path: "some/file.ext", **) end - def resource_link_to(resource, name, url, **options) + def resource_link_to(resource, name, url, **) link_to(name, url, - **options, + **, aria: { label: "A dynamic link to a #{resource} placed using a macro" }) end @@ -632,18 +640,20 @@ def resource_link_to(resource, name, url, **options) @to_test = { # source - "source:/some/file" => resource_link_to("source", "source:/some/file", source_url, class: "source op-uc-link", target: "_top"), - "source:/some/file." => resource_link_to("source", "source:/some/file", source_url, class: "source op-uc-link", target: "_top") + ".", - 'source:"/some/file.ext".' => resource_link_to("source", "source:/some/file.ext", source_url_with_ext, - class: "source op-uc-link", target: "_top") + ".", - "source:/some/file. " => resource_link_to("source", "source:/some/file", source_url, - class: "source op-uc-link", target: "_top") + ".", - 'source:"/some/file.ext". ' => resource_link_to("source", "source:/some/file.ext", source_url_with_ext, - class: "source op-uc-link", target: "_top") + ".", - "source:/some/file, " => resource_link_to("source", "source:/some/file", source_url, - class: "source op-uc-link", target: "_top") + ",", + "source:/some/file" => resource_link_to("source", "source:/some/file", source_url, + class: "source op-uc-link", target: "_top"), + "source:/some/file." => "#{resource_link_to('source', 'source:/some/file', source_url, + class: 'source op-uc-link', target: '_top')}.", + 'source:"/some/file.ext".' => "#{resource_link_to('source', 'source:/some/file.ext', source_url_with_ext, + class: 'source op-uc-link', target: '_top')}.", + "source:/some/file. " => "#{resource_link_to('source', 'source:/some/file', source_url, + class: 'source op-uc-link', target: '_top')}.", + 'source:"/some/file.ext". ' => "#{resource_link_to('source', 'source:/some/file.ext', source_url_with_ext, + class: 'source op-uc-link', target: '_top')}.", + "source:/some/file, " => "#{resource_link_to('source', 'source:/some/file', source_url, + class: 'source op-uc-link', target: '_top')},", "source:/some/file@52" => resource_link_to("source", "source:/some/file@52", source_url(rev: 52), - class: "source op-uc-link", target: "_top"), + class: "source op-uc-link", target: "_top"), 'source:"/some/file.ext@52"' => resource_link_to("source", "source:/some/file.ext@52", source_url_with_ext(rev: 52), class: "source op-uc-link", target: "_top"), 'source:"/some/file#L110"' => resource_link_to("source", "source:/some/file#L110", source_url(anchor: "L110"), @@ -655,7 +665,7 @@ def resource_link_to(resource, name, url, **options) source_url(rev: 52, anchor: "L110"), class: "source op-uc-link", target: "_top"), "export:/some/file" => resource_link_to("export", "export:/some/file", source_url(format: "raw"), - class: "source download op-uc-link", target: "_top"), + class: "source download op-uc-link", target: "_top"), # escaping "!source:/some/file" => "source:/some/file", # invalid expressions diff --git a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb index 51b6ef67d07d..bd866d6cd149 100644 --- a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb +++ b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb @@ -85,7 +85,7 @@#{link_to(linked_project_member.name, { controller: :users, action: :show, id: linked_project_member.id }, - aria: { label: "A dynamic link to a user placed using a macro" }, + aria: { label: 'A dynamic link to a user placed using a macro' }, class: 'user-mention op-uc-link', target: '_top', data: { @@ -184,7 +184,7 @@
#{link_to(linked_project_member.name, { controller: :users, action: :show, id: linked_project_member.id }, - aria: { label: "A dynamic link to a user placed using a macro" }, + aria: { label: 'A dynamic link to a user placed using a macro' }, class: 'user-mention op-uc-link', target: '_top', data: { @@ -212,7 +212,7 @@
#{link_to(linked_project_member.name, { controller: :users, action: :show, id: linked_project_member.id }, - aria: { label: "A dynamic link to a user placed using a macro" }, + aria: { label: 'A dynamic link to a user placed using a macro' }, class: 'user-mention op-uc-link', target: '_top', data: { @@ -241,7 +241,7 @@
#{link_to(linked_project_member.name, { controller: :users, action: :show, id: linked_project_member.id }, - aria: { label: "A dynamic link to a user placed using a macro" }, + aria: { label: 'A dynamic link to a user placed using a macro' }, class: 'user-mention op-uc-link', target: '_top', data: { @@ -273,7 +273,7 @@
#{link_to(linked_project_member.name, { controller: :users, action: :show, id: linked_project_member.id }, - aria: { label: "A dynamic link to a user placed using a macro" }, + aria: { label: 'A dynamic link to a user placed using a macro' }, class: 'user-mention op-uc-link', target: '_top', data: { @@ -302,7 +302,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "A dynamic link to a user placed using a macro" },
+ aria: { label: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
diff --git a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
index d76df8dd925a..94e4cc3a8bd6 100644
--- a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
+++ b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
@@ -172,19 +172,23 @@
wp = work_package.reload
# Prepend "see " so Markly doesn't parse `##...` as an H2 ATX heading.
rendered = format_text("see ###{wp.display_id} here")
+ macro = Nokogiri::HTML.fragment(rendered).at_css("opce-macro-wp-quickinfo")
- expect(rendered).to include(
- %(
with two lines.
HTML From 273a3fb7ed870662169446256af8952bba7c4263 Mon Sep 17 00:00:00 2001 From: Behrokh Satarnejad
- Inline reference to WP:
- Inline reference to WP by ID:
- Inline reference to WP by ID with CF with a dot:
- Inline reference to WP by subject:
Inline reference to project:
- Inline reference to WP:
- Inline reference to WP by ID:
- Inline reference to WP by subject:
Inline reference to project:
#{link}
") } @@ -293,7 +293,7 @@ hover_card_url: hover_card_work_package_path(work_package.id) }, class: "issue work_package op-uc-link", - aria: { label: "A dynamic link to a work package placed using a macro" }, + aria: { description: "A dynamic link to a work package placed using a macro" }, target: "_top") end @@ -324,7 +324,7 @@ content_tag "opce-macro-wp-quickinfo", "", data: { id: "1234", display_id: "1234", detailed: "false" }, - aria: { label: "A dynamic link to a work package placed using a macro" } + aria: { description: "A dynamic link to a work package placed using a macro" } end subject { format_text("foo (bar ##1234)") } @@ -337,7 +337,7 @@ content_tag "opce-macro-wp-quickinfo", "", data: { id: "1234", display_id: "1234", detailed: "true" }, - aria: { label: "A dynamic link to a work package placed using a macro" } + aria: { description: "A dynamic link to a work package placed using a macro" } end subject { format_text("foo (bar ###1234)") } @@ -370,7 +370,7 @@ hover_card_url: hover_card_work_package_path(work_package.id) }, class: "issue work_package op-uc-link", - aria: { label: "A dynamic link to a work package placed using a macro" }, + aria: { description: "A dynamic link to a work package placed using a macro" }, target: "_top") end @@ -399,7 +399,7 @@ expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', aria: { - label: 'A dynamic link to a project placed using a macro' + description: 'A dynamic link to a project placed using a macro' }, class: 'project op-uc-link')}
") } @@ -412,7 +412,7 @@ expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', aria: { - label: 'A dynamic link to a project placed using a macro' + description: 'A dynamic link to a project placed using a macro' }, class: 'project op-uc-link')}
") } @@ -425,7 +425,7 @@ expect(subject).to be_html_eql("#{link_to(subproject.name, project_url, target: '_top', aria: { - label: 'A dynamic link to a project placed using a macro' + description: 'A dynamic link to a project placed using a macro' }, class: 'project op-uc-link')}
") } @@ -627,7 +627,7 @@ def resource_link_to(resource, name, url, **) link_to(name, url, **, - aria: { label: "A dynamic link to a #{resource} placed using a macro" }) + aria: { description: "A dynamic link to a #{resource} placed using a macro" }) end before do @@ -709,7 +709,7 @@ def resource_link_to(resource, name, url, **) let(:expected) do <<~EXPECTED - +
[[CookBook documentation]]
diff --git a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
index bd866d6cd149..58ab042e960d 100644
--- a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
+++ b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
@@ -85,7 +85,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -184,7 +184,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -212,7 +212,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -241,7 +241,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -273,7 +273,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -302,7 +302,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: 'A dynamic link to a user placed using a macro' },
+ aria: { description: 'A dynamic link to a user placed using a macro' },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -373,7 +373,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="/users/#{user.id}/hover_card"
href="/users/#{user.id}"
- aria-label="A dynamic link to a user placed using a macro">Foo Barrit
+ aria-description="A dynamic link to a user placed using a macro">Foo Barrit
EXPECTED
end
@@ -400,7 +400,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="http://openproject.org/users/#{user.id}/hover_card"
href="http://openproject.org/users/#{user.id}"
- aria-label="A dynamic link to a user placed using a macro">Foo Barrit
+ aria-description="A dynamic link to a user placed using a macro">Foo Barrit
EXPECTED
end
@@ -436,7 +436,7 @@
+ aria-description="A dynamic link to a group placed using a macro">
#{linked_project_member_group.name}
@@ -482,7 +482,7 @@
+ aria-description="A dynamic link to a group placed using a macro">
#{linked_project_member_group.name}
diff --git a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
index 94e4cc3a8bd6..ccab6c5a20e9 100644
--- a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
+++ b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
@@ -177,7 +177,7 @@
expect(macro["data-id"]).to eq(wp.id.to_s)
expect(macro["data-display-id"]).to eq(wp.display_id)
expect(macro["data-detailed"]).to eq("false")
- expect(macro["aria-label"]).to eq("A dynamic link to a work package placed using a macro")
+ expect(macro["aria-description"]).to eq("A dynamic link to a work package placed using a macro")
end
it "renders `###PROJ-N` as a detailed quickinfo macro element" do
@@ -188,7 +188,7 @@
expect(macro["data-id"]).to eq(wp.id.to_s)
expect(macro["data-display-id"]).to eq(wp.display_id)
expect(macro["data-detailed"]).to eq("true")
- expect(macro["aria-label"]).to eq("A dynamic link to a work package placed using a macro")
+ expect(macro["aria-description"]).to eq("A dynamic link to a work package placed using a macro")
end
context "when the referenced work package does not exist" do
diff --git a/spec/requests/api/v3/render_resource_spec.rb b/spec/requests/api/v3/render_resource_spec.rb
index 2a310dec51d8..19487f47bc80 100644
--- a/spec/requests/api/v3/render_resource_spec.rb
+++ b/spec/requests/api/v3/render_resource_spec.rb
@@ -95,7 +95,7 @@
##{id}
@@ -191,7 +191,7 @@
#1
\n\nwith two lines.
HTML
From e8df6528c15cd0d6816beab0534af6f8ec48e10b Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
Date: Mon, 6 Jul 2026 12:14:10 +0200
Subject: [PATCH 07/11] Preserve visible link text in accessible names, retain
revision context, move wiki labels onto links, and add hidden context for
non-interactive macros
---
config/locales/js-en.yml | 1 +
.../macros/attribute-label-macro.component.ts | 8 ++--
.../fields/macros/attribute-label-macro.html | 3 ++
.../macros/attribute-value-macro.component.ts | 8 ++--
.../fields/macros/attribute-value-macro.html | 3 ++
.../work-package-quickinfo-macro.component.ts | 5 ++-
.../macros/work-package-quickinfo-macro.html | 2 +-
.../text_formatting/filters/mention_filter.rb | 21 +++++----
.../matchers/attribute_macros.rb | 9 +---
.../matchers/link_handlers/base.rb | 17 +++++--
.../matchers/link_handlers/colon_separator.rb | 3 +-
.../matchers/link_handlers/revisions.rb | 3 +-
.../matchers/link_handlers/work_packages.rb | 5 +--
.../documents/spec/application_helper_spec.rb | 6 +--
.../open_project/markdown_formatting_spec.rb | 2 +-
.../open_project/markdown_formatting_spec.rb | 2 +-
..._inline_page_link_macro_component.html.erb | 2 +-
.../inline_page_link_macro_component.html.erb | 7 ++-
...d_inline_page_link_macro_component_spec.rb | 5 +--
.../inline_page_link_macro_component_spec.rb | 4 +-
.../repositories/revision_representer_spec.rb | 2 +-
.../filters/mention_filter_spec.rb | 8 ++--
.../markdown/attribute_macros_spec.rb | 14 +++---
.../markdown/in_tool_links_spec.rb | 44 +++++++++++--------
.../text_formatting/markdown/mentions_spec.rb | 20 ++++-----
.../link_handlers/work_packages_spec.rb | 4 +-
spec/requests/api/v3/render_resource_spec.rb | 4 +-
27 files changed, 119 insertions(+), 93 deletions(-)
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index 065472f9e949..c1ed4214a3a1 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -174,6 +174,7 @@ en:
wysiwyg: "Switch to WYSIWYG editor"
macro:
attribute_reference:
+ aria_label_with_name: "%{name}. %{description}"
aria_label_resource_link: "A dynamic link to a %{resource} placed using a macro"
aria_label_work_package_attribute: "A dynamic work package attribute placed using a macro"
aria_label_work_package_link: "A dynamic link to a work package placed using a macro"
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
index 2987a3b88c4c..964a5ba750c5 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
+++ b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++ Ng1FieldControlsWrapper,
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, Injector, OnInit, inject } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injector, OnInit, inject } from '@angular/core';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import {
@@ -69,7 +69,7 @@ export class AttributeLabelMacroComponent implements OnInit {
invalid_attribute: (attr:string) => this.I18n.t('js.editor.macro.attribute_reference.invalid_attribute', { name: attr }),
};
- @HostBinding('attr.aria-description') hostAriaDescription:string|null = null;
+ showAriaContext = false;
// The loaded resource, required for help text
resource:HalResource|null = null;
@@ -88,7 +88,7 @@ export class AttributeLabelMacroComponent implements OnInit {
const model = element.dataset.model as SupportedAttributeModels;
const id = element.dataset.id!;
const attributeName = element.dataset.attribute!;
- this.hostAriaDescription = model === 'workPackage' ? this.text.aria_label : null;
+ this.showAriaContext = model === 'workPackage';
this.attributeScope = capitalize(model);
void this.loadResourceAttribute(model, id, attributeName);
@@ -109,7 +109,7 @@ export class AttributeLabelMacroComponent implements OnInit {
}
const schema = await this.schemaCache.ensureLoaded(this.resource);
- this.attribute = schema.attributeFromLocalizedName(attributeName) || attributeName;
+ this.attribute = schema.attributeFromLocalizedName(attributeName) ?? attributeName;
this.label = (schema[this.attribute] as IOPFieldSchema|undefined)?.name;
if (!this.label) {
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.html b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.html
index 78687e3c25a7..0a8c1d1b9ee0 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.html
+++ b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.html
@@ -1,5 +1,8 @@
@if (resource) {
+ @if (showAriaContext) {
+
+ }
}
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
index 707548f072ad..cd0b51df76d9 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
+++ b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
@@ -26,7 +26,7 @@
// See COPYRIGHT and LICENSE files for more details.
//++ Ng1FieldControlsWrapper,
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, Injector, OnInit, ViewChild, inject } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injector, OnInit, ViewChild, inject } from '@angular/core';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import {
@@ -74,7 +74,7 @@ export class AttributeValueMacroComponent implements OnInit {
invalid_attribute: (attr:string) => this.I18n.t('js.editor.macro.attribute_reference.invalid_attribute', { name: attr }),
};
- @HostBinding('attr.aria-description') hostAriaDescription:string|null = null;
+ showAriaContext = false;
resource:HalResource;
@@ -86,7 +86,7 @@ export class AttributeValueMacroComponent implements OnInit {
const id = element.dataset.id!;
const attributeName = element.dataset.attribute!;
element.classList.add(ATTRIBUTE_MACRO_CLASS);
- this.hostAriaDescription = model === 'workPackage' ? this.text.aria_label : null;
+ this.showAriaContext = model === 'workPackage';
if (this.isNestedMacro(model, id, attributeName)) {
const error = this.I18n.t('js.editor.macro.attribute_reference.nested_macro', { model, id });
@@ -121,7 +121,7 @@ export class AttributeValueMacroComponent implements OnInit {
const schema = await this.schemaCache.ensureLoaded(resource);
const proxied = this.schemaCache.proxied(resource, schema);
- const attribute = schema.attributeFromLocalizedName(attributeName) || this.dateAttribute(resource, proxied, attributeName);
+ const attribute = schema.attributeFromLocalizedName(attributeName) ?? this.dateAttribute(resource, proxied, attributeName);
const fieldSchema = proxied.ofProperty(attribute) as IFieldSchema|undefined;
if (fieldSchema) {
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.html b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.html
index f7584cdd6a5e..3cced91e2db5 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.html
+++ b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.html
@@ -9,4 +9,7 @@
containerType="single-view"
[displayFieldOptions]="{ writable: false }"
[fieldName]="fieldName" />
+ @if (showAriaContext) {
+
+ }
}
diff --git a/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.component.ts b/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.component.ts
index a03ec51c944c..3b5087ecf40a 100644
--- a/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.component.ts
+++ b/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.component.ts
@@ -66,7 +66,10 @@ export class WorkPackageQuickinfoMacroComponent implements OnInit {
text = {
not_found: this.I18n.t('js.editor.macro.attribute_reference.not_found'),
- aria_label: this.I18n.t('js.editor.macro.attribute_reference.aria_label_work_package_link'),
+ aria_label: (name:string) => this.I18n.t('js.editor.macro.attribute_reference.aria_label_with_name', {
+ name,
+ description: String(this.I18n.t('js.editor.macro.attribute_reference.aria_label_work_package_link')),
+ }),
};
/** Work package to be shown */
diff --git a/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.html b/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.html
index 3634b0a57ab7..fbd6134af08c 100644
--- a/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.html
+++ b/frontend/src/app/shared/components/fields/macros/work-package-quickinfo-macro.html
@@ -10,7 +10,7 @@
fieldName="type" />
diff --git a/lib/open_project/text_formatting/filters/mention_filter.rb b/lib/open_project/text_formatting/filters/mention_filter.rb
index 48b4ed3485c0..dca2cb9162ab 100644
--- a/lib/open_project/text_formatting/filters/mention_filter.rb
+++ b/lib/open_project/text_formatting/filters/mention_filter.rb
@@ -103,14 +103,14 @@ def user_mention(user)
link_to_user(user,
only_path: context[:only_path],
class: "user-mention",
- aria: { description: resource_link_aria_label("user") })
+ aria: { label: accessible_link_label(user.name, resource_link_aria_label("user")) })
end
def group_mention(group)
link_to_group(group,
only_path: context[:only_path],
class: "user-mention",
- aria: { description: resource_link_aria_label("group") })
+ aria: { label: accessible_link_label(group.name, resource_link_aria_label("group")) })
end
def work_package_mention(work_package, mention)
@@ -136,10 +136,7 @@ def work_package_quickinfo(work_package, detailed:)
"",
data: { id: work_package.id,
display_id: work_package.display_id,
- detailed: },
- aria: {
- description: work_package_link_aria_label
- }
+ detailed: }
end
# Uses the WP's current `formatted_id` rather than the envelope text,
@@ -151,7 +148,7 @@ def work_package_static_macro(work_package, detailed:)
link_to(label,
work_package_path_or_url(id: work_package.display_id, only_path: context[:only_path]),
class: "issue work_package",
- aria: { description: work_package_link_aria_label })
+ aria: { label: accessible_link_label(label, work_package_link_aria_label) })
end
def work_package_link_aria_label
@@ -162,12 +159,20 @@ def resource_link_aria_label(resource)
I18n.t("js.editor.macro.attribute_reference.aria_label_resource_link", resource:)
end
+ def accessible_link_label(name, description)
+ visible_name = Nokogiri::HTML.fragment(name.to_s).text.squish
+ plain_description = Nokogiri::HTML.fragment(description.to_s).text.squish
+ I18n.t("js.editor.macro.attribute_reference.aria_label_with_name",
+ name: visible_name,
+ description: plain_description)
+ end
+
def work_package_link(work_package)
display_id = work_package.display_id
link_to(work_package.formatted_id,
work_package_path_or_url(id: display_id, only_path: context[:only_path]),
class: "issue work_package",
- aria: { description: work_package_link_aria_label },
+ aria: { label: accessible_link_label(work_package.formatted_id, work_package_link_aria_label) },
data: {
hover_card_trigger_target: "trigger",
hover_card_url: hover_card_work_package_path(display_id)
diff --git a/lib/open_project/text_formatting/matchers/attribute_macros.rb b/lib/open_project/text_formatting/matchers/attribute_macros.rb
index 92212dd20251..c78055ded2b8 100644
--- a/lib/open_project/text_formatting/matchers/attribute_macros.rb
+++ b/lib/open_project/text_formatting/matchers/attribute_macros.rb
@@ -92,16 +92,9 @@ def self.process_match(match, _matched_string, context)
macro_attributes[:id] = relative_id(macro_attributes, context) if relative_embed?(macro_attributes)
- tag_options = { data: macro_attributes }
- if work_package_embed?(macro_attributes)
- tag_options[:aria] = {
- description: I18n.t("js.editor.macro.attribute_reference.aria_label_work_package_attribute")
- }
- end
-
ApplicationController.helpers.content_tag "opce-macro-attribute-#{type}",
"",
- **tag_options
+ data: macro_attributes
end
end
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/base.rb b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
index bc0f8f8228e6..4b465b586f83 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/base.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
@@ -101,17 +101,26 @@ def controller; end
private
def link_to(name = nil, options = nil, html_options = nil, &)
- html_options = (html_options || {})
- .except(:title, "title")
- .merge(aria: { description: resource_link_aria_label })
+ html_options ||= {}
+ title = html_options.delete(:title) || html_options.delete("title")
+ description = [resource_link_aria_label, title].compact.join(". ")
+ aria = (html_options[:aria] || {}).merge(label: accessible_link_label(name, description))
- super(name, options, html_options, &) # rubocop:disable Style/SuperArguments
+ super(name, options, html_options.merge(aria:), &)
end
def resource_link_aria_label
resource = matcher.prefix.presence || (matcher.sep == "r" ? "revision" : "resource")
I18n.t("js.editor.macro.attribute_reference.aria_label_resource_link", resource:)
end
+
+ def accessible_link_label(name, description)
+ visible_name = Nokogiri::HTML.fragment(name.to_s).text.squish
+ plain_description = Nokogiri::HTML.fragment(description.to_s).text.squish
+ I18n.t("js.editor.macro.attribute_reference.aria_label_with_name",
+ name: visible_name,
+ description: plain_description)
+ end
end
end
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
index 8f28caa235ad..71bd7526928f 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
@@ -95,7 +95,8 @@ def render_commit # rubocop:disable Metrics/AbcSize
link_to h("#{matcher.project_prefix}#{matcher.identifier}"),
{ only_path: context[:only_path], controller: "/repositories", action: "revision", project_id: project,
rev: changeset.identifier },
- class: "changeset"
+ class: "changeset",
+ title: truncate_single_line(changeset.comments, length: 100)
end
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/revisions.rb b/lib/open_project/text_formatting/matchers/link_handlers/revisions.rb
index 276e76d3eda3..d031c94f1b67 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/revisions.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/revisions.rb
@@ -53,7 +53,8 @@ def call # rubocop:disable Metrics/AbcSize
link_to(h("#{matcher.project_prefix}r#{matcher.identifier}"),
{ only_path: context[:only_path], controller: "/repositories", action: "revision", project_id: project,
rev: changeset.revision },
- class: "changeset")
+ class: "changeset",
+ title: truncate_single_line(changeset.comments, length: 100))
end
end
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/work_packages.rb b/lib/open_project/text_formatting/matchers/link_handlers/work_packages.rb
index 119f8e94ee37..ce8a5b29ca40 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/work_packages.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/work_packages.rb
@@ -108,10 +108,7 @@ def render_work_package_macro(work_package:, fallback_id:, detailed: false)
ApplicationController.helpers.content_tag "opce-macro-wp-quickinfo",
"",
- data: { id:, display_id:, detailed: },
- aria: {
- description: work_package_link_aria_label
- }
+ data: { id:, display_id:, detailed: }
end
# The label keeps what the author wrote (possibly a historical
diff --git a/modules/documents/spec/application_helper_spec.rb b/modules/documents/spec/application_helper_spec.rb
index e9badf9af10c..0fa704d99ee5 100644
--- a/modules/documents/spec/application_helper_spec.rb
+++ b/modules/documents/spec/application_helper_spec.rb
@@ -67,7 +67,7 @@
link_to("Test document",
{ controller: "documents", action: "show", id: document.id },
class: "document op-uc-link",
- aria: { description: "A dynamic link to a document placed using a macro" },
+ aria: { label: "Test document. A dynamic link to a document placed using a macro" },
target: "_top")
end
@@ -111,7 +111,7 @@
it {
expect(subject).to be_html_eql(
""
)
}
@@ -123,7 +123,7 @@
it {
expect(subject).to be_html_eql(
""
)
}
diff --git a/modules/documents/spec/lib/open_project/markdown_formatting_spec.rb b/modules/documents/spec/lib/open_project/markdown_formatting_spec.rb
index d552898db6cb..600a19617edc 100644
--- a/modules/documents/spec/lib/open_project/markdown_formatting_spec.rb
+++ b/modules/documents/spec/lib/open_project/markdown_formatting_spec.rb
@@ -81,7 +81,7 @@ def controller
"My document",
{ controller: "/documents", action: "show", id: document.id, only_path: true },
class: "document op-uc-link",
- aria: { description: "A dynamic link to a document placed using a macro" },
+ aria: { label: "My document. A dynamic link to a document placed using a macro" },
target: "_top"
)
end
diff --git a/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb b/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb
index ce2fce25c83f..1964e7eb19dd 100644
--- a/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb
+++ b/modules/meeting/spec/lib/open_project/markdown_formatting_spec.rb
@@ -85,7 +85,7 @@ def controller
"Monthly coordination",
{ controller: "/meetings", action: "show", project_id: project.id, id: meeting.id, only_path: true },
class: "meeting op-uc-link",
- aria: { description: "A dynamic link to a meeting placed using a macro" },
+ aria: { label: "Monthly coordination. A dynamic link to a meeting placed using a macro" },
target: "_top"
)
end
diff --git a/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb
index 80bf6b5e1cc3..c79be6ac8944 100644
--- a/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb
+++ b/modules/wikis/app/components/wikis/deferred_inline_page_link_macro_component.html.erb
@@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= content_tag("turbo-frame", id: frame_id, src: frame_src, data: { provider_id:, page_identifier: identifier, type: "wiki-page-link" }) do %>
- <%= render(OpPrimer::InlineMacroComponent.new("aria-description": t(".aria_label"))) do |component| %>
+ <%= render(OpPrimer::InlineMacroComponent.new) do |component| %>
<% component.with_leading_visual_icon(icon: :"op-file-doc") %>
<%= render Primer::Beta::Spinner.new(size: :small, display: :flex, mr: 2) %>
diff --git a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb
index aaae278eb072..55e44d9b0602 100644
--- a/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb
+++ b/modules/wikis/app/components/wikis/inline_page_link_macro_component.html.erb
@@ -32,7 +32,12 @@ See COPYRIGHT and LICENSE files for more details.
page_info_result.either(
->(page_info) do
inline_macro.with_leading_visual_icon(icon: :"op-file-doc")
- render(Primer::Beta::Link.new(href: page_info.href, "aria-description": t("wikis.page_links.aria_label"))) do
+ aria_label = t(
+ "js.editor.macro.attribute_reference.aria_label_with_name",
+ name: page_info.title,
+ description: t("wikis.page_links.aria_label")
+ )
+ render(Primer::Beta::Link.new(href: page_info.href, "aria-label": aria_label)) do
page_info.title
end
end,
diff --git a/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb b/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
index e43bc129c8ba..b531b9591f6e 100644
--- a/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
+++ b/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
@@ -38,11 +38,8 @@
before { render_component }
- it "renders the loading wiki page macro with an ARIA description and no title" do
+ it "renders the loading wiki page macro without a title" do
expect(page).to have_css("turbo-frame[data-type='wiki-page-link']")
- expect(page).to have_css(
- ".op-inline-macro[aria-description='#{I18n.t('wikis.deferred_inline_page_link_macro_component.aria_label')}']"
- )
expect(page).to have_no_css(".op-inline-macro[title]")
end
end
diff --git a/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb b/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
index 1cf97ce96861..42d3786fff34 100644
--- a/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
+++ b/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
@@ -47,10 +47,10 @@
before { render_component }
- it "renders a wiki page macro link with an ARIA description and no title" do
+ it "renders a wiki page macro link with an ARIA label and no title" do
expect(page).to have_link(text: page_info.title, href: page_info.href)
expect(page).to have_css(
- ".op-inline-macro a[aria-description='#{I18n.t('wikis.page_links.aria_label')}']",
+ ".op-inline-macro a[aria-label='#{page_info.title}. #{I18n.t('wikis.page_links.aria_label')}']",
text: page_info.title
)
expect(page).to have_no_css(".op-inline-macro[title]")
diff --git a/spec/lib/api/v3/repositories/revision_representer_spec.rb b/spec/lib/api/v3/repositories/revision_representer_spec.rb
index 403bfde3a8f8..3900d5613b1b 100644
--- a/spec/lib/api/v3/repositories/revision_representer_spec.rb
+++ b/spec/lib/api/v3/repositories/revision_representer_spec.rb
@@ -100,7 +100,7 @@
"class=\"issue work_package\" " \
"data-hover-card-trigger-target=\"trigger\" " \
"data-hover-card-url=\"/work_packages/#{id}/hover_card\" " \
- "aria-description=\"A dynamic link to a work package placed using a macro\" " \
+ "aria-label=\"##{id}. A dynamic link to a work package placed using a macro\" " \
"href=\"/work_packages/#{id}\">" \
"##{id}"
end
diff --git a/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb b/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
index c7fc5e48490e..974e441dd209 100644
--- a/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
+++ b/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
@@ -64,7 +64,7 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(">##{work_package.id}<")
expect(rendered).to include(%(href="/work_packages/#{work_package.id}"))
expect(rendered).to include(%(data-hover-card-url="/work_packages/#{work_package.id}/hover_card"))
- expect(rendered).to include(%(aria-description="A dynamic link to a work package placed using a macro"))
+ expect(rendered).to include(%(aria-label="##{work_package.id}. A dynamic link to a work package placed using a macro"))
expect(rendered).not_to include(" title=")
end
end
@@ -103,7 +103,7 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(%(data-id="#{wp.id}"))
expect(rendered).to include(%(data-display-id="#{wp.display_id}"))
expect(rendered).to include(%(data-detailed="false"))
- expect(rendered).to include(%(aria-description="A dynamic link to a work package placed using a macro"))
+ expect(rendered).not_to include("aria-label")
end
end
@@ -122,7 +122,7 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(%(data-id="#{wp.id}"))
expect(rendered).to include(%(data-display-id="#{wp.display_id}"))
expect(rendered).to include(%(data-detailed="true"))
- expect(rendered).to include(%(aria-description="A dynamic link to a work package placed using a macro"))
+ expect(rendered).not_to include("aria-label")
end
end
@@ -278,7 +278,7 @@ def user_mention_tag(user)
user = create(:user, member_with_roles: { project => role })
rendered = format_text(user_mention_tag(user))
- expect(rendered).to include(%(aria-description="A dynamic link to a user placed using a macro"))
+ expect(rendered).to include(%(aria-label="#{user.name}. A dynamic link to a user placed using a macro"))
expect(rendered).not_to include(" title=")
end
diff --git a/spec/lib/open_project/text_formatting/markdown/attribute_macros_spec.rb b/spec/lib/open_project/text_formatting/markdown/attribute_macros_spec.rb
index 5b558a02c41b..c3fb8a5c8286 100644
--- a/spec/lib/open_project/text_formatting/markdown/attribute_macros_spec.rb
+++ b/spec/lib/open_project/text_formatting/markdown/attribute_macros_spec.rb
@@ -61,16 +61,16 @@
- Inline reference to WP:
+ Inline reference to WP:
- Inline reference to WP by ID:
+ Inline reference to WP by ID:
- Inline reference to WP by ID with CF with a dot:
+ Inline reference to WP by ID with CF with a dot:
- Inline reference to WP by subject:
+ Inline reference to WP by subject:
Inline reference to project:
@@ -113,13 +113,13 @@
- Inline reference to WP:
+ Inline reference to WP:
- Inline reference to WP by ID:
+ Inline reference to WP by ID:
- Inline reference to WP by subject:
+ Inline reference to WP by subject:
Inline reference to project:
diff --git a/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb b/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb
index ddba26667b5d..a1b302143a54 100644
--- a/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb
+++ b/spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb
@@ -84,14 +84,20 @@
link_to("r#{changeset1.revision}",
{ controller: "repositories", action: "revision", project_id: identifier, rev: changeset1.revision },
class: "changeset op-uc-link",
- aria: { description: "A dynamic link to a revision placed using a macro" },
+ aria: {
+ label: "r#{changeset1.revision}. A dynamic link to a revision placed using a macro. " \
+ "#{changeset1.comments}"
+ },
target: "_top")
end
let(:changeset_link2) do
link_to("r#{changeset2.revision}",
{ controller: "repositories", action: "revision", project_id: identifier, rev: changeset2.revision },
class: "changeset op-uc-link",
- aria: { description: "A dynamic link to a revision placed using a macro" },
+ aria: {
+ label: "r#{changeset2.revision}. A dynamic link to a revision placed using a macro. " \
+ "#{changeset2.comments}"
+ },
target: "_top")
end
@@ -144,7 +150,7 @@
{ controller: "versions", action: "show", id: version.id },
class: "version op-uc-link",
target: "_top",
- aria: { description: "A dynamic link to a version placed using a macro" })
+ aria: { label: "1.0. A dynamic link to a version placed using a macro" })
end
context "Link with version id" do
@@ -201,7 +207,7 @@
"project plan with milestones",
project_work_packages_path([query.project.id], query_id: query.id),
class: "query op-uc-link",
- aria: { description: "A dynamic link to a view placed using a macro" },
+ aria: { label: "project plan with milestones. A dynamic link to a view placed using a macro" },
target: "_top"
)
end
@@ -225,7 +231,7 @@
"Work packages",
project_work_packages_path([project.id]),
class: "query op-uc-link",
- aria: { description: "A dynamic link to a view placed using a macro" },
+ aria: { label: "Work packages. A dynamic link to a view placed using a macro" },
target: "_top"
)
end
@@ -264,7 +270,8 @@
project_forum_topic_path(project, forum, message1),
class: 'message op-uc-link',
aria: {
- description: 'A dynamic link to a message placed using a macro'
+ label: "#{message1.subject}. " \
+ 'A dynamic link to a message placed using a macro'
},
target: '_top')}
")
}
@@ -277,7 +284,7 @@
link = link_to(message2.subject,
project_forum_topic_path(project, forum, message1, anchor: "message-#{message2.id}", r: message2.id),
class: "message op-uc-link",
- aria: { description: "A dynamic link to a message placed using a macro" },
+ aria: { label: "#{message2.subject}. A dynamic link to a message placed using a macro" },
target: "_top")
expect(subject).to be_html_eql("#{link}
")
}
@@ -293,7 +300,7 @@
hover_card_url: hover_card_work_package_path(work_package.id)
},
class: "issue work_package op-uc-link",
- aria: { description: "A dynamic link to a work package placed using a macro" },
+ aria: { label: "##{work_package.id}. A dynamic link to a work package placed using a macro" },
target: "_top")
end
@@ -323,8 +330,7 @@
let(:work_package_link) do
content_tag "opce-macro-wp-quickinfo",
"",
- data: { id: "1234", display_id: "1234", detailed: "false" },
- aria: { description: "A dynamic link to a work package placed using a macro" }
+ data: { id: "1234", display_id: "1234", detailed: "false" }
end
subject { format_text("foo (bar ##1234)") }
@@ -336,8 +342,7 @@
let(:work_package_link) do
content_tag "opce-macro-wp-quickinfo",
"",
- data: { id: "1234", display_id: "1234", detailed: "true" },
- aria: { description: "A dynamic link to a work package placed using a macro" }
+ data: { id: "1234", display_id: "1234", detailed: "true" }
end
subject { format_text("foo (bar ###1234)") }
@@ -370,7 +375,7 @@
hover_card_url: hover_card_work_package_path(work_package.id)
},
class: "issue work_package op-uc-link",
- aria: { description: "A dynamic link to a work package placed using a macro" },
+ aria: { label: "##{work_package.id}. A dynamic link to a work package placed using a macro" },
target: "_top")
end
@@ -399,7 +404,8 @@
expect(subject).to be_html_eql("#{link_to(subproject.name, project_url,
target: '_top',
aria: {
- description: 'A dynamic link to a project placed using a macro'
+ label: "#{subproject.name}. " \
+ 'A dynamic link to a project placed using a macro'
},
class: 'project op-uc-link')}
")
}
@@ -412,7 +418,8 @@
expect(subject).to be_html_eql("#{link_to(subproject.name, project_url,
target: '_top',
aria: {
- description: 'A dynamic link to a project placed using a macro'
+ label: "#{subproject.name}. " \
+ 'A dynamic link to a project placed using a macro'
},
class: 'project op-uc-link')}
")
}
@@ -425,7 +432,8 @@
expect(subject).to be_html_eql("#{link_to(subproject.name, project_url,
target: '_top',
aria: {
- description: 'A dynamic link to a project placed using a macro'
+ label: "#{subproject.name}. " \
+ 'A dynamic link to a project placed using a macro'
},
class: 'project op-uc-link')}
")
}
@@ -627,7 +635,7 @@ def resource_link_to(resource, name, url, **)
link_to(name,
url,
**,
- aria: { description: "A dynamic link to a #{resource} placed using a macro" })
+ aria: { label: "#{name}. A dynamic link to a #{resource} placed using a macro" })
end
before do
@@ -709,7 +717,7 @@ def resource_link_to(resource, name, url, **)
let(:expected) do
<<~EXPECTED
-
+
[[CookBook documentation]]
diff --git a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
index 58ab042e960d..5a5fa25d2bb7 100644
--- a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
+++ b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
@@ -85,7 +85,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -184,7 +184,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -212,7 +212,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -241,7 +241,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -273,7 +273,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -302,7 +302,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { description: 'A dynamic link to a user placed using a macro' },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -373,7 +373,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="/users/#{user.id}/hover_card"
href="/users/#{user.id}"
- aria-description="A dynamic link to a user placed using a macro">Foo Barrit
+ aria-label="Foo Barrit. A dynamic link to a user placed using a macro. User Foo Barrit">Foo Barrit
EXPECTED
end
@@ -400,7 +400,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="http://openproject.org/users/#{user.id}/hover_card"
href="http://openproject.org/users/#{user.id}"
- aria-description="A dynamic link to a user placed using a macro">Foo Barrit
+ aria-label="Foo Barrit. A dynamic link to a user placed using a macro. User Foo Barrit">Foo Barrit
EXPECTED
end
@@ -436,7 +436,7 @@
+ aria-label="#{linked_project_member_group.name}. A dynamic link to a group placed using a macro. Group #{linked_project_member_group.name}">
#{linked_project_member_group.name}
@@ -482,7 +482,7 @@
+ aria-label="#{linked_project_member_group.name}. A dynamic link to a group placed using a macro">
#{linked_project_member_group.name}
diff --git a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
index ccab6c5a20e9..0c09f8014c23 100644
--- a/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
+++ b/spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb
@@ -177,7 +177,7 @@
expect(macro["data-id"]).to eq(wp.id.to_s)
expect(macro["data-display-id"]).to eq(wp.display_id)
expect(macro["data-detailed"]).to eq("false")
- expect(macro["aria-description"]).to eq("A dynamic link to a work package placed using a macro")
+ expect(macro["aria-label"]).to be_nil
end
it "renders `###PROJ-N` as a detailed quickinfo macro element" do
@@ -188,7 +188,7 @@
expect(macro["data-id"]).to eq(wp.id.to_s)
expect(macro["data-display-id"]).to eq(wp.display_id)
expect(macro["data-detailed"]).to eq("true")
- expect(macro["aria-description"]).to eq("A dynamic link to a work package placed using a macro")
+ expect(macro["aria-label"]).to be_nil
end
context "when the referenced work package does not exist" do
diff --git a/spec/requests/api/v3/render_resource_spec.rb b/spec/requests/api/v3/render_resource_spec.rb
index 19487f47bc80..eac91ccb6928 100644
--- a/spec/requests/api/v3/render_resource_spec.rb
+++ b/spec/requests/api/v3/render_resource_spec.rb
@@ -95,7 +95,7 @@
##{id}
@@ -191,7 +191,7 @@
#1
\n\nwith two lines.
HTML
From 88bd22d32b44111e1c3e4046882797858e86ec14 Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
Date: Mon, 6 Jul 2026 13:01:54 +0200
Subject: [PATCH 08/11] Fix redundant ARIA labels for user and group links
---
lib/open_project/object_linking.rb | 4 ++--
.../matchers/link_handlers/colon_separator.rb | 2 +-
.../matchers/link_handlers/hash_separator.rb | 6 ++++--
modules/wikis/config/locales/en.yml | 1 -
.../text_formatting/markdown/mentions_spec.rb | 16 ++++++++--------
5 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/lib/open_project/object_linking.rb b/lib/open_project/object_linking.rb
index a130d27cd3ac..3959abdf1125 100644
--- a/lib/open_project/object_linking.rb
+++ b/lib/open_project/object_linking.rb
@@ -49,7 +49,7 @@ def link_to_user(user, options = {}) # rubocop:disable Metrics/AbcSize
only_path = options.delete(:only_path) { true }
name = options.delete(:name) { user.name }
- options[:title] ||= I18n.t(:label_user_named, name:)
+ options[:title] = I18n.t(:label_user_named, name:) unless options.key?(:title)
add_hover_card_options(user, options, only_path:)
@@ -63,7 +63,7 @@ def link_to_group(group, options = {})
name = group.name
href = show_group_url(group,
only_path: options.delete(:only_path) { true })
- options[:title] ||= I18n.t(:label_group_named, name:)
+ options[:title] = I18n.t(:label_group_named, name:) unless options.key?(:title)
link_to(name, href, options)
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
index 71bd7526928f..6615b0fbc781 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/colon_separator.rb
@@ -139,7 +139,7 @@ def render_project
def render_user
if (user = User.visible.find_by(login: oid))
- link_to_user(user, only_path: context[:only_path], class: "user-mention")
+ link_to_user(user, only_path: context[:only_path], class: "user-mention", title: nil)
end
end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb b/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb
index 1d3555bc005f..79a460f53eca 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/hash_separator.rb
@@ -109,7 +109,8 @@ def render_user
if user
link_to_user(user,
only_path: context[:only_path],
- class: "user-mention")
+ class: "user-mention",
+ title: nil)
end
end
@@ -119,7 +120,8 @@ def render_group
if group
link_to_group(group,
only_path: context[:only_path],
- class: "user-mention")
+ class: "user-mention",
+ title: nil)
end
end
diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml
index f751d9802e39..ab23fef8e439 100644
--- a/modules/wikis/config/locales/en.yml
+++ b/modules/wikis/config/locales/en.yml
@@ -132,7 +132,6 @@ en:
create_new_wiki_page_dialog:
title: Create new wiki page
deferred_inline_page_link_macro_component:
- aria_label: A dynamic link to a wiki page placed using a macro
loading: Loading…
delete_relation_page_link_confirmation_dialog:
heading: Delete related wiki page link?
diff --git a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
index 5a5fa25d2bb7..d778242dd48c 100644
--- a/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
+++ b/spec/lib/open_project/text_formatting/markdown/mentions_spec.rb
@@ -184,7 +184,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -212,7 +212,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -241,7 +241,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -273,7 +273,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -302,7 +302,7 @@
#{link_to(linked_project_member.name,
{ controller: :users, action: :show, id: linked_project_member.id },
- aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro. User #{linked_project_member.name}" },
+ aria: { label: "#{linked_project_member.name}. A dynamic link to a user placed using a macro" },
class: 'user-mention op-uc-link',
target: '_top',
data: {
@@ -373,7 +373,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="/users/#{user.id}/hover_card"
href="/users/#{user.id}"
- aria-label="Foo Barrit. A dynamic link to a user placed using a macro. User Foo Barrit">Foo Barrit
+ aria-label="Foo Barrit. A dynamic link to a user placed using a macro">Foo Barrit
EXPECTED
end
@@ -400,7 +400,7 @@
data-hover-card-trigger-target="trigger"
data-hover-card-url="http://openproject.org/users/#{user.id}/hover_card"
href="http://openproject.org/users/#{user.id}"
- aria-label="Foo Barrit. A dynamic link to a user placed using a macro. User Foo Barrit">Foo Barrit
+ aria-label="Foo Barrit. A dynamic link to a user placed using a macro">Foo Barrit
EXPECTED
end
@@ -436,7 +436,7 @@
+ aria-label="#{linked_project_member_group.name}. A dynamic link to a group placed using a macro">
#{linked_project_member_group.name}
From 2aa57e1d9d5c691605f77e0ea0773378f90c91c7 Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
Date: Mon, 6 Jul 2026 14:02:26 +0200
Subject: [PATCH 09/11] Update feature specs
---
config/locales/js-en.yml | 2 +-
...d_inline_page_link_macro_component_spec.rb | 27 +++++++--
.../inline_page_link_macro_component_spec.rb | 58 ++++++++++++++++---
.../filters/mention_filter_spec.rb | 4 --
4 files changed, 72 insertions(+), 19 deletions(-)
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index c1ed4214a3a1..3f2c13ebe566 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -174,8 +174,8 @@ en:
wysiwyg: "Switch to WYSIWYG editor"
macro:
attribute_reference:
- aria_label_with_name: "%{name}. %{description}"
aria_label_resource_link: "A dynamic link to a %{resource} placed using a macro"
+ aria_label_with_name: "%{name}. %{description}"
aria_label_work_package_attribute: "A dynamic work package attribute placed using a macro"
aria_label_work_package_link: "A dynamic link to a work package placed using a macro"
invalid_attribute: "The selected attribute '%{name}' does not exist."
diff --git a/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb b/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
index b531b9591f6e..059971e30da2 100644
--- a/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
+++ b/modules/wikis/spec/components/wikis/deferred_inline_page_link_macro_component_spec.rb
@@ -32,14 +32,29 @@
require_module_spec_helper
RSpec.describe Wikis::DeferredInlinePageLinkMacroComponent, type: :component do
- subject(:render_component) do
- render_inline(described_class.new(identifier: "1234", provider_id: "42"))
- end
+ subject(:render_component) { render_inline(component) }
+
+ let(:identifier) { "1234" }
+ let(:provider_id) { "42" }
+ let(:component) { described_class.new(identifier:, provider_id:) }
before { render_component }
- it "renders the loading wiki page macro without a title" do
- expect(page).to have_css("turbo-frame[data-type='wiki-page-link']")
- expect(page).to have_no_css(".op-inline-macro[title]")
+ it "renders a frame that loads the requested wiki page link" do
+ frame = page.find("turbo-frame[data-type='wiki-page-link']")
+
+ expect(frame[:id]).to eq(component.frame_id)
+ expect(frame[:src]).to eq(component.frame_src)
+ expect(frame["data-provider-id"]).to eq(provider_id)
+ expect(frame["data-page-identifier"]).to eq(identifier)
+ end
+
+ it "renders the loading macro" do
+ frame = page.find("turbo-frame[data-type='wiki-page-link']")
+
+ expect(frame).to have_css(
+ ".op-inline-macro",
+ text: I18n.t("wikis.deferred_inline_page_link_macro_component.loading")
+ )
end
end
diff --git a/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb b/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
index 42d3786fff34..77c9705ae88a 100644
--- a/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
+++ b/modules/wikis/spec/components/wikis/inline_page_link_macro_component_spec.rb
@@ -42,17 +42,59 @@
)
end
let(:page_info_result) { Success(page_info) }
+ let(:component) { described_class.new(page_info_result) }
- subject(:render_component) { render_inline(described_class.new(page_info_result)) }
+ subject(:render_component) { render_inline(component) }
before { render_component }
- it "renders a wiki page macro link with an ARIA label and no title" do
- expect(page).to have_link(text: page_info.title, href: page_info.href)
- expect(page).to have_css(
- ".op-inline-macro a[aria-label='#{page_info.title}. #{I18n.t('wikis.page_links.aria_label')}']",
- text: page_info.title
- )
- expect(page).to have_no_css(".op-inline-macro[title]")
+ context "when the page information is available" do
+ it "renders an accessible link to the wiki page" do
+ macro = page.find(".op-inline-macro")
+ link = macro.find("a", text: page_info.title, exact_text: true)
+
+ expect(link[:href]).to eq(page_info.href)
+ expect(link["aria-label"]).to eq(
+ "#{page_info.title}. #{I18n.t('wikis.page_links.aria_label')}"
+ )
+ end
+ end
+
+ context "when retrieving the page information fails" do
+ let(:page_info_result) do
+ Failure(
+ Wikis::Adapters::Results::Error.new(
+ source: Wikis::Adapters::Providers::Internal::Queries::PageInfo,
+ code: error_code
+ )
+ )
+ end
+
+ shared_examples "an unresolved wiki page link" do |error_text_key|
+ it "renders the error without a link" do
+ macro = page.find(".op-inline-macro")
+
+ expect(macro).to have_text(I18n.t(error_text_key))
+ expect(macro).to have_no_link
+ end
+ end
+
+ context "when the page was not found" do
+ let(:error_code) { :not_found }
+
+ it_behaves_like "an unresolved wiki page link", "wikis.page_links.errors.page_not_found"
+ end
+
+ context "when access to the page is forbidden" do
+ let(:error_code) { :forbidden }
+
+ it_behaves_like "an unresolved wiki page link", "wikis.page_links.errors.page_access_forbidden"
+ end
+
+ context "when an unexpected error occurs" do
+ let(:error_code) { :timeout }
+
+ it_behaves_like "an unresolved wiki page link", "wikis.page_links.errors.unexpected"
+ end
end
end
diff --git a/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb b/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
index 974e441dd209..85cfa316fb7b 100644
--- a/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
+++ b/spec/lib/open_project/text_formatting/filters/mention_filter_spec.rb
@@ -65,7 +65,6 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(%(href="/work_packages/#{work_package.id}"))
expect(rendered).to include(%(data-hover-card-url="/work_packages/#{work_package.id}/hover_card"))
expect(rendered).to include(%(aria-label="##{work_package.id}. A dynamic link to a work package placed using a macro"))
- expect(rendered).not_to include(" title=")
end
end
@@ -103,7 +102,6 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(%(data-id="#{wp.id}"))
expect(rendered).to include(%(data-display-id="#{wp.display_id}"))
expect(rendered).to include(%(data-detailed="false"))
- expect(rendered).not_to include("aria-label")
end
end
@@ -122,7 +120,6 @@ def mention_tag(work_package, sep: "#", data_id: nil, data_text: nil)
expect(rendered).to include(%(data-id="#{wp.id}"))
expect(rendered).to include(%(data-display-id="#{wp.display_id}"))
expect(rendered).to include(%(data-detailed="true"))
- expect(rendered).not_to include("aria-label")
end
end
@@ -279,7 +276,6 @@ def user_mention_tag(user)
rendered = format_text(user_mention_tag(user))
expect(rendered).to include(%(aria-label="#{user.name}. A dynamic link to a user placed using a macro"))
- expect(rendered).not_to include(" title=")
end
it "loads many mentioned users with a single users SELECT keyed by id" do
From 465ffa421de6fc1c1f052fa7f7fa8cffc330dc11 Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
Date: Mon, 6 Jul 2026 14:45:32 +0200
Subject: [PATCH 10/11] Fix redundant ARIA labels and extract
accessible_link_label helper
---
.../text_formatting/filters/mention_filter.rb | 9 +---
.../helpers/accessible_link_label.rb | 46 +++++++++++++++++++
.../matchers/link_handlers/base.rb | 8 +---
3 files changed, 48 insertions(+), 15 deletions(-)
create mode 100644 lib/open_project/text_formatting/helpers/accessible_link_label.rb
diff --git a/lib/open_project/text_formatting/filters/mention_filter.rb b/lib/open_project/text_formatting/filters/mention_filter.rb
index dca2cb9162ab..45f320aa0504 100644
--- a/lib/open_project/text_formatting/filters/mention_filter.rb
+++ b/lib/open_project/text_formatting/filters/mention_filter.rb
@@ -35,6 +35,7 @@ class MentionFilter < HTML::Pipeline::Filter
include ActionView::Helpers::UrlHelper
include OpenProject::ObjectLinking
include OpenProject::StaticRouting::UrlHelpers
+ include OpenProject::TextFormatting::Helpers::AccessibleLinkLabel
def call
preload_mentions
@@ -159,14 +160,6 @@ def resource_link_aria_label(resource)
I18n.t("js.editor.macro.attribute_reference.aria_label_resource_link", resource:)
end
- def accessible_link_label(name, description)
- visible_name = Nokogiri::HTML.fragment(name.to_s).text.squish
- plain_description = Nokogiri::HTML.fragment(description.to_s).text.squish
- I18n.t("js.editor.macro.attribute_reference.aria_label_with_name",
- name: visible_name,
- description: plain_description)
- end
-
def work_package_link(work_package)
display_id = work_package.display_id
link_to(work_package.formatted_id,
diff --git a/lib/open_project/text_formatting/helpers/accessible_link_label.rb b/lib/open_project/text_formatting/helpers/accessible_link_label.rb
new file mode 100644
index 000000000000..c2d87aa352cd
--- /dev/null
+++ b/lib/open_project/text_formatting/helpers/accessible_link_label.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+#-- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) the OpenProject GmbH
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# See COPYRIGHT and LICENSE files for more details.
+#++
+
+module OpenProject::TextFormatting
+ module Helpers
+ # Shared helper for building composite ARIA labels on macro-generated links.
+ # Combines the visible link text with a plain-text context description,
+ # stripping any HTML from both before interpolating.
+ module AccessibleLinkLabel
+ def accessible_link_label(name, description)
+ visible_name = Nokogiri::HTML.fragment(name.to_s).text.squish
+ plain_description = Nokogiri::HTML.fragment(description.to_s).text.squish
+ I18n.t("js.editor.macro.attribute_reference.aria_label_with_name",
+ name: visible_name,
+ description: plain_description)
+ end
+ end
+ end
+end
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/base.rb b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
index 4b465b586f83..08b6f97f91a2 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/base.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
@@ -32,6 +32,7 @@ module OpenProject::TextFormatting::Matchers
module LinkHandlers
class Base
include ::OpenProject::TextFormatting::Truncation
+ include ::OpenProject::TextFormatting::Helpers::AccessibleLinkLabel
# used for the work package quick links
include WorkPackagesHelper
# Used for escaping helper 'h()'
@@ -114,13 +115,6 @@ def resource_link_aria_label
I18n.t("js.editor.macro.attribute_reference.aria_label_resource_link", resource:)
end
- def accessible_link_label(name, description)
- visible_name = Nokogiri::HTML.fragment(name.to_s).text.squish
- plain_description = Nokogiri::HTML.fragment(description.to_s).text.squish
- I18n.t("js.editor.macro.attribute_reference.aria_label_with_name",
- name: visible_name,
- description: plain_description)
- end
end
end
end
From caa5f9505d4fb30cf1c4c24acb3bb05b54995e3d Mon Sep 17 00:00:00 2001
From: Behrokh Satarnejad
Date: Mon, 6 Jul 2026 16:54:52 +0200
Subject: [PATCH 11/11] Remove unrelated macro fallback changes
---
.../components/fields/macros/attribute-label-macro.component.ts | 2 +-
.../components/fields/macros/attribute-value-macro.component.ts | 2 +-
lib/open_project/text_formatting/matchers/link_handlers/base.rb | 1 -
3 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
index 964a5ba750c5..9cf37298a6aa 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
+++ b/frontend/src/app/shared/components/fields/macros/attribute-label-macro.component.ts
@@ -109,7 +109,7 @@ export class AttributeLabelMacroComponent implements OnInit {
}
const schema = await this.schemaCache.ensureLoaded(this.resource);
- this.attribute = schema.attributeFromLocalizedName(attributeName) ?? attributeName;
+ this.attribute = schema.attributeFromLocalizedName(attributeName) || attributeName;
this.label = (schema[this.attribute] as IOPFieldSchema|undefined)?.name;
if (!this.label) {
diff --git a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
index cd0b51df76d9..9c3ab821736d 100644
--- a/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
+++ b/frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts
@@ -121,7 +121,7 @@ export class AttributeValueMacroComponent implements OnInit {
const schema = await this.schemaCache.ensureLoaded(resource);
const proxied = this.schemaCache.proxied(resource, schema);
- const attribute = schema.attributeFromLocalizedName(attributeName) ?? this.dateAttribute(resource, proxied, attributeName);
+ const attribute = schema.attributeFromLocalizedName(attributeName) || this.dateAttribute(resource, proxied, attributeName);
const fieldSchema = proxied.ofProperty(attribute) as IFieldSchema|undefined;
if (fieldSchema) {
diff --git a/lib/open_project/text_formatting/matchers/link_handlers/base.rb b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
index 08b6f97f91a2..1a68eb70313e 100644
--- a/lib/open_project/text_formatting/matchers/link_handlers/base.rb
+++ b/lib/open_project/text_formatting/matchers/link_handlers/base.rb
@@ -114,7 +114,6 @@ def resource_link_aria_label
resource = matcher.prefix.presence || (matcher.sep == "r" ? "revision" : "resource")
I18n.t("js.editor.macro.attribute_reference.aria_label_resource_link", resource:)
end
-
end
end
end