Files
wekan/models/lib/linkedCardTarget.js
Lauri Ojansivu 3b3f7ebd77 Allow same-board linked cards while retaining link and write guards
The card-link picker excluded every real card on its own board, and the
server rejected same-board sources. Include the current board and exclude
only sources already mirrored. Preserve coordinate and write validation,
reject link chains, and prevent whole-board self-links through both buttons.

Labels already use the source card. Verify live source-to-mirror updates
and edits through the mirror, same-board authorization failures, whole-board
self-link rejection, and existing cross-board behavior in Chromium.
Focused linked-card Node regression suites pass.

Thanks to RinTheCatato, mohamed-aziz-chamakh and xet7 !

Fixes #5683,
2026-09-27 00:09:56 +03:00

39 lines
1.8 KiB
JavaScript

'use strict';
// Pure guard for the "Link to this card" feature (#5808). Linking a card to an
// existing linked card (or to one that links back) builds a chain/cycle of
// linkedId references. The card helpers resolve linkedId only ONE hop
// (getTitle/getBoardTitle/getRealId read `getCard(linkedId).<field>` directly),
// so a link whose target is itself a linked card resolves to another pointer
// instead of a real card — the card renders as an empty/broken pointer and
// becomes effectively inaccessible (the reported "both cards inaccessible /
// freeze"). Mirrors the #3328 parent/subtask cycle guard, but for linkedId.
//
// Rule: a link target must be a REAL card (not a linked card, linked board, or
// template card). A new mirror can point at a real card on its own board
// without creating a cycle. The picker excludes sources already mirrored.
const LINK_TYPES = new Set(['cardType-linkedCard', 'cardType-linkedBoard']);
// A card that is itself a link (card or board) — never a valid link target,
// because linking to it would create a chain of linkedId pointers.
function isLinkPointerCard(card) {
return !!card && LINK_TYPES.has(card.type);
}
// `mirroredRealCardIds` contains only sources of existing mirrors, not all
// real cards on the destination board.
function isLinkableCardTarget(target, mirroredRealCardIds = []) {
if (!target || !target._id) return false;
// No chains of links, and no template cards.
if (isLinkPointerCard(target) || target.type === 'template-card') return false;
const mirrored = mirroredRealCardIds instanceof Set
? mirroredRealCardIds : new Set(mirroredRealCardIds);
if (mirrored.has(target._id)) return false;
// Even a malformed normal card must not carry another link pointer.
if (target.linkedId) return false;
return true;
}
export { isLinkPointerCard, isLinkableCardTarget };