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
af053ce5
Commit
af053ce5
authored
Apr 13, 2017
by
Filipa Lacerda
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'ee-remove-iife-boards-bundle' into 'master'
Remove IIFEs in boards_bundle.js See merge request !1626
parents
e1bcbd90
13c50143
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
1242 additions
and
1279 deletions
+1242
-1279
app/assets/javascripts/boards/components/board.js
app/assets/javascripts/boards/components/board.js
+83
-85
app/assets/javascripts/boards/components/board_delete.js
app/assets/javascripts/boards/components/board_delete.js
+13
-15
app/assets/javascripts/boards/components/board_sidebar.js
app/assets/javascripts/boards/components/board_sidebar.js
+55
-57
app/assets/javascripts/boards/components/issue_card_inner.js
app/assets/javascripts/boards/components/issue_card_inner.js
+122
-124
app/assets/javascripts/boards/components/modal/empty_state.js
...assets/javascripts/boards/components/modal/empty_state.js
+59
-61
app/assets/javascripts/boards/components/modal/footer.js
app/assets/javascripts/boards/components/modal/footer.js
+63
-65
app/assets/javascripts/boards/components/modal/header.js
app/assets/javascripts/boards/components/modal/header.js
+64
-66
app/assets/javascripts/boards/components/modal/index.js
app/assets/javascripts/boards/components/modal/index.js
+135
-137
app/assets/javascripts/boards/components/modal/list.js
app/assets/javascripts/boards/components/modal/list.js
+133
-135
app/assets/javascripts/boards/components/modal/lists_dropdown.js
...ets/javascripts/boards/components/modal/lists_dropdown.js
+50
-52
app/assets/javascripts/boards/components/modal/tabs.js
app/assets/javascripts/boards/components/modal/tabs.js
+42
-44
app/assets/javascripts/boards/components/new_list_dropdown.js
...assets/javascripts/boards/components/new_list_dropdown.js
+59
-61
app/assets/javascripts/boards/components/sidebar/remove_issue.js
...ets/javascripts/boards/components/sidebar/remove_issue.js
+49
-51
app/assets/javascripts/boards/mixins/modal_mixins.js
app/assets/javascripts/boards/mixins/modal_mixins.js
+10
-12
app/assets/javascripts/boards/mixins/sortable_default_options.js
...ets/javascripts/boards/mixins/sortable_default_options.js
+29
-31
app/assets/javascripts/boards/stores/boards_store.js
app/assets/javascripts/boards/stores/boards_store.js
+115
-117
app/assets/javascripts/boards/stores/modal_store.js
app/assets/javascripts/boards/stores/modal_store.js
+77
-79
app/assets/javascripts/lib/utils/url_utility.js
app/assets/javascripts/lib/utils/url_utility.js
+84
-87
No files found.
app/assets/javascripts/boards/components/board.js
View file @
af053ce5
...
...
@@ -7,100 +7,98 @@ import boardBlankState from './board_blank_state';
require
(
'
./board_delete
'
);
require
(
'
./board_list
'
);
(()
=>
{
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
Board
=
Vue
.
extend
({
template
:
'
#js-board-template
'
,
components
:
{
boardList
,
'
board-delete
'
:
gl
.
issueBoards
.
BoardDelete
,
boardBlankState
,
},
props
:
{
list
:
Object
,
disabled
:
Boolean
,
issueLinkBase
:
String
,
rootPath
:
String
,
},
data
()
{
return
{
detailIssue
:
Store
.
detail
,
filter
:
Store
.
filter
,
};
},
watch
:
{
filter
:
{
handler
()
{
this
.
list
.
page
=
1
;
this
.
list
.
getIssues
(
true
);
},
deep
:
true
,
gl
.
issueBoards
.
Board
=
Vue
.
extend
({
template
:
'
#js-board-template
'
,
components
:
{
boardList
,
'
board-delete
'
:
gl
.
issueBoards
.
BoardDelete
,
boardBlankState
,
},
props
:
{
list
:
Object
,
disabled
:
Boolean
,
issueLinkBase
:
String
,
rootPath
:
String
,
},
data
()
{
return
{
detailIssue
:
Store
.
detail
,
filter
:
Store
.
filter
,
};
},
watch
:
{
filter
:
{
handler
()
{
this
.
list
.
page
=
1
;
this
.
list
.
getIssues
(
true
);
},
detailIssue
:
{
handler
()
{
if
(
!
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
)
return
;
deep
:
true
,
},
detailIssue
:
{
handler
()
{
if
(
!
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
)
return
;
const
issue
=
this
.
list
.
findIssue
(
this
.
detailIssue
.
issue
.
id
);
const
issue
=
this
.
list
.
findIssue
(
this
.
detailIssue
.
issue
.
id
);
if
(
issue
)
{
const
offsetLeft
=
this
.
$el
.
offsetLeft
;
const
boardsList
=
document
.
querySelectorAll
(
'
.boards-list
'
)[
0
];
const
left
=
boardsList
.
scrollLeft
-
offsetLeft
;
let
right
=
(
offsetLeft
+
this
.
$el
.
offsetWidth
);
if
(
issue
)
{
const
offsetLeft
=
this
.
$el
.
offsetLeft
;
const
boardsList
=
document
.
querySelectorAll
(
'
.boards-list
'
)[
0
];
const
left
=
boardsList
.
scrollLeft
-
offsetLeft
;
let
right
=
(
offsetLeft
+
this
.
$el
.
offsetWidth
);
if
(
window
.
innerWidth
>
768
&&
boardsList
.
classList
.
contains
(
'
is-compact
'
))
{
// -290 here because width of boardsList is animating so therefore
// getting the width here is incorrect
// 290 is the width of the sidebar
right
-=
(
boardsList
.
offsetWidth
-
290
);
}
else
{
right
-=
boardsList
.
offsetWidth
;
}
if
(
window
.
innerWidth
>
768
&&
boardsList
.
classList
.
contains
(
'
is-compact
'
))
{
// -290 here because width of boardsList is animating so therefore
// getting the width here is incorrect
// 290 is the width of the sidebar
right
-=
(
boardsList
.
offsetWidth
-
290
);
}
else
{
right
-=
boardsList
.
offsetWidth
;
}
if
(
right
-
boardsList
.
scrollLeft
>
0
)
{
$
(
boardsList
).
animate
({
scrollLeft
:
right
},
this
.
sortableOptions
.
animation
);
}
else
if
(
left
>
0
)
{
$
(
boardsList
).
animate
({
scrollLeft
:
offsetLeft
},
this
.
sortableOptions
.
animation
);
}
if
(
right
-
boardsList
.
scrollLeft
>
0
)
{
$
(
boardsList
).
animate
({
scrollLeft
:
right
},
this
.
sortableOptions
.
animation
);
}
else
if
(
left
>
0
)
{
$
(
boardsList
).
animate
({
scrollLeft
:
offsetLeft
},
this
.
sortableOptions
.
animation
);
}
},
deep
:
true
}
},
methods
:
{
showNewIssueForm
()
{
this
.
$refs
[
'
board-list
'
].
showIssueForm
=
!
this
.
$refs
[
'
board-list
'
].
showIssueForm
;
}
},
mounted
()
{
this
.
sortableOptions
=
gl
.
issueBoards
.
getBoardSortableDefaultOptions
({
disabled
:
this
.
disabled
,
group
:
'
boards
'
,
draggable
:
'
.is-draggable
'
,
handle
:
'
.js-board-handle
'
,
onEnd
:
(
e
)
=>
{
gl
.
issueBoards
.
onEnd
();
}
},
deep
:
true
}
},
methods
:
{
showNewIssueForm
()
{
this
.
$refs
[
'
board-list
'
].
showIssueForm
=
!
this
.
$refs
[
'
board-list
'
].
showIssueForm
;
}
},
mounted
()
{
this
.
sortableOptions
=
gl
.
issueBoards
.
getBoardSortableDefaultOptions
({
disabled
:
this
.
disabled
,
group
:
'
boards
'
,
draggable
:
'
.is-draggable
'
,
handle
:
'
.js-board-handle
'
,
onEnd
:
(
e
)
=>
{
gl
.
issueBoards
.
onEnd
();
if
(
e
.
newIndex
!==
undefined
&&
e
.
oldIndex
!==
e
.
newIndex
)
{
const
order
=
this
.
sortable
.
toArray
();
const
list
=
Store
.
findList
(
'
id
'
,
parseInt
(
e
.
item
.
dataset
.
id
,
10
));
if
(
e
.
newIndex
!==
undefined
&&
e
.
oldIndex
!==
e
.
newIndex
)
{
const
order
=
this
.
sortable
.
toArray
();
const
list
=
Store
.
findList
(
'
id
'
,
parseInt
(
e
.
item
.
dataset
.
id
,
10
));
this
.
$nextTick
(()
=>
{
Store
.
moveList
(
list
,
order
);
});
}
this
.
$nextTick
(()
=>
{
Store
.
moveList
(
list
,
order
);
});
}
});
}
});
this
.
sortable
=
Sortable
.
create
(
this
.
$el
.
parentNode
,
this
.
sortableOptions
);
},
});
})();
this
.
sortable
=
Sortable
.
create
(
this
.
$el
.
parentNode
,
this
.
sortableOptions
);
},
});
app/assets/javascripts/boards/components/board_delete.js
View file @
af053ce5
...
...
@@ -2,22 +2,20 @@
import
Vue
from
'
vue
'
;
(()
=>
{
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
BoardDelete
=
Vue
.
extend
({
props
:
{
list
:
Object
},
methods
:
{
deleteBoard
()
{
$
(
this
.
$el
).
tooltip
(
'
hide
'
);
gl
.
issueBoards
.
BoardDelete
=
Vue
.
extend
({
props
:
{
list
:
Object
},
methods
:
{
deleteBoard
()
{
$
(
this
.
$el
).
tooltip
(
'
hide
'
);
if
(
confirm
(
'
Are you sure you want to delete this list?
'
))
{
this
.
list
.
destroy
();
}
if
(
confirm
(
'
Are you sure you want to delete this list?
'
))
{
this
.
list
.
destroy
();
}
}
}
);
})
()
;
}
});
app/assets/javascripts/boards/components/board_sidebar.js
View file @
af053ce5
...
...
@@ -8,66 +8,64 @@ import Vue from 'vue';
require
(
'
./sidebar/remove_issue
'
);
(()
=>
{
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
BoardSidebar
=
Vue
.
extend
({
props
:
{
currentUser
:
Object
},
data
()
{
return
{
detail
:
Store
.
detail
,
issue
:
{},
list
:
{},
};
},
computed
:
{
showSidebar
()
{
return
Object
.
keys
(
this
.
issue
).
length
;
}
},
watch
:
{
detail
:
{
handler
()
{
if
(
this
.
issue
.
id
!==
this
.
detail
.
issue
.
id
)
{
$
(
'
.js-issue-board-sidebar
'
,
this
.
$el
).
each
((
i
,
el
)
=>
{
$
(
el
).
data
(
'
glDropdown
'
).
clearMenu
();
});
}
this
.
issue
=
this
.
detail
.
issue
;
this
.
list
=
this
.
detail
.
list
;
},
deep
:
true
},
issue
()
{
if
(
this
.
showSidebar
)
{
this
.
$nextTick
(()
=>
{
$
(
'
.right-sidebar
'
).
getNiceScroll
(
0
).
doScrollTop
(
0
,
0
);
$
(
'
.right-sidebar
'
).
getNiceScroll
().
resize
();
gl
.
issueBoards
.
BoardSidebar
=
Vue
.
extend
({
props
:
{
currentUser
:
Object
},
data
()
{
return
{
detail
:
Store
.
detail
,
issue
:
{},
list
:
{},
};
},
computed
:
{
showSidebar
()
{
return
Object
.
keys
(
this
.
issue
).
length
;
}
},
watch
:
{
detail
:
{
handler
()
{
if
(
this
.
issue
.
id
!==
this
.
detail
.
issue
.
id
)
{
$
(
'
.js-issue-board-sidebar
'
,
this
.
$el
).
each
((
i
,
el
)
=>
{
$
(
el
).
data
(
'
glDropdown
'
).
clearMenu
();
});
}
}
this
.
issue
=
this
.
detail
.
issue
;
this
.
list
=
this
.
detail
.
list
;
},
deep
:
true
},
methods
:
{
closeSidebar
()
{
this
.
detail
.
issue
=
{};
issue
()
{
if
(
this
.
showSidebar
)
{
this
.
$nextTick
(()
=>
{
$
(
'
.right-sidebar
'
).
getNiceScroll
(
0
).
doScrollTop
(
0
,
0
);
$
(
'
.right-sidebar
'
).
getNiceScroll
().
resize
();
});
}
},
mounted
()
{
new
IssuableContext
(
this
.
currentUser
);
new
MilestoneSelect
();
new
gl
.
DueDateSelectors
();
new
LabelsSelect
();
new
Sidebar
();
gl
.
Subscription
.
bindAll
(
'
.subscription
'
);
},
components
:
{
removeBtn
:
gl
.
issueBoards
.
RemoveIssueBtn
,
},
});
})();
}
},
methods
:
{
closeSidebar
()
{
this
.
detail
.
issue
=
{};
}
},
mounted
()
{
new
IssuableContext
(
this
.
currentUser
);
new
MilestoneSelect
();
new
gl
.
DueDateSelectors
();
new
LabelsSelect
();
new
Sidebar
();
gl
.
Subscription
.
bindAll
(
'
.subscription
'
);
},
components
:
{
removeBtn
:
gl
.
issueBoards
.
RemoveIssueBtn
,
},
});
app/assets/javascripts/boards/components/issue_card_inner.js
View file @
af053ce5
import
Vue
from
'
vue
'
;
import
eventHub
from
'
../eventhub
'
;
(()
=>
{
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
IssueCardInner
=
Vue
.
extend
({
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
list
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
rootPath
:
{
type
:
String
,
required
:
true
,
},
updateFilters
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
gl
.
issueBoards
.
IssueCardInner
=
Vue
.
extend
({
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
computed
:
{
cardUrl
()
{
return
`
${
this
.
issueLinkBase
}
/
${
this
.
issue
.
id
}
`
;
},
assigneeUrl
()
{
return
`
${
this
.
rootPath
}${
this
.
issue
.
assignee
.
username
}
`
;
},
assigneeUrlTitle
()
{
return
`Assigned to
${
this
.
issue
.
assignee
.
name
}
`
;
},
avatarUrlTitle
()
{
return
`Avatar for
${
this
.
issue
.
assignee
.
name
}
`
;
},
issueId
()
{
return
`#
${
this
.
issue
.
id
}
`
;
},
showLabelFooter
()
{
return
this
.
issue
.
labels
.
find
(
l
=>
this
.
showLabel
(
l
))
!==
undefined
;
},
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
methods
:
{
showLabel
(
label
)
{
if
(
!
this
.
list
)
return
true
;
list
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
rootPath
:
{
type
:
String
,
required
:
true
,
},
updateFilters
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
computed
:
{
cardUrl
()
{
return
`
${
this
.
issueLinkBase
}
/
${
this
.
issue
.
id
}
`
;
},
assigneeUrl
()
{
return
`
${
this
.
rootPath
}${
this
.
issue
.
assignee
.
username
}
`
;
},
assigneeUrlTitle
()
{
return
`Assigned to
${
this
.
issue
.
assignee
.
name
}
`
;
},
avatarUrlTitle
()
{
return
`Avatar for
${
this
.
issue
.
assignee
.
name
}
`
;
},
issueId
()
{
return
`#
${
this
.
issue
.
id
}
`
;
},
showLabelFooter
()
{
return
this
.
issue
.
labels
.
find
(
l
=>
this
.
showLabel
(
l
))
!==
undefined
;
},
},
methods
:
{
showLabel
(
label
)
{
if
(
!
this
.
list
)
return
true
;
return
!
this
.
list
.
label
||
label
.
id
!==
this
.
list
.
label
.
id
;
},
filterByLabel
(
label
,
e
)
{
if
(
!
this
.
updateFilters
)
return
;
return
!
this
.
list
.
label
||
label
.
id
!==
this
.
list
.
label
.
id
;
},
filterByLabel
(
label
,
e
)
{
if
(
!
this
.
updateFilters
)
return
;
const
filterPath
=
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
.
split
(
'
&
'
);
const
labelTitle
=
encodeURIComponent
(
label
.
title
);
const
param
=
`label_name[]=
${
labelTitle
}
`
;
const
labelIndex
=
filterPath
.
indexOf
(
param
);
$
(
e
.
currentTarget
).
tooltip
(
'
hide
'
);
const
filterPath
=
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
.
split
(
'
&
'
);
const
labelTitle
=
encodeURIComponent
(
label
.
title
);
const
param
=
`label_name[]=
${
labelTitle
}
`
;
const
labelIndex
=
filterPath
.
indexOf
(
param
);
$
(
e
.
currentTarget
).
tooltip
(
'
hide
'
);
if
(
labelIndex
===
-
1
)
{
filterPath
.
push
(
param
);
}
else
{
filterPath
.
splice
(
labelIndex
,
1
);
}
if
(
labelIndex
===
-
1
)
{
filterPath
.
push
(
param
);
}
else
{
filterPath
.
splice
(
labelIndex
,
1
);
}
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
=
filterPath
.
join
(
'
&
'
);
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
=
filterPath
.
join
(
'
&
'
);
Store
.
updateFiltersUrl
();
Store
.
updateFiltersUrl
();
eventHub
.
$emit
(
'
updateTokens
'
);
},
labelStyle
(
label
)
{
return
{
backgroundColor
:
label
.
color
,
color
:
label
.
textColor
,
};
},
eventHub
.
$emit
(
'
updateTokens
'
);
},
labelStyle
(
label
)
{
return
{
backgroundColor
:
label
.
color
,
color
:
label
.
textColor
,
};
},
template
:
`
<div>
<div class="card-header">
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"
aria-hidden="true"
/>
<a
class="js-no-trigger"
:href="cardUrl"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issue.id"
>
{{ issueId }}
</span>
</h4>
},
template
:
`
<div>
<div class="card-header">
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"
aria-hidden="true"
/>
<a
class="card-assignee has-tooltip js-no-trigger"
:href="assigneeUrl"
:title="assigneeUrlTitle"
v-if="issue.assignee"
data-container="body"
class="js-no-trigger"
:href="cardUrl"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issue.id"
>
<img
class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="avatarUrlTitle"
/>
</a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button
class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels"
type="button"
v-if="showLabel(label)"
@click="filterByLabel(label, $event)"
:style="labelStyle(label)"
:title="label.description"
data-container="body">
{{ label.title }}
</button>
</div>
{{ issueId }}
</span>
</h4>
<a
class="card-assignee has-tooltip js-no-trigger"
:href="assigneeUrl"
:title="assigneeUrlTitle"
v-if="issue.assignee"
data-container="body"
>
<img
class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="avatarUrlTitle"
/>
</a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button
class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels"
type="button"
v-if="showLabel(label)"
@click="filterByLabel(label, $event)"
:style="labelStyle(label)"
:title="label.description"
data-container="body">
{{ label.title }}
</button>
</div>
`
,
});
})
()
;
</div>
`
,
});
app/assets/javascripts/boards/components/modal/empty_state.js
View file @
af053ce5
import
Vue
from
'
vue
'
;
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalEmptyState
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
ModalStore
.
store
;
gl
.
issueBoards
.
ModalEmptyState
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
ModalStore
.
store
;
},
props
:
{
image
:
{
type
:
String
,
required
:
true
,
},
props
:
{
image
:
{
type
:
String
,
required
:
true
,
},
newIssuePath
:
{
type
:
String
,
required
:
true
,
},
newIssuePath
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
contents
()
{
const
obj
=
{
title
:
'
You haven
\'
t added any issues to your project yet
'
,
content
:
`
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
`
,
};
},
computed
:
{
contents
()
{
const
obj
=
{
title
:
'
You haven
\'
t added any issues to your project yet
'
,
content
:
`
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
`
,
};
if
(
this
.
activeTab
===
'
selected
'
)
{
obj
.
title
=
'
You haven
\'
t selected any issues yet
'
;
obj
.
content
=
`
Go back to <strong>Open issues</strong> and select some issues
to add to your board.
`
;
}
if
(
this
.
activeTab
===
'
selected
'
)
{
obj
.
title
=
'
You haven
\'
t selected any issues yet
'
;
obj
.
content
=
`
Go back to <strong>Open issues</strong> and select some issues
to add to your board.
`
;
}
return
obj
;
},
return
obj
;
},
template
:
`
<section class="empty-state">
<div class="row
">
<div class="col-xs-12 col-sm-6 col-sm-push-6
">
<aside class="svg-content" v-html="image"></aside
>
<
/div
>
<div class="col-xs-12 col-sm-6 col-sm-pull-6"
>
<div class="text-content
">
<h4>{{ contents.title }}</h4
>
<p v-html="contents.content"></p
>
<a
:href="newIssuePath"
class="btn btn-success btn-inverted
"
v-if="activeTab === 'all'">
New issue
</a>
<button
type="button"
class="btn btn-default
"
@click="changeTab('all')
"
v-if="activeTab === 'selected'">
Open issues
</button>
</
div
>
},
template
:
`
<section class="empty-state
">
<div class="row
">
<div class="col-xs-12 col-sm-6 col-sm-push-6"
>
<
aside class="svg-content" v-html="image"></aside
>
</div
>
<div class="col-xs-12 col-sm-6 col-sm-pull-6
">
<div class="text-content"
>
<h4>{{ contents.title }}</h4
>
<p v-html="contents.content"></p>
<a
:href="newIssuePath
"
class="btn btn-success btn-inverted"
v-if="activeTab === 'all'">
New issue
</a>
<button
type="button
"
class="btn btn-default
"
@click="changeTab('all')"
v-if="activeTab === 'selected'">
Open issues
</
button
>
</div>
</div>
</
section
>
`
,
});
})
()
;
</
div
>
</section>
`
,
});
app/assets/javascripts/boards/components/modal/footer.js
View file @
af053ce5
...
...
@@ -5,81 +5,79 @@ import Vue from 'vue';
require
(
'
./lists_dropdown
'
);
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalFooter
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
{
modal
:
ModalStore
.
store
,
state
:
gl
.
issueBoards
.
BoardsStore
.
state
,
};
gl
.
issueBoards
.
ModalFooter
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
{
modal
:
ModalStore
.
store
,
state
:
gl
.
issueBoards
.
BoardsStore
.
state
,
};
},
computed
:
{
submitDisabled
()
{
return
!
ModalStore
.
selectedCount
();
},
computed
:
{
submitDisabled
()
{
return
!
ModalStore
.
selectedCount
();
},
submitText
()
{
const
count
=
ModalStore
.
selectedCount
();
submitText
()
{
const
count
=
ModalStore
.
selectedCount
();
return
`Add
${
count
>
0
?
count
:
''
}
${
gl
.
text
.
pluralize
(
'
issue
'
,
count
)}
`
;
},
return
`Add
${
count
>
0
?
count
:
''
}
${
gl
.
text
.
pluralize
(
'
issue
'
,
count
)}
`
;
},
methods
:
{
addIssues
()
{
const
list
=
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
0
];
const
selectedIssues
=
ModalStore
.
getSelectedIssues
();
const
issueIds
=
selectedIssues
.
map
(
issue
=>
issue
.
globalId
);
},
methods
:
{
addIssues
()
{
const
list
=
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
0
];
const
selectedIssues
=
ModalStore
.
getSelectedIssues
();
const
issueIds
=
selectedIssues
.
map
(
issue
=>
issue
.
globalId
);
// Post the data to the backend
gl
.
boardService
.
bulkUpdate
(
issueIds
,
{
add_label_ids
:
[
list
.
label
.
id
],
milestone_id
:
this
.
state
.
currentBoard
.
milestone_id
,
}).
catch
(()
=>
{
new
Flash
(
'
Failed to update issues, please try again.
'
,
'
alert
'
);
// Post the data to the backend
gl
.
boardService
.
bulkUpdate
(
issueIds
,
{
add_label_ids
:
[
list
.
label
.
id
],
milestone_id
:
this
.
state
.
currentBoard
.
milestone_id
,
}).
catch
(()
=>
{
new
Flash
(
'
Failed to update issues, please try again.
'
,
'
alert
'
);
selectedIssues
.
forEach
((
issue
)
=>
{
list
.
removeIssue
(
issue
);
list
.
issuesSize
-=
1
;
});
});
// Add the issues on the frontend
selectedIssues
.
forEach
((
issue
)
=>
{
list
.
add
Issue
(
issue
);
list
.
issuesSize
+
=
1
;
list
.
remove
Issue
(
issue
);
list
.
issuesSize
-
=
1
;
});
});
this
.
toggleModal
(
false
);
},
},
components
:
{
'
lists-dropdown
'
:
gl
.
issueBoards
.
ModalFooterListsDropdown
,
// Add the issues on the frontend
selectedIssues
.
forEach
((
issue
)
=>
{
list
.
addIssue
(
issue
);
list
.
issuesSize
+=
1
;
});
this
.
toggleModal
(
false
);
},
template
:
`
<footer
class="form-actions add-issues-footer">
<div class="pull-left">
<button
class="btn btn-success"
type="button"
:disabled="submitDisabled"
@click="addIssues">
{{ submitText }}
</button>
<span class="inline add-issues-footer-to-list">
to list
</span>
<lists-dropdown></lists-dropdown>
</div>
},
components
:
{
'
lists-dropdown
'
:
gl
.
issueBoards
.
ModalFooterListsDropdown
,
},
template
:
`
<footer
class="form-actions add-issues-footer">
<div class="pull-left">
<button
class="btn btn-
default pull-right
"
class="btn btn-
success
"
type="button"
@click="toggleModal(false)">
Cancel
:disabled="submitDisabled"
@click="addIssues">
{{ submitText }}
</button>
</footer>
`
,
});
})();
<span class="inline add-issues-footer-to-list">
to list
</span>
<lists-dropdown></lists-dropdown>
</div>
<button
class="btn btn-default pull-right"
type="button"
@click="toggleModal(false)">
Cancel
</button>
</footer>
`
,
});
app/assets/javascripts/boards/components/modal/header.js
View file @
af053ce5
...
...
@@ -3,80 +3,78 @@ import modalFilters from './filters';
require
(
'
./tabs
'
);
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalHeader
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
props
:
{
projectId
:
{
type
:
Number
,
required
:
true
,
},
milestonePath
:
{
type
:
String
,
required
:
true
,
},
labelPath
:
{
type
:
String
,
required
:
true
,
},
gl
.
issueBoards
.
ModalHeader
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
props
:
{
projectId
:
{
type
:
Number
,
required
:
true
,
},
data
()
{
return
ModalStore
.
store
;
milestonePath
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
selectAllText
()
{
if
(
ModalStore
.
selectedCount
()
!==
this
.
issues
.
length
||
this
.
issues
.
length
===
0
)
{
return
'
Select all
'
;
}
return
'
Deselect all
'
;
},
showSearch
()
{
return
this
.
activeTab
===
'
all
'
&&
!
this
.
loading
&&
this
.
issuesCount
>
0
;
},
labelPath
:
{
type
:
String
,
required
:
true
,
},
methods
:
{
toggleAll
()
{
this
.
$refs
.
selectAllBtn
.
blur
();
},
data
()
{
return
ModalStore
.
store
;
},
computed
:
{
selectAllText
()
{
if
(
ModalStore
.
selectedCount
()
!==
this
.
issues
.
length
||
this
.
issues
.
length
===
0
)
{
return
'
Select all
'
;
}
ModalStore
.
toggleAll
();
},
return
'
Deselect all
'
;
},
showSearch
()
{
return
this
.
activeTab
===
'
all
'
&&
!
this
.
loading
&&
this
.
issuesCount
>
0
;
},
components
:
{
'
modal-tabs
'
:
gl
.
issueBoards
.
ModalTabs
,
modalFilters
,
},
methods
:
{
toggleAll
()
{
this
.
$refs
.
selectAllBtn
.
blur
();
ModalStore
.
toggleAll
();
},
template
:
`
<div>
<header class="add-issues-header form-actions">
<h2>
Add issues
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
@click="toggleModal(false)">
<span aria-hidden="true">×</span>
</button>
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters :store="filter" />
},
components
:
{
'
modal-tabs
'
:
gl
.
issueBoards
.
ModalTabs
,
modalFilters
,
},
template
:
`
<div>
<header class="add-issues-header form-actions">
<h2>
Add issues
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
ref="selectAllBtn"
@click="toggleAll">
{{ selectAllText }}
class="close"
data-dismiss="modal"
aria-label="Close"
@click="toggleModal(false)">
<span aria-hidden="true">×</span>
</button>
</div>
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters :store="filter" />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
ref="selectAllBtn"
@click="toggleAll">
{{ selectAllText }}
</button>
</div>
`
,
});
})
()
;
</div>
`
,
});
app/assets/javascripts/boards/components/modal/index.js
View file @
af053ce5
...
...
@@ -8,160 +8,158 @@ require('./list');
require
(
'
./footer
'
);
require
(
'
./empty_state
'
);
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
IssuesModal
=
Vue
.
extend
({
props
:
{
blankStateImage
:
{
type
:
String
,
required
:
true
,
},
newIssuePath
:
{
type
:
String
,
required
:
true
,
},
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
rootPath
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
Number
,
required
:
true
,
},
milestonePath
:
{
type
:
String
,
required
:
true
,
},
labelPath
:
{
type
:
String
,
required
:
true
,
},
gl
.
issueBoards
.
IssuesModal
=
Vue
.
extend
({
props
:
{
blankStateImage
:
{
type
:
String
,
required
:
true
,
},
data
()
{
return
ModalStore
.
store
;
newIssuePath
:
{
type
:
String
,
required
:
true
,
},
watch
:
{
page
()
{
this
.
loadIssues
();
},
showAddIssuesModal
()
{
if
(
this
.
showAddIssuesModal
&&
!
this
.
issues
.
length
)
{
this
.
loading
=
true
;
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
rootPath
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
Number
,
required
:
true
,
},
milestonePath
:
{
type
:
String
,
required
:
true
,
},
labelPath
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
ModalStore
.
store
;
},
watch
:
{
page
()
{
this
.
loadIssues
();
},
showAddIssuesModal
()
{
if
(
this
.
showAddIssuesModal
&&
!
this
.
issues
.
length
)
{
this
.
loading
=
true
;
this
.
loadIssues
()
.
then
(()
=>
{
this
.
loading
=
false
;
});
}
else
if
(
!
this
.
showAddIssuesModal
)
{
this
.
issues
=
[];
this
.
selectedIssues
=
[];
this
.
issuesCount
=
false
;
}
},
filter
:
{
handler
()
{
if
(
this
.
$el
.
tagName
)
{
this
.
page
=
1
;
this
.
filterLoading
=
true
;
this
.
loadIssues
()
this
.
loadIssues
(
true
)
.
then
(()
=>
{
this
.
l
oading
=
false
;
this
.
filterL
oading
=
false
;
});
}
else
if
(
!
this
.
showAddIssuesModal
)
{
this
.
issues
=
[];
this
.
selectedIssues
=
[];
this
.
issuesCount
=
false
;
}
},
filter
:
{
handler
()
{
if
(
this
.
$el
.
tagName
)
{
this
.
page
=
1
;
this
.
filterLoading
=
true
;
this
.
loadIssues
(
true
)
.
then
(()
=>
{
this
.
filterLoading
=
false
;
});
}
},
deep
:
true
,
},
deep
:
true
,
},
methods
:
{
loadIssues
(
clearIssues
=
false
)
{
if
(
!
this
.
showAddIssuesModal
)
return
false
;
return
gl
.
boardService
.
getBacklog
(
queryData
(
this
.
filter
.
path
,
{
page
:
this
.
page
,
per
:
this
.
perPage
,
})).
then
((
res
)
=>
{
const
data
=
res
.
json
();
if
(
clearIssues
)
{
this
.
issues
=
[];
}
},
methods
:
{
loadIssues
(
clearIssues
=
false
)
{
if
(
!
this
.
showAddIssuesModal
)
return
false
;
data
.
issues
.
forEach
((
issueObj
)
=>
{
const
issue
=
new
ListIssue
(
issueObj
);
const
foundSelectedIssue
=
ModalStore
.
findSelectedIssue
(
issue
);
issue
.
selected
=
!!
foundSelectedIssue
;
return
gl
.
boardService
.
getBacklog
(
queryData
(
this
.
filter
.
path
,
{
page
:
this
.
page
,
per
:
this
.
perPage
,
})).
then
((
res
)
=>
{
const
data
=
res
.
json
();
this
.
issues
.
push
(
issue
);
});
if
(
clearIssues
)
{
this
.
issues
=
[];
}
this
.
loadingNewPage
=
false
;
data
.
issues
.
forEach
((
issueObj
)
=>
{
const
issue
=
new
ListIssue
(
issueObj
);
const
foundSelectedIssue
=
ModalStore
.
findSelectedIssue
(
issue
);
issue
.
selected
=
!!
foundSelectedIssue
;
if
(
!
this
.
issuesCount
)
{
this
.
issuesCount
=
data
.
size
;
}
this
.
issues
.
push
(
issue
);
});
},
},
computed
:
{
showList
()
{
if
(
this
.
activeTab
===
'
selected
'
)
{
return
this
.
selectedIssues
.
length
>
0
;
}
return
this
.
issuesCount
>
0
;
},
showEmptyState
()
{
if
(
!
this
.
loading
&&
this
.
issuesCount
===
0
)
{
return
true
;
}
this
.
loadingNewPage
=
false
;
return
this
.
activeTab
===
'
selected
'
&&
this
.
selectedIssues
.
length
===
0
;
},
if
(
!
this
.
issuesCount
)
{
this
.
issuesCount
=
data
.
size
;
}
});
},
created
()
{
this
.
page
=
1
;
},
computed
:
{
showList
()
{
if
(
this
.
activeTab
===
'
selected
'
)
{
return
this
.
selectedIssues
.
length
>
0
;
}
return
this
.
issuesCount
>
0
;
},
components
:
{
'
modal-header
'
:
gl
.
issueBoards
.
ModalHeader
,
'
modal-list
'
:
gl
.
issueBoards
.
ModalList
,
'
modal-footer
'
:
gl
.
issueBoards
.
ModalFooter
,
'
empty-state
'
:
gl
.
issueBoards
.
ModalEmptyState
,
showEmptyState
()
{
if
(
!
this
.
loading
&&
this
.
issuesCount
===
0
)
{
return
true
;
}
return
this
.
activeTab
===
'
selected
'
&&
this
.
selectedIssues
.
length
===
0
;
},
template
:
`
<div
class="add-issues-modal"
v-if="showAddIssuesModal">
<div class="add-issues-container">
<modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
v-if="!loading && showList && !filterLoading"></modal-list>
<empty-state
v-if="showEmptyState"
:image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state>
<section
class="add-issues-list text-center"
v-if="loading || filterLoading">
<div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>
</section>
<modal-footer></modal-footer>
</div>
},
created
()
{
this
.
page
=
1
;
},
components
:
{
'
modal-header
'
:
gl
.
issueBoards
.
ModalHeader
,
'
modal-list
'
:
gl
.
issueBoards
.
ModalList
,
'
modal-footer
'
:
gl
.
issueBoards
.
ModalFooter
,
'
empty-state
'
:
gl
.
issueBoards
.
ModalEmptyState
,
},
template
:
`
<div
class="add-issues-modal"
v-if="showAddIssuesModal">
<div class="add-issues-container">
<modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
v-if="!loading && showList && !filterLoading"></modal-list>
<empty-state
v-if="showEmptyState"
:image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state>
<section
class="add-issues-list text-center"
v-if="loading || filterLoading">
<div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>
</section>
<modal-footer></modal-footer>
</div>
`
,
});
})
()
;
</div>
`
,
});
app/assets/javascripts/boards/components/modal/list.js
View file @
af053ce5
...
...
@@ -3,159 +3,157 @@
import
Vue
from
'
vue
'
;
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalList
=
Vue
.
extend
({
props
:
{
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
rootPath
:
{
type
:
String
,
required
:
true
,
},
image
:
{
type
:
String
,
required
:
true
,
},
gl
.
issueBoards
.
ModalList
=
Vue
.
extend
({
props
:
{
issueLinkBase
:
{
type
:
String
,
required
:
true
,
},
data
()
{
return
ModalStore
.
store
;
rootPath
:
{
type
:
String
,
required
:
true
,
},
watch
:
{
activeTab
()
{
if
(
this
.
activeTab
===
'
all
'
)
{
ModalStore
.
purgeUnselectedIssues
();
}
},
image
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
loopIssues
()
{
if
(
this
.
activeTab
===
'
all
'
)
{
return
this
.
issues
;
}
return
this
.
selectedIssues
;
},
groupedIssues
()
{
const
groups
=
[];
this
.
loopIssues
.
forEach
((
issue
,
i
)
=>
{
const
index
=
i
%
this
.
columns
;
if
(
!
groups
[
index
])
{
groups
.
push
([]);
}
groups
[
index
].
push
(
issue
);
});
},
data
()
{
return
ModalStore
.
store
;
},
watch
:
{
activeTab
()
{
if
(
this
.
activeTab
===
'
all
'
)
{
ModalStore
.
purgeUnselectedIssues
();
}
},
},
computed
:
{
loopIssues
()
{
if
(
this
.
activeTab
===
'
all
'
)
{
return
this
.
issues
;
}
return
groups
;
},
return
this
.
selectedIssues
;
},
methods
:
{
scrollHandler
()
{
const
currentPage
=
Math
.
floor
(
this
.
issues
.
length
/
this
.
perPage
);
groupedIssues
()
{
const
groups
=
[];
this
.
loopIssues
.
forEach
((
issue
,
i
)
=>
{
const
index
=
i
%
this
.
columns
;
if
((
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
100
)
&&
!
this
.
loadingNewPage
&&
currentPage
===
this
.
page
)
{
this
.
loadingNewPage
=
true
;
this
.
page
+=
1
;
if
(
!
groups
[
index
])
{
groups
.
push
([]);
}
},
toggleIssue
(
e
,
issue
)
{
if
(
e
.
target
.
tagName
!==
'
A
'
)
{
ModalStore
.
toggleIssue
(
issue
);
}
},
listHeight
()
{
return
this
.
$refs
.
list
.
getBoundingClientRect
().
height
;
},
scrollHeight
()
{
return
this
.
$refs
.
list
.
scrollHeight
;
},
scrollTop
()
{
return
this
.
$refs
.
list
.
scrollTop
+
this
.
listHeight
();
},
showIssue
(
issue
)
{
if
(
this
.
activeTab
===
'
all
'
)
return
true
;
const
index
=
ModalStore
.
selectedIssueIndex
(
issue
);
return
index
!==
-
1
;
},
setColumnCount
()
{
const
breakpoint
=
bp
.
getBreakpointSize
();
groups
[
index
].
push
(
issue
);
});
if
(
breakpoint
===
'
lg
'
||
breakpoint
===
'
md
'
)
{
this
.
columns
=
3
;
}
else
if
(
breakpoint
===
'
sm
'
)
{
this
.
columns
=
2
;
}
else
{
this
.
columns
=
1
;
}
},
return
groups
;
},
mounted
()
{
this
.
scrollHandlerWrapper
=
this
.
scrollHandler
.
bind
(
this
);
this
.
setColumnCountWrapper
=
this
.
setColumnCount
.
bind
(
this
);
this
.
setColumnCount
(
);
},
methods
:
{
scrollHandler
()
{
const
currentPage
=
Math
.
floor
(
this
.
issues
.
length
/
this
.
perPage
);
this
.
$refs
.
list
.
addEventListener
(
'
scroll
'
,
this
.
scrollHandlerWrapper
);
window
.
addEventListener
(
'
resize
'
,
this
.
setColumnCountWrapper
);
if
((
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
100
)
&&
!
this
.
loadingNewPage
&&
currentPage
===
this
.
page
)
{
this
.
loadingNewPage
=
true
;
this
.
page
+=
1
;
}
},
toggleIssue
(
e
,
issue
)
{
if
(
e
.
target
.
tagName
!==
'
A
'
)
{
ModalStore
.
toggleIssue
(
issue
);
}
},
listHeight
()
{
return
this
.
$refs
.
list
.
getBoundingClientRect
().
height
;
},
beforeDestroy
()
{
this
.
$refs
.
list
.
removeEventListener
(
'
scroll
'
,
this
.
scrollHandlerWrapper
);
window
.
removeEventListener
(
'
resize
'
,
this
.
setColumnCountWrapper
);
scrollHeight
()
{
return
this
.
$refs
.
list
.
scrollHeight
;
},
components
:
{
'
issue-card-inner
'
:
gl
.
issueBoards
.
IssueCardInner
,
scrollTop
()
{
return
this
.
$refs
.
list
.
scrollTop
+
this
.
listHeight
();
},
template
:
`
<section
class="add-issues-list add-issues-list-columns"
ref="list">
showIssue
(
issue
)
{
if
(
this
.
activeTab
===
'
all
'
)
return
true
;
const
index
=
ModalStore
.
selectedIssueIndex
(
issue
);
return
index
!==
-
1
;
},
setColumnCount
()
{
const
breakpoint
=
bp
.
getBreakpointSize
();
if
(
breakpoint
===
'
lg
'
||
breakpoint
===
'
md
'
)
{
this
.
columns
=
3
;
}
else
if
(
breakpoint
===
'
sm
'
)
{
this
.
columns
=
2
;
}
else
{
this
.
columns
=
1
;
}
},
},
mounted
()
{
this
.
scrollHandlerWrapper
=
this
.
scrollHandler
.
bind
(
this
);
this
.
setColumnCountWrapper
=
this
.
setColumnCount
.
bind
(
this
);
this
.
setColumnCount
();
this
.
$refs
.
list
.
addEventListener
(
'
scroll
'
,
this
.
scrollHandlerWrapper
);
window
.
addEventListener
(
'
resize
'
,
this
.
setColumnCountWrapper
);
},
beforeDestroy
()
{
this
.
$refs
.
list
.
removeEventListener
(
'
scroll
'
,
this
.
scrollHandlerWrapper
);
window
.
removeEventListener
(
'
resize
'
,
this
.
setColumnCountWrapper
);
},
components
:
{
'
issue-card-inner
'
:
gl
.
issueBoards
.
IssueCardInner
,
},
template
:
`
<section
class="add-issues-list add-issues-list-columns"
ref="list">
<div
class="empty-state add-issues-empty-state-filter text-center"
v-if="issuesCount > 0 && issues.length === 0">
<div
class="empty-state add-issues-empty-state-filter text-center"
v-if="issuesCount > 0 && issues.length === 0">
<div
class="svg-content"
v-html="image">
</div>
<div class="text-content">
<h4>
There are no issues to show.
</h4>
</div>
class="svg-content"
v-html="image">
</div>
<div class="text-content">
<h4>
There are no issues to show.
</h4>
</div>
</div>
<div
v-for="group in groupedIssues"
class="add-issues-list-column">
<div
v-for="group in groupedIssues"
class="add-issues-list-column">
v-for="issue in group"
v-if="showIssue(issue)"
class="card-parent">
<div
v-for="issue in group"
v-if="showIssue(issue)"
class="card-parent">
<div
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
</div>
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
</div>
</div>
</
section
>
`
,
});
})
()
;
</
div
>
</section>
`
,
});
app/assets/javascripts/boards/components/modal/lists_dropdown.js
View file @
af053ce5
import
Vue
from
'
vue
'
;
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalFooterListsDropdown
=
Vue
.
extend
({
data
()
{
return
{
modal
:
ModalStore
.
store
,
state
:
gl
.
issueBoards
.
BoardsStore
.
state
,
};
gl
.
issueBoards
.
ModalFooterListsDropdown
=
Vue
.
extend
({
data
()
{
return
{
modal
:
ModalStore
.
store
,
state
:
gl
.
issueBoards
.
BoardsStore
.
state
,
};
},
computed
:
{
selected
()
{
return
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
0
];
},
computed
:
{
selected
()
{
return
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
0
];
},
},
destroyed
()
{
this
.
modal
.
selectedList
=
null
;
},
template
:
`
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
class="dropdown-label-box"
:style="{ backgroundColor: selected.label.color }">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="list in state.lists"
v-if="list.type == 'label'">
<a
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
},
destroyed
()
{
this
.
modal
.
selectedList
=
null
;
},
template
:
`
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
class="dropdown-label-box"
:style="{ backgroundColor: selected.label.color }">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="list in state.lists"
v-if="list.type == 'label'">
<a
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
`
,
});
})
()
;
</div>
`
,
});
app/assets/javascripts/boards/components/modal/tabs.js
View file @
af053ce5
import
Vue
from
'
vue
'
;
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalTabs
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
ModalStore
.
store
;
gl
.
issueBoards
.
ModalTabs
=
Vue
.
extend
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
data
()
{
return
ModalStore
.
store
;
},
computed
:
{
selectedCount
()
{
return
ModalStore
.
selectedCount
();
},
computed
:
{
selectedCount
()
{
return
ModalStore
.
selectedCount
();
},
},
destroyed
()
{
this
.
activeTab
=
'
all
'
;
},
template
:
`
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')">
Open issues
<span class="badge">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')">
Selected issues
<span class="badge">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
`
,
});
})();
},
destroyed
()
{
this
.
activeTab
=
'
all
'
;
},
template
:
`
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')">
Open issues
<span class="badge">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')">
Selected issues
<span class="badge">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
`
,
});
app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
af053ce5
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
(()
=>
{
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
$
(
document
).
off
(
'
created.label
'
).
on
(
'
created.label
'
,
(
e
,
label
)
=>
{
Store
.
new
({
$
(
document
).
off
(
'
created.label
'
).
on
(
'
created.label
'
,
(
e
,
label
)
=>
{
Store
.
new
({
title
:
label
.
title
,
position
:
Store
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
title
:
label
.
title
,
position
:
Store
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
title
:
label
.
title
,
color
:
label
.
color
}
});
color
:
label
.
color
}
});
});
gl
.
issueBoards
.
newListDropdownInit
=
()
=>
{
$
(
'
.js-new-board-list
'
).
each
(
function
()
{
const
$this
=
$
(
this
);
new
gl
.
CreateLabelDropdown
(
$this
.
closest
(
'
.dropdown
'
).
find
(
'
.dropdown-new-label
'
),
$this
.
data
(
'
namespace-path
'
),
$this
.
data
(
'
project-path
'
));
gl
.
issueBoards
.
newListDropdownInit
=
()
=>
{
$
(
'
.js-new-board-list
'
).
each
(
function
()
{
const
$this
=
$
(
this
);
new
gl
.
CreateLabelDropdown
(
$this
.
closest
(
'
.dropdown
'
).
find
(
'
.dropdown-new-label
'
),
$this
.
data
(
'
namespace-path
'
),
$this
.
data
(
'
project-path
'
));
$this
.
glDropdown
({
data
(
term
,
callback
)
{
$
.
get
(
$this
.
attr
(
'
data-labels
'
))
.
then
((
resp
)
=>
{
callback
(
resp
);
});
},
renderRow
(
label
)
{
const
active
=
Store
.
findList
(
'
title
'
,
label
.
title
);
const
$li
=
$
(
'
<li />
'
);
const
$a
=
$
(
'
<a />
'
,
{
class
:
(
active
?
`is-active js-board-list-
${
active
.
id
}
`
:
''
),
text
:
label
.
title
,
href
:
'
#
'
});
const
$labelColor
=
$
(
'
<span />
'
,
{
class
:
'
dropdown-label-box
'
,
style
:
`background-color:
${
label
.
color
}
`
$this
.
glDropdown
({
data
(
term
,
callback
)
{
$
.
get
(
$this
.
attr
(
'
data-labels
'
))
.
then
((
resp
)
=>
{
callback
(
resp
);
});
},
renderRow
(
label
)
{
const
active
=
Store
.
findList
(
'
title
'
,
label
.
title
);
const
$li
=
$
(
'
<li />
'
);
const
$a
=
$
(
'
<a />
'
,
{
class
:
(
active
?
`is-active js-board-list-
${
active
.
id
}
`
:
''
),
text
:
label
.
title
,
href
:
'
#
'
});
const
$labelColor
=
$
(
'
<span />
'
,
{
class
:
'
dropdown-label-box
'
,
style
:
`background-color:
${
label
.
color
}
`
});
return
$li
.
append
(
$a
.
prepend
(
$labelColor
));
},
search
:
{
fields
:
[
'
title
'
]
},
filterable
:
true
,
selectable
:
true
,
multiSelect
:
true
,
clicked
(
label
,
$el
,
e
)
{
e
.
preventDefault
();
return
$li
.
append
(
$a
.
prepend
(
$labelColor
));
},
search
:
{
fields
:
[
'
title
'
]
},
filterable
:
true
,
selectable
:
true
,
multiSelect
:
true
,
clicked
(
label
,
$el
,
e
)
{
e
.
preventDefault
();
if
(
!
Store
.
findList
(
'
title
'
,
label
.
title
))
{
Store
.
new
({
if
(
!
Store
.
findList
(
'
title
'
,
label
.
title
))
{
Store
.
new
({
title
:
label
.
title
,
position
:
Store
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
title
:
label
.
title
,
position
:
Store
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
title
:
label
.
title
,
color
:
label
.
color
}
});
color
:
label
.
color
}
});
Store
.
state
.
lists
=
_
.
sortBy
(
Store
.
state
.
lists
,
'
position
'
);
}
Store
.
state
.
lists
=
_
.
sortBy
(
Store
.
state
.
lists
,
'
position
'
);
}
}
);
}
});
};
}
)()
;
}
)
;
};
app/assets/javascripts/boards/components/sidebar/remove_issue.js
View file @
af053ce5
...
...
@@ -3,64 +3,62 @@
import
Vue
from
'
vue
'
;
(()
=>
{
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
const
Store
=
gl
.
issueBoards
.
BoardsStore
;
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
RemoveIssueBtn
=
Vue
.
extend
({
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
list
:
{
type
:
Object
,
required
:
true
,
},
gl
.
issueBoards
.
RemoveIssueBtn
=
Vue
.
extend
({
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
methods
:
{
removeIssue
()
{
const
issue
=
this
.
issue
;
const
lists
=
issue
.
getLists
();
const
labelIds
=
lists
.
map
(
list
=>
list
.
label
.
id
);
const
data
=
{
remove_label_ids
:
labelIds
,
};
if
(
Store
.
state
.
currentBoard
.
milestone_id
)
{
data
.
milestone_id
=
-
1
;
}
list
:
{
type
:
Object
,
required
:
true
,
},
},
methods
:
{
removeIssue
()
{
const
issue
=
this
.
issue
;
const
lists
=
issue
.
getLists
();
const
labelIds
=
lists
.
map
(
list
=>
list
.
label
.
id
);
const
data
=
{
remove_label_ids
:
labelIds
,
};
// Post the remove data
gl
.
boardService
.
bulkUpdate
([
issue
.
globalId
],
data
).
catch
(()
=>
{
new
Flash
(
'
Failed to remove issue from board, please try again.
'
,
'
alert
'
);
if
(
Store
.
state
.
currentBoard
.
milestone_id
)
{
data
.
milestone_id
=
-
1
;
}
lists
.
forEach
((
list
)
=>
{
list
.
addIssue
(
issue
);
});
});
// Post the remove data
gl
.
boardService
.
bulkUpdate
([
issue
.
globalId
],
data
).
catch
(()
=>
{
new
Flash
(
'
Failed to remove issue from board, please try again.
'
,
'
alert
'
);
// Remove from the frontend store
lists
.
forEach
((
list
)
=>
{
list
.
remove
Issue
(
issue
);
list
.
add
Issue
(
issue
);
});
});
// Remove from the frontend store
lists
.
forEach
((
list
)
=>
{
list
.
removeIssue
(
issue
);
});
Store
.
detail
.
issue
=
{};
},
Store
.
detail
.
issue
=
{};
},
template
:
`
<div
class="block list"
v-if="list.type !== 'closed'">
<button
class="btn btn-default btn-block"
type="button
"
@click="removeIssue">
Remove from board
</button>
</
div
>
`
,
});
})
()
;
},
template
:
`
<div
class="block list"
v-if="list.type !== 'closed'">
<button
class="btn btn-default btn-block
"
type="button"
@click="removeIssue">
Remove from board
</
button
>
</div>
`
,
});
app/assets/javascripts/boards/mixins/modal_mixins.js
View file @
af053ce5
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
gl
.
issueBoards
.
ModalMixins
=
{
methods
:
{
toggleModal
(
toggle
)
{
ModalStore
.
store
.
showAddIssuesModal
=
toggle
;
},
changeTab
(
tab
)
{
ModalStore
.
store
.
activeTab
=
tab
;
},
gl
.
issueBoards
.
ModalMixins
=
{
methods
:
{
toggleModal
(
toggle
)
{
ModalStore
.
store
.
showAddIssuesModal
=
toggle
;
},
};
})();
changeTab
(
tab
)
{
ModalStore
.
store
.
activeTab
=
tab
;
},
},
};
app/assets/javascripts/boards/mixins/sortable_default_options.js
View file @
af053ce5
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
((
w
)
=>
{
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
onStart
=
()
=>
{
$
(
'
.has-tooltip
'
).
tooltip
(
'
hide
'
)
.
tooltip
(
'
disable
'
);
document
.
body
.
classList
.
add
(
'
is-dragging
'
);
};
gl
.
issueBoards
.
onEnd
=
()
=>
{
$
(
'
.has-tooltip
'
).
tooltip
(
'
enable
'
);
document
.
body
.
classList
.
remove
(
'
is-dragging
'
);
};
gl
.
issueBoards
.
onStart
=
()
=>
{
$
(
'
.has-tooltip
'
).
tooltip
(
'
hide
'
)
.
tooltip
(
'
disable
'
);
document
.
body
.
classList
.
add
(
'
is-dragging
'
);
};
gl
.
issueBoards
.
touchEnabled
=
(
'
ontouchstart
'
in
window
)
||
window
.
DocumentTouch
&&
document
instanceof
DocumentTouch
;
gl
.
issueBoards
.
onEnd
=
()
=>
{
$
(
'
.has-tooltip
'
).
tooltip
(
'
enable
'
);
document
.
body
.
classList
.
remove
(
'
is-dragging
'
);
};
gl
.
issueBoards
.
getBoardSortableDefaultOptions
=
(
obj
)
=>
{
const
defaultSortOptions
=
{
animation
:
200
,
forceFallback
:
true
,
fallbackClass
:
'
is-dragging
'
,
fallbackOnBody
:
true
,
ghostClass
:
'
is-ghost
'
,
filter
:
'
.board-delete, .btn
'
,
delay
:
gl
.
issueBoards
.
touchEnabled
?
100
:
0
,
scrollSensitivity
:
gl
.
issueBoards
.
touchEnabled
?
60
:
100
,
scrollSpeed
:
20
,
onStart
:
gl
.
issueBoards
.
onStart
,
onEnd
:
gl
.
issueBoards
.
onEnd
};
gl
.
issueBoards
.
touchEnabled
=
(
'
ontouchstart
'
in
window
)
||
window
.
DocumentTouch
&&
document
instanceof
DocumentTouch
;
Object
.
keys
(
obj
).
forEach
((
key
)
=>
{
defaultSortOptions
[
key
]
=
obj
[
key
];
});
return
defaultSortOptions
;
gl
.
issueBoards
.
getBoardSortableDefaultOptions
=
(
obj
)
=>
{
const
defaultSortOptions
=
{
animation
:
200
,
forceFallback
:
true
,
fallbackClass
:
'
is-dragging
'
,
fallbackOnBody
:
true
,
ghostClass
:
'
is-ghost
'
,
filter
:
'
.board-delete, .btn
'
,
delay
:
gl
.
issueBoards
.
touchEnabled
?
100
:
0
,
scrollSensitivity
:
gl
.
issueBoards
.
touchEnabled
?
60
:
100
,
scrollSpeed
:
20
,
onStart
:
gl
.
issueBoards
.
onStart
,
onEnd
:
gl
.
issueBoards
.
onEnd
};
})(
window
);
Object
.
keys
(
obj
).
forEach
((
key
)
=>
{
defaultSortOptions
[
key
]
=
obj
[
key
];
});
return
defaultSortOptions
;
};
app/assets/javascripts/boards/stores/boards_store.js
View file @
af053ce5
...
...
@@ -3,134 +3,132 @@
import
Cookies
from
'
js-cookie
'
;
(()
=>
{
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
gl
.
issueBoards
.
BoardsStore
=
{
disabled
:
false
,
filter
:
{
path
:
''
,
},
state
:
{},
detail
:
{
issue
:
{}
},
moving
:
{
issue
:
{},
list
:
{}
},
create
()
{
this
.
state
.
lists
=
[];
this
.
filter
.
path
=
gl
.
utils
.
getUrlParamsArray
().
join
(
'
&
'
);
},
createNewListDropdownData
()
{
this
.
state
.
currentBoard
=
{};
this
.
state
.
currentPage
=
''
;
this
.
state
.
reload
=
false
;
},
addList
(
listObj
)
{
const
list
=
new
List
(
listObj
);
this
.
state
.
lists
.
push
(
list
);
gl
.
issueBoards
.
BoardsStore
=
{
disabled
:
false
,
filter
:
{
path
:
''
,
},
state
:
{},
detail
:
{
issue
:
{}
},
moving
:
{
issue
:
{},
list
:
{}
},
create
()
{
this
.
state
.
lists
=
[];
this
.
filter
.
path
=
gl
.
utils
.
getUrlParamsArray
().
join
(
'
&
'
);
},
createNewListDropdownData
()
{
this
.
state
.
currentBoard
=
{};
this
.
state
.
currentPage
=
''
;
this
.
state
.
reload
=
false
;
},
addList
(
listObj
)
{
const
list
=
new
List
(
listObj
);
this
.
state
.
lists
.
push
(
list
);
return
list
;
},
new
(
listObj
)
{
const
list
=
this
.
addList
(
listObj
);
return
list
;
},
new
(
listObj
)
{
const
list
=
this
.
addList
(
listObj
);
list
.
save
()
.
then
(()
=>
{
this
.
state
.
lists
=
_
.
sortBy
(
this
.
state
.
lists
,
'
position
'
);
});
this
.
removeBlankState
();
},
updateNewListDropdown
(
listId
)
{
$
(
`.js-board-list-
${
listId
}
`
).
removeClass
(
'
is-active
'
);
},
shouldAddBlankState
()
{
// Decide whether to add the blank state
return
!
(
this
.
state
.
lists
.
filter
(
list
=>
list
.
type
!==
'
closed
'
)[
0
]);
},
addBlankState
()
{
if
(
!
this
.
shouldAddBlankState
()
||
this
.
welcomeIsHidden
()
||
this
.
disabled
)
return
;
this
.
addList
({
id
:
'
blank
'
,
list_type
:
'
blank
'
,
title
:
'
Welcome to your Issue Board!
'
,
position
:
0
list
.
save
()
.
then
(()
=>
{
this
.
state
.
lists
=
_
.
sortBy
(
this
.
state
.
lists
,
'
position
'
);
});
this
.
removeBlankState
();
},
updateNewListDropdown
(
listId
)
{
$
(
`.js-board-list-
${
listId
}
`
).
removeClass
(
'
is-active
'
);
},
shouldAddBlankState
()
{
// Decide whether to add the blank state
return
!
(
this
.
state
.
lists
.
filter
(
list
=>
list
.
type
!==
'
closed
'
)[
0
]);
},
addBlankState
()
{
if
(
!
this
.
shouldAddBlankState
()
||
this
.
welcomeIsHidden
()
||
this
.
disabled
)
return
;
this
.
state
.
lists
=
_
.
sortBy
(
this
.
state
.
lists
,
'
position
'
);
},
removeBlankState
()
{
this
.
removeList
(
'
blank
'
);
this
.
addList
({
id
:
'
blank
'
,
list_type
:
'
blank
'
,
title
:
'
Welcome to your Issue Board!
'
,
position
:
0
});
Cookies
.
set
(
'
issue_board_welcome_hidden
'
,
'
true
'
,
{
expires
:
365
*
10
,
path
:
''
});
},
welcomeIsHidden
()
{
return
Cookies
.
get
(
'
issue_board_welcome_hidden
'
)
===
'
true
'
;
},
removeList
(
id
,
type
=
'
blank
'
)
{
const
list
=
this
.
findList
(
'
id
'
,
id
,
type
);
this
.
state
.
lists
=
_
.
sortBy
(
this
.
state
.
lists
,
'
position
'
);
},
removeBlankState
()
{
this
.
removeList
(
'
blank
'
);
if
(
!
list
)
return
;
Cookies
.
set
(
'
issue_board_welcome_hidden
'
,
'
true
'
,
{
expires
:
365
*
10
,
path
:
''
});
},
welcomeIsHidden
()
{
return
Cookies
.
get
(
'
issue_board_welcome_hidden
'
)
===
'
true
'
;
},
removeList
(
id
,
type
=
'
blank
'
)
{
const
list
=
this
.
findList
(
'
id
'
,
id
,
type
);
this
.
state
.
lists
=
this
.
state
.
lists
.
filter
(
list
=>
list
.
id
!==
id
);
},
moveList
(
listFrom
,
orderLists
)
{
orderLists
.
forEach
((
id
,
i
)
=>
{
const
list
=
this
.
findList
(
'
id
'
,
parseInt
(
id
,
10
));
if
(
!
list
)
return
;
list
.
position
=
i
;
});
listFrom
.
update
();
},
moveIssueToList
(
listFrom
,
listTo
,
issue
,
newIndex
)
{
const
issueTo
=
listTo
.
findIssue
(
issue
.
id
);
const
issueLists
=
issue
.
getLists
();
const
listLabels
=
issueLists
.
map
(
listIssue
=>
listIssue
.
label
);
this
.
state
.
lists
=
this
.
state
.
lists
.
filter
(
list
=>
list
.
id
!==
id
);
},
moveList
(
listFrom
,
orderLists
)
{
orderLists
.
forEach
((
id
,
i
)
=>
{
const
list
=
this
.
findList
(
'
id
'
,
parseInt
(
id
,
10
));
if
(
!
issueTo
)
{
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
else
{
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
issueTo
.
removeLabel
(
listFrom
.
label
);
}
list
.
position
=
i
;
});
listFrom
.
update
();
},
moveIssueToList
(
listFrom
,
listTo
,
issue
,
newIndex
)
{
const
issueTo
=
listTo
.
findIssue
(
issue
.
id
);
const
issueLists
=
issue
.
getLists
();
const
listLabels
=
issueLists
.
map
(
listIssue
=>
listIssue
.
label
);
if
(
listTo
.
type
===
'
closed
'
)
{
issueLists
.
forEach
((
list
)
=>
{
list
.
removeIssue
(
issue
);
});
issue
.
removeLabels
(
listLabels
);
}
else
{
listFrom
.
removeIssue
(
issue
);
}
},
moveIssueInList
(
list
,
issue
,
oldIndex
,
newIndex
,
idArray
)
{
const
beforeId
=
parseInt
(
idArray
[
newIndex
-
1
],
10
)
||
null
;
const
afterId
=
parseInt
(
idArray
[
newIndex
+
1
],
10
)
||
null
;
if
(
!
issueTo
)
{
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
else
{
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
issueTo
.
removeLabel
(
listFrom
.
label
);
}
if
(
listTo
.
type
===
'
closed
'
)
{
issueLists
.
forEach
((
list
)
=>
{
list
.
removeIssue
(
issue
);
});
issue
.
removeLabels
(
listLabels
);
}
else
{
listFrom
.
removeIssue
(
issue
);
}
},
moveIssueInList
(
list
,
issue
,
oldIndex
,
newIndex
,
idArray
)
{
const
beforeId
=
parseInt
(
idArray
[
newIndex
-
1
],
10
)
||
null
;
const
afterId
=
parseInt
(
idArray
[
newIndex
+
1
],
10
)
||
null
;
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
},
findList
(
key
,
val
,
type
=
'
label
'
)
{
return
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
byType
=
type
?
list
[
'
type
'
]
===
type
:
true
;
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
},
findList
(
key
,
val
,
type
=
'
label
'
)
{
return
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
byType
=
type
?
list
[
'
type
'
]
===
type
:
true
;
return
list
[
key
]
===
val
&&
byType
;
})[
0
];
},
updateFiltersUrl
(
replaceState
=
false
)
{
if
(
replaceState
)
{
history
.
replaceState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
}
else
{
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
}
return
list
[
key
]
===
val
&&
byType
;
})[
0
];
},
updateFiltersUrl
(
replaceState
=
false
)
{
if
(
replaceState
)
{
history
.
replaceState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
}
else
{
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
}
}
;
}
)()
;
}
};
app/assets/javascripts/boards/stores/modal_store.js
View file @
af053ce5
(()
=>
{
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
class
ModalStore
{
constructor
()
{
this
.
store
=
{
columns
:
3
,
issues
:
[],
issuesCount
:
false
,
selectedIssues
:
[],
showAddIssuesModal
:
false
,
activeTab
:
'
all
'
,
selectedList
:
null
,
searchTerm
:
''
,
loading
:
false
,
loadingNewPage
:
false
,
filterLoading
:
false
,
page
:
1
,
perPage
:
50
,
filter
:
{
path
:
''
,
},
};
}
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
issueBoards
=
window
.
gl
.
issueBoards
||
{};
class
ModalStore
{
constructor
()
{
this
.
store
=
{
columns
:
3
,
issues
:
[],
issuesCount
:
false
,
selectedIssues
:
[],
showAddIssuesModal
:
false
,
activeTab
:
'
all
'
,
selectedList
:
null
,
searchTerm
:
''
,
loading
:
false
,
loadingNewPage
:
false
,
filterLoading
:
false
,
page
:
1
,
perPage
:
50
,
filter
:
{
path
:
''
,
},
};
}
selectedCount
()
{
return
this
.
getSelectedIssues
().
length
;
}
selectedCount
()
{
return
this
.
getSelectedIssues
().
length
;
}
toggleIssue
(
issueObj
)
{
const
issue
=
issueObj
;
const
selected
=
issue
.
selected
;
toggleIssue
(
issueObj
)
{
const
issue
=
issueObj
;
const
selected
=
issue
.
selected
;
issue
.
selected
=
!
selected
;
issue
.
selected
=
!
selected
;
if
(
!
selected
)
{
this
.
addSelectedIssue
(
issue
);
}
else
{
this
.
removeSelectedIssue
(
issue
);
}
if
(
!
selected
)
{
this
.
addSelectedIssue
(
issue
);
}
else
{
this
.
removeSelectedIssue
(
issue
);
}
}
toggleAll
()
{
const
select
=
this
.
selectedCount
()
!==
this
.
store
.
issues
.
length
;
toggleAll
()
{
const
select
=
this
.
selectedCount
()
!==
this
.
store
.
issues
.
length
;
this
.
store
.
issues
.
forEach
((
issue
)
=>
{
const
issueUpdate
=
issue
;
this
.
store
.
issues
.
forEach
((
issue
)
=>
{
const
issueUpdate
=
issue
;
if
(
issueUpdate
.
selected
!==
select
)
{
issueUpdate
.
selected
=
select
;
if
(
issueUpdate
.
selected
!==
select
)
{
issueUpdate
.
selected
=
select
;
if
(
select
)
{
this
.
addSelectedIssue
(
issue
);
}
else
{
this
.
removeSelectedIssue
(
issue
);
}
if
(
select
)
{
this
.
addSelectedIssue
(
issue
);
}
else
{
this
.
removeSelectedIssue
(
issue
);
}
});
}
}
});
}
getSelectedIssues
()
{
return
this
.
store
.
selectedIssues
.
filter
(
issue
=>
issue
.
selected
);
}
getSelectedIssues
()
{
return
this
.
store
.
selectedIssues
.
filter
(
issue
=>
issue
.
selected
);
}
addSelectedIssue
(
issue
)
{
const
index
=
this
.
selectedIssueIndex
(
issue
);
addSelectedIssue
(
issue
)
{
const
index
=
this
.
selectedIssueIndex
(
issue
);
if
(
index
===
-
1
)
{
this
.
store
.
selectedIssues
.
push
(
issue
);
}
if
(
index
===
-
1
)
{
this
.
store
.
selectedIssues
.
push
(
issue
);
}
}
removeSelectedIssue
(
issue
,
forcePurge
=
false
)
{
if
(
this
.
store
.
activeTab
===
'
all
'
||
forcePurge
)
{
this
.
store
.
selectedIssues
=
this
.
store
.
selectedIssues
.
filter
(
fIssue
=>
fIssue
.
id
!==
issue
.
id
);
}
removeSelectedIssue
(
issue
,
forcePurge
=
false
)
{
if
(
this
.
store
.
activeTab
===
'
all
'
||
forcePurge
)
{
this
.
store
.
selectedIssues
=
this
.
store
.
selectedIssues
.
filter
(
fIssue
=>
fIssue
.
id
!==
issue
.
id
);
}
}
purgeUnselectedIssues
()
{
this
.
store
.
selectedIssues
.
forEach
((
issue
)
=>
{
if
(
!
issue
.
selected
)
{
this
.
removeSelectedIssue
(
issue
,
true
);
}
});
}
purgeUnselectedIssues
()
{
this
.
store
.
selectedIssues
.
forEach
((
issue
)
=>
{
if
(
!
issue
.
selected
)
{
this
.
removeSelectedIssue
(
issue
,
true
);
}
});
}
selectedIssueIndex
(
issue
)
{
return
this
.
store
.
selectedIssues
.
indexOf
(
issue
);
}
selectedIssueIndex
(
issue
)
{
return
this
.
store
.
selectedIssues
.
indexOf
(
issue
);
}
findSelectedIssue
(
issue
)
{
return
this
.
store
.
selectedIssues
.
filter
(
filteredIssue
=>
filteredIssue
.
id
===
issue
.
id
)[
0
];
}
findSelectedIssue
(
issue
)
{
return
this
.
store
.
selectedIssues
.
filter
(
filteredIssue
=>
filteredIssue
.
id
===
issue
.
id
)[
0
];
}
}
gl
.
issueBoards
.
ModalStore
=
new
ModalStore
();
})();
gl
.
issueBoards
.
ModalStore
=
new
ModalStore
();
app/assets/javascripts/lib/utils/url_utility.js
View file @
af053ce5
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
(
function
()
{
(
function
(
w
)
{
var
base
;
if
(
w
.
gl
==
null
)
{
w
.
gl
=
{};
var
base
;
var
w
=
window
;
if
(
w
.
gl
==
null
)
{
w
.
gl
=
{};
}
if
((
base
=
w
.
gl
).
utils
==
null
)
{
base
.
utils
=
{};
}
// Returns an array containing the value(s) of the
// of the key passed as an argument
w
.
gl
.
utils
.
getParameterValues
=
function
(
sParam
)
{
var
i
,
sPageURL
,
sParameterName
,
sURLVariables
,
values
;
sPageURL
=
decodeURIComponent
(
window
.
location
.
search
.
substring
(
1
));
sURLVariables
=
sPageURL
.
split
(
'
&
'
);
sParameterName
=
void
0
;
values
=
[];
i
=
0
;
while
(
i
<
sURLVariables
.
length
)
{
sParameterName
=
sURLVariables
[
i
].
split
(
'
=
'
);
if
(
sParameterName
[
0
]
===
sParam
)
{
values
.
push
(
sParameterName
[
1
].
replace
(
/
\+
/g
,
'
'
));
}
if
((
base
=
w
.
gl
).
utils
==
null
)
{
base
.
utils
=
{};
i
+=
1
;
}
return
values
;
};
// @param {Object} params - url keys and value to merge
// @param {String} url
w
.
gl
.
utils
.
mergeUrlParams
=
function
(
params
,
url
)
{
var
lastChar
,
newUrl
,
paramName
,
paramValue
,
pattern
;
newUrl
=
decodeURIComponent
(
url
);
for
(
paramName
in
params
)
{
paramValue
=
params
[
paramName
];
pattern
=
new
RegExp
(
"
\\
b(
"
+
paramName
+
"
=).*?(&|$)
"
);
if
(
paramValue
==
null
)
{
newUrl
=
newUrl
.
replace
(
pattern
,
''
);
}
else
if
(
url
.
search
(
pattern
)
!==
-
1
)
{
newUrl
=
newUrl
.
replace
(
pattern
,
"
$1
"
+
paramValue
+
"
$2
"
);
}
else
{
newUrl
=
""
+
newUrl
+
(
newUrl
.
indexOf
(
'
?
'
)
>
0
?
'
&
'
:
'
?
'
)
+
paramName
+
"
=
"
+
paramValue
;
}
// Returns an array containing the value(s) of the
// of the key passed as an argument
w
.
gl
.
utils
.
getParameterValues
=
function
(
sParam
)
{
var
i
,
sPageURL
,
sParameterName
,
sURLVariables
,
values
;
sPageURL
=
decodeURIComponent
(
window
.
location
.
search
.
substring
(
1
));
sURLVariables
=
sPageURL
.
split
(
'
&
'
);
sParameterName
=
void
0
;
values
=
[];
i
=
0
;
while
(
i
<
sURLVariables
.
length
)
{
sParameterName
=
sURLVariables
[
i
].
split
(
'
=
'
);
if
(
sParameterName
[
0
]
===
sParam
)
{
values
.
push
(
sParameterName
[
1
].
replace
(
/
\+
/g
,
'
'
));
}
i
+=
1
;
}
// Remove a trailing ampersand
lastChar
=
newUrl
[
newUrl
.
length
-
1
];
if
(
lastChar
===
'
&
'
)
{
newUrl
=
newUrl
.
slice
(
0
,
-
1
);
}
return
newUrl
;
};
// removes parameter query string from url. returns the modified url
w
.
gl
.
utils
.
removeParamQueryString
=
function
(
url
,
param
)
{
var
urlVariables
,
variables
;
url
=
decodeURIComponent
(
url
);
urlVariables
=
url
.
split
(
'
&
'
);
return
((
function
()
{
var
j
,
len
,
results
;
results
=
[];
for
(
j
=
0
,
len
=
urlVariables
.
length
;
j
<
len
;
j
+=
1
)
{
variables
=
urlVariables
[
j
];
if
(
variables
.
indexOf
(
param
)
===
-
1
)
{
results
.
push
(
variables
);
}
return
values
;
};
// @param {Object} params - url keys and value to merge
// @param {String} url
w
.
gl
.
utils
.
mergeUrlParams
=
function
(
params
,
url
)
{
var
lastChar
,
newUrl
,
paramName
,
paramValue
,
pattern
;
newUrl
=
decodeURIComponent
(
url
);
for
(
paramName
in
params
)
{
paramValue
=
params
[
paramName
];
pattern
=
new
RegExp
(
"
\\
b(
"
+
paramName
+
"
=).*?(&|$)
"
);
if
(
paramValue
==
null
)
{
newUrl
=
newUrl
.
replace
(
pattern
,
''
);
}
else
if
(
url
.
search
(
pattern
)
!==
-
1
)
{
newUrl
=
newUrl
.
replace
(
pattern
,
"
$1
"
+
paramValue
+
"
$2
"
);
}
else
{
newUrl
=
""
+
newUrl
+
(
newUrl
.
indexOf
(
'
?
'
)
>
0
?
'
&
'
:
'
?
'
)
+
paramName
+
"
=
"
+
paramValue
;
}
}
// Remove a trailing ampersand
lastChar
=
newUrl
[
newUrl
.
length
-
1
];
if
(
lastChar
===
'
&
'
)
{
newUrl
=
newUrl
.
slice
(
0
,
-
1
);
}
return
newUrl
;
};
// removes parameter query string from url. returns the modified url
w
.
gl
.
utils
.
removeParamQueryString
=
function
(
url
,
param
)
{
var
urlVariables
,
variables
;
url
=
decodeURIComponent
(
url
);
urlVariables
=
url
.
split
(
'
&
'
);
return
((
function
()
{
var
j
,
len
,
results
;
results
=
[];
for
(
j
=
0
,
len
=
urlVariables
.
length
;
j
<
len
;
j
+=
1
)
{
variables
=
urlVariables
[
j
];
if
(
variables
.
indexOf
(
param
)
===
-
1
)
{
results
.
push
(
variables
);
}
}
return
results
;
})()).
join
(
'
&
'
);
};
w
.
gl
.
utils
.
removeParams
=
(
params
)
=>
{
const
url
=
new
URL
(
window
.
location
.
href
);
params
.
forEach
((
param
)
=>
{
url
.
search
=
w
.
gl
.
utils
.
removeParamQueryString
(
url
.
search
,
param
);
});
return
url
.
href
;
};
w
.
gl
.
utils
.
getLocationHash
=
function
(
url
)
{
var
hashIndex
;
if
(
typeof
url
===
'
undefined
'
)
{
// Note: We can't use window.location.hash here because it's
// not consistent across browsers - Firefox will pre-decode it
url
=
window
.
location
.
href
;
}
hashIndex
=
url
.
indexOf
(
'
#
'
);
return
hashIndex
===
-
1
?
null
:
url
.
substring
(
hashIndex
+
1
);
};
}
return
results
;
})()).
join
(
'
&
'
);
};
w
.
gl
.
utils
.
removeParams
=
(
params
)
=>
{
const
url
=
new
URL
(
window
.
location
.
href
);
params
.
forEach
((
param
)
=>
{
url
.
search
=
w
.
gl
.
utils
.
removeParamQueryString
(
url
.
search
,
param
);
});
return
url
.
href
;
};
w
.
gl
.
utils
.
getLocationHash
=
function
(
url
)
{
var
hashIndex
;
if
(
typeof
url
===
'
undefined
'
)
{
// Note: We can't use window.location.hash here because it's
// not consistent across browsers - Firefox will pre-decode it
url
=
window
.
location
.
href
;
}
hashIndex
=
url
.
indexOf
(
'
#
'
);
return
hashIndex
===
-
1
?
null
:
url
.
substring
(
hashIndex
+
1
);
};
w
.
gl
.
utils
.
refreshCurrentPage
=
()
=>
gl
.
utils
.
visitUrl
(
document
.
location
.
href
);
w
.
gl
.
utils
.
refreshCurrentPage
=
()
=>
gl
.
utils
.
visitUrl
(
document
.
location
.
href
);
w
.
gl
.
utils
.
visitUrl
=
(
url
)
=>
{
document
.
location
.
href
=
url
;
};
})(
window
);
}).
call
(
window
);
w
.
gl
.
utils
.
visitUrl
=
(
url
)
=>
{
document
.
location
.
href
=
url
;
};
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