Repository navigation
fix: workflow search keyword - #7729
Conversation
|
|
||
| onMounted(() => { | ||
| model.openNodeMenu = (anchor: Model.AnchorConfig) => { | ||
| showAnchor.value && anchorData.value?.id === anchor.id ? closeNodeMenu() : openNodeMenu(anchor) |
There was a problem hiding this comment.
Code Review: highlightedStepName Refactor
✅ Improvements in the New Version
- Correct whole-keyword matching — old code highlighted any character contained in the keyword (substring of chars); new code matches the full keyword as a substring. This is the right behavior.
- Proper regex escaping (
escapeRegExp) — special characters like*,(,.no longer break or throw innew RegExp. Good. - HTML escaping (
escape) — prevents XSS/injection from user-provided text and keywords. Major security fix. - Uses a CSS variable (
--mk-warning) instead of hardcoded color for one branch — better theming consistency. - Simpler and more readable via
splitwith capture group + odd-index check.
⚠️ Issues / Potential Problems
index % 2fragility: With a capture group,String.splitguarantees even indices are non-matches and odd indices are matches — but only if the regex always has exactly one capture group and never matches an empty string. Edge case: ifkeyWord.valuecan be whitespace-only or the pattern could match empty string, odd/even logic may break. The current guard!keyWord.valuecovers empty, but consider trimming:if (!keyWord.value.trim()).- Inconsistent styling: one branch uses
var(--mk-warning), the other hardcodes#FFC60A. Prefer a CSS variable for both (or move styles to a class, e.g..highlight), which would also remove inline style duplication. - Quoting style inconsistency:
'background: #FF8800;'vs'background: #FFC60A'— one keeps trailing semicolon, new one doesn't; also mixes quote styles elsewhere in the file. Minor. - Performance: for each render per node, this runs
escape/regex. Fine normally, but if called very frequently (hundreds of nodes, on keystroke), considercomputed-caching the compiled regex per keyword rather than rebuilding it each call.
💡 Suggested Optimization
const highlightClass = computed(() => currentKeyWord.value ? 'highlight-active' : 'highlight')
const highlightedStepName = (contentText: string) => {
if (!keyWord.value.trim()) return escape(contentText)
const pattern = new RegExp(`(${escapeRegExp(keyWord.value)})`, 'gi')
return contentText
.split(pattern)
.map((part, i) => i % 2
? `<span class="${highlightClass.value}">${escape(part)}</span>`
: escape(part))
.join('')
}(Move background colors into CSS classes for maintainability.)
Summary
The refactor is a clear improvement — fixes correctness, security (XSS), and readability. Remaining nits are minor: trim-check the keyword, unify styling via CSS classes/variables, and optionally cache the regex.
| }) | ||
| </script> | ||
|
|
||
| <template> |
There was a problem hiding this comment.
Here's a review of the changes:
Issues
-
Logic change in
closeSearchis questionable- props.logicFlow.clearSelectElements() + focusCurrentNode()
focusCurrentNode()re-selects and re-highlights everything using the currentsearchKeyword. ButcloseSearchjust setsearchKeyword.value = '', so the keyword-based highlighting will match nothing — yet it still callsselectElementByIdandfocusOnonmatchedNodes[0]. If the intent was to clean up, this now leaves one node selected/focused instead of clearing everything. Consider whether you actually wantclearSelectElements()plus a manualclearSelectOnsweep, or intentionally keep focus. -
Loop variable shadowing
const node = matchedNodes.value[currentIndex.value] ... props.logicFlow.graphModel.nodes.forEach((nodeModel) => ...)
Good that you renamed to
nodeModel, but note the laterprops.logicFlow.graphModel.nodes.forEach((node) => ...)inonBeforeUnmountshadows nothing problematic — still, keep naming consistent. -
Repeated keyword-computation cost
keyword.toLocaleLowerCase()is computed inside the loop for every node. Hoist it:const kw = keyword.toLocaleLowerCase() // ... String(nodeModel.properties.stepName ?? '').toLocaleLowerCase().includes(kw)
-
searchKeyword.value.trim()timing
InfocusCurrentNodeyou trim the keyword for matching, but thewatch(searchKeyword, ...)/matchedNodescomputed may use the untrimmed value. Ensure both use the same normalization or counts/indices may mismatch (e.g., keyword " x " matches differently in the two places). -
Redundant focus call
props.logicFlow.getNodeModelById(node.id)?.focusOn?.() props.logicFlow.focusOn(node.id)
Two focus mechanisms back-to-back; likely one suffices. The
getNodeModelById(...)?.focusOn?.()appears redundant — remove it unless it has side effectslogicFlow.focusOnlacks. -
Extra loop on close/unmount could be avoided
IfclearSelectElements()triggers each node's clear internally, the manualnodeModel.clearSelectOn?.()sweep may be unnecessary duplication. Verify LogicFlow's behavior; otherwise you keep two sources of truth for selection state. -
Guard against missing properties
nodeModel.properties.stepNameassumespropertiesalways exists; safer:nodeModel.properties?.stepName ?? ''.
Minor optimizations
- Cache
graphModel.nodesonce instead of walking it multiple times per call. - Precompile lowercase
stepNamecomparisons only whenkeywordis truthy (already partly done with thekeyword &&short-circuit — good). - Consider debouncing the highlight sweep on rapid typing if node lists are large.
Overall: functionally plausible, but #1 (behavior change in closeSearch) and #5 (double focus) deserve a second look.
| reactive(node).properties.showNode = expanded | ||
| }) | ||
| } | ||
|
|
There was a problem hiding this comment.
Code Review
Main issue: reactive(node) usage is problematic
-
reactive()created inside the loopprops.logicFlow.graphModel.nodes.forEach((node) => { reactive(node).properties.showNode = expanded })
- Calling
reactive(node)on every iteration creates an ephemeral proxy. Vue caches proxies internally, so writes do go through the same cached proxy for the same raw object, but this relies on implementation details of how LogicFlow stores nodes. - If LogicFlow's internal models use their own reactivity system (MobX in some versions) or frozen/plain objects,
reactive()may silently do nothing or cause two separate reactive systems fighting over updates.
- Calling
-
Better alternative: mutate properties via LogicFlow's own API instead of hijacking Vue reactivity:
node.setProperty('showNode', expanded)
or if direct mutation is truly needed, wrap once at a higher level (e.g.,
const nodes = reactive(props.logicFlow.graphModel.nodes)) rather than callingreactive()repeatedly inside the loop. -
Unavailable while batch operation — mutating every node synchronously triggers N reactive notifications; if there are many nodes, consider batching (e.g., toggling only collapsed/visible ones or using
pauseTracking-style strategies).
Comment drift: the comment changed from “不替换节点数据,保留表单内容” to “与节点视图共用 Vue 响应式代理…” — that's fine, but make sure it actually describes behavior; if the "shared proxy" only works by accident of Vue's proxy cache, the comment may mislead future maintainers.
Minor suggestions
- Type safety:
node.propertiesmay beRecord<string, unknown>; ensureshowNodeis typed, e.g. via a custom node model type. - Consider extracting
const { nodes } = props.logicFlow.graphModelbefore the loop for readability (negligible perf impact). - If this runs often (e.g., watch triggered frequently), add a guard to skip work when all nodes already have the target state.
Summary: the core risk is depending on reactive() against non-Vue-owned objects (LogicFlow models). Prefer LogicFlow's setProperty API; keep reactive() only for objects you create and own.
fix: workflow search keyword