Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
4cdf9c9d
Commit
4cdf9c9d
authored
Oct 13, 2021
by
orozot
Committed by
Natalia Tepluhina
Oct 13, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix: search setting only highlight matched text
parent
afc46c74
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
83 additions
and
13 deletions
+83
-13
app/assets/javascripts/search_settings/components/search_settings.vue
...avascripts/search_settings/components/search_settings.vue
+46
-12
app/assets/javascripts/search_settings/constants.js
app/assets/javascripts/search_settings/constants.js
+3
-0
spec/frontend/search_settings/components/search_settings_spec.js
...ontend/search_settings/components/search_settings_spec.js
+34
-1
No files found.
app/assets/javascripts/search_settings/components/search_settings.vue
View file @
4cdf9c9d
<
script
>
import
{
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
import
{
uniq
}
from
'
lodash
'
;
import
{
EXCLUDED_NODES
,
HIDE_CLASS
,
HIGHLIGHT_CLASS
,
TYPING_DELAY
}
from
'
../constants
'
;
import
{
uniq
,
escapeRegExp
}
from
'
lodash
'
;
import
{
EXCLUDED_NODES
,
HIDE_CLASS
,
HIGHLIGHT_CLASS
,
NONE_PADDING_CLASS
,
TYPING_DELAY
,
}
from
'
../constants
'
;
const
origExpansions
=
new
Map
();
...
...
@@ -37,9 +43,13 @@ const resetSections = ({ sectionSelector }) => {
};
const
clearHighlights
=
()
=>
{
document
.
querySelectorAll
(
`.
${
HIGHLIGHT_CLASS
}
`
)
.
forEach
((
element
)
=>
element
.
classList
.
remove
(
HIGHLIGHT_CLASS
));
document
.
querySelectorAll
(
`.
${
HIGHLIGHT_CLASS
}
`
).
forEach
((
element
)
=>
{
const
{
parentNode
}
=
element
;
const
textNode
=
document
.
createTextNode
(
element
.
textContent
);
parentNode
.
replaceChild
(
textNode
,
element
);
parentNode
.
normalize
();
});
};
const
hideSectionsExcept
=
(
sectionSelector
,
visibleSections
)
=>
{
...
...
@@ -50,17 +60,41 @@ const hideSectionsExcept = (sectionSelector, visibleSections) => {
});
};
const
highlightElements
=
(
elements
=
[])
=>
{
elements
.
forEach
((
element
)
=>
element
.
classList
.
add
(
HIGHLIGHT_CLASS
));
const
transformMatchElement
=
(
element
,
searchTerm
)
=>
{
const
textStr
=
element
.
textContent
;
const
escapedSearchTerm
=
new
RegExp
(
`(
${
escapeRegExp
(
searchTerm
)}
)`
,
'
gi
'
);
const
textList
=
textStr
.
split
(
escapedSearchTerm
);
const
replaceFragment
=
document
.
createDocumentFragment
();
textList
.
forEach
((
text
)
=>
{
let
addElement
=
document
.
createTextNode
(
text
);
if
(
escapedSearchTerm
.
test
(
text
))
{
addElement
=
document
.
createElement
(
'
mark
'
);
addElement
.
className
=
`
${
HIGHLIGHT_CLASS
}
${
NONE_PADDING_CLASS
}
`
;
addElement
.
textContent
=
text
;
escapedSearchTerm
.
lastIndex
=
0
;
}
replaceFragment
.
appendChild
(
addElement
);
});
return
replaceFragment
;
};
const
highlightElements
=
(
elements
=
[],
searchTerm
)
=>
{
elements
.
forEach
((
element
)
=>
{
const
replaceFragment
=
transformMatchElement
(
element
,
searchTerm
);
element
.
innerHTML
=
''
;
element
.
appendChild
(
replaceFragment
);
});
};
const
displayResults
=
({
sectionSelector
,
expandSection
},
matches
)
=>
{
const
displayResults
=
({
sectionSelector
,
expandSection
,
searchTerm
},
matches
)
=>
{
const
elements
=
matches
.
map
((
match
)
=>
match
.
parentElement
);
const
sections
=
uniq
(
elements
.
map
((
element
)
=>
findSettingsSection
(
sectionSelector
,
element
)));
hideSectionsExcept
(
sectionSelector
,
sections
);
sections
.
forEach
(
expandSection
);
highlightElements
(
elements
);
highlightElements
(
elements
,
searchTerm
);
};
const
clearResults
=
(
params
)
=>
{
...
...
@@ -116,21 +150,21 @@ export default {
},
methods
:
{
search
(
value
)
{
this
.
searchTerm
=
value
;
const
displayOptions
=
{
sectionSelector
:
this
.
sectionSelector
,
expandSection
:
this
.
expandSection
,
collapseSection
:
this
.
collapseSection
,
isExpanded
:
this
.
isExpandedFn
,
searchTerm
:
this
.
searchTerm
,
};
this
.
searchTerm
=
value
;
clearResults
(
displayOptions
);
if
(
value
.
length
)
{
saveExpansionState
(
document
.
querySelectorAll
(
this
.
sectionSelector
),
displayOptions
);
displayResults
(
displayOptions
,
search
(
this
.
searchRoot
,
value
));
displayResults
(
displayOptions
,
search
(
this
.
searchRoot
,
this
.
searchTerm
));
}
else
{
restoreExpansionState
(
displayOptions
);
}
...
...
app/assets/javascripts/search_settings/constants.js
View file @
4cdf9c9d
...
...
@@ -7,5 +7,8 @@ export const HIDE_CLASS = 'gl-display-none';
// used to highlight the text that matches the * search term
export
const
HIGHLIGHT_CLASS
=
'
gl-bg-orange-100
'
;
// used to remove padding for text that matches the * search term
export
const
NONE_PADDING_CLASS
=
'
gl-p-0
'
;
// How many seconds to wait until the user * stops typing
export
const
TYPING_DELAY
=
400
;
spec/frontend/search_settings/components/search_settings_spec.js
View file @
4cdf9c9d
...
...
@@ -11,6 +11,7 @@ describe('search_settings/components/search_settings.vue', () => {
const
GENERAL_SETTINGS_ID
=
'
js-general-settings
'
;
const
ADVANCED_SETTINGS_ID
=
'
js-advanced-settings
'
;
const
EXTRA_SETTINGS_ID
=
'
js-extra-settings
'
;
const
TEXT_CONTAIN_SEARCH_TERM
=
`This text contain
${
SEARCH_TERM
}
and <script>alert("111")</script> others.`
;
let
wrapper
;
...
...
@@ -33,6 +34,21 @@ describe('search_settings/components/search_settings.vue', () => {
const
visibleSectionsCount
=
()
=>
document
.
querySelectorAll
(
`
${
SECTION_SELECTOR
}
:not(.
${
HIDE_CLASS
}
)`
).
length
;
const
highlightedElementsCount
=
()
=>
document
.
querySelectorAll
(
`.
${
HIGHLIGHT_CLASS
}
`
).
length
;
const
highlightedTextNodes
=
()
=>
{
const
highlightedList
=
Array
.
from
(
document
.
querySelectorAll
(
`.
${
HIGHLIGHT_CLASS
}
`
));
return
highlightedList
.
every
((
element
)
=>
{
return
element
.
textContent
.
toLowerCase
()
===
SEARCH_TERM
.
toLowerCase
();
});
};
const
matchParentElement
=
()
=>
{
const
highlightedList
=
Array
.
from
(
document
.
querySelectorAll
(
`.
${
HIGHLIGHT_CLASS
}
`
));
return
highlightedList
.
map
((
element
)
=>
{
return
element
.
parentNode
;
});
};
const
findSearchBox
=
()
=>
wrapper
.
find
(
GlSearchBoxByType
);
const
search
=
(
term
)
=>
{
findSearchBox
().
vm
.
$emit
(
'
input
'
,
term
);
...
...
@@ -52,6 +68,7 @@ describe('search_settings/components/search_settings.vue', () => {
</section>
<section id="
${
EXTRA_SETTINGS_ID
}
" class="settings">
<span>
${
SEARCH_TERM
}
</span>
<span>
${
TEXT_CONTAIN_SEARCH_TERM
}
</span>
</section>
</div>
</div>
...
...
@@ -82,7 +99,23 @@ describe('search_settings/components/search_settings.vue', () => {
it
(
'
highlight elements that match the search term
'
,
()
=>
{
search
(
SEARCH_TERM
);
expect
(
highlightedElementsCount
()).
toBe
(
1
);
expect
(
highlightedElementsCount
()).
toBe
(
2
);
});
it
(
'
highlight only search term and not the whole line
'
,
()
=>
{
search
(
SEARCH_TERM
);
expect
(
highlightedTextNodes
()).
toBe
(
true
);
});
it
(
'
prevents search xss
'
,
()
=>
{
search
(
SEARCH_TERM
);
const
parentNodeList
=
matchParentElement
();
parentNodeList
.
forEach
((
element
)
=>
{
const
scriptElement
=
element
.
getElementsByTagName
(
'
script
'
);
expect
(
scriptElement
.
length
).
toBe
(
0
);
});
});
describe
(
'
default
'
,
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment