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
8b090caf
Commit
8b090caf
authored
Oct 10, 2018
by
Mike Greiling
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Prettify environments feature_highlight and filtered_search modules
parent
4d0db16f
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
459 additions
and
405 deletions
+459
-405
app/assets/javascripts/environments/components/container.vue
app/assets/javascripts/environments/components/container.vue
+32
-32
app/assets/javascripts/environments/components/empty_state.vue
...ssets/javascripts/environments/components/empty_state.vue
+16
-16
app/assets/javascripts/environments/components/environment_rollback.vue
...ascripts/environments/components/environment_rollback.vue
+3
-1
app/assets/javascripts/environments/folder/environments_folder_bundle.js
...scripts/environments/folder/environments_folder_bundle.js
+28
-27
app/assets/javascripts/environments/folder/environments_folder_view.vue
...ascripts/environments/folder/environments_folder_view.vue
+34
-37
app/assets/javascripts/environments/index.js
app/assets/javascripts/environments/index.js
+32
-31
app/assets/javascripts/environments/mixins/environments_mixin.js
...ets/javascripts/environments/mixins/environments_mixin.js
+11
-9
app/assets/javascripts/feature_highlight/feature_highlight.js
...assets/javascripts/feature_highlight/feature_highlight.js
+5
-11
app/assets/javascripts/feature_highlight/feature_highlight_helper.js
...javascripts/feature_highlight/feature_highlight_helper.js
+12
-6
app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
...ltered_search/admin_runners_filtered_search_token_keys.js
+18
-15
app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
...ed_search/components/recent_searches_dropdown_content.vue
+5
-3
app/assets/javascripts/filtered_search/dropdown_emoji.js
app/assets/javascripts/filtered_search/dropdown_emoji.js
+9
-6
app/assets/javascripts/filtered_search/dropdown_hint.js
app/assets/javascripts/filtered_search/dropdown_hint.js
+10
-9
app/assets/javascripts/filtered_search/dropdown_non_user.js
app/assets/javascripts/filtered_search/dropdown_non_user.js
+3
-5
app/assets/javascripts/filtered_search/dropdown_utils.js
app/assets/javascripts/filtered_search/dropdown_utils.js
+29
-25
app/assets/javascripts/filtered_search/filtered_search_dropdown.js
...s/javascripts/filtered_search/filtered_search_dropdown.js
+7
-5
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
...javascripts/filtered_search/filtered_search_token_keys.js
+16
-12
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
.../javascripts/filtered_search/filtered_search_tokenizer.js
+35
-28
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+68
-57
app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
...ts/filtered_search/issuable_filtered_search_token_keys.js
+74
-58
app/assets/javascripts/filtered_search/recent_searches_root.js
...ssets/javascripts/filtered_search/recent_searches_root.js
+4
-7
app/assets/javascripts/filtered_search/stores/recent_searches_store.js
...vascripts/filtered_search/stores/recent_searches_store.js
+8
-5
No files found.
app/assets/javascripts/environments/components/container.vue
View file @
8b090caf
<
script
>
<
script
>
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
tablePagination
from
'
../../vue_shared/components/table_pagination.vue
'
;
import
environmentTable
from
'
../components/environments_table.vue
'
;
import
environmentTable
from
'
../components/environments_table.vue
'
;
export
default
{
export
default
{
components
:
{
components
:
{
environmentTable
,
environmentTable
,
tablePagination
,
tablePagination
,
},
props
:
{
isLoading
:
{
type
:
Boolean
,
required
:
true
,
},
},
props
:
{
environments
:
{
isLoading
:
{
type
:
Array
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
environments
:
{
type
:
Array
,
required
:
true
,
},
pagination
:
{
type
:
Object
,
required
:
true
,
},
canCreateDeployment
:
{
type
:
Boolean
,
required
:
true
,
},
canReadEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
},
},
methods
:
{
pagination
:
{
onChangePage
(
page
)
{
type
:
Object
,
this
.
$emit
(
'
onChangePage
'
,
page
);
required
:
true
,
},
},
},
};
canCreateDeployment
:
{
type
:
Boolean
,
required
:
true
,
},
canReadEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
},
methods
:
{
onChangePage
(
page
)
{
this
.
$emit
(
'
onChangePage
'
,
page
);
},
},
};
</
script
>
</
script
>
<
template
>
<
template
>
...
...
app/assets/javascripts/environments/components/empty_state.vue
View file @
8b090caf
<
script
>
<
script
>
export
default
{
export
default
{
name
:
'
EnvironmentsEmptyState
'
,
name
:
'
EnvironmentsEmptyState
'
,
props
:
{
props
:
{
newPath
:
{
newPath
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
canCreateEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
helpPath
:
{
type
:
String
,
required
:
true
,
},
},
},
};
canCreateEnvironment
:
{
type
:
Boolean
,
required
:
true
,
},
helpPath
:
{
type
:
String
,
required
:
true
,
},
},
};
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"blank-state-row"
>
<div
class=
"blank-state-row"
>
...
...
app/assets/javascripts/environments/components/environment_rollback.vue
View file @
8b090caf
...
@@ -38,7 +38,9 @@ export default {
...
@@ -38,7 +38,9 @@ export default {
computed
:
{
computed
:
{
title
()
{
title
()
{
return
this
.
isLastDeployment
?
s__
(
'
Environments|Re-deploy to environment
'
)
:
s__
(
'
Environments|Rollback environment
'
);
return
this
.
isLastDeployment
?
s__
(
'
Environments|Re-deploy to environment
'
)
:
s__
(
'
Environments|Rollback environment
'
);
},
},
},
},
...
...
app/assets/javascripts/environments/folder/environments_folder_bundle.js
View file @
8b090caf
...
@@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
...
@@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
Vue
.
use
(
Translate
);
Vue
.
use
(
Translate
);
export
default
()
=>
new
Vue
({
export
default
()
=>
el
:
'
#environments-folder-list-view
'
,
new
Vue
({
components
:
{
el
:
'
#environments-folder-list-view
'
,
environmentsFolderApp
,
components
:
{
},
environmentsFolderApp
,
data
()
{
},
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
data
()
{
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
return
{
return
{
endpoint
:
environmentsData
.
endpoint
,
endpoint
:
environmentsData
.
endpoint
,
folderName
:
environmentsData
.
folderName
,
folderName
:
environmentsData
.
folderName
,
cssContainerClass
:
environmentsData
.
cssClass
,
cssContainerClass
:
environmentsData
.
cssClass
,
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
};
};
},
},
render
(
createElement
)
{
render
(
createElement
)
{
return
createElement
(
'
environments-folder-app
'
,
{
return
createElement
(
'
environments-folder-app
'
,
{
props
:
{
props
:
{
endpoint
:
this
.
endpoint
,
endpoint
:
this
.
endpoint
,
folderName
:
this
.
folderName
,
folderName
:
this
.
folderName
,
cssContainerClass
:
this
.
cssContainerClass
,
cssContainerClass
:
this
.
cssContainerClass
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
},
},
});
});
},
},
});
});
app/assets/javascripts/environments/folder/environments_folder_view.vue
View file @
8b090caf
<
script
>
<
script
>
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
import
environmentsMixin
from
'
../mixins/environments_mixin
'
;
import
CIPaginationMixin
from
'
../../vue_shared/mixins/ci_pagination_api_mixin
'
;
import
CIPaginationMixin
from
'
../../vue_shared/mixins/ci_pagination_api_mixin
'
;
import
StopEnvironmentModal
from
'
../components/stop_environment_modal.vue
'
;
import
StopEnvironmentModal
from
'
../components/stop_environment_modal.vue
'
;
export
default
{
export
default
{
components
:
{
components
:
{
StopEnvironmentModal
,
StopEnvironmentModal
,
},
},
mixins
:
[
mixins
:
[
environmentsMixin
,
CIPaginationMixin
],
environmentsMixin
,
CIPaginationMixin
,
],
props
:
{
props
:
{
endpoint
:
{
endpoint
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
folderName
:
{
folderName
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
cssContainerClass
:
{
cssContainerClass
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
canCreateDeployment
:
{
canCreateDeployment
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
canReadEnvironment
:
{
canReadEnvironment
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
},
methods
:
{
},
successCallback
(
resp
)
{
methods
:
{
this
.
saveData
(
resp
);
successCallback
(
resp
)
{
},
this
.
saveData
(
resp
);
},
},
};
},
};
</
script
>
</
script
>
<
template
>
<
template
>
<div
:class=
"cssContainerClass"
>
<div
:class=
"cssContainerClass"
>
...
...
app/assets/javascripts/environments/index.js
View file @
8b090caf
...
@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
...
@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
Vue
.
use
(
Translate
);
Vue
.
use
(
Translate
);
export
default
()
=>
new
Vue
({
export
default
()
=>
el
:
'
#environments-list-view
'
,
new
Vue
({
components
:
{
el
:
'
#environments-list-view
'
,
environmentsComponent
,
components
:
{
},
environmentsComponent
,
data
()
{
},
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
data
()
{
const
environmentsData
=
document
.
querySelector
(
this
.
$options
.
el
).
dataset
;
return
{
return
{
endpoint
:
environmentsData
.
environmentsDataEndpoint
,
endpoint
:
environmentsData
.
environmentsDataEndpoint
,
newEnvironmentPath
:
environmentsData
.
newEnvironmentPath
,
newEnvironmentPath
:
environmentsData
.
newEnvironmentPath
,
helpPagePath
:
environmentsData
.
helpPagePath
,
helpPagePath
:
environmentsData
.
helpPagePath
,
cssContainerClass
:
environmentsData
.
cssClass
,
cssContainerClass
:
environmentsData
.
cssClass
,
canCreateEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateEnvironment
),
canCreateEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateEnvironment
),
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canCreateDeployment
:
convertPermissionToBoolean
(
environmentsData
.
canCreateDeployment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
canReadEnvironment
:
convertPermissionToBoolean
(
environmentsData
.
canReadEnvironment
),
};
};
},
},
render
(
createElement
)
{
render
(
createElement
)
{
return
createElement
(
'
environments-component
'
,
{
return
createElement
(
'
environments-component
'
,
{
props
:
{
props
:
{
endpoint
:
this
.
endpoint
,
endpoint
:
this
.
endpoint
,
newEnvironmentPath
:
this
.
newEnvironmentPath
,
newEnvironmentPath
:
this
.
newEnvironmentPath
,
helpPagePath
:
this
.
helpPagePath
,
helpPagePath
:
this
.
helpPagePath
,
cssContainerClass
:
this
.
cssContainerClass
,
cssContainerClass
:
this
.
cssContainerClass
,
canCreateEnvironment
:
this
.
canCreateEnvironment
,
canCreateEnvironment
:
this
.
canCreateEnvironment
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canCreateDeployment
:
this
.
canCreateDeployment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
canReadEnvironment
:
this
.
canReadEnvironment
,
},
},
});
});
},
},
});
});
app/assets/javascripts/environments/mixins/environments_mixin.js
View file @
8b090caf
...
@@ -4,9 +4,7 @@
...
@@ -4,9 +4,7 @@
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
Visibility
from
'
visibilityjs
'
;
import
Visibility
from
'
visibilityjs
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
{
import
{
getParameterByName
}
from
'
../../lib/utils/common_utils
'
;
getParameterByName
,
}
from
'
../../lib/utils/common_utils
'
;
import
{
s__
}
from
'
../../locale
'
;
import
{
s__
}
from
'
../../locale
'
;
import
Flash
from
'
../../flash
'
;
import
Flash
from
'
../../flash
'
;
import
eventHub
from
'
../event_hub
'
;
import
eventHub
from
'
../event_hub
'
;
...
@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
...
@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
import
container
from
'
../components/container.vue
'
;
import
container
from
'
../components/container.vue
'
;
export
default
{
export
default
{
components
:
{
components
:
{
environmentTable
,
environmentTable
,
container
,
container
,
...
@@ -65,7 +62,8 @@ export default {
...
@@ -65,7 +62,8 @@ export default {
updateContent
(
parameters
)
{
updateContent
(
parameters
)
{
this
.
updateInternalState
(
parameters
);
this
.
updateInternalState
(
parameters
);
// fetch new data
// fetch new data
return
this
.
service
.
fetchEnvironments
(
this
.
requestData
)
return
this
.
service
.
fetchEnvironments
(
this
.
requestData
)
.
then
(
response
=>
this
.
successCallback
(
response
))
.
then
(
response
=>
this
.
successCallback
(
response
))
.
then
(()
=>
{
.
then
(()
=>
{
// restart polling
// restart polling
...
@@ -88,7 +86,8 @@ export default {
...
@@ -88,7 +86,8 @@ export default {
if
(
!
this
.
isMakingRequest
)
{
if
(
!
this
.
isMakingRequest
)
{
this
.
isLoading
=
true
;
this
.
isLoading
=
true
;
this
.
service
.
postAction
(
endpoint
)
this
.
service
.
postAction
(
endpoint
)
.
then
(()
=>
this
.
fetchEnvironments
())
.
then
(()
=>
this
.
fetchEnvironments
())
.
catch
(()
=>
{
.
catch
(()
=>
{
this
.
isLoading
=
false
;
this
.
isLoading
=
false
;
...
@@ -100,7 +99,8 @@ export default {
...
@@ -100,7 +99,8 @@ export default {
fetchEnvironments
()
{
fetchEnvironments
()
{
this
.
isLoading
=
true
;
this
.
isLoading
=
true
;
return
this
.
service
.
fetchEnvironments
(
this
.
requestData
)
return
this
.
service
.
fetchEnvironments
(
this
.
requestData
)
.
then
(
this
.
successCallback
)
.
then
(
this
.
successCallback
)
.
catch
(
this
.
errorCallback
);
.
catch
(
this
.
errorCallback
);
},
},
...
@@ -111,7 +111,9 @@ export default {
...
@@ -111,7 +111,9 @@ export default {
stopEnvironment
(
environment
)
{
stopEnvironment
(
environment
)
{
const
endpoint
=
environment
.
stop_path
;
const
endpoint
=
environment
.
stop_path
;
const
errorMessage
=
s__
(
'
Environments|An error occurred while stopping the environment, please try again
'
);
const
errorMessage
=
s__
(
'
Environments|An error occurred while stopping the environment, please try again
'
,
);
this
.
postAction
({
endpoint
,
errorMessage
});
this
.
postAction
({
endpoint
,
errorMessage
});
},
},
},
},
...
@@ -149,7 +151,7 @@ export default {
...
@@ -149,7 +151,7 @@ export default {
data
:
this
.
requestData
,
data
:
this
.
requestData
,
successCallback
:
this
.
successCallback
,
successCallback
:
this
.
successCallback
,
errorCallback
:
this
.
errorCallback
,
errorCallback
:
this
.
errorCallback
,
notificationCallback
:
(
isMakingRequest
)
=>
{
notificationCallback
:
isMakingRequest
=>
{
this
.
isMakingRequest
=
isMakingRequest
;
this
.
isMakingRequest
=
isMakingRequest
;
},
},
});
});
...
...
app/assets/javascripts/feature_highlight/feature_highlight.js
View file @
8b090caf
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
{
import
{
getSelector
,
inserted
}
from
'
./feature_highlight_helper
'
;
getSelector
,
import
{
togglePopover
,
mouseenter
,
debouncedMouseleave
}
from
'
../shared/popover
'
;
inserted
,
}
from
'
./feature_highlight_helper
'
;
import
{
togglePopover
,
mouseenter
,
debouncedMouseleave
,
}
from
'
../shared/popover
'
;
export
function
setupFeatureHighlightPopover
(
id
,
debounceTimeout
=
300
)
{
export
function
setupFeatureHighlightPopover
(
id
,
debounceTimeout
=
300
)
{
const
$selector
=
$
(
getSelector
(
id
));
const
$selector
=
$
(
getSelector
(
id
));
...
@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
...
@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
export
function
findHighestPriorityFeature
()
{
export
function
findHighestPriorityFeature
()
{
let
priorityFeature
;
let
priorityFeature
;
const
sortedFeatureEls
=
[].
slice
.
call
(
document
.
querySelectorAll
(
'
.js-feature-highlight
'
)).
sort
((
a
,
b
)
=>
const
sortedFeatureEls
=
[].
slice
(
a
.
dataset
.
highlightPriority
||
0
)
<
(
b
.
dataset
.
highlightPriority
||
0
));
.
call
(
document
.
querySelectorAll
(
'
.js-feature-highlight
'
))
.
sort
((
a
,
b
)
=>
(
a
.
dataset
.
highlightPriority
||
0
)
<
(
b
.
dataset
.
highlightPriority
||
0
));
const
[
priorityFeatureEl
]
=
sortedFeatureEls
;
const
[
priorityFeatureEl
]
=
sortedFeatureEls
;
if
(
priorityFeatureEl
)
{
if
(
priorityFeatureEl
)
{
...
...
app/assets/javascripts/feature_highlight/feature_highlight_helper.js
View file @
8b090caf
...
@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
...
@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
export
const
getSelector
=
highlightId
=>
`.js-feature-highlight[data-highlight=
${
highlightId
}
]`
;
export
const
getSelector
=
highlightId
=>
`.js-feature-highlight[data-highlight=
${
highlightId
}
]`
;
export
function
dismiss
(
highlightId
)
{
export
function
dismiss
(
highlightId
)
{
axios
.
post
(
this
.
attr
(
'
data-dismiss-endpoint
'
),
{
axios
feature_name
:
highlightId
,
.
post
(
this
.
attr
(
'
data-dismiss-endpoint
'
),
{
})
feature_name
:
highlightId
,
.
catch
(()
=>
Flash
(
__
(
'
An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.
'
)));
})
.
catch
(()
=>
Flash
(
__
(
'
An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.
'
,
),
),
);
togglePopover
.
call
(
this
,
false
);
togglePopover
.
call
(
this
,
false
);
this
.
hide
();
this
.
hide
();
...
@@ -23,8 +30,7 @@ export function inserted() {
...
@@ -23,8 +30,7 @@ export function inserted() {
const
$popover
=
$
(
this
);
const
$popover
=
$
(
this
);
const
dismissWrapper
=
dismiss
.
bind
(
$popover
,
highlightId
);
const
dismissWrapper
=
dismiss
.
bind
(
$popover
,
highlightId
);
$
(
`#
${
popoverId
}
.dismiss-feature-highlight`
)
$
(
`#
${
popoverId
}
.dismiss-feature-highlight`
).
on
(
'
click
'
,
dismissWrapper
);
.
on
(
'
click
'
,
dismissWrapper
);
const
lazyImg
=
$
(
`#
${
popoverId
}
.feature-highlight-illustration`
)[
0
];
const
lazyImg
=
$
(
`#
${
popoverId
}
.feature-highlight-illustration`
)[
0
];
if
(
lazyImg
)
{
if
(
lazyImg
)
{
...
...
app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
View file @
8b090caf
import
FilteredSearchTokenKeys
from
'
./filtered_search_token_keys
'
;
import
FilteredSearchTokenKeys
from
'
./filtered_search_token_keys
'
;
const
tokenKeys
=
[{
const
tokenKeys
=
[
key
:
'
status
'
,
{
type
:
'
string
'
,
key
:
'
status
'
,
param
:
'
status
'
,
type
:
'
string
'
,
symbol
:
''
,
param
:
'
status
'
,
icon
:
'
messages
'
,
symbol
:
''
,
tag
:
'
status
'
,
icon
:
'
messages
'
,
},
{
tag
:
'
status
'
,
key
:
'
type
'
,
},
type
:
'
string
'
,
{
param
:
'
type
'
,
key
:
'
type
'
,
symbol
:
''
,
type
:
'
string
'
,
icon
:
'
cube
'
,
param
:
'
type
'
,
tag
:
'
type
'
,
symbol
:
''
,
}];
icon
:
'
cube
'
,
tag
:
'
type
'
,
},
];
const
AdminRunnersFilteredSearchTokenKeys
=
new
FilteredSearchTokenKeys
(
tokenKeys
);
const
AdminRunnersFilteredSearchTokenKeys
=
new
FilteredSearchTokenKeys
(
tokenKeys
);
...
...
app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
View file @
8b090caf
...
@@ -21,9 +21,11 @@ export default {
...
@@ -21,9 +21,11 @@ export default {
},
},
computed
:
{
computed
:
{
processedItems
()
{
processedItems
()
{
return
this
.
items
.
map
((
item
)
=>
{
return
this
.
items
.
map
(
item
=>
{
const
{
tokens
,
searchToken
}
const
{
tokens
,
searchToken
}
=
FilteredSearchTokenizer
.
processTokens
(
=
FilteredSearchTokenizer
.
processTokens
(
item
,
this
.
allowedKeys
);
item
,
this
.
allowedKeys
,
);
const
resultantTokens
=
tokens
.
map
(
token
=>
({
const
resultantTokens
=
tokens
.
map
(
token
=>
({
prefix
:
`
${
token
.
key
}
:`
,
prefix
:
`
${
token
.
key
}
:`
,
...
...
app/assets/javascripts/filtered_search/dropdown_emoji.js
View file @
8b090caf
...
@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
...
@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
};
};
import
(
/* webpackChunkName: 'emoji' */
'
~/emoji
'
)
import
(
/* webpackChunkName: 'emoji' */
'
~/emoji
'
)
.
then
(({
glEmojiTag
})
=>
{
this
.
glEmojiTag
=
glEmojiTag
;
})
.
then
(({
glEmojiTag
})
=>
{
.
catch
(()
=>
{
/* ignore error and leave emoji name in the search bar */
});
this
.
glEmojiTag
=
glEmojiTag
;
})
.
catch
(()
=>
{
/* ignore error and leave emoji name in the search bar */
});
this
.
unbindEvents
();
this
.
unbindEvents
();
this
.
bindEvents
();
this
.
bindEvents
();
...
@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
...
@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
}
itemClicked
(
e
)
{
itemClicked
(
e
)
{
super
.
itemClicked
(
e
,
(
selected
)
=>
{
super
.
itemClicked
(
e
,
selected
=>
{
const
name
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
const
name
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
return
DropdownUtils
.
getEscapedText
(
name
);
return
DropdownUtils
.
getEscapedText
(
name
);
});
});
...
@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
...
@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
// Replace empty gl-emoji tag to real content
// Replace empty gl-emoji tag to real content
const
dropdownItems
=
[...
this
.
dropdown
.
querySelectorAll
(
'
.filter-dropdown-item
'
)];
const
dropdownItems
=
[...
this
.
dropdown
.
querySelectorAll
(
'
.filter-dropdown-item
'
)];
dropdownItems
.
forEach
(
(
dropdownItem
)
=>
{
dropdownItems
.
forEach
(
dropdownItem
=>
{
const
name
=
dropdownItem
.
querySelector
(
'
.js-data-value
'
).
innerText
;
const
name
=
dropdownItem
.
querySelector
(
'
.js-data-value
'
).
innerText
;
const
emojiTag
=
this
.
glEmojiTag
(
name
);
const
emojiTag
=
this
.
glEmojiTag
(
name
);
const
emojiElement
=
dropdownItem
.
querySelector
(
'
gl-emoji
'
);
const
emojiElement
=
dropdownItem
.
querySelector
(
'
gl-emoji
'
);
...
@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
...
@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
}
init
()
{
init
()
{
this
.
droplab
this
.
droplab
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
).
init
();
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
).
init
();
}
}
}
}
app/assets/javascripts/filtered_search/dropdown_hint.js
View file @
8b090caf
...
@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
...
@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
previousInputValues
.
forEach
((
value
,
index
)
=>
{
previousInputValues
.
forEach
((
value
,
index
)
=>
{
searchTerms
.
push
(
value
);
searchTerms
.
push
(
value
);
if
(
index
===
previousInputValues
.
length
-
1
if
(
&&
token
.
indexOf
(
value
.
toLowerCase
())
!==
-
1
)
{
index
===
previousInputValues
.
length
-
1
&&
token
.
indexOf
(
value
.
toLowerCase
())
!==
-
1
)
{
searchTerms
.
pop
();
searchTerms
.
pop
();
}
}
});
});
...
@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
...
@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
}
}
renderContent
()
{
renderContent
()
{
const
dropdownData
=
this
.
tokenKeys
.
get
()
const
dropdownData
=
this
.
tokenKeys
.
get
().
map
(
tokenKey
=>
({
.
map
(
tokenKey
=>
({
icon
:
`
${
gon
.
sprite_icons
}
#
${
tokenKey
.
icon
}
`
,
icon
:
`
${
gon
.
sprite_icons
}
#
${
tokenKey
.
icon
}
`
,
hint
:
tokenKey
.
key
,
hint
:
tokenKey
.
key
,
tag
:
`:
${
tokenKey
.
tag
}
`
,
tag
:
`:
${
tokenKey
.
tag
}
`
,
type
:
tokenKey
.
type
,
type
:
tokenKey
.
type
,
}));
}));
this
.
droplab
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
Filter
],
this
.
config
);
this
.
droplab
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
Filter
],
this
.
config
);
this
.
droplab
.
setData
(
this
.
hookId
,
dropdownData
);
this
.
droplab
.
setData
(
this
.
hookId
,
dropdownData
);
...
...
app/assets/javascripts/filtered_search/dropdown_non_user.js
View file @
8b090caf
...
@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
...
@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
}
}
itemClicked
(
e
)
{
itemClicked
(
e
)
{
super
.
itemClicked
(
e
,
(
selected
)
=>
{
super
.
itemClicked
(
e
,
selected
=>
{
const
title
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
const
title
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
return
`
${
this
.
symbol
}${
DropdownUtils
.
getEscapedText
(
title
)}
`
;
return
`
${
this
.
symbol
}${
DropdownUtils
.
getEscapedText
(
title
)}
`
;
});
});
}
}
renderContent
(
forceShowList
=
false
)
{
renderContent
(
forceShowList
=
false
)
{
this
.
droplab
this
.
droplab
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
);
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
);
super
.
renderContent
(
forceShowList
);
super
.
renderContent
(
forceShowList
);
}
}
init
()
{
init
()
{
this
.
droplab
this
.
droplab
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
).
init
();
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
).
init
();
}
}
}
}
app/assets/javascripts/filtered_search/dropdown_utils.js
View file @
8b090caf
...
@@ -41,7 +41,7 @@ export default class DropdownUtils {
...
@@ -41,7 +41,7 @@ export default class DropdownUtils {
// Removes the first character if it is a quotation so that we can search
// Removes the first character if it is a quotation so that we can search
// with multiple words
// with multiple words
if
((
value
[
0
]
===
'
"
'
||
value
[
0
]
===
'
\'
'
)
&&
title
.
indexOf
(
'
'
)
!==
-
1
)
{
if
((
value
[
0
]
===
'
"
'
||
value
[
0
]
===
"
'
"
)
&&
title
.
indexOf
(
'
'
)
!==
-
1
)
{
value
=
value
.
slice
(
1
);
value
=
value
.
slice
(
1
);
}
}
...
@@ -82,11 +82,13 @@ export default class DropdownUtils {
...
@@ -82,11 +82,13 @@ export default class DropdownUtils {
// Reduce the colors to 4
// Reduce the colors to 4
colors
.
length
=
Math
.
min
(
colors
.
length
,
4
);
colors
.
length
=
Math
.
min
(
colors
.
length
,
4
);
const
color
=
colors
.
map
((
c
,
i
)
=>
{
const
color
=
colors
const
percentFirst
=
Math
.
floor
(
spacing
*
i
);
.
map
((
c
,
i
)
=>
{
const
percentSecond
=
Math
.
floor
(
spacing
*
(
i
+
1
));
const
percentFirst
=
Math
.
floor
(
spacing
*
i
);
return
`
${
c
}
${
percentFirst
}
%,
${
c
}
${
percentSecond
}
%`
;
const
percentSecond
=
Math
.
floor
(
spacing
*
(
i
+
1
));
}).
join
(
'
,
'
);
return
`
${
c
}
${
percentFirst
}
%,
${
c
}
${
percentSecond
}
%`
;
})
.
join
(
'
,
'
);
return
`linear-gradient(
${
color
}
)`
;
return
`linear-gradient(
${
color
}
)`
;
}
}
...
@@ -97,17 +99,16 @@ export default class DropdownUtils {
...
@@ -97,17 +99,16 @@ export default class DropdownUtils {
data
.
forEach
(
DropdownUtils
.
mergeDuplicateLabels
.
bind
(
null
,
dataMap
));
data
.
forEach
(
DropdownUtils
.
mergeDuplicateLabels
.
bind
(
null
,
dataMap
));
Object
.
keys
(
dataMap
)
Object
.
keys
(
dataMap
).
forEach
(
key
=>
{
.
forEach
((
key
)
=>
{
const
label
=
dataMap
[
key
];
const
label
=
dataMap
[
key
];
if
(
label
.
multipleColors
)
{
if
(
label
.
multipleColors
)
{
label
.
color
=
DropdownUtils
.
duplicateLabelColor
(
label
.
multipleColors
);
label
.
color
=
DropdownUtils
.
duplicateLabelColor
(
label
.
multipleColors
);
label
.
text_color
=
'
#000000
'
;
label
.
text_color
=
'
#000000
'
;
}
}
results
.
push
(
label
);
results
.
push
(
label
);
});
});
results
.
preprocessed
=
true
;
results
.
preprocessed
=
true
;
...
@@ -118,8 +119,7 @@ export default class DropdownUtils {
...
@@ -118,8 +119,7 @@ export default class DropdownUtils {
const
{
input
,
allowedKeys
}
=
config
;
const
{
input
,
allowedKeys
}
=
config
;
const
updatedItem
=
item
;
const
updatedItem
=
item
;
const
searchInput
=
DropdownUtils
.
getSearchQuery
(
input
);
const
searchInput
=
DropdownUtils
.
getSearchQuery
(
input
);
const
{
lastToken
,
tokens
}
=
const
{
lastToken
,
tokens
}
=
FilteredSearchTokenizer
.
processTokens
(
searchInput
,
allowedKeys
);
FilteredSearchTokenizer
.
processTokens
(
searchInput
,
allowedKeys
);
const
lastKey
=
lastToken
.
key
||
lastToken
||
''
;
const
lastKey
=
lastToken
.
key
||
lastToken
||
''
;
const
allowMultiple
=
item
.
type
===
'
array
'
;
const
allowMultiple
=
item
.
type
===
'
array
'
;
const
itemInExistingTokens
=
tokens
.
some
(
t
=>
t
.
key
===
item
.
hint
);
const
itemInExistingTokens
=
tokens
.
some
(
t
=>
t
.
key
===
item
.
hint
);
...
@@ -154,7 +154,10 @@ export default class DropdownUtils {
...
@@ -154,7 +154,10 @@ export default class DropdownUtils {
static
getVisualTokenValues
(
visualToken
)
{
static
getVisualTokenValues
(
visualToken
)
{
const
tokenName
=
visualToken
&&
visualToken
.
querySelector
(
'
.name
'
).
textContent
.
trim
();
const
tokenName
=
visualToken
&&
visualToken
.
querySelector
(
'
.name
'
).
textContent
.
trim
();
let
tokenValue
=
visualToken
&&
visualToken
.
querySelector
(
'
.value
'
)
&&
visualToken
.
querySelector
(
'
.value
'
).
textContent
.
trim
();
let
tokenValue
=
visualToken
&&
visualToken
.
querySelector
(
'
.value
'
)
&&
visualToken
.
querySelector
(
'
.value
'
).
textContent
.
trim
();
if
(
tokenName
===
'
label
'
&&
tokenValue
)
{
if
(
tokenName
===
'
label
'
&&
tokenValue
)
{
// remove leading symbol and wrapping quotes
// remove leading symbol and wrapping quotes
tokenValue
=
tokenValue
.
replace
(
/^~
(
"|'
)?(
.*
)
/
,
'
$2
'
).
replace
(
/
(
"|'
)
$/
,
''
);
tokenValue
=
tokenValue
.
replace
(
/^~
(
"|'
)?(
.*
)
/
,
'
$2
'
).
replace
(
/
(
"|'
)
$/
,
''
);
...
@@ -174,7 +177,7 @@ export default class DropdownUtils {
...
@@ -174,7 +177,7 @@ export default class DropdownUtils {
tokens
.
splice
(
inputIndex
+
1
);
tokens
.
splice
(
inputIndex
+
1
);
}
}
tokens
.
forEach
(
(
token
)
=>
{
tokens
.
forEach
(
token
=>
{
if
(
token
.
classList
.
contains
(
'
js-visual-token
'
))
{
if
(
token
.
classList
.
contains
(
'
js-visual-token
'
))
{
const
name
=
token
.
querySelector
(
'
.name
'
);
const
name
=
token
.
querySelector
(
'
.name
'
);
const
value
=
token
.
querySelector
(
'
.value
'
);
const
value
=
token
.
querySelector
(
'
.value
'
);
...
@@ -194,8 +197,9 @@ export default class DropdownUtils {
...
@@ -194,8 +197,9 @@ export default class DropdownUtils {
values
.
push
(
name
.
innerText
);
values
.
push
(
name
.
innerText
);
}
}
}
else
if
(
token
.
classList
.
contains
(
'
input-token
'
))
{
}
else
if
(
token
.
classList
.
contains
(
'
input-token
'
))
{
const
{
isLastVisualTokenValid
}
=
const
{
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
isLastVisualTokenValid
,
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
inputValue
=
input
&&
input
.
value
;
const
inputValue
=
input
&&
input
.
value
;
...
@@ -209,9 +213,7 @@ export default class DropdownUtils {
...
@@ -209,9 +213,7 @@ export default class DropdownUtils {
}
}
});
});
return
values
return
values
.
map
(
value
=>
value
.
trim
()).
join
(
'
'
);
.
map
(
value
=>
value
.
trim
())
.
join
(
'
'
);
}
}
static
getSearchInput
(
filteredSearchInput
)
{
static
getSearchInput
(
filteredSearchInput
)
{
...
@@ -227,7 +229,9 @@ export default class DropdownUtils {
...
@@ -227,7 +229,9 @@ export default class DropdownUtils {
// Replace all spaces inside quote marks with underscores
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
// This helps with matching the beginning & end of a token:key
inputValue
=
inputValue
.
replace
(
/
((
'
[^
'
]
*'
{0,1})
|
(
"
[^
"
]
*"
{0,1})
|:
\s
+
)
/g
,
str
=>
str
.
replace
(
/
\s
/g
,
'
_
'
));
inputValue
=
inputValue
.
replace
(
/
((
'
[^
'
]
*'
{0,1})
|
(
"
[^
"
]
*"
{0,1})
|:
\s
+
)
/g
,
str
=>
str
.
replace
(
/
\s
/g
,
'
_
'
),
);
// Get the right position for the word selected
// Get the right position for the word selected
// Regex matches first space
// Regex matches first space
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown.js
View file @
8b090caf
...
@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
...
@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
dispatchInputEvent
()
{
dispatchInputEvent
()
{
// Propogate input change to FilteredSearchDropdownManager
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
// so that it can determine which dropdowns to open
this
.
input
.
dispatchEvent
(
new
CustomEvent
(
'
input
'
,
{
this
.
input
.
dispatchEvent
(
bubbles
:
true
,
new
CustomEvent
(
'
input
'
,
{
cancelable
:
true
,
bubbles
:
true
,
}));
cancelable
:
true
,
}),
);
}
}
dispatchFormSubmitEvent
()
{
dispatchFormSubmitEvent
()
{
...
@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
...
@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
if
(
!
data
)
return
;
if
(
!
data
)
return
;
const
results
=
data
.
map
(
(
o
)
=>
{
const
results
=
data
.
map
(
o
=>
{
const
updated
=
o
;
const
updated
=
o
;
updated
.
droplab_hidden
=
false
;
updated
.
droplab_hidden
=
false
;
return
updated
;
return
updated
;
...
...
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
View file @
8b090caf
...
@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
...
@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
}
}
searchByKeyParam
(
keyParam
)
{
searchByKeyParam
(
keyParam
)
{
return
this
.
tokenKeysWithAlternative
.
find
((
tokenKey
)
=>
{
return
(
let
tokenKeyParam
=
tokenKey
.
key
;
this
.
tokenKeysWithAlternative
.
find
(
tokenKey
=>
{
let
tokenKeyParam
=
tokenKey
.
key
;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction'
// e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam
=
tokenKeyParam
.
replace
(
'
-
'
,
'
_
'
);
tokenKeyParam
=
tokenKeyParam
.
replace
(
'
-
'
,
'
_
'
);
if
(
tokenKey
.
param
)
{
if
(
tokenKey
.
param
)
{
tokenKeyParam
+=
`_
${
tokenKey
.
param
}
`
;
tokenKeyParam
+=
`_
${
tokenKey
.
param
}
`
;
}
}
return
keyParam
===
tokenKeyParam
;
return
keyParam
===
tokenKeyParam
;
})
||
null
;
})
||
null
);
}
}
searchByConditionUrl
(
url
)
{
searchByConditionUrl
(
url
)
{
...
@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
...
@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
}
}
searchByConditionKeyValue
(
key
,
value
)
{
searchByConditionKeyValue
(
key
,
value
)
{
return
this
.
conditions
return
(
.
find
(
condition
=>
condition
.
tokenKey
===
key
&&
condition
.
value
===
value
)
||
null
;
this
.
conditions
.
find
(
condition
=>
condition
.
tokenKey
===
key
&&
condition
.
value
===
value
)
||
null
);
}
}
addExtraTokensForMergeRequests
()
{
addExtraTokensForMergeRequests
()
{
...
...
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
View file @
8b090caf
...
@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
...
@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
static
processTokens
(
input
,
allowedKeys
)
{
static
processTokens
(
input
,
allowedKeys
)
{
// Regex extracts `(token):(symbol)(value)`
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
// Values that start with a double quote must end in a double quote (same for single)
const
tokenRegex
=
new
RegExp
(
`(
${
allowedKeys
.
join
(
'
|
'
)}
):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`
,
'
g
'
);
const
tokenRegex
=
new
RegExp
(
`(
${
allowedKeys
.
join
(
'
|
'
)}
):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`
,
'
g
'
,
);
const
tokens
=
[];
const
tokens
=
[];
const
tokenIndexes
=
[];
// stores key+value for simple search
const
tokenIndexes
=
[];
// stores key+value for simple search
let
lastToken
=
null
;
let
lastToken
=
null
;
const
searchToken
=
input
.
replace
(
tokenRegex
,
(
match
,
key
,
symbol
,
v1
,
v2
,
v3
)
=>
{
const
searchToken
=
let
tokenValue
=
v1
||
v2
||
v3
;
input
let
tokenSymbol
=
symbol
;
.
replace
(
tokenRegex
,
(
match
,
key
,
symbol
,
v1
,
v2
,
v3
)
=>
{
let
tokenIndex
=
''
;
let
tokenValue
=
v1
||
v2
||
v3
;
let
tokenSymbol
=
symbol
;
if
(
tokenValue
===
'
~
'
||
tokenValue
===
'
%
'
||
tokenValue
===
'
@
'
)
{
let
tokenIndex
=
''
;
tokenSymbol
=
tokenValue
;
tokenValue
=
''
;
if
(
tokenValue
===
'
~
'
||
tokenValue
===
'
%
'
||
tokenValue
===
'
@
'
)
{
}
tokenSymbol
=
tokenValue
;
tokenValue
=
''
;
tokenIndex
=
`
${
key
}
:
${
tokenValue
}
`
;
}
// Prevent adding duplicates
tokenIndex
=
`
${
key
}
:
${
tokenValue
}
`
;
if
(
tokenIndexes
.
indexOf
(
tokenIndex
)
===
-
1
)
{
tokenIndexes
.
push
(
tokenIndex
);
// Prevent adding duplicates
if
(
tokenIndexes
.
indexOf
(
tokenIndex
)
===
-
1
)
{
tokens
.
push
({
tokenIndexes
.
push
(
tokenIndex
);
key
,
value
:
tokenValue
||
''
,
tokens
.
push
({
symbol
:
tokenSymbol
||
''
,
key
,
});
value
:
tokenValue
||
''
,
}
symbol
:
tokenSymbol
||
''
,
});
return
''
;
}
}).
replace
(
/
\s{2,}
/g
,
'
'
).
trim
()
||
''
;
return
''
;
})
.
replace
(
/
\s{2,}
/g
,
'
'
)
.
trim
()
||
''
;
if
(
tokens
.
length
>
0
)
{
if
(
tokens
.
length
>
0
)
{
const
last
=
tokens
[
tokens
.
length
-
1
];
const
last
=
tokens
[
tokens
.
length
-
1
];
const
lastString
=
`
${
last
.
key
}
:
${
last
.
symbol
}${
last
.
value
}
`
;
const
lastString
=
`
${
last
.
key
}
:
${
last
.
symbol
}${
last
.
value
}
`
;
lastToken
=
input
.
lastIndexOf
(
lastString
)
===
lastToken
=
input
.
length
-
lastString
.
length
?
last
:
searchToken
;
input
.
l
astIndexOf
(
lastString
)
===
input
.
l
ength
-
lastString
.
length
?
last
:
searchToken
;
}
else
{
}
else
{
lastToken
=
searchToken
;
lastToken
=
searchToken
;
}
}
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
View file @
8b090caf
...
@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
...
@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
return
{
return
{
lastVisualToken
,
lastVisualToken
,
isLastVisualTokenValid
:
lastVisualToken
===
null
||
lastVisualToken
.
className
.
indexOf
(
'
filtered-search-term
'
)
!==
-
1
||
(
lastVisualToken
&&
lastVisualToken
.
querySelector
(
'
.value
'
)
!==
null
),
isLastVisualTokenValid
:
lastVisualToken
===
null
||
lastVisualToken
.
className
.
indexOf
(
'
filtered-search-term
'
)
!==
-
1
||
(
lastVisualToken
&&
lastVisualToken
.
querySelector
(
'
.value
'
)
!==
null
),
};
};
}
}
...
@@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
...
@@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
}
}
static
unselectTokens
()
{
static
unselectTokens
()
{
const
otherTokens
=
FilteredSearchContainer
.
container
.
querySelectorAll
(
'
.js-visual-token .selectable.selected
'
);
const
otherTokens
=
FilteredSearchContainer
.
container
.
querySelectorAll
(
'
.js-visual-token .selectable.selected
'
,
);
[].
forEach
.
call
(
otherTokens
,
t
=>
t
.
classList
.
remove
(
'
selected
'
));
[].
forEach
.
call
(
otherTokens
,
t
=>
t
.
classList
.
remove
(
'
selected
'
));
}
}
...
@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
...
@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
}
}
static
createVisualTokenElementHTML
(
options
=
{})
{
static
createVisualTokenElementHTML
(
options
=
{})
{
const
{
const
{
canEdit
=
true
,
uppercaseTokenName
=
false
,
capitalizeTokenValue
=
false
}
=
options
;
canEdit
=
true
,
uppercaseTokenName
=
false
,
capitalizeTokenValue
=
false
,
}
=
options
;
return
`
return
`
<div class="
${
canEdit
?
'
selectable
'
:
'
hidden
'
}
" role="button">
<div class="
${
canEdit
?
'
selectable
'
:
'
hidden
'
}
" role="button">
...
@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
...
@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
return
AjaxCache
.
retrieve
(
labelsEndpoint
)
return
AjaxCache
.
retrieve
(
labelsEndpoint
)
.
then
(
FilteredSearchVisualTokens
.
preprocessLabel
.
bind
(
null
,
labelsEndpoint
))
.
then
(
FilteredSearchVisualTokens
.
preprocessLabel
.
bind
(
null
,
labelsEndpoint
))
.
then
((
labels
)
=>
{
.
then
(
labels
=>
{
const
matchingLabel
=
(
labels
||
[]).
find
(
label
=>
`~
${
DropdownUtils
.
getEscapedText
(
label
.
title
)}
`
===
tokenValue
);
const
matchingLabel
=
(
labels
||
[]).
find
(
label
=>
`~
${
DropdownUtils
.
getEscapedText
(
label
.
title
)}
`
===
tokenValue
,
);
if
(
!
matchingLabel
)
{
if
(
!
matchingLabel
)
{
return
;
return
;
}
}
FilteredSearchVisualTokens
FilteredSearchVisualTokens
.
setTokenStyle
(
.
setTokenStyle
(
tokenValueContainer
,
matchingLabel
.
color
,
matchingLabel
.
text_color
);
tokenValueContainer
,
matchingLabel
.
color
,
matchingLabel
.
text_color
,
);
})
})
.
catch
(()
=>
new
Flash
(
'
An error occurred while fetching label colors.
'
));
.
catch
(()
=>
new
Flash
(
'
An error occurred while fetching label colors.
'
));
}
}
...
@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
...
@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
}
}
const
username
=
tokenValue
.
replace
(
/^@/
,
''
);
const
username
=
tokenValue
.
replace
(
/^@/
,
''
);
return
UsersCache
.
retrieve
(
username
)
return
(
.
then
((
user
)
=>
{
UsersCache
.
retrieve
(
username
)
if
(
!
user
)
{
.
then
(
user
=>
{
return
;
if
(
!
user
)
{
}
return
;
}
/* eslint-disable no-param-reassign */
tokenValueContainer
.
dataset
.
originalValue
=
tokenValue
;
/* eslint-disable no-param-reassign */
tokenValueElement
.
innerHTML
=
`
tokenValueContainer
.
dataset
.
originalValue
=
tokenValue
;
tokenValueElement
.
innerHTML
=
`
<img class="avatar s20" src="
${
user
.
avatar_url
}
" alt="">
<img class="avatar s20" src="
${
user
.
avatar_url
}
" alt="">
${
_
.
escape
(
user
.
name
)}
${
_
.
escape
(
user
.
name
)}
`
;
`
;
/* eslint-enable no-param-reassign */
/* eslint-enable no-param-reassign */
})
})
// ignore error and leave username in the search bar
// ignore error and leave username in the search bar
.
catch
(()
=>
{
});
.
catch
(()
=>
{})
);
}
}
static
updateEmojiTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
)
{
static
updateEmojiTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
)
{
const
container
=
tokenValueContainer
;
const
container
=
tokenValueContainer
;
const
element
=
tokenValueElement
;
const
element
=
tokenValueElement
;
return
import
(
/* webpackChunkName: 'emoji' */
'
../emoji
'
)
return
(
.
then
((
Emoji
)
=>
{
import
(
/* webpackChunkName: 'emoji' */
'
../emoji
'
)
if
(
!
Emoji
.
isEmojiNameValid
(
tokenValue
))
{
.
then
(
Emoji
=>
{
return
;
if
(
!
Emoji
.
isEmojiNameValid
(
tokenValue
))
{
}
return
;
}
container
.
dataset
.
originalValue
=
tokenValue
;
element
.
innerHTML
=
Emoji
.
glEmojiTag
(
tokenValue
);
container
.
dataset
.
originalValue
=
tokenValue
;
})
element
.
innerHTML
=
Emoji
.
glEmojiTag
(
tokenValue
);
// ignore error and leave emoji name in the search bar
})
.
catch
(()
=>
{
});
// ignore error and leave emoji name in the search bar
.
catch
(()
=>
{})
);
}
}
static
renderVisualTokenValue
(
parentElement
,
tokenName
,
tokenValue
)
{
static
renderVisualTokenValue
(
parentElement
,
tokenName
,
tokenValue
)
{
...
@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
...
@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
const
tokenType
=
tokenName
.
toLowerCase
();
const
tokenType
=
tokenName
.
toLowerCase
();
if
(
tokenType
===
'
label
'
)
{
if
(
tokenType
===
'
label
'
)
{
FilteredSearchVisualTokens
.
updateLabelTokenColor
(
tokenValueContainer
,
tokenValue
);
FilteredSearchVisualTokens
.
updateLabelTokenColor
(
tokenValueContainer
,
tokenValue
);
}
else
if
(
(
tokenType
===
'
author
'
)
||
(
tokenType
===
'
assignee
'
)
)
{
}
else
if
(
tokenType
===
'
author
'
||
tokenType
===
'
assignee
'
)
{
FilteredSearchVisualTokens
.
updateUserTokenAppearance
(
FilteredSearchVisualTokens
.
updateUserTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
);
);
}
else
if
(
tokenType
===
'
my-reaction
'
)
{
}
else
if
(
tokenType
===
'
my-reaction
'
)
{
FilteredSearchVisualTokens
.
updateEmojiTokenAppearance
(
FilteredSearchVisualTokens
.
updateEmojiTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
);
);
}
}
}
}
static
addVisualTokenElement
(
name
,
value
,
options
=
{})
{
static
addVisualTokenElement
(
name
,
value
,
options
=
{})
{
const
{
const
{
isSearchTerm
=
false
,
canEdit
,
uppercaseTokenName
,
capitalizeTokenValue
}
=
options
;
isSearchTerm
=
false
,
canEdit
,
uppercaseTokenName
,
capitalizeTokenValue
,
}
=
options
;
const
li
=
document
.
createElement
(
'
li
'
);
const
li
=
document
.
createElement
(
'
li
'
);
li
.
classList
.
add
(
'
js-visual-token
'
);
li
.
classList
.
add
(
'
js-visual-token
'
);
li
.
classList
.
add
(
isSearchTerm
?
'
filtered-search-term
'
:
'
filtered-search-token
'
);
li
.
classList
.
add
(
isSearchTerm
?
'
filtered-search-term
'
:
'
filtered-search-token
'
);
...
@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
...
@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
}
}
static
addValueToPreviousVisualTokenElement
(
value
)
{
static
addValueToPreviousVisualTokenElement
(
value
)
{
const
{
lastVisualToken
,
isLastVisualTokenValid
}
=
const
{
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
lastVisualToken
,
isLastVisualTokenValid
,
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
if
(
!
isLastVisualTokenValid
&&
lastVisualToken
.
classList
.
contains
(
'
filtered-search-token
'
))
{
if
(
!
isLastVisualTokenValid
&&
lastVisualToken
.
classList
.
contains
(
'
filtered-search-token
'
))
{
const
name
=
FilteredSearchVisualTokens
.
getLastTokenPartial
();
const
name
=
FilteredSearchVisualTokens
.
getLastTokenPartial
();
...
@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
...
@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
}
}
}
}
static
addFilterVisualToken
(
tokenName
,
tokenValue
,
{
static
addFilterVisualToken
(
canEdit
,
tokenName
,
uppercaseTokenName
=
false
,
tokenValue
,
capitalizeTokenValue
=
false
,
{
canEdit
,
uppercaseTokenName
=
false
,
capitalizeTokenValue
=
false
}
=
{},
}
=
{})
{
)
{
const
{
lastVisualToken
,
isLastVisualTokenValid
}
const
{
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
lastVisualToken
,
isLastVisualTokenValid
,
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
const
{
addVisualTokenElement
}
=
FilteredSearchVisualTokens
;
const
{
addVisualTokenElement
}
=
FilteredSearchVisualTokens
;
if
(
isLastVisualTokenValid
)
{
if
(
isLastVisualTokenValid
)
{
...
@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
...
@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
static
tokenizeInput
()
{
static
tokenizeInput
()
{
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
{
isLastVisualTokenValid
}
=
const
{
isLastVisualTokenValid
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
if
(
input
.
value
)
{
if
(
input
.
value
)
{
if
(
isLastVisualTokenValid
)
{
if
(
isLastVisualTokenValid
)
{
...
@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
...
@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
FilteredSearchVisualTokens
.
tokenizeInput
();
FilteredSearchVisualTokens
.
tokenizeInput
();
if
(
!
tokenContainer
.
lastElementChild
.
isEqualNode
(
inputLi
))
{
if
(
!
tokenContainer
.
lastElementChild
.
isEqualNode
(
inputLi
))
{
const
{
isLastVisualTokenValid
}
=
const
{
isLastVisualTokenValid
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
if
(
!
isLastVisualTokenValid
)
{
if
(
!
isLastVisualTokenValid
)
{
const
lastPartial
=
FilteredSearchVisualTokens
.
getLastTokenPartial
();
const
lastPartial
=
FilteredSearchVisualTokens
.
getLastTokenPartial
();
...
...
app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
View file @
8b090caf
import
FilteredSearchTokenKeys
from
'
./filtered_search_token_keys
'
;
import
FilteredSearchTokenKeys
from
'
./filtered_search_token_keys
'
;
export
const
tokenKeys
=
[{
export
const
tokenKeys
=
[
key
:
'
author
'
,
{
type
:
'
string
'
,
key
:
'
author
'
,
param
:
'
username
'
,
type
:
'
string
'
,
symbol
:
'
@
'
,
param
:
'
username
'
,
icon
:
'
pencil
'
,
symbol
:
'
@
'
,
tag
:
'
@author
'
,
icon
:
'
pencil
'
,
},
{
tag
:
'
@author
'
,
key
:
'
assignee
'
,
},
type
:
'
string
'
,
{
param
:
'
username
'
,
key
:
'
assignee
'
,
symbol
:
'
@
'
,
type
:
'
string
'
,
icon
:
'
user
'
,
param
:
'
username
'
,
tag
:
'
@assignee
'
,
symbol
:
'
@
'
,
},
{
icon
:
'
user
'
,
key
:
'
milestone
'
,
tag
:
'
@assignee
'
,
type
:
'
string
'
,
},
param
:
'
title
'
,
{
symbol
:
'
%
'
,
key
:
'
milestone
'
,
icon
:
'
clock
'
,
type
:
'
string
'
,
tag
:
'
%milestone
'
,
param
:
'
title
'
,
},
{
symbol
:
'
%
'
,
key
:
'
label
'
,
icon
:
'
clock
'
,
type
:
'
array
'
,
tag
:
'
%milestone
'
,
param
:
'
name[]
'
,
},
symbol
:
'
~
'
,
{
icon
:
'
labels
'
,
key
:
'
label
'
,
tag
:
'
~label
'
,
type
:
'
array
'
,
}];
param
:
'
name[]
'
,
symbol
:
'
~
'
,
icon
:
'
labels
'
,
tag
:
'
~label
'
,
},
];
if
(
gon
.
current_user_id
)
{
if
(
gon
.
current_user_id
)
{
// Appending tokenkeys only logged-in
// Appending tokenkeys only logged-in
...
@@ -42,36 +47,47 @@ if (gon.current_user_id) {
...
@@ -42,36 +47,47 @@ if (gon.current_user_id) {
});
});
}
}
export
const
alternativeTokenKeys
=
[{
export
const
alternativeTokenKeys
=
[
key
:
'
label
'
,
{
type
:
'
string
'
,
key
:
'
label
'
,
param
:
'
name
'
,
type
:
'
string
'
,
symbol
:
'
~
'
,
param
:
'
name
'
,
}];
symbol
:
'
~
'
,
},
];
export
const
conditions
=
[{
export
const
conditions
=
[
url
:
'
assignee_id=0
'
,
{
tokenKey
:
'
assignee
'
,
url
:
'
assignee_id=0
'
,
value
:
'
none
'
,
tokenKey
:
'
assignee
'
,
},
{
value
:
'
none
'
,
url
:
'
milestone_title=No+Milestone
'
,
},
tokenKey
:
'
milestone
'
,
{
value
:
'
none
'
,
url
:
'
milestone_title=No+Milestone
'
,
},
{
tokenKey
:
'
milestone
'
,
url
:
'
milestone_title=%23upcoming
'
,
value
:
'
none
'
,
tokenKey
:
'
milestone
'
,
},
value
:
'
upcoming
'
,
{
},
{
url
:
'
milestone_title=%23upcoming
'
,
url
:
'
milestone_title=%23started
'
,
tokenKey
:
'
milestone
'
,
tokenKey
:
'
milestone
'
,
value
:
'
upcoming
'
,
value
:
'
started
'
,
},
},
{
{
url
:
'
label_name[]=No+Label
'
,
url
:
'
milestone_title=%23started
'
,
tokenKey
:
'
label
'
,
tokenKey
:
'
milestone
'
,
value
:
'
none
'
,
value
:
'
started
'
,
}];
},
{
url
:
'
label_name[]=No+Label
'
,
tokenKey
:
'
label
'
,
value
:
'
none
'
,
},
];
const
IssuableFilteredSearchTokenKeys
=
const
IssuableFilteredSearchTokenKeys
=
new
FilteredSearchTokenKeys
(
new
FilteredSearchTokenKeys
(
tokenKeys
,
alternativeTokenKeys
,
conditions
);
tokenKeys
,
alternativeTokenKeys
,
conditions
,
);
export
default
IssuableFilteredSearchTokenKeys
;
export
default
IssuableFilteredSearchTokenKeys
;
app/assets/javascripts/filtered_search/recent_searches_root.js
View file @
8b090caf
...
@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
...
@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
import
eventHub
from
'
./event_hub
'
;
import
eventHub
from
'
./event_hub
'
;
class
RecentSearchesRoot
{
class
RecentSearchesRoot
{
constructor
(
constructor
(
recentSearchesStore
,
recentSearchesService
,
wrapperElement
)
{
recentSearchesStore
,
recentSearchesService
,
wrapperElement
,
)
{
this
.
store
=
recentSearchesStore
;
this
.
store
=
recentSearchesStore
;
this
.
service
=
recentSearchesService
;
this
.
service
=
recentSearchesService
;
this
.
wrapperElement
=
wrapperElement
;
this
.
wrapperElement
=
wrapperElement
;
...
@@ -35,7 +31,9 @@ class RecentSearchesRoot {
...
@@ -35,7 +31,9 @@ class RecentSearchesRoot {
components
:
{
components
:
{
RecentSearchesDropdownContent
,
RecentSearchesDropdownContent
,
},
},
data
()
{
return
state
;
},
data
()
{
return
state
;
},
template
:
`
template
:
`
<recent-searches-dropdown-content
<recent-searches-dropdown-content
:items="recentSearches"
:items="recentSearches"
...
@@ -57,7 +55,6 @@ class RecentSearchesRoot {
...
@@ -57,7 +55,6 @@ class RecentSearchesRoot {
this
.
vm
.
$destroy
();
this
.
vm
.
$destroy
();
}
}
}
}
}
}
export
default
RecentSearchesRoot
;
export
default
RecentSearchesRoot
;
app/assets/javascripts/filtered_search/stores/recent_searches_store.js
View file @
8b090caf
...
@@ -2,11 +2,14 @@ import _ from 'underscore';
...
@@ -2,11 +2,14 @@ import _ from 'underscore';
class
RecentSearchesStore
{
class
RecentSearchesStore
{
constructor
(
initialState
=
{},
allowedKeys
)
{
constructor
(
initialState
=
{},
allowedKeys
)
{
this
.
state
=
Object
.
assign
({
this
.
state
=
Object
.
assign
(
isLocalStorageAvailable
:
true
,
{
recentSearches
:
[],
isLocalStorageAvailable
:
true
,
allowedKeys
,
recentSearches
:
[],
},
initialState
);
allowedKeys
,
},
initialState
,
);
}
}
addRecentSearch
(
newSearch
)
{
addRecentSearch
(
newSearch
)
{
...
...
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