265 Commits

Author SHA1 Message Date
Tom Hykel ff6d46b067 Merge pull request #23720 from opf/task/69498-documents-admin-page-type-button-has-a-w
[STC-563] Fix label of new document type button in admin
2026-06-13 17:19:35 +02:00
Tomas Hykel 48b2f9d3ac [OP-18996] fix: Do not adjust capitalization of new document types 2026-06-12 16:20:40 +02:00
Tomas Hykel 4ecd255b59 fix: Documents admin page: "+Type" button has a wrong label ("+Add") (WP #69498) 2026-06-12 13:17:09 +00:00
Oliver Günther d851d25524 Merge remote-tracking branch 'origin/release/17.5' into dev 2026-06-10 11:29:44 +02:00
Kabiru Mwenja bb317278ba Merge pull request #23582 from opf/bug/stc-811-documents-pagination-breaks-upon-filtering-the-list
[STC-811] Fix pagination for filtered Documents & Reserved identifiers
2026-06-09 14:32:29 +03:00
ulferts db815d0d22 Merge remote-tracking branch 'origin/dev' into merge-release/17.5-20260609045502 2026-06-09 09:32:19 +02:00
Judith Roth 595872727c [STC-779] Add tests for CTRL-Z in documents
https://community.openproject.org/wp/STC-779
2026-06-08 16:26:19 +02:00
Tom Hykel 10c85224a9 Update modules/documents/spec/controllers/documents_controller_spec.rb
Co-authored-by: Kabiru Mwenja <k.mwenja@openproject.com>
2026-06-08 09:42:24 +02:00
Tomas Hykel 285f3381a9 [STC-811] Fix pagination for filtered Documents & Reserved identifiers 2026-06-05 14:18:05 +02:00
ihordubas99 4e73bd47d3 remove spec that cannot be reliably reproduced after BlockNote 0.51 upgrade 2026-06-03 14:56:04 +03:00
Alexander Brandon Coles fa5b7ee1b5 Merge remote-tracking branch 'opf/dev' into merge-release/17.5-20260603051246 2026-06-03 09:52:08 +02:00
Judith Roth 90317785d7 [#74654] Test markdown generation of BlockNote extensions
https://community.openproject.org/wp/74654
2026-06-02 17:02:40 +02:00
OpenProject Actions CI b2f72fd486 Merge branch 'release/17.5' into dev 2026-05-30 04:49:04 +00:00
Judith Roth 4dd97c267c [#75231] Add tests for inserting WP links with # notation
https://community.openproject.org/wp/75231

in BlockNote editor
2026-05-29 10:30:29 +02:00
Judith Roth f1b1ab307d [#75231] Fix test for op-blocknote-extensions in open project
https://community.openproject.org/wp/75231
2026-05-29 10:30:29 +02:00
Kabiru Mwenja f12fc7551f Migrate remaining mailers to format_mail_html / format_mail_text (#23387)
The view-helper migration introduced by the parent PR now covers the
remaining mailer surfaces: UserMailer (`message_posted`, `news_added`,
`news_comment_added`), ProjectMailer (`project_created`),
ProjectArtifactsMailer (`creation_wizard_submitted`), MemberMailer
(`added_project`, `updated_project`, `updated_global`), AnnouncementMailer
(`announce`), DocumentsMailer (`document_added`), and the shared mailer
layout (`localized_emails_header`, `localized_emails_footer`).

Sites drop the `static_html: true` / `only_path: false` / `plain_text: true`
boilerplate; `render_mode:` pinning lives in the helper.

The layout previously called `OpenProject::TextFormatting::Renderer.format_text`
directly, bypassing the helper layer. The empty visibility cache (no
current_user-scoped preload at layout time) is handled by the existing
fallback in `LinkHandlers::WorkPackages#text_only?` — covered by a new
sanity spec in `user_mailer_spec.rb` that exercises the header path with
a WP reference and asserts plain-text formatted_id rendering.

Per-bucket regression coverage added: absolute-URL and formatted_id
assertions across both classic and semantic identifier modes, mirroring
the WorkPackageMailer spec pattern.
2026-05-28 12:48:43 +03:00
Kabiru Mwenja 8a213e2c03 Move browser-action helpers to spec/support sibling file
Park BlockNoteEditorBrowserActions next to BlockNoteEditorInput in
spec/support/form_fields/primerized/. spec/support/**/*.rb is autoloaded
by rails_helper, so the spec file just `include`s the qualified module
name. Keeps raw-driver concerns (DOM Range selection, paste
ClipboardEvent dispatch, W3C-action Delete) out of the high-level page
object — that one stays focused on semantic actions like paste_links
and attach_file — and avoids the test-file fatigue of a 50-line helper
module inside the describe scope.
2026-05-21 15:36:39 +03:00
Kabiru Mwenja 28c94cfb46 Extract browser-action helpers in external-link spec
The two delete-path tests and the multi-inline-node paste test each
inlined a slab of `page.execute_script` to drive the editor's
contenteditable inside its shadow root, with significant duplication
around DOM Range setup and ClipboardEvent construction.

Pull these into a `BlockNoteEditorBrowserActions` module:

- `select_text_in_external_link(start_offset:, end_offset:)` — selects
  a substring of the first link's text node, with String-slicing
  semantics for the offsets (negatives count from the end).
- `send_forward_delete` — forward Delete via the W3C actions API,
  needed because Capybara's send_keys does not reach PM's editable in
  this setup.
- `paste_clipboard_into(element, html:, plain:)` — fires a paste
  ClipboardEvent on the given editor element.

The deletion-test comments are also tightened to describe the current
invariant (apply gate reseats the widget on any range deletion) rather
than the pre-rebuild mapping mechanics they used to enumerate.
2026-05-21 11:58:36 +03:00
Kabiru Mwenja 4113a2c382 Add spec for editing tail of surviving external link
Locks in the invariant that editing inside an existing link routes
through decoration mapping rather than a rebuild: the ReplaceStep
slice carries no link mark, so the apply gate maps the existing
widget set instead of recomputing it. The screen-reader hint must
shrink with the link run from the right and stay anchored to the
new tail, leaving exactly one hint on the surviving link.
2026-05-21 11:20:41 +03:00
Kabiru Mwenja ee4dfdeb19 Drop "Limitation" doc, codify deletion invariant in spec
The orphan-widget concern Copilot raised on the apply gate isn't
reachable. Forcing the gate's slice check off and deleting a whole
linked range still produces a clean DOM — PM's WidgetType.map
resolves the widget's position with assoc=-1 (which the widget's
side: -1 enforces), finds the anchor inside the deleted content,
and reports deleted: true. PM drops the decoration on its own.

Replacing the "Limitation" JSDoc paragraph (which described a
mental model that doesn't match WidgetType.map's rules) with a
feature spec that exercises a single-tx delete via DOM Range +
W3C actions Delete. The spec fails if side, the apply gate, or
buildDecorations ever stops preserving the invariant — serving
the disclosure purpose the JSDoc tried to, but mechanically.
2026-05-21 11:20:40 +03:00
Kabiru Mwenja cd735c01de Separate sr-only link hint from preceding link text
The sr-only "Open link in a new tab" hint becomes part of the link's
computed accessible name. Without a separator between the link text and
the hint, AT can announce them as a single concatenated word — descendant
text-node concatenation isn't guaranteed to insert whitespace, especially
in contenteditable. Prefix the widget text with an NBSP and relax the
feature spec to use `include` so the separator detail stays an
implementation concern of the extension.
2026-05-21 11:20:40 +03:00
Kabiru Mwenja 50b1a986c6 Add screen-reader hint for external links in BlockNote
VoiceOver and NVDA do not announce aria-describedby on links inside
contenteditable regions — both screen readers switch to edit mode and
ignore supplementary ARIA there. Even moving the attribute to the <a>
itself does not help.

Add an ExternalLinkA11yExtension that injects a sr-only,
contenteditable="false" span as a child of each external <a> via a
ProseMirror widget decoration wrapped in the link mark. The span text
becomes part of the link's accessible name, which screen readers
announce in every mode.

- Widget uses marks: [linkMark] + side: -1 so it renders inside the
  anchor and stays attached to the preceding link run on insertion.
- sameLinkContinues coalesces runs split across inline nodes (e.g.
  link text with a nested bold mark) so each link gets exactly one hint.
- readDescription warns once if #open-blank-target-link-description is
  missing, surfacing silent empty-hint regressions.
- Extracts isHrefExternal from isLinkExternal so the extension can
  check ProseMirror mark attrs (URL strings) directly.
- Decorations never mutate the document model — no DOMObserver
  mutation loop, no Yjs/collaboration side effects, no persistence.

References https://community.openproject.org/wp/73721
2026-05-21 11:20:38 +03:00
Judith Roth 3a97308083 [#73664] Create work package links through # notation in documents / BlockNote
https://community.openproject.org/wp/73664
2026-05-20 14:56:40 +02:00
Ivan Kuchin bd7a0cfde1 Merge pull request #22930 from opf/fix-multiple-equality-non-equailty-filters
fix multiple equality, non equality filters
2026-05-07 15:52:07 +02:00
Ivan Kuchin 7756f44acc fix multiple equality, non equality filters 2026-05-05 20:33:25 +02:00
Judith Roth f9b0f21c8b Merge pull request #23009 from opf/jr/bump/op-blocknote-extensions-version-to-0-0-24
Update op-blocknote-extensions to version 0.0.24 for improvements in the search input
2026-05-05 13:12:10 +02:00
Judith Roth c38d935f59 [#69706] Fix tests for op-blocknote-extensions 0.0.24
In 0.0.24 a callback was added that removes the whole search input from
the DOM / document when a blur event (e.g. clicking anywhere else)
occurs. This broke the capybara tests, since they somehow triggered that
blur and then the whole input was removed from the DOM before the tests
were finished testing it.
Therefore now all interaction of with the work package search input is
done via js whithout any interruptions that cause a blur event.
I am not happy about this solution and asked to relax that blur
requirement to e.g. only remove the search when the document / browser
tab is closed, but this was refused. So this is the only solution to
keep the tests in at all.

https://community.openproject.org/wp/69706
2026-05-04 14:51:36 +02:00
Kabiru Mwenja 92a1346f81 Merge pull request #22696 from opf/feature/blocknote-native-extensions
(Blocknote): Add external link capture
2026-04-23 13:41:48 +03:00
Kabiru Mwenja 01cd3f5663 Drop block-form rescue modifier in capture window cleanup
Lint/RedundantCopDisableDirective flagged the inline Style/RescueModifier
disable as unnecessary on CI. Locally the cop fires and the disable is
needed, so the constructs disagree across environments. Rewrite the
ensure-block cleanup to use a rescue-in-do form instead — no modifier,
no directive, same behavior.
2026-04-23 13:02:56 +03:00
Kabiru Mwenja 2077f295cc Migrate from TipTap extensions to BlockNote native extensions
Use BlockNote's createExtension() with prosemirrorPlugins instead of
TipTap's Extension.create() via the internal _tiptapOptions escape
hatch. The BlockNote team has clarified that _tiptapOptions is not
part of the public API and should not be relied on.

Functionally identical — same ProseMirror plugin, same mousedown
interception — just wrapped in the supported BlockNote primitive.
2026-04-23 12:18:17 +03:00
Kabiru Mwenja 81dd632371 Harden link capture plugin and improve test reliability
Handle Text node event targets by normalizing to parentElement
before calling closest('a'). Guard against clicks on anchors
outside the editor content with view.dom.contains(). Replace
brittle sleep with rspec-wait polling for window count.
2026-04-23 12:15:17 +03:00
Kabiru Mwenja db857fd872 Fix double-window bug by moving link capture to ProseMirror plugin
TipTap's Link extension handles clicks via ProseMirror's mouseup-based
handleClick, which fires before any DOM click event. The Stimulus
controller's click interception couldn't prevent TipTap from also
opening a window, resulting in two windows on every external link click.

Replace the Stimulus click interception with a TipTap extension that
uses handleDOMEvents.mousedown. Returning true from mousedown prevents
ProseMirror from creating its internal MouseDown tracker, so the entire
handleClick chain never fires. Only our redirect window opens.

The extension is conditionally registered via _tiptapOptions only when
external link capture is enabled. When disabled, TipTap's default
openOnClick behavior handles link clicks natively.
2026-04-23 12:15:17 +03:00
Kabiru Mwenja 8a48ee7a22 Add click-interception for external links in BlockNote editor
ProseMirror's internal DOMObserver re-parses and re-renders any node
whose attributes change, creating infinite loops when the body-level
ExternalLinksController writes target, rel, aria-describedby, or
rewrites href on links inside the editor.

Instead of modifying the DOM, a standalone ProseMirrorExternalLinksController
intercepts clicks on external links and routes them through
/external_redirect via window.open. The document model retains original
URLs, Yjs collaboration is unaffected, and no re-render loops occur.

TipTap's Link extension already renders target="_blank" and
rel="noopener noreferrer nofollow" from its mark schema defaults,
so those attributes are handled natively by ProseMirror.

Shared link utilities (isLinkExternal, shouldProcessLink,
buildRedirectUrl) are extracted into link-handling helpers so both
controllers use a single source of truth without inheritance coupling.
2026-04-23 12:15:16 +03:00
Oliver Günther b7516040a9 Merge remote-tracking branch 'origin/release/17.3' into dev 2026-04-21 12:07:33 +02:00
Oliver Günther ef6ba24d90 Prevent moving of projects when using documents update service 2026-04-21 12:06:51 +02:00
Kabiru Mwenja 8c0d0c28b1 Merge pull request #22564 from opf/refinements/72665-collaboration-improvements
Collaboration refinements: require HocuspocusProvider, context-aware error messages
2026-04-01 14:15:39 +03:00
Kabiru Mwenja 5e3dd6a1d1 fix(documents): strip invisible characters from document titles
Documents created with zero-width Unicode characters (e.g. U+200B)
in their titles become unclickable on the index page, making them
hard to manage or delete.

Introduce RemoveInvisibleCharacters normalizer, replacing the former
RemoveAsciiControlCharacters. It strips both ASCII control characters
and Unicode zero-width characters, with each category defined as a
named constant for clarity. Apply it to Document#title and update
existing callers (Project#identifier, CustomField#name).

Add a shared RSpec example "strips invisible characters" to verify
normalization consistently across all three models.
2026-03-31 18:18:41 +03:00
ihordubas99 1e09c24945 Implementation/72687 new connection error restore banner v2 (#22591)
---------

Co-authored-by: Kabiru Mwenja <k.mwenja@openproject.com>
2026-03-31 16:25:18 +03:00
Kabiru Mwenja 8272768057 Remove test-mode fallback, require HocuspocusProvider, add context-aware error messages
Extract non-IndexedDB refinements from PR #22125 so they can ship
independently while IndexedDB offline persistence is evaluated separately.

- Gate collaboration on Setting.real_time_text_collaboration_enabled?
  instead of hardcoding it to true
- Remove the test-mode fallback that created a standalone Y.Doc without
  a provider; HocuspocusProvider is now required for document editing
- Refactor useCollaboration hooks: callback-based timeout with proactive
  cancel on sync, extracted useProviderAuthError hook, JSDoc comments
- Add read/write context-aware connection error messages (readonly users
  see "real-time updates will resume" vs writers see "changes will sync")
- Add blocked offline mode: when the server is unreachable and there is
  no local cache, hide the editor entirely to prevent an empty Y.Doc
  from being synced as authoritative content on reconnect
- Update feature specs to use real hocuspocus shared context instead of
  stubbing collaboration_enabled, add offline blocking tests
2026-03-30 16:43:48 +03:00
Judith Roth b2619e723d Merge pull request #21810 from opf/code-maintenance/70175-better-testing-setup-for-blocknote-hocuspocus
Code maintenance/70175 better testing setup for blocknote hocuspocus
2026-02-23 14:23:43 +01:00
Judith Roth 3b72cce9ce Adapt tests for new Hocuspocus repo location 2026-02-23 13:59:45 +01:00
Kabiru Mwenja 2e596397bb Clarify websocket address is needed 2026-02-23 12:48:57 +03:00
Kabiru Mwenja b22162ecb0 Unify hocuspocus URL validation error message
Use a single I18n message for all invalid URL cases (wrong scheme
and unparseable URLs) so the contract and component show consistent
text to users.
2026-02-23 12:17:06 +03:00
Kabiru Mwenja ab57760873 Add inline form validation on submit 2026-02-23 12:17:05 +03:00
Kabiru Mwenja 7cfe171690 Use inline field validation for invalid hocuspocus URL
Replace the warning banner approach with Primer's inline field
validation (invalid: / validation_message:) on the hocuspocus URL
text field. This shows the error directly on the field when the
current value uses an unsupported protocol, which works for both
env-var-overridden (read-only) and user-submitted values.
2026-02-23 12:17:05 +03:00
Kabiru Mwenja e8250a7729 Add hocuspocus URL env var sticky error banner validation 2026-02-23 12:17:04 +03:00
Kabiru Mwenja 878906ded4 Add URL scheme validation for hocuspocus server URL
Adds contract-level validation to ensure the collaborative editing
hocuspocus URL only accepts WebSocket protocols (ws://, wss://).
Previously, misconfiguration with https:// would silently cause
Content Security Policy errors at runtime.

https://community.openproject.org/wp/71888
2026-02-23 12:17:04 +03:00
Judith Roth 3c4e193f7c [#70175] Refactor to make it more reusable
So hopefully more specs will use it.

See https://community.openproject.org/wp/70175
2026-02-20 18:16:27 +01:00
Judith Roth 7613e3f498 [#70175] Better testing setup for BlockNote/Hocuspocus
https://community.openproject.org/work_packages/70175

This adds the capacity to run integration tests with a real Hocuspocus
backend (either locally or in docker).
2026-02-19 15:24:54 +01:00
Judith Roth 47bfe4bdf4 Explicitly create collaborative documents in tests 2026-02-19 15:24:54 +01:00