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
dfb08243
Commit
dfb08243
authored
Oct 24, 2018
by
Mike Greiling
Committed by
Tim Zallmann
Oct 24, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Prettify all the things (part 7)
parent
f8f1466a
Changes
48
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
48 changed files
with
826 additions
and
679 deletions
+826
-679
app/assets/javascripts/breadcrumb.js
app/assets/javascripts/breadcrumb.js
+8
-7
app/assets/javascripts/build_artifacts.js
app/assets/javascripts/build_artifacts.js
+11
-7
app/assets/javascripts/ci_variable_list/ajax_variable_list.js
...assets/javascripts/ci_variable_list/ajax_variable_list.js
+29
-27
app/assets/javascripts/ci_variable_list/ci_variable_list.js
app/assets/javascripts/ci_variable_list/ci_variable_list.js
+21
-16
app/assets/javascripts/ci_variable_list/native_form_variable_list.js
...javascripts/ci_variable_list/native_form_variable_list.js
+1
-4
app/assets/javascripts/clusters/stores/clusters_store.js
app/assets/javascripts/clusters/stores/clusters_store.js
+2
-6
app/assets/javascripts/comment_type_toggle.js
app/assets/javascripts/comment_type_toggle.js
+32
-24
app/assets/javascripts/commit/image_file.js
app/assets/javascripts/commit/image_file.js
+179
-141
app/assets/javascripts/commit/pipelines/pipelines_bundle.js
app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+6
-4
app/assets/javascripts/commit/pipelines/pipelines_table.vue
app/assets/javascripts/commit/pipelines/pipelines_table.vue
+59
-63
app/assets/javascripts/commit_merge_requests.js
app/assets/javascripts/commit_merge_requests.js
+4
-3
app/assets/javascripts/commits.js
app/assets/javascripts/commits.js
+27
-11
app/assets/javascripts/commons/bootstrap.js
app/assets/javascripts/commons/bootstrap.js
+10
-2
app/assets/javascripts/confirm_danger_modal.js
app/assets/javascripts/confirm_danger_modal.js
+14
-10
app/assets/javascripts/contextual_sidebar.js
app/assets/javascripts/contextual_sidebar.js
+5
-2
app/assets/javascripts/create_item_dropdown.js
app/assets/javascripts/create_item_dropdown.js
+5
-8
app/assets/javascripts/create_label.js
app/assets/javascripts/create_label.js
+31
-32
app/assets/javascripts/deploy_keys/components/app.vue
app/assets/javascripts/deploy_keys/components/app.vue
+4
-2
app/assets/javascripts/deploy_keys/service/index.js
app/assets/javascripts/deploy_keys/service/index.js
+3
-6
app/assets/javascripts/diff.js
app/assets/javascripts/diff.js
+9
-5
app/assets/javascripts/dropzone_input.js
app/assets/javascripts/dropzone_input.js
+26
-15
app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
...s/javascripts/emoji/support/is_emoji_unicode_supported.js
+30
-25
app/assets/javascripts/experimental_flags.js
app/assets/javascripts/experimental_flags.js
+1
-1
app/assets/javascripts/files_comment_button.js
app/assets/javascripts/files_comment_button.js
+8
-4
app/assets/javascripts/filterable_list.js
app/assets/javascripts/filterable_list.js
+16
-9
app/assets/javascripts/flash.js
app/assets/javascripts/flash.js
+17
-16
app/assets/javascripts/fly_out_nav.js
app/assets/javascripts/fly_out_nav.js
+33
-19
app/assets/javascripts/gl_field_error.js
app/assets/javascripts/gl_field_error.js
+2
-1
app/assets/javascripts/gl_field_errors.js
app/assets/javascripts/gl_field_errors.js
+9
-5
app/assets/javascripts/gl_form.js
app/assets/javascripts/gl_form.js
+12
-7
app/assets/javascripts/group_avatar.js
app/assets/javascripts/group_avatar.js
+3
-2
app/assets/javascripts/group_label_subscription.js
app/assets/javascripts/group_label_subscription.js
+7
-3
app/assets/javascripts/groups/components/item_stats.vue
app/assets/javascripts/groups/components/item_stats.vue
+36
-36
app/assets/javascripts/groups/components/item_stats_value.vue
...assets/javascripts/groups/components/item_stats_value.vue
+43
-43
app/assets/javascripts/groups/new_group_child.js
app/assets/javascripts/groups/new_group_child.js
+12
-10
app/assets/javascripts/groups/store/groups_store.js
app/assets/javascripts/groups/store/groups_store.js
+12
-11
app/assets/javascripts/groups/transfer_dropdown.js
app/assets/javascripts/groups/transfer_dropdown.js
+1
-1
app/assets/javascripts/groups_select.js
app/assets/javascripts/groups_select.js
+6
-3
app/assets/javascripts/helpers/avatar_helper.js
app/assets/javascripts/helpers/avatar_helper.js
+3
-1
app/assets/javascripts/image_diff/image_diff.js
app/assets/javascripts/image_diff/image_diff.js
+4
-2
app/assets/javascripts/image_diff/init_discussion_tab.js
app/assets/javascripts/image_diff/init_discussion_tab.js
+2
-1
app/assets/javascripts/image_diff/replaced_image_diff.js
app/assets/javascripts/image_diff/replaced_image_diff.js
+7
-8
app/assets/javascripts/importer_status.js
app/assets/javascripts/importer_status.js
+62
-60
app/assets/javascripts/init_changes_dropdown.js
app/assets/javascripts/init_changes_dropdown.js
+1
-1
app/assets/javascripts/init_notes.js
app/assets/javascripts/init_notes.js
+1
-7
app/assets/javascripts/integrations/integration_settings_form.js
...ets/javascripts/integrations/integration_settings_form.js
+3
-2
app/assets/javascripts/issuable/auto_width_dropdown_select.js
...assets/javascripts/issuable/auto_width_dropdown_select.js
+4
-1
app/assets/javascripts/issuable_bulk_update_actions.js
app/assets/javascripts/issuable_bulk_update_actions.js
+5
-5
No files found.
app/assets/javascripts/breadcrumb.js
View file @
dfb08243
import
$
from
'
jquery
'
;
export
const
addTooltipToEl
=
(
el
)
=>
{
export
const
addTooltipToEl
=
el
=>
{
const
textEl
=
el
.
querySelector
(
'
.js-breadcrumb-item-text
'
);
if
(
textEl
&&
textEl
.
scrollWidth
>
textEl
.
offsetWidth
)
{
...
...
@@ -14,17 +14,18 @@ export default () => {
const
breadcrumbs
=
document
.
querySelector
(
'
.js-breadcrumbs-list
'
);
if
(
breadcrumbs
)
{
const
topLevelLinks
=
[...
breadcrumbs
.
children
].
filter
(
el
=>
!
el
.
classList
.
contains
(
'
dropdown
'
))
const
topLevelLinks
=
[...
breadcrumbs
.
children
]
.
filter
(
el
=>
!
el
.
classList
.
contains
(
'
dropdown
'
))
.
map
(
el
=>
el
.
querySelector
(
'
a
'
))
.
filter
(
el
=>
el
);
const
$expander
=
$
(
'
.js-breadcrumbs-collapsed-expander
'
);
topLevelLinks
.
forEach
(
el
=>
addTooltipToEl
(
el
));
$expander
.
closest
(
'
.dropdown
'
)
.
on
(
'
show.bs.dropdown hide.bs.dropdown
'
,
(
e
)
=>
{
$
(
'
.js-breadcrumbs-collapsed-expander
'
,
e
.
currentTarget
)
.
toggleClass
(
'
open
'
)
.
tooltip
(
'
hide
'
);
});
$expander
.
closest
(
'
.dropdown
'
)
.
on
(
'
show.bs.dropdown hide.bs.dropdown
'
,
e
=>
{
$
(
'
.js-breadcrumbs-collapsed-expander
'
,
e
.
currentTarget
)
.
toggleClass
(
'
open
'
)
.
tooltip
(
'
hide
'
);
});
}
};
app/assets/javascripts/build_artifacts.js
View file @
dfb08243
...
...
@@ -12,16 +12,16 @@ export default class BuildArtifacts {
}
// eslint-disable-next-line class-methods-use-this
disablePropagation
()
{
$
(
'
.top-block
'
).
on
(
'
click
'
,
'
.download
'
,
function
(
e
)
{
$
(
'
.top-block
'
).
on
(
'
click
'
,
'
.download
'
,
function
(
e
)
{
return
e
.
stopPropagation
();
});
return
$
(
'
.tree-holder
'
).
on
(
'
click
'
,
'
tr[data-link] a
'
,
function
(
e
)
{
return
$
(
'
.tree-holder
'
).
on
(
'
click
'
,
'
tr[data-link] a
'
,
function
(
e
)
{
return
e
.
stopImmediatePropagation
();
});
}
// eslint-disable-next-line class-methods-use-this
setupEntryClick
()
{
return
$
(
'
.tree-holder
'
).
on
(
'
click
'
,
'
tr[data-link]
'
,
function
()
{
return
$
(
'
.tree-holder
'
).
on
(
'
click
'
,
'
tr[data-link]
'
,
function
()
{
visitUrl
(
this
.
dataset
.
link
,
convertPermissionToBoolean
(
this
.
dataset
.
externalLink
));
});
}
...
...
@@ -37,11 +37,15 @@ export default class BuildArtifacts {
// We want the tooltip to show if you hover anywhere on the row
// But be placed below and in the middle of the file name
$
(
'
.js-artifact-tree-row
'
)
.
on
(
'
mouseenter
'
,
(
e
)
=>
{
$
(
e
.
currentTarget
).
find
(
'
.js-artifact-tree-tooltip
'
).
tooltip
(
'
show
'
);
.
on
(
'
mouseenter
'
,
e
=>
{
$
(
e
.
currentTarget
)
.
find
(
'
.js-artifact-tree-tooltip
'
)
.
tooltip
(
'
show
'
);
})
.
on
(
'
mouseleave
'
,
(
e
)
=>
{
$
(
e
.
currentTarget
).
find
(
'
.js-artifact-tree-tooltip
'
).
tooltip
(
'
hide
'
);
.
on
(
'
mouseleave
'
,
e
=>
{
$
(
e
.
currentTarget
)
.
find
(
'
.js-artifact-tree-tooltip
'
)
.
tooltip
(
'
hide
'
);
});
}
}
app/assets/javascripts/ci_variable_list/ajax_variable_list.js
View file @
dfb08243
...
...
@@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status';
import
VariableList
from
'
./ci_variable_list
'
;
function
generateErrorBoxContent
(
errors
)
{
const
errorList
=
[].
concat
(
errors
).
map
(
errorString
=>
`
const
errorList
=
[].
concat
(
errors
).
map
(
errorString
=>
`
<li>
${
_
.
escape
(
errorString
)}
</li>
`
);
`
,
);
return
`
<p>
...
...
@@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export
default
class
AjaxVariableList
{
constructor
({
container
,
saveButton
,
errorBox
,
formField
=
'
variables
'
,
saveEndpoint
,
})
{
constructor
({
container
,
saveButton
,
errorBox
,
formField
=
'
variables
'
,
saveEndpoint
})
{
this
.
container
=
container
;
this
.
saveButton
=
saveButton
;
this
.
errorBox
=
errorBox
;
...
...
@@ -58,18 +54,21 @@ export default class AjaxVariableList {
// to match it up in `updateRowsWithPersistedVariables`
this
.
variableList
.
toggleEnableRow
(
false
);
return
axios
.
patch
(
this
.
saveEndpoint
,
{
variables_attributes
:
this
.
variableList
.
getAllData
(),
},
{
// We want to be able to process the `res.data` from a 400 error response
// and print the validation messages such as duplicate variable keys
validateStatus
:
status
=>
(
status
>=
statusCodes
.
OK
&&
status
<
statusCodes
.
MULTIPLE_CHOICES
)
||
status
===
statusCodes
.
BAD_REQUEST
,
})
.
then
((
res
)
=>
{
return
axios
.
patch
(
this
.
saveEndpoint
,
{
variables_attributes
:
this
.
variableList
.
getAllData
(),
},
{
// We want to be able to process the `res.data` from a 400 error response
// and print the validation messages such as duplicate variable keys
validateStatus
:
status
=>
(
status
>=
statusCodes
.
OK
&&
status
<
statusCodes
.
MULTIPLE_CHOICES
)
||
status
===
statusCodes
.
BAD_REQUEST
,
},
)
.
then
(
res
=>
{
loadingIcon
.
classList
.
toggle
(
'
hide
'
,
true
);
this
.
variableList
.
toggleEnableRow
(
true
);
...
...
@@ -90,18 +89,21 @@ export default class AjaxVariableList {
}
updateRowsWithPersistedVariables
(
persistedVariables
=
[])
{
const
persistedVariableMap
=
[].
concat
(
persistedVariables
).
reduce
((
variableMap
,
variable
)
=>
({
...
variableMap
,
[
variable
.
key
]:
variable
,
}),
{});
const
persistedVariableMap
=
[].
concat
(
persistedVariables
).
reduce
(
(
variableMap
,
variable
)
=>
({
...
variableMap
,
[
variable
.
key
]:
variable
,
}),
{},
);
this
.
container
.
querySelectorAll
(
'
.js-row
'
).
forEach
(
(
row
)
=>
{
this
.
container
.
querySelectorAll
(
'
.js-row
'
).
forEach
(
row
=>
{
// If we submitted a row that was destroyed, remove it so we don't try
// to destroy it again which would cause a BE error
const
destroyInput
=
row
.
querySelector
(
'
.js-ci-variable-input-destroy
'
);
if
(
convertPermissionToBoolean
(
destroyInput
.
value
))
{
row
.
remove
();
// Update the ID input so any future edits and `_destroy` will apply on the BE
// Update the ID input so any future edits and `_destroy` will apply on the BE
}
else
{
const
key
=
row
.
querySelector
(
'
.js-ci-variable-input-key
'
).
value
;
const
persistedVariable
=
persistedVariableMap
[
key
];
...
...
app/assets/javascripts/ci_variable_list/ci_variable_list.js
View file @
dfb08243
...
...
@@ -16,10 +16,7 @@ function createEnvironmentItem(value) {
}
export
default
class
VariableList
{
constructor
({
container
,
formField
,
})
{
constructor
({
container
,
formField
})
{
this
.
$container
=
$
(
container
);
this
.
formField
=
formField
;
this
.
environmentDropdownMap
=
new
WeakMap
();
...
...
@@ -71,7 +68,7 @@ export default class VariableList {
this
.
initRow
(
rowEl
);
});
this
.
$container
.
on
(
'
click
'
,
'
.js-row-remove-button
'
,
(
e
)
=>
{
this
.
$container
.
on
(
'
click
'
,
'
.js-row-remove-button
'
,
e
=>
{
e
.
preventDefault
();
this
.
removeRow
(
$
(
e
.
currentTarget
).
closest
(
'
.js-row
'
));
});
...
...
@@ -81,7 +78,7 @@ export default class VariableList {
.
join
(
'
,
'
);
// Remove any empty rows except the last row
this
.
$container
.
on
(
'
blur
'
,
inputSelector
,
(
e
)
=>
{
this
.
$container
.
on
(
'
blur
'
,
inputSelector
,
e
=>
{
const
$row
=
$
(
e
.
currentTarget
).
closest
(
'
.js-row
'
);
if
(
$row
.
is
(
'
:not(:last-child)
'
)
&&
!
this
.
checkIfRowTouched
(
$row
))
{
...
...
@@ -136,7 +133,7 @@ export default class VariableList {
$rowClone
.
removeAttr
(
'
data-is-persisted
'
);
// Reset the inputs to their defaults
Object
.
keys
(
this
.
inputMap
).
forEach
(
(
name
)
=>
{
Object
.
keys
(
this
.
inputMap
).
forEach
(
name
=>
{
const
entry
=
this
.
inputMap
[
name
];
$rowClone
.
find
(
entry
.
selector
).
val
(
entry
.
default
);
});
...
...
@@ -171,7 +168,7 @@ export default class VariableList {
}
checkIfRowTouched
(
$row
)
{
return
Object
.
keys
(
this
.
inputMap
).
some
(
(
name
)
=>
{
return
Object
.
keys
(
this
.
inputMap
).
some
(
name
=>
{
const
entry
=
this
.
inputMap
[
name
];
const
$el
=
$row
.
find
(
entry
.
selector
);
return
$el
.
length
&&
$el
.
val
()
!==
entry
.
default
;
...
...
@@ -190,11 +187,14 @@ export default class VariableList {
getAllData
()
{
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
const
validRows
=
this
.
$container
.
find
(
'
.js-row
'
).
toArray
().
slice
(
0
,
-
1
);
const
validRows
=
this
.
$container
.
find
(
'
.js-row
'
)
.
toArray
()
.
slice
(
0
,
-
1
);
return
validRows
.
map
(
(
rowEl
)
=>
{
return
validRows
.
map
(
rowEl
=>
{
const
resultant
=
{};
Object
.
keys
(
this
.
inputMap
).
forEach
(
(
name
)
=>
{
Object
.
keys
(
this
.
inputMap
).
forEach
(
name
=>
{
const
entry
=
this
.
inputMap
[
name
];
const
$input
=
$
(
rowEl
).
find
(
entry
.
selector
);
if
(
$input
.
length
)
{
...
...
@@ -207,11 +207,16 @@ export default class VariableList {
}
getEnvironmentValues
()
{
const
valueMap
=
this
.
$container
.
find
(
this
.
inputMap
.
environment_scope
.
selector
).
toArray
()
.
reduce
((
prevValueMap
,
envInput
)
=>
({
...
prevValueMap
,
[
envInput
.
value
]:
envInput
.
value
,
}),
{});
const
valueMap
=
this
.
$container
.
find
(
this
.
inputMap
.
environment_scope
.
selector
)
.
toArray
()
.
reduce
(
(
prevValueMap
,
envInput
)
=>
({
...
prevValueMap
,
[
envInput
.
value
]:
envInput
.
value
,
}),
{},
);
return
Object
.
keys
(
valueMap
).
map
(
createEnvironmentItem
);
}
...
...
app/assets/javascripts/ci_variable_list/native_form_variable_list.js
View file @
dfb08243
...
...
@@ -2,10 +2,7 @@ import $ from 'jquery';
import
VariableList
from
'
./ci_variable_list
'
;
// Used for the variable list on scheduled pipeline edit page
export
default
function
setupNativeFormVariableList
({
container
,
formField
=
'
variables
'
,
})
{
export
default
function
setupNativeFormVariableList
({
container
,
formField
=
'
variables
'
})
{
const
$container
=
$
(
container
);
const
variableList
=
new
VariableList
({
...
...
app/assets/javascripts/clusters/stores/clusters_store.js
View file @
dfb08243
...
...
@@ -76,12 +76,8 @@ export default class ClusterStore {
this
.
state
.
status
=
serverState
.
status
;
this
.
state
.
statusReason
=
serverState
.
status_reason
;
serverState
.
applications
.
forEach
((
serverAppEntry
)
=>
{
const
{
name
:
appId
,
status
,
status_reason
:
statusReason
,
}
=
serverAppEntry
;
serverState
.
applications
.
forEach
(
serverAppEntry
=>
{
const
{
name
:
appId
,
status
,
status_reason
:
statusReason
}
=
serverAppEntry
;
this
.
state
.
applications
[
appId
]
=
{
...(
this
.
state
.
applications
[
appId
]
||
{}),
...
...
app/assets/javascripts/comment_type_toggle.js
View file @
dfb08243
...
...
@@ -24,36 +24,44 @@ class CommentTypeToggle {
setConfig
()
{
const
config
=
{
InputSetter
:
[{
input
:
this
.
noteTypeInput
,
valueAttribute
:
'
data-value
'
,
},
{
input
:
this
.
submitButton
,
valueAttribute
:
'
data-submit-text
'
,
}],
InputSetter
:
[
{
input
:
this
.
noteTypeInput
,
valueAttribute
:
'
data-value
'
,
},
{
input
:
this
.
submitButton
,
valueAttribute
:
'
data-submit-text
'
,
},
],
};
if
(
this
.
closeButton
)
{
config
.
InputSetter
.
push
({
input
:
this
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
},
{
input
:
this
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
});
config
.
InputSetter
.
push
(
{
input
:
this
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
},
{
input
:
this
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
},
);
}
if
(
this
.
reopenButton
)
{
config
.
InputSetter
.
push
({
input
:
this
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
},
{
input
:
this
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
});
config
.
InputSetter
.
push
(
{
input
:
this
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
},
{
input
:
this
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
},
);
}
return
config
;
...
...
app/assets/javascripts/commit/image_file.js
View file @
dfb08243
This diff is collapsed.
Click to expand it.
app/assets/javascripts/commit/pipelines/pipelines_bundle.js
View file @
dfb08243
...
...
@@ -19,11 +19,13 @@ export default () => {
const
pipelineTableViewEl
=
document
.
querySelector
(
'
#commit-pipeline-table-view
'
);
if
(
pipelineTableViewEl
)
{
// Update MR and Commits tabs
pipelineTableViewEl
.
addEventListener
(
'
update-pipelines-count
'
,
(
event
)
=>
{
if
(
event
.
detail
.
pipelines
&&
// Update MR and Commits tabs
pipelineTableViewEl
.
addEventListener
(
'
update-pipelines-count
'
,
event
=>
{
if
(
event
.
detail
.
pipelines
&&
event
.
detail
.
pipelines
.
count
&&
event
.
detail
.
pipelines
.
count
.
all
)
{
event
.
detail
.
pipelines
.
count
.
all
)
{
const
badge
=
document
.
querySelector
(
'
.js-pipelines-mr-count
'
);
badge
.
textContent
=
event
.
detail
.
pipelines
.
count
.
all
;
...
...
app/assets/javascripts/commit/pipelines/pipelines_table.vue
View file @
dfb08243
<
script
>
import
PipelinesService
from
'
../../pipelines/services/pipelines_service
'
;
import
PipelineStore
from
'
../../pipelines/stores/pipelines_store
'
;
import
pipelinesMixin
from
'
../../pipelines/mixins/pipelines
'
;
import
PipelinesService
from
'
../../pipelines/services/pipelines_service
'
;
import
PipelineStore
from
'
../../pipelines/stores/pipelines_store
'
;
import
pipelinesMixin
from
'
../../pipelines/mixins/pipelines
'
;
export
default
{
mixins
:
[
pipelinesMixin
,
],
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
true
,
},
autoDevopsHelpPath
:
{
type
:
String
,
required
:
true
,
},
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
viewType
:
{
type
:
String
,
required
:
false
,
default
:
'
child
'
,
},
export
default
{
mixins
:
[
pipelinesMixin
],
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
true
,
},
autoDevopsHelpPath
:
{
type
:
String
,
required
:
true
,
},
errorStateSvgPath
:
{
type
:
String
,
required
:
true
,
},
viewType
:
{
type
:
String
,
required
:
false
,
default
:
'
child
'
,
},
},
data
()
{
const
store
=
new
PipelineStore
();
data
()
{
const
store
=
new
PipelineStore
();
return
{
store
,
state
:
store
.
state
,
};
},
return
{
store
,
state
:
store
.
state
,
};
},
computed
:
{
shouldRenderTable
()
{
return
!
this
.
isLoading
&&
this
.
state
.
pipelines
.
length
>
0
&&
!
this
.
hasError
;
},
shouldRenderErrorState
()
{
return
this
.
hasError
&&
!
this
.
isLoading
;
},
computed
:
{
shouldRenderTable
()
{
return
!
this
.
isLoading
&&
this
.
state
.
pipelines
.
length
>
0
&&
!
this
.
hasError
;
},
created
()
{
this
.
service
=
new
PipelinesService
(
this
.
endpoint
)
;
shouldRenderErrorState
()
{
return
this
.
hasError
&&
!
this
.
isLoading
;
},
methods
:
{
successCallback
(
resp
)
{
// depending of the endpoint the response can either bring a `pipelines` key or not.
const
pipelines
=
resp
.
data
.
pipelines
||
resp
.
data
;
this
.
setCommonData
(
pipelines
);
},
created
()
{
this
.
service
=
new
PipelinesService
(
this
.
endpoint
);
},
methods
:
{
successCallback
(
resp
)
{
// depending of the endpoint the response can either bring a `pipelines` key or not.
const
pipelines
=
resp
.
data
.
pipelines
||
resp
.
data
;
this
.
setCommonData
(
pipelines
);
const
updatePipelinesEvent
=
new
CustomEvent
(
'
update-pipelines-count
'
,
{
detail
:
{
pipelines
:
resp
.
data
,
},
});
const
updatePipelinesEvent
=
new
CustomEvent
(
'
update-pipelines-count
'
,
{
detail
:
{
pipelines
:
resp
.
data
,
},
});
// notifiy to update the count in tabs
if
(
this
.
$el
.
parentElement
)
{
this
.
$el
.
parentElement
.
dispatchEvent
(
updatePipelinesEvent
);
}
},
// notifiy to update the count in tabs
if
(
this
.
$el
.
parentElement
)
{
this
.
$el
.
parentElement
.
dispatchEvent
(
updatePipelinesEvent
);
}
},
};
},
};
</
script
>
<
template
>
<div
class=
"content-list pipelines"
>
...
...
app/assets/javascripts/commit_merge_requests.js
View file @
dfb08243
...
...
@@ -50,7 +50,7 @@ export function createContent(mergeRequests) {
if
(
mergeRequests
.
length
===
0
)
{
$content
.
text
(
s__
(
'
Commits|No related merge requests found
'
));
}
else
{
mergeRequests
.
forEach
(
(
mergeRequest
)
=>
{
mergeRequests
.
forEach
(
mergeRequest
=>
{
const
$header
=
createHeader
(
$content
.
children
().
length
,
mergeRequests
.
length
);
const
$item
=
createItem
(
mergeRequest
);
$content
.
append
(
$header
);
...
...
@@ -64,8 +64,9 @@ export function createContent(mergeRequests) {
export
function
fetchCommitMergeRequests
()
{
const
$container
=
$
(
'
.merge-requests
'
);
axios
.
get
(
$container
.
data
(
'
projectCommitPath
'
))
.
then
((
response
)
=>
{
axios
.
get
(
$container
.
data
(
'
projectCommitPath
'
))
.
then
(
response
=>
{
const
$content
=
createContent
(
response
.
data
);
$container
.
html
(
$content
);
...
...
app/assets/javascripts/commits.js
View file @
dfb08243
...
...
@@ -32,22 +32,31 @@ export default class CommitsList {
if
(
search
===
this
.
lastSearch
)
return
Promise
.
resolve
();
const
commitsUrl
=
`
${
form
.
attr
(
'
action
'
)}
?
${
form
.
serialize
()}
`
;
this
.
content
.
fadeTo
(
'
fast
'
,
0.5
);
const
params
=
form
.
serializeArray
().
reduce
((
acc
,
obj
)
=>
Object
.
assign
(
acc
,
{
[
obj
.
name
]:
obj
.
value
,
}),
{});
const
params
=
form
.
serializeArray
().
reduce
(
(
acc
,
obj
)
=>
Object
.
assign
(
acc
,
{
[
obj
.
name
]:
obj
.
value
,
}),
{},
);
return
axios
.
get
(
form
.
attr
(
'
action
'
),
{
params
,
})
return
axios
.
get
(
form
.
attr
(
'
action
'
),
{
params
,
})
.
then
(({
data
})
=>
{
this
.
lastSearch
=
search
;
this
.
content
.
html
(
data
.
html
);
this
.
content
.
fadeTo
(
'
fast
'
,
1.0
);
// Change url so if user reload a page - search results are saved
window
.
history
.
replaceState
({
page
:
commitsUrl
,
},
document
.
title
,
commitsUrl
);
window
.
history
.
replaceState
(
{
page
:
commitsUrl
,
},
document
.
title
,
commitsUrl
,
);
})
.
catch
(()
=>
{
this
.
content
.
fadeTo
(
'
fast
'
,
1.0
);
...
...
@@ -75,8 +84,15 @@ export default class CommitsList {
processedData
=
$processedData
.
not
(
`li.js-commit-header[data-day='
${
loadedShownDayFirst
}
']`
);
// Update commits count in the previous commits header.
commitsCount
+=
Number
(
$
(
processedData
).
nextUntil
(
'
li.js-commit-header
'
).
first
().
find
(
'
li.commit
'
).
length
);
$commitsHeadersLast
.
find
(
'
span.commits-count
'
).
text
(
`
${
commitsCount
}
${
pluralize
(
'
commit
'
,
commitsCount
)}
`
);
commitsCount
+=
Number
(
$
(
processedData
)
.
nextUntil
(
'
li.js-commit-header
'
)
.
first
()
.
find
(
'
li.commit
'
).
length
,
);
$commitsHeadersLast
.
find
(
'
span.commits-count
'
)
.
text
(
`
${
commitsCount
}
${
pluralize
(
'
commit
'
,
commitsCount
)}
`
);
}
localTimeAgo
(
$processedData
.
find
(
'
.js-timeago
'
));
...
...
app/assets/javascripts/commons/bootstrap.js
View file @
dfb08243
...
...
@@ -5,6 +5,14 @@ import 'bootstrap';
// custom jQuery functions
$
.
fn
.
extend
({
disable
()
{
return
$
(
this
).
prop
(
'
disabled
'
,
true
).
addClass
(
'
disabled
'
);
},
enable
()
{
return
$
(
this
).
prop
(
'
disabled
'
,
false
).
removeClass
(
'
disabled
'
);
},
disable
()
{
return
$
(
this
)
.
prop
(
'
disabled
'
,
true
)
.
addClass
(
'
disabled
'
);
},
enable
()
{
return
$
(
this
)
.
prop
(
'
disabled
'
,
false
)
.
removeClass
(
'
disabled
'
);
},
});
app/assets/javascripts/confirm_danger_modal.js
View file @
dfb08243
...
...
@@ -13,19 +13,23 @@ function openConfirmDangerModal($form, text) {
$submit
.
disable
();
$input
.
focus
();
$
(
'
.js-confirm-danger-input
'
).
off
(
'
input
'
).
on
(
'
input
'
,
function
handleInput
()
{
const
confirmText
=
rstrip
(
$
(
this
).
val
());
if
(
confirmText
===
confirmTextMatch
)
{
$submit
.
enable
();
}
else
{
$submit
.
disable
();
}
});
$
(
'
.js-confirm-danger-submit
'
).
off
(
'
click
'
).
on
(
'
click
'
,
()
=>
$form
.
submit
());
$
(
'
.js-confirm-danger-input
'
)
.
off
(
'
input
'
)
.
on
(
'
input
'
,
function
handleInput
()
{
const
confirmText
=
rstrip
(
$
(
this
).
val
());
if
(
confirmText
===
confirmTextMatch
)
{
$submit
.
enable
();
}
else
{
$submit
.
disable
();
}
});
$
(
'
.js-confirm-danger-submit
'
)
.
off
(
'
click
'
)
.
on
(
'
click
'
,
()
=>
$form
.
submit
());
}
export
default
function
initConfirmDangerModal
()
{
$
(
document
).
on
(
'
click
'
,
'
.js-confirm-danger
'
,
(
e
)
=>
{
$
(
document
).
on
(
'
click
'
,
'
.js-confirm-danger
'
,
e
=>
{
e
.
preventDefault
();
const
$btn
=
$
(
e
.
target
);
const
$form
=
$btn
.
closest
(
'
form
'
);
...
...
app/assets/javascripts/contextual_sidebar.js
View file @
dfb08243
...
...
@@ -20,8 +20,11 @@ export default class ContextualSidebar {
}
bindEvents
()
{
document
.
addEventListener
(
'
click
'
,
(
e
)
=>
{
if
(
!
e
.
target
.
closest
(
'
.nav-sidebar
'
)
&&
(
bp
.
getBreakpointSize
()
===
'
sm
'
||
bp
.
getBreakpointSize
()
===
'
md
'
))
{
document
.
addEventListener
(
'
click
'
,
e
=>
{
if
(
!
e
.
target
.
closest
(
'
.nav-sidebar
'
)
&&
(
bp
.
getBreakpointSize
()
===
'
sm
'
||
bp
.
getBreakpointSize
()
===
'
md
'
)
)
{
this
.
toggleCollapsedSidebar
(
true
);
}
});
...
...
app/assets/javascripts/create_item_dropdown.js
View file @
dfb08243
...
...
@@ -36,7 +36,7 @@ export default class CreateItemDropdown {
},
selectable
:
true
,
toggleLabel
(
selected
)
{
return
(
selected
&&
'
id
'
in
selected
)
?
_
.
escape
(
selected
.
title
)
:
this
.
defaultToggleLabel
;
return
selected
&&
'
id
'
in
selected
?
_
.
escape
(
selected
.
title
)
:
this
.
defaultToggleLabel
;
},
fieldName
:
this
.
fieldName
,
text
(
item
)
{
...
...
@@ -46,7 +46,7 @@ export default class CreateItemDropdown {
return
_
.
escape
(
item
.
id
);
},
onFilter
:
this
.
toggleCreateNewButton
.
bind
(
this
),
clicked
:
(
options
)
=>
{
clicked
:
options
=>
{
options
.
e
.
preventDefault
();
this
.
onSelect
();
},
...
...
@@ -77,9 +77,8 @@ export default class CreateItemDropdown {
getData
(
term
,
callback
)
{
this
.
getDataOption
(
term
,
(
data
=
[])
=>
{
// Ensure the selected item isn't already in the data to avoid duplicates
const
alreadyHasSelectedItem
=
this
.
selectedItem
&&
data
.
some
(
item
=>
item
.
id
===
this
.
selectedItem
.
id
,
);
const
alreadyHasSelectedItem
=
this
.
selectedItem
&&
data
.
some
(
item
=>
item
.
id
===
this
.
selectedItem
.
id
);
let
uniqueData
=
data
;
if
(
!
alreadyHasSelectedItem
)
{
...
...
@@ -106,9 +105,7 @@ export default class CreateItemDropdown {
if
(
newValue
)
{
this
.
selectedItem
=
this
.
createNewItemFromValue
(
newValue
);
this
.
$dropdownContainer
.
find
(
'
.js-dropdown-create-new-item code
'
)
.
text
(
newValue
);
this
.
$dropdownContainer
.
find
(
'
.js-dropdown-create-new-item code
'
).
text
(
newValue
);
}
this
.
toggleFooter
(
!
newValue
);
...
...
app/assets/javascripts/create_label.js
View file @
dfb08243
...
...
@@ -37,7 +37,7 @@ export default class CreateLabelDropdown {
addBinding
()
{
const
self
=
this
;
this
.
$colorSuggestions
.
on
(
'
click
'
,
function
(
e
)
{
this
.
$colorSuggestions
.
on
(
'
click
'
,
function
(
e
)
{
const
$this
=
$
(
this
);
self
.
addColorValue
(
e
,
$this
);
});
...
...
@@ -47,7 +47,7 @@ export default class CreateLabelDropdown {
this
.
$dropdownBack
.
on
(
'
click
'
,
this
.
resetForm
.
bind
(
this
));
this
.
$cancelButton
.
on
(
'
click
'
,
function
(
e
)
{
this
.
$cancelButton
.
on
(
'
click
'
,
function
(
e
)
{
e
.
preventDefault
();
e
.
stopPropagation
();
...
...
@@ -79,13 +79,9 @@ export default class CreateLabelDropdown {
}
resetForm
()
{
this
.
$newLabelField
.
val
(
''
)
.
trigger
(
'
change
'
);
this
.
$newLabelField
.
val
(
''
).
trigger
(
'
change
'
);
this
.
$newColorField
.
val
(
''
)
.
trigger
(
'
change
'
);
this
.
$newColorField
.
val
(
''
).
trigger
(
'
change
'
);
this
.
$colorPreview
.
css
(
'
background-color
'
,
''
)
...
...
@@ -97,31 +93,34 @@ export default class CreateLabelDropdown {
e
.
preventDefault
();
e
.
stopPropagation
();
Api
.
newLabel
(
this
.
namespacePath
,
this
.
projectPath
,
{
title
:
this
.
$newLabelField
.
val
(),
color
:
this
.
$newColorField
.
val
(),
},
(
label
)
=>
{
this
.
$newLabelCreateButton
.
enable
();
if
(
label
.
message
)
{
let
errors
;
if
(
typeof
label
.
message
===
'
string
'
)
{
errors
=
label
.
message
;
Api
.
newLabel
(
this
.
namespacePath
,
this
.
projectPath
,
{
title
:
this
.
$newLabelField
.
val
(),
color
:
this
.
$newColorField
.
val
(),
},
label
=>
{
this
.
$newLabelCreateButton
.
enable
();
if
(
label
.
message
)
{
let
errors
;
if
(
typeof
label
.
message
===
'
string
'
)
{
errors
=
label
.
message
;
}
else
{
errors
=
Object
.
keys
(
label
.
message
)
.
map
(
key
=>
`
${
humanize
(
key
)}
${
label
.
message
[
key
].
join
(
'
,
'
)}
`
)
.
join
(
'
<br/>
'
);
}
this
.
$newLabelError
.
html
(
errors
).
show
();
}
else
{
errors
=
Object
.
keys
(
label
.
message
).
map
(
key
=>
`
${
humanize
(
key
)}
${
label
.
message
[
key
].
join
(
'
,
'
)}
`
,
).
join
(
'
<br/>
'
);
}
this
.
$dropdownBack
.
trigger
(
'
click
'
);
this
.
$newLabelError
.
html
(
errors
)
.
show
();
}
else
{
this
.
$dropdownBack
.
trigger
(
'
click
'
);
$
(
document
).
trigger
(
'
created.label
'
,
label
);
}
});
$
(
document
).
trigger
(
'
created.label
'
,
label
);
}
},
);
}
}
app/assets/javascripts/deploy_keys/components/app.vue
View file @
dfb08243
...
...
@@ -95,8 +95,10 @@ export default {
.
catch
(()
=>
new
Flash
(
s__
(
'
DeployKeys|Error enabling deploy key
'
)));
},
disableKey
(
deployKey
,
callback
)
{
// eslint-disable-next-line no-alert
if
(
window
.
confirm
(
s__
(
'
DeployKeys|You are going to remove this deploy key. Are you sure?
'
)))
{
if
(
// eslint-disable-next-line no-alert
window
.
confirm
(
s__
(
'
DeployKeys|You are going to remove this deploy key. Are you sure?
'
))
)
{
this
.
service
.
disableKey
(
deployKey
.
id
)
.
then
(
this
.
fetchKeys
)
...
...
app/assets/javascripts/deploy_keys/service/index.js
View file @
dfb08243
...
...
@@ -8,17 +8,14 @@ export default class DeployKeysService {
}
getKeys
()
{
return
this
.
axios
.
get
()
.
then
(
response
=>
response
.
data
);
return
this
.
axios
.
get
().
then
(
response
=>
response
.
data
);
}
enableKey
(
id
)
{
return
this
.
axios
.
put
(
`
${
id
}
/enable`
)
.
then
(
response
=>
response
.
data
);
return
this
.
axios
.
put
(
`
${
id
}
/enable`
).
then
(
response
=>
response
.
data
);
}
disableKey
(
id
)
{
return
this
.
axios
.
put
(
`
${
id
}
/disable`
)
.
then
(
response
=>
response
.
data
);
return
this
.
axios
.
put
(
`
${
id
}
/disable`
).
then
(
response
=>
response
.
data
);
}
}
app/assets/javascripts/diff.js
View file @
dfb08243
...
...
@@ -21,9 +21,12 @@ export default class Diff {
});
const
tab
=
document
.
getElementById
(
'
diffs
'
);
if
(
!
tab
||
(
tab
&&
tab
.
dataset
&&
tab
.
dataset
.
isLocked
!==
''
))
FilesCommentButton
.
init
(
$diffFile
);
if
(
!
tab
||
(
tab
&&
tab
.
dataset
&&
tab
.
dataset
.
isLocked
!==
''
))
FilesCommentButton
.
init
(
$diffFile
);
const
firstFile
=
$
(
'
.files
'
).
first
().
get
(
0
);
const
firstFile
=
$
(
'
.files
'
)
.
first
()
.
get
(
0
);
const
canCreateNote
=
firstFile
&&
firstFile
.
hasAttribute
(
'
data-can-create-note
'
);
$diffFile
.
each
((
index
,
file
)
=>
imageDiffHelper
.
initImageDiff
(
file
,
canCreateNote
));
...
...
@@ -73,9 +76,10 @@ export default class Diff {
const
view
=
file
.
data
(
'
view
'
);
const
params
=
{
since
,
to
,
bottom
,
offset
,
unfold
,
view
};
axios
.
get
(
link
,
{
params
})
.
then
(({
data
})
=>
$target
.
parent
().
replaceWith
(
data
))
.
catch
(()
=>
flash
(
__
(
'
An error occurred while loading diff
'
)));
axios
.
get
(
link
,
{
params
})
.
then
(({
data
})
=>
$target
.
parent
().
replaceWith
(
data
))
.
catch
(()
=>
flash
(
__
(
'
An error occurred while loading diff
'
)));
}
openAnchoredDiff
(
cb
)
{
...
...
app/assets/javascripts/dropzone_input.js
View file @
dfb08243
...
...
@@ -136,7 +136,7 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
$cancelButton
.
on
(
'
click
'
,
(
e
)
=>
{
$cancelButton
.
on
(
'
click
'
,
e
=>
{
e
.
preventDefault
();
e
.
stopPropagation
();
Dropzone
.
forElement
(
$formDropzone
.
get
(
0
)).
removeAllFiles
(
true
);
...
...
@@ -146,8 +146,10 @@ export default function dropzoneInput(form) {
// clear dropzone files queue, change status of failed files to undefined,
// and add that files to the dropzone files queue again.
// addFile() adds file to dropzone files queue and upload it.
$retryLink
.
on
(
'
click
'
,
(
e
)
=>
{
const
dropzoneInstance
=
Dropzone
.
forElement
(
e
.
target
.
closest
(
'
.js-main-target-form
'
).
querySelector
(
'
.div-dropzone
'
));
$retryLink
.
on
(
'
click
'
,
e
=>
{
const
dropzoneInstance
=
Dropzone
.
forElement
(
e
.
target
.
closest
(
'
.js-main-target-form
'
).
querySelector
(
'
.div-dropzone
'
),
);
const
failedFiles
=
dropzoneInstance
.
files
;
e
.
preventDefault
();
...
...
@@ -156,7 +158,7 @@ export default function dropzoneInput(form) {
// uploading of files that are being uploaded at the moment.
dropzoneInstance
.
removeAllFiles
(
true
);
failedFiles
.
map
(
(
failedFile
)
=>
{
failedFiles
.
map
(
failedFile
=>
{
const
file
=
failedFile
;
if
(
file
.
status
===
Dropzone
.
ERROR
)
{
...
...
@@ -168,7 +170,7 @@ export default function dropzoneInput(form) {
});
});
// eslint-disable-next-line consistent-return
handlePaste
=
(
event
)
=>
{
handlePaste
=
event
=>
{
const
pasteEvent
=
event
.
originalEvent
;
if
(
pasteEvent
.
clipboardData
&&
pasteEvent
.
clipboardData
.
items
)
{
const
image
=
isImage
(
pasteEvent
);
...
...
@@ -182,7 +184,7 @@ export default function dropzoneInput(form) {
}
};
isImage
=
(
data
)
=>
{
isImage
=
data
=>
{
let
i
=
0
;
while
(
i
<
data
.
clipboardData
.
items
.
length
)
{
const
item
=
data
.
clipboardData
.
items
[
i
];
...
...
@@ -203,8 +205,12 @@ export default function dropzoneInput(form) {
const
caretStart
=
textarea
.
selectionStart
;
const
caretEnd
=
textarea
.
selectionEnd
;
const
textEnd
=
$
(
child
).
val
().
length
;
const
beforeSelection
=
$
(
child
).
val
().
substring
(
0
,
caretStart
);
const
afterSelection
=
$
(
child
).
val
().
substring
(
caretEnd
,
textEnd
);
const
beforeSelection
=
$
(
child
)
.
val
()
.
substring
(
0
,
caretStart
);
const
afterSelection
=
$
(
child
)
.
val
()
.
substring
(
caretEnd
,
textEnd
);
$
(
child
).
val
(
beforeSelection
+
formattedText
+
afterSelection
);
textarea
.
setSelectionRange
(
caretStart
+
formattedText
.
length
,
caretEnd
+
formattedText
.
length
);
textarea
.
style
.
height
=
`
${
textarea
.
scrollHeight
}
px`
;
...
...
@@ -212,11 +218,11 @@ export default function dropzoneInput(form) {
return
formTextarea
.
trigger
(
'
input
'
);
};
addFileToForm
=
(
path
)
=>
{
addFileToForm
=
path
=>
{
$
(
form
).
append
(
`<input type="hidden" name="files[]" value="
${
_
.
escape
(
path
)}
">`
);
};
getFilename
=
(
e
)
=>
{
getFilename
=
e
=>
{
let
value
;
if
(
window
.
clipboardData
&&
window
.
clipboardData
.
getData
)
{
value
=
window
.
clipboardData
.
getData
(
'
Text
'
);
...
...
@@ -231,7 +237,7 @@ export default function dropzoneInput(form) {
const
closeSpinner
=
()
=>
$uploadingProgressContainer
.
addClass
(
'
hide
'
);
const
showError
=
(
message
)
=>
{
const
showError
=
message
=>
{
$uploadingErrorContainer
.
removeClass
(
'
hide
'
);
$uploadingErrorMessage
.
html
(
message
);
};
...
...
@@ -252,14 +258,15 @@ export default function dropzoneInput(form) {
showSpinner
();
closeAlertMessage
();
axios
.
post
(
uploadsPath
,
formData
)
axios
.
post
(
uploadsPath
,
formData
)
.
then
(({
data
})
=>
{
const
md
=
data
.
link
.
markdown
;
insertToTextArea
(
filename
,
md
);
closeSpinner
();
})
.
catch
(
(
e
)
=>
{
.
catch
(
e
=>
{
showError
(
e
.
response
.
data
.
message
);
closeSpinner
();
});
...
...
@@ -267,7 +274,8 @@ export default function dropzoneInput(form) {
updateAttachingMessage
=
(
files
,
messageContainer
)
=>
{
let
attachingMessage
;
const
filesCount
=
files
.
filter
(
file
=>
file
.
status
===
'
uploading
'
||
file
.
status
===
'
queued
'
).
length
;
const
filesCount
=
files
.
filter
(
file
=>
file
.
status
===
'
uploading
'
||
file
.
status
===
'
queued
'
)
.
length
;
// Dinamycally change uploading files text depending on files number in
// dropzone files queue.
...
...
@@ -282,7 +290,10 @@ export default function dropzoneInput(form) {
form
.
find
(
'
.markdown-selector
'
).
click
(
function
onMarkdownClick
(
e
)
{
e
.
preventDefault
();
$
(
this
).
closest
(
'
.gfm-form
'
).
find
(
'
.div-dropzone
'
).
click
();
$
(
this
)
.
closest
(
'
.gfm-form
'
)
.
find
(
'
.div-dropzone
'
)
.
click
();
formTextarea
.
focus
();
});
...
...
app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
View file @
dfb08243
...
...
@@ -13,9 +13,11 @@ const rainbowCodePoint = 127752; // parseInt('1F308', 16)
function
isRainbowFlagEmoji
(
emojiUnicode
)
{
const
characters
=
Array
.
from
(
emojiUnicode
);
// Length 4 because flags are made of 2 characters which are surrogate pairs
return
emojiUnicode
.
length
===
4
&&
return
(
emojiUnicode
.
length
===
4
&&
characters
[
0
].
codePointAt
(
0
)
===
baseFlagCodePoint
&&
characters
[
1
].
codePointAt
(
0
)
===
rainbowCodePoint
;
characters
[
1
].
codePointAt
(
0
)
===
rainbowCodePoint
);
}
// Chrome <57 renders keycaps oddly
...
...
@@ -26,22 +28,28 @@ function isKeycapEmoji(emojiUnicode) {
}
// Check for a skin tone variation emoji which aren't always supported
const
tone1
=
127995
;
// parseInt('1F3FB', 16)
const
tone5
=
127999
;
// parseInt('1F3FF', 16)
const
tone1
=
127995
;
// parseInt('1F3FB', 16)
const
tone5
=
127999
;
// parseInt('1F3FF', 16)
function
isSkinToneComboEmoji
(
emojiUnicode
)
{
return
emojiUnicode
.
length
>
2
&&
Array
.
from
(
emojiUnicode
).
some
((
char
)
=>
{
const
cp
=
char
.
codePointAt
(
0
);
return
cp
>=
tone1
&&
cp
<=
tone5
;
});
return
(
emojiUnicode
.
length
>
2
&&
Array
.
from
(
emojiUnicode
).
some
(
char
=>
{
const
cp
=
char
.
codePointAt
(
0
);
return
cp
>=
tone1
&&
cp
<=
tone5
;
})
);
}
// macOS supports most skin tone emoji's but
// doesn't support the skin tone versions of horse racing
const
horseRacingCodePoint
=
127943
;
// parseInt('1F3C7', 16)
const
horseRacingCodePoint
=
127943
;
// parseInt('1F3C7', 16)
function
isHorceRacingSkinToneComboEmoji
(
emojiUnicode
)
{
const
firstCharacter
=
Array
.
from
(
emojiUnicode
)[
0
];
return
firstCharacter
&&
firstCharacter
.
codePointAt
(
0
)
===
horseRacingCodePoint
&&
isSkinToneComboEmoji
(
emojiUnicode
);
return
(
firstCharacter
&&
firstCharacter
.
codePointAt
(
0
)
===
horseRacingCodePoint
&&
isSkinToneComboEmoji
(
emojiUnicode
)
);
}
// Check for `family_*`, `kiss_*`, `couple_*`
...
...
@@ -52,7 +60,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16)
function
isPersonZwjEmoji
(
emojiUnicode
)
{
let
hasPersonEmoji
=
false
;
let
hasZwj
=
false
;
Array
.
from
(
emojiUnicode
).
forEach
(
(
character
)
=>
{
Array
.
from
(
emojiUnicode
).
forEach
(
character
=>
{
const
cp
=
character
.
codePointAt
(
0
);
if
(
cp
===
zwj
)
{
hasZwj
=
true
;
...
...
@@ -80,10 +88,7 @@ function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
// in `isEmojiUnicodeSupported` logic
function
checkSkinToneModifierSupport
(
unicodeSupportMap
,
emojiUnicode
)
{
const
isSkinToneResult
=
isSkinToneComboEmoji
(
emojiUnicode
);
return
(
(
unicodeSupportMap
.
skinToneModifier
&&
isSkinToneResult
)
||
!
isSkinToneResult
);
return
(
unicodeSupportMap
.
skinToneModifier
&&
isSkinToneResult
)
||
!
isSkinToneResult
;
}
// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
...
...
@@ -91,8 +96,7 @@ function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
function
checkHorseRacingSkinToneComboEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
)
{
const
isHorseRacingSkinToneResult
=
isHorceRacingSkinToneComboEmoji
(
emojiUnicode
);
return
(
(
unicodeSupportMap
.
horseRacing
&&
isHorseRacingSkinToneResult
)
||
!
isHorseRacingSkinToneResult
(
unicodeSupportMap
.
horseRacing
&&
isHorseRacingSkinToneResult
)
||
!
isHorseRacingSkinToneResult
);
}
...
...
@@ -100,10 +104,7 @@ function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnico
// in `isEmojiUnicodeSupported` logic
function
checkPersonEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
)
{
const
isPersonZwjResult
=
isPersonZwjEmoji
(
emojiUnicode
);
return
(
(
unicodeSupportMap
.
personZwj
&&
isPersonZwjResult
)
||
!
isPersonZwjResult
);
return
(
unicodeSupportMap
.
personZwj
&&
isPersonZwjResult
)
||
!
isPersonZwjResult
;
}
// Takes in a support map and determines whether
...
...
@@ -111,16 +112,20 @@ function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
//
// Combines all the edge case tests into a one-stop shop method
function
isEmojiUnicodeSupported
(
unicodeSupportMap
=
{},
emojiUnicode
,
unicodeVersion
)
{
const
isOlderThanChrome57
=
unicodeSupportMap
.
meta
&&
unicodeSupportMap
.
meta
.
isChrome
&&
const
isOlderThanChrome57
=
unicodeSupportMap
.
meta
&&
unicodeSupportMap
.
meta
.
isChrome
&&
unicodeSupportMap
.
meta
.
chromeVersion
<
57
;
// For comments about each scenario, see the comments above each individual respective function
return
unicodeSupportMap
[
unicodeVersion
]
&&
return
(
unicodeSupportMap
[
unicodeVersion
]
&&
!
(
isOlderThanChrome57
&&
isKeycapEmoji
(
emojiUnicode
))
&&
checkFlagEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
)
&&
checkSkinToneModifierSupport
(
unicodeSupportMap
,
emojiUnicode
)
&&
checkHorseRacingSkinToneComboEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
)
&&
checkPersonEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
);
checkPersonEmojiSupport
(
unicodeSupportMap
,
emojiUnicode
)
);
}
export
{
...
...
app/assets/javascripts/experimental_flags.js
View file @
dfb08243
...
...
@@ -2,7 +2,7 @@ import $ from 'jquery';
import
Cookies
from
'
js-cookie
'
;
export
default
()
=>
{
$
(
'
.js-experiment-feature-toggle
'
).
on
(
'
change
'
,
(
e
)
=>
{
$
(
'
.js-experiment-feature-toggle
'
).
on
(
'
change
'
,
e
=>
{
const
el
=
e
.
target
;
Cookies
.
set
(
el
.
name
,
el
.
value
,
{
...
...
app/assets/javascripts/files_comment_button.js
View file @
dfb08243
...
...
@@ -25,13 +25,15 @@ export default {
if
(
!
this
.
userCanCreateNote
)
{
// data-can-create-note is an empty string when true, otherwise undefined
this
.
userCanCreateNote
=
$diffFile
.
closest
(
DIFF_CONTAINER_SELECTOR
).
data
(
'
canCreateNote
'
)
===
''
;
this
.
userCanCreateNote
=
$diffFile
.
closest
(
DIFF_CONTAINER_SELECTOR
).
data
(
'
canCreateNote
'
)
===
''
;
}
this
.
isParallelView
=
Cookies
.
get
(
'
diff_view
'
)
===
'
parallel
'
;
if
(
this
.
userCanCreateNote
)
{
$diffFile
.
on
(
'
mouseover
'
,
LINE_COLUMN_CLASSES
,
e
=>
this
.
showButton
(
this
.
isParallelView
,
e
))
$diffFile
.
on
(
'
mouseover
'
,
LINE_COLUMN_CLASSES
,
e
=>
this
.
showButton
(
this
.
isParallelView
,
e
))
.
on
(
'
mouseleave
'
,
LINE_COLUMN_CLASSES
,
e
=>
this
.
hideButton
(
this
.
isParallelView
,
e
));
}
},
...
...
@@ -64,9 +66,11 @@ export default {
},
validateButtonParent
(
buttonParentElement
)
{
return
!
buttonParentElement
.
classList
.
contains
(
EMPTY_CELL_CLASS
)
&&
return
(
!
buttonParentElement
.
classList
.
contains
(
EMPTY_CELL_CLASS
)
&&
!
buttonParentElement
.
classList
.
contains
(
UNFOLDABLE_LINE_CLASS
)
&&
!
buttonParentElement
.
classList
.
contains
(
NO_COMMENT_CLASS
)
&&
!
buttonParentElement
.
parentNode
.
classList
.
contains
(
DIFF_EXPANDED_CLASS
);
!
buttonParentElement
.
parentNode
.
classList
.
contains
(
DIFF_EXPANDED_CLASS
)
);
},
};
app/assets/javascripts/filterable_list.js
View file @
dfb08243
...
...
@@ -65,12 +65,15 @@ export default class FilterableList {
this
.
isBusy
=
true
;
return
axios
.
get
(
this
.
getFilterEndpoint
(),
{
params
,
}).
then
((
res
)
=>
{
this
.
onFilterSuccess
(
res
,
params
);
this
.
onFilterComplete
();
}).
catch
(()
=>
this
.
onFilterComplete
());
return
axios
.
get
(
this
.
getFilterEndpoint
(),
{
params
,
})
.
then
(
res
=>
{
this
.
onFilterSuccess
(
res
,
params
);
this
.
onFilterComplete
();
})
.
catch
(()
=>
this
.
onFilterComplete
());
}
onFilterSuccess
(
response
,
queryData
)
{
...
...
@@ -81,9 +84,13 @@ export default class FilterableList {
// Change url so if user reload a page - search results are saved
const
currentPath
=
this
.
getPagePath
(
queryData
);
return
window
.
history
.
replaceState
({
page
:
currentPath
,
},
document
.
title
,
currentPath
);
return
window
.
history
.
replaceState
(
{
page
:
currentPath
,
},
document
.
title
,
currentPath
,
);
}
onFilterComplete
()
{
...
...
app/assets/javascripts/flash.js
View file @
dfb08243
...
...
@@ -8,14 +8,19 @@ const hideFlash = (flashEl, fadeTransition = true) => {
});
}
flashEl
.
addEventListener
(
'
transitionend
'
,
()
=>
{
flashEl
.
remove
();
window
.
dispatchEvent
(
new
Event
(
'
resize
'
));
if
(
document
.
body
.
classList
.
contains
(
'
flash-shown
'
))
document
.
body
.
classList
.
remove
(
'
flash-shown
'
);
},
{
once
:
true
,
passive
:
true
,
});
flashEl
.
addEventListener
(
'
transitionend
'
,
()
=>
{
flashEl
.
remove
();
window
.
dispatchEvent
(
new
Event
(
'
resize
'
));
if
(
document
.
body
.
classList
.
contains
(
'
flash-shown
'
))
document
.
body
.
classList
.
remove
(
'
flash-shown
'
);
},
{
once
:
true
,
passive
:
true
,
},
);
if
(
!
fadeTransition
)
flashEl
.
dispatchEvent
(
new
Event
(
'
transitionend
'
));
};
...
...
@@ -84,7 +89,9 @@ const createFlash = function createFlash(
flashEl
.
innerHTML
+=
createAction
(
actionConfig
);
if
(
actionConfig
.
clickHandler
)
{
flashEl
.
querySelector
(
'
.flash-action
'
).
addEventListener
(
'
click
'
,
e
=>
actionConfig
.
clickHandler
(
e
));
flashEl
.
querySelector
(
'
.flash-action
'
)
.
addEventListener
(
'
click
'
,
e
=>
actionConfig
.
clickHandler
(
e
));
}
}
...
...
@@ -95,11 +102,5 @@ const createFlash = function createFlash(
return
flashContainer
;
};
export
{
createFlash
as
default
,
createFlashEl
,
createAction
,
hideFlash
,
removeFlashClickListener
,
};
export
{
createFlash
as
default
,
createFlashEl
,
createAction
,
hideFlash
,
removeFlashClickListener
};
window
.
Flash
=
createFlash
;
app/assets/javascripts/fly_out_nav.js
View file @
dfb08243
...
...
@@ -11,9 +11,13 @@ let sidebar;
export
const
mousePos
=
[];
export
const
setSidebar
=
(
el
)
=>
{
sidebar
=
el
;
};
export
const
setSidebar
=
el
=>
{
sidebar
=
el
;
};
export
const
getOpenMenu
=
()
=>
currentOpenMenu
;
export
const
setOpenMenu
=
(
menu
=
null
)
=>
{
currentOpenMenu
=
menu
;
};
export
const
setOpenMenu
=
(
menu
=
null
)
=>
{
currentOpenMenu
=
menu
;
};
export
const
slope
=
(
a
,
b
)
=>
(
b
.
y
-
a
.
y
)
/
(
b
.
x
-
a
.
x
);
...
...
@@ -21,9 +25,10 @@ let headerHeight = 50;
export
const
getHeaderHeight
=
()
=>
headerHeight
;
export
const
isSidebarCollapsed
=
()
=>
sidebar
&&
sidebar
.
classList
.
contains
(
'
sidebar-collapsed-desktop
'
);
export
const
isSidebarCollapsed
=
()
=>
sidebar
&&
sidebar
.
classList
.
contains
(
'
sidebar-collapsed-desktop
'
);
export
const
canShowActiveSubItems
=
(
el
)
=>
{
export
const
canShowActiveSubItems
=
el
=>
{
if
(
el
.
classList
.
contains
(
'
active
'
)
&&
!
isSidebarCollapsed
())
{
return
false
;
}
...
...
@@ -31,7 +36,10 @@ export const canShowActiveSubItems = (el) => {
return
true
;
};
export
const
canShowSubItems
=
()
=>
bp
.
getBreakpointSize
()
===
'
sm
'
||
bp
.
getBreakpointSize
()
===
'
md
'
||
bp
.
getBreakpointSize
()
===
'
lg
'
;
export
const
canShowSubItems
=
()
=>
bp
.
getBreakpointSize
()
===
'
sm
'
||
bp
.
getBreakpointSize
()
===
'
md
'
||
bp
.
getBreakpointSize
()
===
'
lg
'
;
export
const
getHideSubItemsInterval
=
()
=>
{
if
(
!
currentOpenMenu
||
!
mousePos
.
length
)
return
0
;
...
...
@@ -41,11 +49,12 @@ export const getHideSubItemsInterval = () => {
const
currentMousePosY
=
currentMousePos
.
y
;
const
[
menuTop
,
menuBottom
]
=
menuCornerLocs
;
if
(
currentMousePosY
<
menuTop
.
y
||
currentMousePosY
>
menuBottom
.
y
)
return
0
;
if
(
currentMousePosY
<
menuTop
.
y
||
currentMousePosY
>
menuBottom
.
y
)
return
0
;
if
(
slope
(
prevMousePos
,
menuBottom
)
<
slope
(
currentMousePos
,
menuBottom
)
&&
slope
(
prevMousePos
,
menuTop
)
>
slope
(
currentMousePos
,
menuTop
))
{
if
(
slope
(
prevMousePos
,
menuBottom
)
<
slope
(
currentMousePos
,
menuBottom
)
&&
slope
(
prevMousePos
,
menuTop
)
>
slope
(
currentMousePos
,
menuTop
)
)
{
return
HIDE_INTERVAL_TIMEOUT
;
}
...
...
@@ -56,11 +65,12 @@ export const calculateTop = (boundingRect, outerHeight) => {
const
windowHeight
=
window
.
innerHeight
;
const
bottomOverflow
=
windowHeight
-
(
boundingRect
.
top
+
outerHeight
);
return
bottomOverflow
<
0
?
(
boundingRect
.
top
-
outerHeight
)
+
boundingRect
.
height
:
boundingRect
.
top
;
return
bottomOverflow
<
0
?
boundingRect
.
top
-
outerHeight
+
boundingRect
.
height
:
boundingRect
.
top
;
};
export
const
hideMenu
=
(
el
)
=>
{
export
const
hideMenu
=
el
=>
{
if
(
!
el
)
return
;
const
parentEl
=
el
.
parentNode
;
...
...
@@ -101,7 +111,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
}
};
export
const
showSubLevelItems
=
(
el
)
=>
{
export
const
showSubLevelItems
=
el
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
const
isIconOnly
=
subItems
&&
subItems
.
classList
.
contains
(
'
is-fly-out-only
'
);
...
...
@@ -128,16 +138,20 @@ export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
},
timeout
);
};
export
const
mouseLeaveTopItem
=
(
el
)
=>
{
export
const
mouseLeaveTopItem
=
el
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
if
(
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
)
||
(
subItems
&&
subItems
===
currentOpenMenu
))
return
;
if
(
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
)
||
(
subItems
&&
subItems
===
currentOpenMenu
)
)
return
;
el
.
classList
.
remove
(
IS_OVER_CLASS
);
};
export
const
documentMouseMove
=
(
e
)
=>
{
export
const
documentMouseMove
=
e
=>
{
mousePos
.
push
({
x
:
e
.
clientX
,
y
:
e
.
clientY
,
...
...
@@ -146,7 +160,7 @@ export const documentMouseMove = (e) => {
if
(
mousePos
.
length
>
6
)
mousePos
.
shift
();
};
export
const
subItemsMouseLeave
=
(
relatedTarget
)
=>
{
export
const
subItemsMouseLeave
=
relatedTarget
=>
{
clearTimeout
(
timeoutId
);
if
(
relatedTarget
&&
!
relatedTarget
.
closest
(
`.
${
IS_OVER_CLASS
}
`
))
{
...
...
@@ -174,7 +188,7 @@ export default () => {
headerHeight
=
document
.
querySelector
(
'
.nav-sidebar
'
).
offsetTop
;
items
.
forEach
(
(
el
)
=>
{
items
.
forEach
(
el
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
if
(
subItems
)
{
...
...
app/assets/javascripts/gl_field_error.js
View file @
dfb08243
...
...
@@ -116,7 +116,8 @@ export default class GlFieldError {
this
.
form
.
focusOnFirstInvalid
.
apply
(
this
.
form
);
// For UX, wait til after first invalid submission to check each keyup
this
.
inputElement
.
off
(
'
keyup.fieldValidator
'
)
this
.
inputElement
.
off
(
'
keyup.fieldValidator
'
)
.
on
(
'
keyup.fieldValidator
'
,
this
.
updateValidity
.
bind
(
this
));
}
...
...
app/assets/javascripts/gl_field_errors.js
View file @
dfb08243
...
...
@@ -16,9 +16,12 @@ export default class GlFieldErrors {
initValidators
()
{
// register selectors here as needed
const
validateSelectors
=
[
'
:text
'
,
'
:password
'
,
'
[type=email]
'
]
.
map
(
selector
=>
`input
${
selector
}
`
).
join
(
'
,
'
);
.
map
(
selector
=>
`input
${
selector
}
`
)
.
join
(
'
,
'
);
this
.
state
.
inputs
=
this
.
form
.
find
(
validateSelectors
).
toArray
()
this
.
state
.
inputs
=
this
.
form
.
find
(
validateSelectors
)
.
toArray
()
.
filter
(
input
=>
!
input
.
classList
.
contains
(
customValidationFlag
))
.
map
(
input
=>
new
GlFieldError
({
input
,
formErrors
:
this
}));
...
...
@@ -42,7 +45,7 @@ export default class GlFieldErrors {
/* Public method for triggering validity updates manually */
updateFormValidityState
()
{
this
.
state
.
inputs
.
forEach
(
(
field
)
=>
{
this
.
state
.
inputs
.
forEach
(
field
=>
{
if
(
field
.
state
.
submitted
)
{
field
.
updateValidity
();
}
...
...
@@ -50,8 +53,9 @@ export default class GlFieldErrors {
}
focusOnFirstInvalid
()
{
const
firstInvalid
=
this
.
state
.
inputs
.
filter
(
input
=>
!
input
.
inputDomElement
.
validity
.
valid
)[
0
];
const
firstInvalid
=
this
.
state
.
inputs
.
filter
(
input
=>
!
input
.
inputDomElement
.
validity
.
valid
,
)[
0
];
firstInvalid
.
inputElement
.
focus
();
}
}
app/assets/javascripts/gl_form.js
View file @
dfb08243
...
...
@@ -39,7 +39,10 @@ export default class GLForm {
this
.
form
.
find
(
'
.div-dropzone
'
).
remove
();
this
.
form
.
addClass
(
'
gfm-form
'
);
// remove notify commit author checkbox for non-commit notes
gl
.
utils
.
disableButtonIfEmptyField
(
this
.
form
.
find
(
'
.js-note-text
'
),
this
.
form
.
find
(
'
.js-comment-button, .js-note-new-discussion
'
));
gl
.
utils
.
disableButtonIfEmptyField
(
this
.
form
.
find
(
'
.js-note-text
'
),
this
.
form
.
find
(
'
.js-comment-button, .js-note-new-discussion
'
),
);
this
.
autoComplete
=
new
GfmAutoComplete
(
gl
.
GfmAutoComplete
&&
gl
.
GfmAutoComplete
.
dataSources
);
this
.
autoComplete
.
setup
(
this
.
form
.
find
(
'
.js-gfm-input
'
),
this
.
enableGFM
);
dropzoneInput
(
this
.
form
);
...
...
@@ -55,11 +58,9 @@ export default class GLForm {
}
setupAutosize
()
{
this
.
textarea
.
off
(
'
autosize:resized
'
)
.
on
(
'
autosize:resized
'
,
this
.
setHeightData
.
bind
(
this
));
this
.
textarea
.
off
(
'
autosize:resized
'
).
on
(
'
autosize:resized
'
,
this
.
setHeightData
.
bind
(
this
));
this
.
textarea
.
off
(
'
mouseup.autosize
'
)
.
on
(
'
mouseup.autosize
'
,
this
.
destroyAutosize
.
bind
(
this
));
this
.
textarea
.
off
(
'
mouseup.autosize
'
).
on
(
'
mouseup.autosize
'
,
this
.
destroyAutosize
.
bind
(
this
));
setTimeout
(()
=>
{
autosize
(
this
.
textarea
);
...
...
@@ -91,10 +92,14 @@ export default class GLForm {
addEventListeners
()
{
this
.
textarea
.
on
(
'
focus
'
,
function
focusTextArea
()
{
$
(
this
).
closest
(
'
.md-area
'
).
addClass
(
'
is-focused
'
);
$
(
this
)
.
closest
(
'
.md-area
'
)
.
addClass
(
'
is-focused
'
);
});
this
.
textarea
.
on
(
'
blur
'
,
function
blurTextArea
()
{
$
(
this
).
closest
(
'
.md-area
'
).
removeClass
(
'
is-focused
'
);
$
(
this
)
.
closest
(
'
.md-area
'
)
.
removeClass
(
'
is-focused
'
);
});
}
}
app/assets/javascripts/group_avatar.js
View file @
dfb08243
...
...
@@ -7,8 +7,9 @@ export default function groupAvatar() {
});
$
(
'
.js-group-avatar-input
'
).
on
(
'
change
'
,
function
onChangeAvatarInput
()
{
const
form
=
$
(
this
).
closest
(
'
form
'
);
// eslint-disable-next-line no-useless-escape
const
filename
=
$
(
this
).
val
().
replace
(
/^.*
[\\\/]
/
,
''
);
const
filename
=
$
(
this
)
.
val
()
.
replace
(
/^.*
[\\\/]
/
,
''
);
// eslint-disable-line no-useless-escape
return
form
.
find
(
'
.js-avatar-filename
'
).
text
(
filename
);
});
}
app/assets/javascripts/group_label_subscription.js
View file @
dfb08243
...
...
@@ -23,7 +23,8 @@ export default class GroupLabelSubscription {
event
.
preventDefault
();
const
url
=
this
.
$unsubscribeButtons
.
attr
(
'
data-url
'
);
axios
.
post
(
url
)
axios
.
post
(
url
)
.
then
(()
=>
{
this
.
toggleSubscriptionButtons
();
this
.
$unsubscribeButtons
.
removeAttr
(
'
data-url
'
);
...
...
@@ -39,7 +40,8 @@ export default class GroupLabelSubscription {
this
.
$unsubscribeButtons
.
attr
(
'
data-url
'
,
url
);
axios
.
post
(
url
)
axios
.
post
(
url
)
.
then
(()
=>
GroupLabelSubscription
.
setNewTooltip
(
$btn
))
.
then
(()
=>
this
.
toggleSubscriptionButtons
())
.
catch
(()
=>
flash
(
__
(
'
There was an error when subscribing to this label.
'
)));
...
...
@@ -58,6 +60,8 @@ export default class GroupLabelSubscription {
const
newTitle
=
tooltipTitles
[
type
];
$
(
'
.js-unsubscribe-button
'
,
$button
.
closest
(
'
.label-actions-list
'
))
.
tooltip
(
'
hide
'
).
attr
(
'
title
'
,
newTitle
).
tooltip
(
'
_fixTitle
'
);
.
tooltip
(
'
hide
'
)
.
attr
(
'
title
'
,
newTitle
)
.
tooltip
(
'
_fixTitle
'
);
}
}
app/assets/javascripts/groups/components/item_stats.vue
View file @
dfb08243
<
script
>
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
timeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
ITEM_TYPE
,
VISIBILITY_TYPE_ICON
,
GROUP_VISIBILITY_TYPE
,
PROJECT_VISIBILITY_TYPE
,
}
from
'
../constants
'
;
import
itemStatsValue
from
'
./item_stats_value.vue
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
timeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
ITEM_TYPE
,
VISIBILITY_TYPE_ICON
,
GROUP_VISIBILITY_TYPE
,
PROJECT_VISIBILITY_TYPE
,
}
from
'
../constants
'
;
import
itemStatsValue
from
'
./item_stats_value.vue
'
;
export
default
{
components
:
{
icon
,
timeAgoTooltip
,
itemStatsValue
,
export
default
{
components
:
{
icon
,
timeAgoTooltip
,
itemStatsValue
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
visibilityIcon
()
{
return
VISIBILITY_TYPE_ICON
[
this
.
item
.
visibility
];
},
computed
:
{
visibilityIcon
()
{
return
VISIBILITY_TYPE_ICON
[
this
.
item
.
visibility
];
},
visibilityTooltip
()
{
if
(
this
.
item
.
type
===
ITEM_TYPE
.
GROUP
)
{
return
GROUP_VISIBILITY_TYPE
[
this
.
item
.
visibility
];
}
return
PROJECT_VISIBILITY_TYPE
[
this
.
item
.
visibility
];
},
isProject
()
{
return
this
.
item
.
type
===
ITEM_TYPE
.
PROJECT
;
},
isGroup
()
{
return
this
.
item
.
type
===
ITEM_TYPE
.
GROUP
;
},
visibilityTooltip
()
{
if
(
this
.
item
.
type
===
ITEM_TYPE
.
GROUP
)
{
return
GROUP_VISIBILITY_TYPE
[
this
.
item
.
visibility
];
}
return
PROJECT_VISIBILITY_TYPE
[
this
.
item
.
visibility
];
},
};
isProject
()
{
return
this
.
item
.
type
===
ITEM_TYPE
.
PROJECT
;
},
isGroup
()
{
return
this
.
item
.
type
===
ITEM_TYPE
.
GROUP
;
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/groups/components/item_stats_value.vue
View file @
dfb08243
<
script
>
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
components
:
{
icon
,
export
default
{
components
:
{
icon
,
},
directives
:
{
tooltip
,
},
props
:
{
title
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
directives
:
{
tooltip
,
cssClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
props
:
{
title
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
cssClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
iconName
:
{
type
:
String
,
required
:
true
,
},
tooltipPlacement
:
{
type
:
String
,
required
:
false
,
default
:
'
bottom
'
,
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value
:
{
type
:
[
Number
,
String
],
required
:
false
,
default
:
''
,
},
iconName
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
isValuePresent
()
{
return
this
.
value
!==
''
;
}
,
tooltipPlacement
:
{
type
:
String
,
required
:
false
,
default
:
'
bottom
'
,
},
};
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value
:
{
type
:
[
Number
,
String
],
required
:
false
,
default
:
''
,
},
},
computed
:
{
isValuePresent
()
{
return
this
.
value
!==
''
;
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/groups/new_group_child.js
View file @
dfb08243
...
...
@@ -37,20 +37,22 @@ export default class NewGroupChild {
getDroplabConfig
()
{
return
{
InputSetter
:
[{
input
:
this
.
newGroupChildButton
,
valueAttribute
:
'
data-value
'
,
inputAttribute
:
'
data-action
'
,
},
{
input
:
this
.
newGroupChildButton
,
valueAttribute
:
'
data-text
'
,
}],
InputSetter
:
[
{
input
:
this
.
newGroupChildButton
,
valueAttribute
:
'
data-value
'
,
inputAttribute
:
'
data-action
'
,
},
{
input
:
this
.
newGroupChildButton
,
valueAttribute
:
'
data-text
'
,
},
],
};
}
bindEvents
()
{
this
.
newGroupChildButton
.
addEventListener
(
'
click
'
,
this
.
onClickNewGroupChildButton
.
bind
(
this
));
this
.
newGroupChildButton
.
addEventListener
(
'
click
'
,
this
.
onClickNewGroupChildButton
.
bind
(
this
));
}
onClickNewGroupChildButton
(
e
)
{
...
...
app/assets/javascripts/groups/store/groups_store.js
View file @
dfb08243
...
...
@@ -17,13 +17,14 @@ export default class GroupsStore {
}
setSearchedGroups
(
rawGroups
)
{
const
formatGroups
=
groups
=>
groups
.
map
((
group
)
=>
{
const
formattedGroup
=
this
.
formatGroupItem
(
group
);
if
(
formattedGroup
.
children
&&
formattedGroup
.
children
.
length
)
{
formattedGroup
.
children
=
formatGroups
(
formattedGroup
.
children
);
}
return
formattedGroup
;
});
const
formatGroups
=
groups
=>
groups
.
map
(
group
=>
{
const
formattedGroup
=
this
.
formatGroupItem
(
group
);
if
(
formattedGroup
.
children
&&
formattedGroup
.
children
.
length
)
{
formattedGroup
.
children
=
formatGroups
(
formattedGroup
.
children
);
}
return
formattedGroup
;
});
if
(
rawGroups
&&
rawGroups
.
length
)
{
this
.
state
.
groups
=
formatGroups
(
rawGroups
);
...
...
@@ -62,10 +63,10 @@ export default class GroupsStore {
formatGroupItem
(
rawGroupItem
)
{
const
groupChildren
=
rawGroupItem
.
children
||
[];
const
groupIsOpen
=
(
groupChildren
.
length
>
0
)
||
false
;
const
childrenCount
=
this
.
hideProjects
?
rawGroupItem
.
subgroup_count
:
rawGroupItem
.
children_count
;
const
groupIsOpen
=
groupChildren
.
length
>
0
||
false
;
const
childrenCount
=
this
.
hideProjects
?
rawGroupItem
.
subgroup_count
:
rawGroupItem
.
children_count
;
return
{
id
:
rawGroupItem
.
id
,
...
...
app/assets/javascripts/groups/transfer_dropdown.js
View file @
dfb08243
...
...
@@ -22,7 +22,7 @@ export default class TransferDropdown {
search
:
{
fields
:
[
'
text
'
]
},
data
:
extraOptions
.
concat
(
this
.
data
),
text
:
item
=>
item
.
text
,
clicked
:
(
options
)
=>
{
clicked
:
options
=>
{
const
{
e
}
=
options
;
e
.
preventDefault
();
this
.
assignSelected
(
options
.
selectedObj
);
...
...
app/assets/javascripts/groups_select.js
View file @
dfb08243
...
...
@@ -23,7 +23,7 @@ export default function groupsSelect() {
axios
[
params
.
type
.
toLowerCase
()](
params
.
url
,
{
params
:
params
.
data
,
})
.
then
(
(
res
)
=>
{
.
then
(
res
=>
{
const
results
=
res
.
data
||
[];
const
headers
=
normalizeHeaders
(
res
.
headers
);
const
currentPage
=
parseInt
(
headers
[
'
X-PAGE
'
],
10
)
||
0
;
...
...
@@ -36,7 +36,8 @@ export default function groupsSelect() {
more
,
},
});
}).
catch
(
params
.
error
);
})
.
catch
(
params
.
error
);
},
data
(
search
,
page
)
{
return
{
...
...
@@ -68,7 +69,9 @@ export default function groupsSelect() {
}
},
formatResult
(
object
)
{
return
`<div class='group-result'> <div class='group-name'>
${
object
.
full_name
}
</div> <div class='group-path'>
${
object
.
full_path
}
</div> </div>`
;
return
`<div class='group-result'> <div class='group-name'>
${
object
.
full_name
}
</div> <div class='group-path'>
${
object
.
full_path
}
</div> </div>`
;
},
formatSelection
(
object
)
{
return
object
.
full_name
;
...
...
app/assets/javascripts/helpers/avatar_helper.js
View file @
dfb08243
...
...
@@ -19,7 +19,9 @@ export function renderIdenticon(entity, options = {}) {
const
bgClass
=
getIdenticonBackgroundClass
(
entity
.
id
);
const
title
=
getIdenticonTitle
(
entity
.
name
);
return
`<div class="avatar identicon
${
_
.
escape
(
sizeClass
)}
${
_
.
escape
(
bgClass
)}
">
${
_
.
escape
(
title
)}
</div>`
;
return
`<div class="avatar identicon
${
_
.
escape
(
sizeClass
)}
${
_
.
escape
(
bgClass
)}
">
${
_
.
escape
(
title
,
)}
</div>`
;
}
export
function
renderAvatar
(
entity
,
options
=
{})
{
...
...
app/assets/javascripts/image_diff/image_diff.js
View file @
dfb08243
...
...
@@ -60,8 +60,10 @@ export default class ImageDiff {
}
renderBadge
(
discussionEl
,
index
)
{
const
imageBadge
=
imageDiffHelper
.
generateBadgeFromDiscussionDOM
(
this
.
imageFrameEl
,
discussionEl
);
const
imageBadge
=
imageDiffHelper
.
generateBadgeFromDiscussionDOM
(
this
.
imageFrameEl
,
discussionEl
,
);
this
.
imageBadges
.
push
(
imageBadge
);
...
...
app/assets/javascripts/image_diff/init_discussion_tab.js
View file @
dfb08243
...
...
@@ -8,5 +8,6 @@ export default () => {
const
diffFileEls
=
document
.
querySelectorAll
(
'
.timeline-content .diff-file.js-image-file
'
);
[...
diffFileEls
].
forEach
(
diffFileEl
=>
imageDiffHelper
.
initImageDiff
(
diffFileEl
,
canCreateNote
,
renderCommentBadge
));
imageDiffHelper
.
initImageDiff
(
diffFileEl
,
canCreateNote
,
renderCommentBadge
),
);
};
app/assets/javascripts/image_diff/replaced_image_diff.js
View file @
dfb08243
...
...
@@ -26,7 +26,7 @@ export default class ReplacedImageDiff extends ImageDiff {
this
.
imageEls
=
{};
const
viewTypeNames
=
Object
.
getOwnPropertyNames
(
viewTypes
);
viewTypeNames
.
forEach
(
(
viewType
)
=>
{
viewTypeNames
.
forEach
(
viewType
=>
{
this
.
imageEls
[
viewType
]
=
this
.
imageFrameEls
[
viewType
].
querySelector
(
'
img
'
);
});
}
...
...
@@ -79,13 +79,12 @@ export default class ReplacedImageDiff extends ImageDiff {
// Re-render indicator in new view
if
(
indicator
.
removed
)
{
const
normalizedIndicator
=
imageDiffHelper
.
resizeCoordinatesToImageElement
(
this
.
imageEl
,
{
x
:
indicator
.
x
,
y
:
indicator
.
y
,
width
:
indicator
.
image
.
width
,
height
:
indicator
.
image
.
height
,
});
const
normalizedIndicator
=
imageDiffHelper
.
resizeCoordinatesToImageElement
(
this
.
imageEl
,
{
x
:
indicator
.
x
,
y
:
indicator
.
y
,
width
:
indicator
.
image
.
width
,
height
:
indicator
.
image
.
height
,
});
imageDiffHelper
.
showCommentIndicator
(
this
.
imageFrameEl
,
normalizedIndicator
);
}
}
...
...
app/assets/javascripts/importer_status.js
View file @
dfb08243
...
...
@@ -60,66 +60,71 @@ class ImporterStatus {
attributes
=
Object
.
assign
(
repoData
,
attributes
);
}
return
axios
.
post
(
this
.
importUrl
,
attributes
)
.
then
(({
data
})
=>
{
const
job
=
$
(
`tr#repo_
${
id
}
`
);
job
.
attr
(
'
id
'
,
`project_
${
data
.
id
}
`
);
job
.
find
(
'
.import-target
'
).
html
(
`<a href="
${
data
.
full_path
}
">
${
data
.
full_path
}
</a>`
);
$
(
'
table.import-jobs tbody
'
).
prepend
(
job
);
job
.
addClass
(
'
table-active
'
);
const
connectingVerb
=
this
.
ciCdOnly
?
__
(
'
connecting
'
)
:
__
(
'
importing
'
);
job
.
find
(
'
.import-actions
'
).
html
(
sprintf
(
_
.
escape
(
__
(
'
%{loadingIcon} Started
'
)),
{
loadingIcon
:
`<i class="fa fa-spinner fa-spin" aria-label="
${
_
.
escape
(
connectingVerb
)}
"></i>`
,
},
false
,
));
})
.
catch
((
error
)
=>
{
let
details
=
error
;
const
$statusField
=
$
(
`#repo_
${
this
.
id
}
.job-status`
);
$statusField
.
text
(
__
(
'
Failed
'
));
if
(
error
.
response
&&
error
.
response
.
data
&&
error
.
response
.
data
.
errors
)
{
details
=
error
.
response
.
data
.
errors
;
}
flash
(
sprintf
(
__
(
'
An error occurred while importing project: %{details}
'
),
{
details
}));
});
return
axios
.
post
(
this
.
importUrl
,
attributes
)
.
then
(({
data
})
=>
{
const
job
=
$
(
`tr#repo_
${
id
}
`
);
job
.
attr
(
'
id
'
,
`project_
${
data
.
id
}
`
);
job
.
find
(
'
.import-target
'
).
html
(
`<a href="
${
data
.
full_path
}
">
${
data
.
full_path
}
</a>`
);
$
(
'
table.import-jobs tbody
'
).
prepend
(
job
);
job
.
addClass
(
'
table-active
'
);
const
connectingVerb
=
this
.
ciCdOnly
?
__
(
'
connecting
'
)
:
__
(
'
importing
'
);
job
.
find
(
'
.import-actions
'
).
html
(
sprintf
(
_
.
escape
(
__
(
'
%{loadingIcon} Started
'
)),
{
loadingIcon
:
`<i class="fa fa-spinner fa-spin" aria-label="
${
_
.
escape
(
connectingVerb
,
)}
"></i>`
,
},
false
,
),
);
})
.
catch
(
error
=>
{
let
details
=
error
;
const
$statusField
=
$
(
`#repo_
${
this
.
id
}
.job-status`
);
$statusField
.
text
(
__
(
'
Failed
'
));
if
(
error
.
response
&&
error
.
response
.
data
&&
error
.
response
.
data
.
errors
)
{
details
=
error
.
response
.
data
.
errors
;
}
flash
(
sprintf
(
__
(
'
An error occurred while importing project: %{details}
'
),
{
details
}));
});
}
autoUpdate
()
{
return
axios
.
get
(
this
.
jobsUrl
)
.
then
(({
data
=
[]
})
=>
{
data
.
forEach
((
job
)
=>
{
const
jobItem
=
$
(
`#project_
${
job
.
id
}
`
);
const
statusField
=
jobItem
.
find
(
'
.job-status
'
);
const
spinner
=
'
<i class="fa fa-spinner fa-spin"></i>
'
;
switch
(
job
.
import_status
)
{
case
'
finished
'
:
jobItem
.
removeClass
(
'
table-active
'
).
addClass
(
'
table-success
'
);
statusField
.
html
(
`<span><i class="fa fa-check"></i>
${
__
(
'
Done
'
)}
</span>`
);
break
;
case
'
scheduled
'
:
statusField
.
html
(
`
${
spinner
}
${
__
(
'
Scheduled
'
)}
`
);
break
;
case
'
started
'
:
statusField
.
html
(
`
${
spinner
}
${
__
(
'
Started
'
)}
`
);
break
;
case
'
failed
'
:
statusField
.
html
(
__
(
'
Failed
'
));
break
;
default
:
statusField
.
html
(
job
.
import_status
);
break
;
}
});
return
axios
.
get
(
this
.
jobsUrl
).
then
(({
data
=
[]
})
=>
{
data
.
forEach
(
job
=>
{
const
jobItem
=
$
(
`#project_
${
job
.
id
}
`
);
const
statusField
=
jobItem
.
find
(
'
.job-status
'
);
const
spinner
=
'
<i class="fa fa-spinner fa-spin"></i>
'
;
switch
(
job
.
import_status
)
{
case
'
finished
'
:
jobItem
.
removeClass
(
'
table-active
'
).
addClass
(
'
table-success
'
);
statusField
.
html
(
`<span><i class="fa fa-check"></i>
${
__
(
'
Done
'
)}
</span>`
);
break
;
case
'
scheduled
'
:
statusField
.
html
(
`
${
spinner
}
${
__
(
'
Scheduled
'
)}
`
);
break
;
case
'
started
'
:
statusField
.
html
(
`
${
spinner
}
${
__
(
'
Started
'
)}
`
);
break
;
case
'
failed
'
:
statusField
.
html
(
__
(
'
Failed
'
));
break
;
default
:
statusField
.
html
(
job
.
import_status
);
break
;
}
});
});
}
setAutoUpdate
()
{
...
...
@@ -141,7 +146,4 @@ function initImporterStatus() {
}
}
export
{
initImporterStatus
as
default
,
ImporterStatus
,
};
export
{
initImporterStatus
as
default
,
ImporterStatus
};
app/assets/javascripts/init_changes_dropdown.js
View file @
dfb08243
import
$
from
'
jquery
'
;
import
{
stickyMonitor
}
from
'
./lib/utils/sticky
'
;
export
default
(
stickyTop
)
=>
{
export
default
stickyTop
=>
{
stickyMonitor
(
document
.
querySelector
(
'
.js-diff-files-changed
'
),
stickyTop
);
$
(
'
.js-diff-stats-dropdown
'
).
glDropdown
({
...
...
app/assets/javascripts/init_notes.js
View file @
dfb08243
...
...
@@ -2,13 +2,7 @@ import Notes from './notes';
export
default
()
=>
{
const
dataEl
=
document
.
querySelector
(
'
.js-notes-data
'
);
const
{
notesUrl
,
notesIds
,
now
,
diffView
,
enableGFM
,
}
=
JSON
.
parse
(
dataEl
.
innerHTML
);
const
{
notesUrl
,
notesIds
,
now
,
diffView
,
enableGFM
}
=
JSON
.
parse
(
dataEl
.
innerHTML
);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
...
...
app/assets/javascripts/integrations/integration_settings_form.js
View file @
dfb08243
...
...
@@ -97,7 +97,8 @@ export default class IntegrationSettingsForm {
testSettings
(
formData
)
{
this
.
toggleSubmitBtnState
(
true
);
return
axios
.
put
(
this
.
testEndPoint
,
formData
)
return
axios
.
put
(
this
.
testEndPoint
,
formData
)
.
then
(({
data
})
=>
{
if
(
data
.
error
)
{
let
flashActions
;
...
...
@@ -105,7 +106,7 @@ export default class IntegrationSettingsForm {
if
(
data
.
test_failed
)
{
flashActions
=
{
title
:
'
Save anyway
'
,
clickHandler
:
(
e
)
=>
{
clickHandler
:
e
=>
{
e
.
preventDefault
();
this
.
$form
.
submit
();
},
...
...
app/assets/javascripts/issuable/auto_width_dropdown_select.js
View file @
dfb08243
...
...
@@ -27,7 +27,10 @@ class AutoWidthDropdownSelect {
// We have to look at the parent because
// `offsetParent` on a `display: none;` is `null`
const
offsetParentWidth
=
$
(
this
).
parent
().
offsetParent
().
width
();
const
offsetParentWidth
=
$
(
this
)
.
parent
()
.
offsetParent
()
.
width
();
// Reset any width to let it naturally flow
$dropdown
.
css
(
'
width
'
,
'
auto
'
);
if
(
$dropdown
.
outerWidth
(
false
)
>
offsetParentWidth
)
{
...
...
app/assets/javascripts/issuable_bulk_update_actions.js
View file @
dfb08243
...
...
@@ -32,7 +32,7 @@ export default {
onFormSubmitFailure
()
{
this
.
form
.
find
(
'
[type="submit"]
'
).
enable
();
return
new
Flash
(
"
Issue update failed
"
);
return
new
Flash
(
'
Issue update failed
'
);
},
getSelectedIssues
()
{
...
...
@@ -63,7 +63,7 @@ export default {
const
result
=
[];
const
labelsToKeep
=
this
.
$labelDropdown
.
data
(
'
indeterminate
'
);
this
.
getLabelsFromSelection
().
forEach
(
(
id
)
=>
{
this
.
getLabelsFromSelection
().
forEach
(
id
=>
{
if
(
labelsToKeep
.
indexOf
(
id
)
===
-
1
)
{
result
.
push
(
id
);
}
...
...
@@ -89,8 +89,8 @@ export default {
issuable_ids
:
this
.
form
.
find
(
'
input[name="update[issuable_ids]"]
'
).
val
(),
subscription_event
:
this
.
form
.
find
(
'
input[name="update[subscription_event]"]
'
).
val
(),
add_label_ids
:
[],
remove_label_ids
:
[]
}
remove_label_ids
:
[]
,
}
,
};
if
(
this
.
willUpdateLabels
)
{
formData
.
update
.
add_label_ids
=
this
.
$labelDropdown
.
data
(
'
marked
'
);
...
...
@@ -134,7 +134,7 @@ export default {
// Collect unique label IDs for all checked issues
this
.
getElement
(
'
.selected-issuable:checked
'
).
each
((
i
,
el
)
=>
{
issuableLabels
=
this
.
getElement
(
`#
${
this
.
prefixId
}${
el
.
dataset
.
id
}
`
).
data
(
'
labels
'
);
issuableLabels
.
forEach
(
(
labelId
)
=>
{
issuableLabels
.
forEach
(
labelId
=>
{
// Store unique IDs
if
(
uniqueIds
.
indexOf
(
labelId
)
===
-
1
)
{
uniqueIds
.
push
(
labelId
);
...
...
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