16
Makefile
16
Makefile
@@ -12,30 +12,30 @@ sign_dir=$(build_dir)/sign
|
||||
cert_dir=$(HOME)/.nextcloud/certificates
|
||||
|
||||
|
||||
default: package
|
||||
default: build
|
||||
|
||||
clean-build:
|
||||
rm -rf $(build_dir)
|
||||
|
||||
clean-dist:
|
||||
rm -rf js/node_modules
|
||||
rm -rf node_modules/
|
||||
|
||||
install-deps: install-deps-js
|
||||
composer install
|
||||
|
||||
install-deps-js:
|
||||
cd js && npm install
|
||||
npm ci
|
||||
|
||||
build: install-deps build-js
|
||||
build: clean-dist install-deps build-js
|
||||
|
||||
build-js: install-deps-js
|
||||
cd js && npm run build
|
||||
npm run build
|
||||
|
||||
build-js-dev: install-deps
|
||||
cd js && npm run dev
|
||||
npm run dev
|
||||
|
||||
watch:
|
||||
cd js && npm run watch
|
||||
npm run watch
|
||||
|
||||
# appstore: clean install-deps
|
||||
appstore: clean-build build
|
||||
@@ -96,7 +96,7 @@ test-integration:
|
||||
cd tests/integration && ./run.sh
|
||||
|
||||
test-js: install-deps
|
||||
cd js && run test
|
||||
npm run test
|
||||
|
||||
package:
|
||||
krankerl package
|
||||
|
||||
@@ -38,7 +38,7 @@ return [
|
||||
['name' => 'board#deleteUndo', 'url' => '/boards/{boardId}/deleteUndo', 'verb' => 'POST'],
|
||||
['name' => 'board#getUserPermissions', 'url' => '/boards/{boardId}/permissions', 'verb' => 'GET'],
|
||||
['name' => 'board#addAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'POST'],
|
||||
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'PUT'],
|
||||
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
||||
|
||||
|
||||
1228
package-lock.json
generated
1228
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -29,11 +29,11 @@
|
||||
"@babel/polyfill": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.3",
|
||||
"@nextcloud/auth": "^1.2.1",
|
||||
"@nextcloud/axios": "^1.3.1",
|
||||
"@nextcloud/l10n": "^1.0.1",
|
||||
"@nextcloud/router": "^1.0.0",
|
||||
"@nextcloud/vue": "^1.2.7",
|
||||
"fuse.js": "^3.4.5",
|
||||
"nextcloud-axios": "^0.2.1",
|
||||
"nextcloud-server": "^0.15.10",
|
||||
"nextcloud-vue-collections": "^0.7.1",
|
||||
"tippy.js": "^4.3.5",
|
||||
@@ -77,17 +77,16 @@
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.1.2",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^5.0.2",
|
||||
"jest": "^25.1.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"prettier-eslint": "^9.0.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"stylelint": "^8.4.0",
|
||||
"stylelint-config-recommended-scss": "^3.3.0",
|
||||
"stylelint-scss": "^3.13.0",
|
||||
"stylelint-webpack-plugin": "^0.10.5",
|
||||
"url-loader": "^3.0.0",
|
||||
"vue-jest": "^3.0.5",
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</style>
|
||||
<script>
|
||||
import { Modal } from '@nextcloud/vue/dist/Components/Modal'
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export default {
|
||||
name: 'BoardSelector',
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<script>
|
||||
import { Modal } from '@nextcloud/vue/dist/Components/Modal'
|
||||
import { Multiselect } from '@nextcloud/vue/dist/Components/Multiselect'
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export default {
|
||||
name: 'CardSelector',
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<h2><a href="#">{{ board.title }}</a></h2>
|
||||
</div>
|
||||
<div v-if="board" class="board-actions">
|
||||
<div id="stack-add" v-click-outside="hideAddStack">
|
||||
<div v-if="canManage" id="stack-add" v-click-outside="hideAddStack">
|
||||
<Actions v-if="!isAddStackVisible">
|
||||
<ActionButton icon="icon-add" :title="t('deck', 'Add new stack')" @click.stop="showAddStack" />
|
||||
</Actions>
|
||||
@@ -77,7 +77,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import { Actions, ActionButton } from '@nextcloud/vue'
|
||||
|
||||
export default {
|
||||
@@ -101,6 +101,10 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'canEdit',
|
||||
'canManage',
|
||||
]),
|
||||
...mapState({
|
||||
compactMode: state => state.compactMode,
|
||||
}),
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
<p />
|
||||
</div>
|
||||
<div v-else-if="board" class="board">
|
||||
<Container lock-axix="y" orientation="horizontal" @drop="onDropStack">
|
||||
<Container lock-axix="y"
|
||||
orientation="horizontal"
|
||||
:drag-handle-selector="dragHandleSelector"
|
||||
@drop="onDropStack">
|
||||
<Draggable v-for="stack in stacksByBoard" :key="stack.id">
|
||||
<Stack :stack="stack" />
|
||||
</Draggable>
|
||||
@@ -46,7 +49,7 @@
|
||||
<script>
|
||||
|
||||
import { Container, Draggable } from 'vue-smooth-dnd'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Controls from '../Controls'
|
||||
import Stack from './Stack'
|
||||
|
||||
@@ -77,9 +80,15 @@ export default {
|
||||
board: state => state.currentBoard,
|
||||
showArchived: state => state.showArchived,
|
||||
}),
|
||||
...mapGetters([
|
||||
'canEdit',
|
||||
]),
|
||||
stacksByBoard() {
|
||||
return this.$store.getters.stacksByBoard(this.board.id)
|
||||
},
|
||||
dragHandleSelector() {
|
||||
return this.canEdit ? null : '.no-drag'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
id: 'fetchData',
|
||||
|
||||
@@ -33,7 +33,10 @@
|
||||
<TagsTabSidebar :board="board" />
|
||||
</AppSidebarTab>
|
||||
|
||||
<AppSidebarTab :order="2" name="Deleted items" icon="icon-delete">
|
||||
<AppSidebarTab v-if="canEdit"
|
||||
:order="2"
|
||||
name="Deleted items"
|
||||
icon="icon-delete">
|
||||
<DeletedTabSidebar :board="board" />
|
||||
</AppSidebarTab>
|
||||
|
||||
@@ -44,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import SharingTabSidebar from './SharingTabSidebar'
|
||||
import TagsTabSidebar from './TagsTabSidebar'
|
||||
import DeletedTabSidebar from './DeletedTabSidebar'
|
||||
@@ -73,6 +76,7 @@ export default {
|
||||
board: state => state.currentBoard,
|
||||
labels: state => state.labels,
|
||||
}),
|
||||
...mapGetters(['canEdit']),
|
||||
},
|
||||
methods: {
|
||||
closeSidebar() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<Multiselect
|
||||
v-if="canShare"
|
||||
v-model="addAcl"
|
||||
:placeholder="t('deck', 'Share board with a user, group or circle …')"
|
||||
:options="formatedSharees"
|
||||
@@ -17,6 +18,9 @@
|
||||
<Avatar :user="board.owner.uid" />
|
||||
<span class="has-tooltip username">
|
||||
{{ board.owner.displayname }}
|
||||
<span v-if="!isCurrentUser(board.owner.uid)" class="board-owner-label">
|
||||
{{ t('deck', 'Board owner') }}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li v-for="acl in board.acl" :key="acl.participant.uid">
|
||||
@@ -29,17 +33,17 @@
|
||||
<span v-if="acl.type===7">{{ t('deck', '(Circle)') }}</span>
|
||||
</span>
|
||||
|
||||
<ActionCheckbox :checked="acl.permissionEdit" @change="clickEditAcl(acl)">
|
||||
<ActionCheckbox v-if="!isCurrentUser(acl.participant.uid) && (canManage || (canEdit && canShare))" :checked="acl.permissionEdit" @change="clickEditAcl(acl)">
|
||||
{{ t('deck', 'Can edit') }}
|
||||
</ActionCheckbox>
|
||||
<Actions>
|
||||
<ActionCheckbox :checked="acl.permissionShare" @change="clickShareAcl(acl)">
|
||||
<Actions v-if="!isCurrentUser(acl.participant.uid)" :force-menu="true">
|
||||
<ActionCheckbox v-if="canManage || canShare" :checked="acl.permissionShare" @change="clickShareAcl(acl)">
|
||||
{{ t('deck', 'Can share') }}
|
||||
</ActionCheckbox>
|
||||
<ActionCheckbox :checked="acl.permissionManage" @change="clickManageAcl(acl)">
|
||||
<ActionCheckbox v-if="canManage" :checked="acl.permissionManage" @change="clickManageAcl(acl)">
|
||||
{{ t('deck', 'Can manage') }}
|
||||
</ActionCheckbox>
|
||||
<ActionButton icon="icon-delete" @click="clickDeleteAcl(acl)">
|
||||
<ActionButton v-if="canManage" icon="icon-delete" @click="clickDeleteAcl(acl)">
|
||||
{{ t('deck', 'Delete') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
@@ -61,6 +65,7 @@ import { ActionButton } from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { ActionCheckbox } from '@nextcloud/vue/dist/Components/ActionCheckbox'
|
||||
import { CollectionList } from 'nextcloud-vue-collections'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
export default {
|
||||
name: 'SharingTabSidebar',
|
||||
@@ -86,9 +91,15 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
sharees: 'sharees',
|
||||
}),
|
||||
...mapGetters([
|
||||
'sharees',
|
||||
'canEdit',
|
||||
'canManage',
|
||||
'canShare',
|
||||
]),
|
||||
isCurrentUser() {
|
||||
return (uid) => uid === getCurrentUser().uid
|
||||
},
|
||||
formatedSharees() {
|
||||
return this.unallocatedSharees.map(item => {
|
||||
|
||||
@@ -173,6 +184,9 @@ export default {
|
||||
padding: 12px 9px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.board-owner-label {
|
||||
opacity: .7;
|
||||
}
|
||||
.avatarLabel {
|
||||
padding: 6px
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
<div class="stack">
|
||||
<div class="stack--header">
|
||||
<transition name="fade" mode="out-in">
|
||||
<h3 v-if="!editing" @click="startEditing(stack)">
|
||||
<h3 v-if="!canManage">
|
||||
{{ stack.title }}
|
||||
</h3>
|
||||
<h3 v-else-if="!editing" @click="startEditing(stack)">
|
||||
{{ stack.title }}
|
||||
</h3>
|
||||
<form v-else @submit.prevent="finishedEdit(stack)">
|
||||
@@ -36,12 +39,12 @@
|
||||
value="">
|
||||
</form>
|
||||
</transition>
|
||||
<Actions :force-menu="true">
|
||||
<Actions v-if="canManage" :force-menu="true">
|
||||
<ActionButton icon="icon-delete" @click="deleteStack(stack)">
|
||||
{{ t('deck', 'Delete stack') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<Actions>
|
||||
<Actions v-if="canEdit">
|
||||
<ActionButton icon="icon-add" @click="showAddCard=true">
|
||||
{{ t('deck', 'Add card') }}
|
||||
</ActionButton>
|
||||
@@ -63,7 +66,11 @@
|
||||
value="">
|
||||
</form>
|
||||
|
||||
<Container :get-child-payload="payloadForCard(stack.id)" group-name="stack" @drop="($event) => onDropCard(stack.id, $event)">
|
||||
<Container :get-child-payload="payloadForCard(stack.id)"
|
||||
group-name="stack"
|
||||
:drag-handle-selector="dragHandleSelector"
|
||||
@should-accept-drop="canEdit"
|
||||
@drop="($event) => onDropCard(stack.id, $event)">
|
||||
<Draggable v-for="card in cardsByStack(stack.id)" :key="card.id">
|
||||
<CardItem v-if="card" :id="card.id" />
|
||||
</Draggable>
|
||||
@@ -73,6 +80,7 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { mapGetters } from 'vuex'
|
||||
import { Container, Draggable } from 'vue-smooth-dnd'
|
||||
import { Actions } from '@nextcloud/vue/dist/Components/Actions'
|
||||
import { ActionButton } from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
@@ -103,13 +111,19 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'canManage',
|
||||
'canEdit',
|
||||
]),
|
||||
cardsByStack() {
|
||||
return (id) => this.$store.getters.cardsByStack(id)
|
||||
},
|
||||
dragHandleSelector() {
|
||||
return this.canEdit ? null : '.no-drag'
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onDropCard(stackId, event) {
|
||||
const { addedIndex, removedIndex, payload } = event
|
||||
const card = Object.assign({}, payload)
|
||||
|
||||
@@ -21,8 +21,14 @@
|
||||
<div :style="{ backgroundColor: `#${label.color}`, color:textColor(label.color) }" class="label-title">
|
||||
<span>{{ label.title }}</span>
|
||||
</div>
|
||||
<button v-tooltip="t('deck', 'Edit')" class="icon-rename" @click="clickEdit(label)" />
|
||||
<button v-tooltip="t('deck', 'Delete')" class="icon-delete" @click="deleteLabel(label.id)" />
|
||||
<button v-if="canManage"
|
||||
v-tooltip="t('deck', 'Edit')"
|
||||
class="icon-rename"
|
||||
@click="clickEdit(label)" />
|
||||
<button v-if="canManage"
|
||||
v-tooltip="t('deck', 'Delete')"
|
||||
class="icon-delete"
|
||||
@click="deleteLabel(label.id)" />
|
||||
</template>
|
||||
</li>
|
||||
|
||||
@@ -43,7 +49,7 @@
|
||||
<ColorPicker :value="'#' + addLabelObj.color" @input="updateColor" />
|
||||
</template>
|
||||
</li>
|
||||
<button @click="clickShowAddLabel()">
|
||||
<button v-if="canManage" @click="clickShowAddLabel()">
|
||||
<span class="icon-add" />{{ t('deck', 'Add a new label') }}
|
||||
</button>
|
||||
</ul>
|
||||
@@ -75,6 +81,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
labels: 'currentBoardLabels',
|
||||
canManage: 'canManage',
|
||||
}),
|
||||
addLabelObjValidated() {
|
||||
if (this.addLabelObj.title === '') {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<div class="section-details">
|
||||
<Multiselect v-model="allLabels"
|
||||
:multiple="true"
|
||||
:disabled="!canEdit"
|
||||
:options="currentBoard.labels"
|
||||
:placeholder="t('deck', 'Assign a tag to this card…')"
|
||||
:taggable="true"
|
||||
@@ -61,6 +62,7 @@
|
||||
</div>
|
||||
<div class="section-details">
|
||||
<Multiselect v-model="assignedUsers"
|
||||
:disabled="!canEdit"
|
||||
:multiple="true"
|
||||
:options="assignableUsers"
|
||||
:placeholder="t('deck', 'Assign a user to this card…')"
|
||||
@@ -85,10 +87,11 @@
|
||||
:placeholder="t('deck', 'Set a due date')"
|
||||
type="datetime"
|
||||
lang="en"
|
||||
:disabled="!canEdit"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
confirm
|
||||
@change="setDue()" />
|
||||
<Actions>
|
||||
<Actions v-if="canEdit">
|
||||
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
|
||||
{{ t('deck', 'Remove due date') }}
|
||||
</ActionButton>
|
||||
@@ -104,6 +107,7 @@
|
||||
</div>
|
||||
|
||||
<h5>{{ t('deck', 'Description') }}</h5>
|
||||
<!-- FIXME: make sure the editor is disabled when canEdit is false -->
|
||||
<VueEasymde ref="markdownEditor" v-model="copiedCard.description" :configs="mdeConfig" />
|
||||
</AppSidebarTab>
|
||||
|
||||
@@ -127,7 +131,7 @@ import { Multiselect } from '@nextcloud/vue/dist/Components/Multiselect'
|
||||
import { AppSidebar } from '@nextcloud/vue/dist/Components/AppSidebar'
|
||||
import { AppSidebarTab } from '@nextcloud/vue/dist/Components/AppSidebarTab'
|
||||
import { DatetimePicker } from '@nextcloud/vue/dist/Components/DatetimePicker'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import VueEasymde from 'vue-easymde/dist/VueEasyMDE.common'
|
||||
import { Actions } from '@nextcloud/vue/dist/Components/Actions'
|
||||
import { ActionButton } from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
@@ -186,6 +190,7 @@ export default {
|
||||
currentBoard: state => state.currentBoard,
|
||||
assignableUsers: state => state.assignableUsers,
|
||||
}),
|
||||
...mapGetters(['canEdit']),
|
||||
currentCard() {
|
||||
return this.$store.getters.cardById(this.id)
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
class="card"
|
||||
@click.self="openCard">
|
||||
<div class="card-upper">
|
||||
<h3 v-if="showArchived">
|
||||
<h3 v-if="showArchived || !canEdit">
|
||||
{{ card.title }}
|
||||
</h3>
|
||||
<h3 v-else-if="!editing" @click.stop="startEditing(card)">
|
||||
@@ -47,7 +47,7 @@
|
||||
<input type="button" class="icon-confirm" @click="finishedEdit(card)">
|
||||
</form>
|
||||
|
||||
<Actions v-if="!editing" @click.stop.prevent>
|
||||
<Actions v-if="canEdit && !editing" @click.stop.prevent>
|
||||
<ActionButton v-if="showArchived === false" icon="icon-user" @click="assignCardToMe()">
|
||||
{{ t('deck', 'Assign to me') }}
|
||||
</ActionButton>
|
||||
@@ -103,8 +103,8 @@ import { Actions } from '@nextcloud/vue/dist/Components/Actions'
|
||||
import { ActionButton } from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { Multiselect } from '@nextcloud/vue/dist/Components/Multiselect'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import { mapState } from 'vuex'
|
||||
import axios from 'nextcloud-axios'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
import CardBadges from './CardBadges'
|
||||
import Color from '../../mixins/color'
|
||||
@@ -139,6 +139,9 @@ export default {
|
||||
showArchived: state => state.showArchived,
|
||||
currentBoard: state => state.currentBoard,
|
||||
}),
|
||||
...mapGetters([
|
||||
'canEdit',
|
||||
]),
|
||||
card() {
|
||||
return this.$store.getters.cardById(this.id)
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { mapGetters } from 'vuex'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import { Multiselect } from '@nextcloud/vue/dist/Components/Multiselect'
|
||||
|
||||
@@ -124,17 +124,20 @@ export default {
|
||||
|
||||
// do not show actions while the item is loading
|
||||
if (this.loading === false) {
|
||||
const canManage = this.board.permissions.PERMISSION_MANAGE
|
||||
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.editTitle = this.board.title
|
||||
this.editColor = '#' + this.board.color
|
||||
this.editing = true
|
||||
},
|
||||
icon: 'icon-rename',
|
||||
text: t('deck', 'Edit board'),
|
||||
})
|
||||
if (canManage) {
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.editTitle = this.board.title
|
||||
this.editColor = '#' + this.board.color
|
||||
this.editing = true
|
||||
},
|
||||
icon: 'icon-rename',
|
||||
text: t('deck', 'Edit board'),
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
action: async() => {
|
||||
@@ -154,46 +157,47 @@ export default {
|
||||
icon: 'icon-clone',
|
||||
text: t('deck', 'Clone board'),
|
||||
})
|
||||
if (canManage) {
|
||||
if (!this.board.archived) {
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.loading = true
|
||||
this.$store.dispatch('archiveBoard', this.board)
|
||||
},
|
||||
icon: 'icon-archive',
|
||||
text: t('deck', 'Archive board'),
|
||||
})
|
||||
} else {
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.loading = true
|
||||
this.$store.dispatch('unarchiveBoard', this.board)
|
||||
},
|
||||
icon: 'icon-archive',
|
||||
text: t('deck', 'Unarchive board'),
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.board.archived) {
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.loading = true
|
||||
this.$store.dispatch('archiveBoard', this.board)
|
||||
this.boardApi.deleteBoard(this.board)
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
this.deleted = true
|
||||
this.undoTimeoutHandle = setTimeout(() => {
|
||||
this.$store.dispatch('removeBoard', this.board)
|
||||
}, 7000)
|
||||
})
|
||||
},
|
||||
icon: 'icon-archive',
|
||||
text: t('deck', 'Archive board'),
|
||||
})
|
||||
} else {
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.loading = true
|
||||
this.$store.dispatch('unarchiveBoard', this.board)
|
||||
},
|
||||
icon: 'icon-archive',
|
||||
text: t('deck', 'Unarchive board'),
|
||||
icon: 'icon-delete',
|
||||
text: t('deck', 'Delete board'),
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
action: () => {
|
||||
this.hideMenu()
|
||||
this.loading = true
|
||||
this.boardApi.deleteBoard(this.board)
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
this.deleted = true
|
||||
this.undoTimeoutHandle = setTimeout(() => {
|
||||
this.$store.dispatch('removeBoard', this.board)
|
||||
}, 7000)
|
||||
})
|
||||
},
|
||||
icon: 'icon-delete',
|
||||
text: t('deck', 'Delete board'),
|
||||
})
|
||||
|
||||
actions.push({
|
||||
action: () => {
|
||||
const route = this.routeTo
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
/**
|
||||
* This class handles all the api communication with the Deck backend.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export class CardApi {
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
export class CommentApi {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export class StackApi {
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import 'url-search-params-polyfill'
|
||||
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import axios from 'nextcloud-axios'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { BoardApi } from './../services/BoardApi'
|
||||
import stack from './stack'
|
||||
import card from './card'
|
||||
@@ -96,6 +96,15 @@ export default new Vuex.Store({
|
||||
currentBoardLabels: state => {
|
||||
return state.currentBoard ? state.currentBoard.labels : []
|
||||
},
|
||||
canEdit: state => {
|
||||
return state.currentBoard ? state.currentBoard.permissions.PERMISSION_EDIT : false
|
||||
},
|
||||
canManage: state => {
|
||||
return state.currentBoard ? state.currentBoard.permissions.PERMISSION_MANAGE : false
|
||||
},
|
||||
canShare: state => {
|
||||
return state.currentBoard ? state.currentBoard.permissions.PERMISSION_SHARE : false
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
toggleShowArchived(state) {
|
||||
@@ -218,7 +227,7 @@ export default new Vuex.Store({
|
||||
updateAclFromCurrentBoard(state, acl) {
|
||||
for (const acl_ in state.currentBoard.acl) {
|
||||
if (state.currentBoard.acl[acl_].participant.uid === acl.participant.uid) {
|
||||
state.currentBoard.acl[acl_] = acl
|
||||
Vue.set(state.currentBoard.acl, acl_, acl)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -246,6 +255,12 @@ export default new Vuex.Store({
|
||||
commit('setAssignableUsers', board.users)
|
||||
},
|
||||
|
||||
async refreshBoard({ commit }, boardId) {
|
||||
const board = await apiClient.loadById(boardId)
|
||||
commit('setCurrentBoard', board)
|
||||
commit('setAssignableUsers', board.users)
|
||||
},
|
||||
|
||||
toggleShowArchived({ commit }) {
|
||||
commit('toggleShowArchived')
|
||||
},
|
||||
@@ -392,7 +407,7 @@ export default new Vuex.Store({
|
||||
apiClient.addAcl(newAcl)
|
||||
.then((returnAcl) => {
|
||||
commit('addAclToCurrentBoard', returnAcl)
|
||||
dispatch('loadBoardById', newAcl.boardId)
|
||||
dispatch('refreshBoard', newAcl.boardId)
|
||||
})
|
||||
},
|
||||
updateAclFromCurrentBoard({ commit }, acl) {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<div id="stack-add" ng-if="boardservice.canManage() && checkCanEdit()">
|
||||
<form class="ng-pristine ng-valid" ng-submit="createStack()">
|
||||
<label for="new-stack-input-<?php p($_['headerControlsId']); ?>" class="hidden-visually"><?php p($l->t('Add a new stack')); ?></label>
|
||||
<input type="text" class="no-close" placeholder="<?php p($l->t('Add a new stack')); ?>"
|
||||
ng-focus="status.addStack=true"
|
||||
ng-blur="status.addStack=false"
|
||||
ng-model="newStack.title" required
|
||||
id="new-stack-input-<?php p($_['headerControlsId']); ?>"
|
||||
maxlength="100" />
|
||||
<button class="button-inline icon icon-add" ng-style="{'opacity':'{{status.addStack ? 1: 0.5}}'}" type="submit" title="<?php p($l->t('Submit')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('Submit')); ?></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button ng-if="params.filter!='archive'" ng-click="switchFilter('archive')" data-toggle="tooltip" data-placement="bottom" title="<?php p($l->t('Show archived cards')); ?>">
|
||||
<i class="icon icon-archive" style="opacity:0.5;"></i>
|
||||
<span class="hidden-visually"><?php p($l->t('Show archived cards')); ?></span>
|
||||
</button>
|
||||
<button ng-if="params.filter=='archive'" ng-click="switchFilter('')" data-toggle="tooltip" data-placement="bottom" title="<?php p($l->t('Hide archived cards')); ?>">
|
||||
<i class="icon icon-archive"></i>
|
||||
<span class="hidden-visually"><?php p($l->t('Hide archived cards')); ?></span>
|
||||
</button>
|
||||
<button ng-click="toggleCompactMode()" data-toggle="tooltip" data-placement="bottom" title="<?php p($l->t('Toggle compact mode')); ?>">
|
||||
<i class="icon" ng-class="{ 'icon-toggle-compact-collapsed': compactMode, 'icon-toggle-compact-expanded': !compactMode }"></i>
|
||||
<span class="hidden-visually"><?php p($l->t('Toggle compact mode')); ?></span>
|
||||
</button>
|
||||
<button ui-sref="board.detail({ id: id, tab: 0})" data-toggle="tooltip" data-placement="bottom" title="<?php p($l->t('Show board details')); ?>">
|
||||
<i class="icon icon-settings"></i>
|
||||
<span class="hidden-visually"><?php p($l->t('Show board details')); ?></span>
|
||||
</button>
|
||||
@@ -1,187 +0,0 @@
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div id="emptycontent">
|
||||
<div class="icon-{{ statusservice.icon }}"></div>
|
||||
<h2>{{ statusservice.title }}</h2>
|
||||
<p>{{ statusservice.text }}</p></div>
|
||||
</div>
|
||||
|
||||
<div id="controls">
|
||||
<div class="crumb">
|
||||
<a href="#" class="icon-home" title="<?php p($l->t('All Boards')); ?>"></a>
|
||||
</div>
|
||||
<div class="crumb" ng-if="boardservice.getCurrent().archived">
|
||||
<a class="icon-archive"></a>
|
||||
<a ui-sref="list({ filter: 'archived' })"><?php p($l->t('Archived boards')); ?></a>
|
||||
</div>
|
||||
<div class="crumb title">
|
||||
<a class="bullet"><span class="board-bullet" ng-style="{'background-color':'#' + boardservice.getCurrent().color}"></span></a>
|
||||
<a ui-sref=".({filter: ''})">{{ boardservice.getCurrent().title }}</a>
|
||||
<a ui-sref=".detail({ tab: 0 })" title="<?php p($l->t('Share board')); ?>"><span class="icon icon-share"></span></a>
|
||||
</div>
|
||||
<div class="crumb title" ng-if="params.filter=='archive'">
|
||||
<a><span class="icon icon-archive"></span></a>
|
||||
<a><?php p($l->t('Archived cards')); ?></a>
|
||||
</div>
|
||||
|
||||
<div class="board-header-controls hidden">
|
||||
<?php print_unescaped($this->inc('part.board.headerControls', ['headerControlsId' => 'main'])); ?>
|
||||
</div>
|
||||
<div class="board-header-controls app-popover-menu-utils">
|
||||
<button class="icon-more button" title="<?php p($l->t('Actions')) ?>"></button>
|
||||
<div class="popovermenu hidden">
|
||||
<div id="popover-controls">
|
||||
<?php print_unescaped($this->inc('part.board.headerControls', ['headerControlsId' => 'popover'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="board" class="scroll-container" ng-click="sidebar.show=false" ui-sref="board" ng-class="{'card-selected': params.cardId}">
|
||||
<search on-search="search" class="ng-hide"></search>
|
||||
|
||||
<div id="innerBoard" data-ng-model="stacks" data-as-sortable="sortOptionsStack">
|
||||
<div class="stack" ng-repeat="s in stacks" data-as-sortable-item
|
||||
data-columnindex="{{$index}}" id="column{{$index}}"
|
||||
style="">
|
||||
<h3 data-as-sortable-item-handle>
|
||||
<span class="editable-inline"
|
||||
ng-show="!s.status.editStack"
|
||||
ng-click="s.status.editStack=true">{{ s.title }}</span>
|
||||
|
||||
<form ng-if="s.status.editStack" ng-submit="stackservice.update(s); s.status.editStack=false">
|
||||
<input type="text" placeholder="<?php p($l->t('Add a new stack')); ?>"
|
||||
ng-blur="stackservice.update(s); s.status.editStack=false" ng-model="s.title"
|
||||
autofocus-on-insert required maxlength="100" />
|
||||
</form>
|
||||
<button class="icon-delete button-inline stack-actions"
|
||||
ng-if="!s.status.editStack"
|
||||
ng-click="stackDelete(s)"></button>
|
||||
</h3>
|
||||
<ul data-as-sortable="sortOptions" is-disabled="!boardservice.canEdit() || filter==='archive'" data-ng-model="s.cards" class="card-list" ng-class="{emptyStack: !s.cards.length}">
|
||||
<li class="card as-sortable-item"
|
||||
ng-repeat="c in s.cards"
|
||||
data-as-sortable-item
|
||||
ng-click="$event.stopPropagation()"
|
||||
ui-sref="board.card({boardId: id, cardId: c.id})"
|
||||
ng-class="{'archived': cardservice.get(c.id).archived, 'has-labels': cardservice.get(c.id).labels.length>0, 'current': cardservice.get(c.id).id == params.cardId, 'overdue': cardservice.get(c.id).overdue == 3, 'now': cardservice.get(c.id).overdue == 2, 'next': cardservice.get(c.id).overdue == 1, 'has-tasks': getCheckboxes(cardservice.get(c.id).description)[1] > 0, 'has-tasks-completed': getCheckboxes(cardservice.get(c.id).description)[1] > 0 && getCheckboxes(cardservice.get(c.id).description)[1] == getCheckboxes(cardservice.get(c.id).description)[0] }"
|
||||
nv-file-drop="" uploader="uploader" options="{cardId: c.id}">
|
||||
<div class="drop-indicator" uploader="uploader" nv-file-over>
|
||||
<p><?php p($l->t('Drop your files here to upload it to the card')); ?></p>
|
||||
</div>
|
||||
<div data-as-sortable-item-handle>
|
||||
<div class="card-upper">
|
||||
<h4>
|
||||
<span class="editable-inline"
|
||||
ng-show="!c.status.editCard"
|
||||
ng-click="$event.stopPropagation(); startTitleEdit(c)">{{cardservice.get(c.id).title}}</span>
|
||||
<form ng-if="c.status.editCard" ng-submit="finishTitleEdit(c)">
|
||||
<input
|
||||
class="input-inline"
|
||||
type="text"
|
||||
ng-blur="finishTitleEdit(c)"
|
||||
ng-model="c.renameTitle"
|
||||
autofocus-on-insert
|
||||
required
|
||||
maxlength="100">
|
||||
</form>
|
||||
</h4>
|
||||
<ul class="labels compact-item" ng-if="!compactMode">
|
||||
<li ng-repeat="label in cardservice.get(c.id).labels"
|
||||
ng-style="labelStyle(label.color)" title="{{ label.title }}">
|
||||
<span>{{ label.title }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-controls compact-item" ng-if="!compactMode">
|
||||
<i class="icon icon-description" ng-if="cardservice.get(c.id).description" title="{{ cardservice.get(c.id).description }}"></i>
|
||||
<span class="due" ng-if="cardservice.get(c.id).duedate" ng-class="{'overdue': cardservice.get(c.id).overdue == 3, 'now': cardservice.get(c.id).overdue == 2, 'next': cardservice.get(c.id).overdue == 1 }" title="{{ cardservice.get(c.id).duedate }}">
|
||||
<i class="icon icon-badge"></i>
|
||||
<span data-timestamp="{{ cardservice.get(c.id).duedate | dateToTimestamp }}" class="live-relative-timestamp">{{ cardservice.get(c.id).duedate | relativeDateFilterString }}</span>
|
||||
</span>
|
||||
<div class="card-tasks" ng-if="getCheckboxes(cardservice.get(c.id).description)[1] > 0">
|
||||
<i class="icon icon-checkmark"></i>
|
||||
<span>{{ getCheckboxes(cardservice.get(c.id).description)[0] }}/{{ getCheckboxes(cardservice.get(c.id).description)[1] }}</span>
|
||||
</div>
|
||||
<div class="card-files" ng-if="attachmentCount(cardservice.get(c.id)) > 0">
|
||||
<i class="icon icon-files-dark"></i>
|
||||
<span>{{ attachmentCount(cardservice.get(c.id)) }}</span>
|
||||
</div>
|
||||
<div class="card-comments" ng-if="unreadCommentCount(cardservice.get(c.id)) > 0">
|
||||
<i class="icon icon-comment"></i>
|
||||
<span>{{ unreadCommentCount(cardservice.get(c.id)) }}</span>
|
||||
</div>
|
||||
<div class="card-assigned-users">
|
||||
<div class="assigned-user" ng-repeat="user in cardservice.get(c.id).assignedUsers | limitTo: 3">
|
||||
<avatar data-user="{{ user.participant.uid }}" data-displayname="{{ user.participant.displayname }}" data-tooltip></avatar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-popover-menu-utils" ng-if="!boardservice.isArchived()">
|
||||
<button class="button-inline card-options icon-more" ng-model="card" title="<?php p($l->t('Actions')) ?>"></button>
|
||||
<div class="popovermenu hidden">
|
||||
<ul>
|
||||
<li ng-if="!isCurrentUserAssigned(c)">
|
||||
<a class="menuitem action action-rename permanent"
|
||||
data-action="AssignToMe"
|
||||
ng-click="cardAssignToMe(c); $event.stopPropagation();"><span
|
||||
class="icon icon-user"></span><span><?php p($l->t('Assign card to me')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="isCurrentUserAssigned(c)">
|
||||
<a class="menuitem action action-rename permanent"
|
||||
data-action="UnassignFromMe"
|
||||
ng-click="cardUnassignFromMe(c); $event.stopPropagation();"><span
|
||||
class="icon icon-user"></span><span><?php p($l->t('Unassign card from me')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="params.filter!=='archive'">
|
||||
<a class="menuitem action action-rename permanent"
|
||||
data-action="Archive"
|
||||
ng-click="cardArchive(c); $event.stopPropagation();"><span
|
||||
class="icon icon-archive"></span><span><?php p($l->t('Archive card')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="params.filter==='archive'">
|
||||
<a class="menuitem action action-rename permanent"
|
||||
data-action="Unarchive"
|
||||
ng-click="cardUnarchive(c); $event.stopPropagation();"><span
|
||||
class="icon icon-archive"></span><span><?php p($l->t('Unarchive card')); ?></span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="menuitem action action-delete permanent"
|
||||
data-action="Delete"
|
||||
ng-click="cardDelete(c); $event.stopPropagation();"><span
|
||||
class="icon icon-delete"></span><span><?php p($l->t('Delete card')); ?></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- CREATE CARD //-->
|
||||
<div
|
||||
class="card create"
|
||||
ng-class="{emptyStack: !s.cards.length}"
|
||||
ng-style="{'border-color':'#{{ boardservice.getCurrent().color }}'}"
|
||||
ng-if="boardservice.canEdit() && checkCanEdit() && params.filter !== 'archive'">
|
||||
<form name="addCardForm{{ s.id }}" ng-submit="createCard(s.id, newCard.title)">
|
||||
<h4 ng-if="status.addCard[s.id]">
|
||||
<input type="text" autofocus-on-insert
|
||||
ng-model="newCard.title"
|
||||
ng-blur="status.addCard[s.id]=false"
|
||||
ng-style="{'border-color':'{{ boardservice.getCurrent().color | textColorFilter }}'}"
|
||||
maxlength="100"
|
||||
required placeholder="<?php p($l->t('Enter a card title')); ?>"/>
|
||||
</h4>
|
||||
</form>
|
||||
<div ng-if="!status.addCard[s.id]" ng-click="status.addCard[s.id]=true" title="<?php p($l->t('Add card')); ?>">
|
||||
<i class="icon icon-add"></i>
|
||||
<span class="hidden-visually"><?php p($l->t('Add card')); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,174 +0,0 @@
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div id="emptycontent">
|
||||
<div class="icon-{{ statusservice.icon }}"></div>
|
||||
<h2>{{ statusservice.title }}</h2>
|
||||
<p>{{ statusservice.text }}</p></div>
|
||||
</div>
|
||||
<div id="sidebar-header">
|
||||
<a class="icon-close" ui-sref="board" ng-click="sidebar.show=!sidebar.show" title="<?php p($l->t('Close')); ?>"> <?php
|
||||
?><span class="hidden-visually"><?php p($l->t('Close')); ?></span><?php
|
||||
?></a>
|
||||
<h3>{{ boardservice.getCurrent().title }}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="tabHeaders">
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Sharing')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Tags')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Deleted items')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==4)}" ui-sref="{tab: 4}" ng-if="isTimelineEnabled()"><a><?php p($l->t('Timeline')); ?></a></li>
|
||||
|
||||
</ul>
|
||||
<div class="tabsContainer">
|
||||
<div id="tabBoardShare" class="tab" ng-if="params.tab==0 || !params.tab">
|
||||
<ui-select ng-if="boardservice.canShare()" ng-model="status.addSharee" theme="select2"
|
||||
title="<?php p($l->t('Select users or groups to share with')); ?>"
|
||||
placeholder="<?php p($l->t('Select users or groups to share with')); ?>"
|
||||
on-select="aclAdd(status.addSharee)" search-enabled="true">
|
||||
<ui-select-match placeholder="<?php p($l->t('Select users or groups to share with')); ?>">
|
||||
<span><i class="icon icon-{{aclTypeString($item)}}"></i> {{ $item.participant.displayname }}</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices refresh="searchForUser($select.search)" refresh-delay="0" repeat="sharee in boardservice.sharees">
|
||||
<div class="avatardiv" avatar data-user="{{ sharee.participant.uid }}" data-displayname="{{ sharee.participant.displayname }}" ng-if="sharee.type==OC.Share.SHARE_TYPE_USER"></div>
|
||||
<div class="avatardiv" ng-if="sharee.type==OC.Share.SHARE_TYPE_GROUP"><i class="icon icon-{{aclTypeString(sharee)}}" ></i></div>
|
||||
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_GROUP">
|
||||
{{ sharee.participant.displayname }} (<?php p($l->t('Group')); ?>)
|
||||
</span>
|
||||
<div class="avatardiv circles" ng-if="sharee.type==OC.Share.SHARE_TYPE_CIRCLE"><i class="icon icon-circles icon-white"></i></div>
|
||||
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_CIRCLE">
|
||||
{{ sharee.participant.displayname }} (<?php p($l->t('Circle')); ?>)
|
||||
</span>
|
||||
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_USER">
|
||||
{{ sharee.participant.displayname }}
|
||||
</span>
|
||||
</ui-select-choices>
|
||||
<ui-select-no-choice>
|
||||
<?php p($l->t('No matching user or group found.')); ?>
|
||||
</ui-select-no-choice>
|
||||
</ui-select>
|
||||
|
||||
<ul id="shareWithList" class="shareWithList">
|
||||
<li>
|
||||
<span class="icon-loading-small" style="display:none;"></span>
|
||||
<div class="avatardiv" avatar data-user="{{ boardservice.getCurrent().owner.uid }}" data-displayname="{{ boardservice.getCurrent().owner.displayname }}" ng-if="boardservice.id"></div>
|
||||
<span class="has-tooltip username">
|
||||
{{ boardservice.getCurrent().owner.displayname }}
|
||||
</span>
|
||||
</li>
|
||||
<li ng-repeat="acl in boardservice.getCurrent().acl track by acl.participant.primaryKey">
|
||||
<span class="icon-loading-small" style="display:none;" title="<?php p($l->t('Loading')); ?>"></span>
|
||||
<div class="avatardiv" avatar data-contactsmenu="true" data-user="{{ acl.participant.uid }}" data-displayname="{{ acl.participant.displayname }}" ng-if="acl.type==OC.Share.SHARE_TYPE_USER"></div>
|
||||
<div class="avatardiv" ng-if="acl.type!=OC.Share.SHARE_TYPE_USER"><i class="icon icon-{{aclTypeString(acl)}}" ></i></div>
|
||||
|
||||
<span class="has-tooltip username" ng-if="acl.type==OC.Share.SHARE_TYPE_USER">
|
||||
{{ acl.participant.displayname }}
|
||||
</span>
|
||||
<span class="has-tooltip username" ng-if="acl.type==OC.Share.SHARE_TYPE_GROUP">
|
||||
{{ acl.participant.displayname }} (<?php p($l->t('Group')); ?>)
|
||||
</span>
|
||||
<span class="has-tooltip username" ng-if="acl.type==OC.Share.SHARE_TYPE_CIRCLE">
|
||||
{{ acl.participant.displayname }} (<?php p($l->t('Circle')); ?> {{ acl.participant.typeString }})
|
||||
<div>{{ acl.participant.circleOwner.display_name }}</div>
|
||||
</span>
|
||||
|
||||
<span class="sharingOptionsGroup">
|
||||
<span class="shareOption"ng-if="boardservice.canManage()">
|
||||
<input type="checkbox" class="permissions checkbox" id="checkbox-permission-{{ acl.id }}-edit" ng-model="acl.permissionEdit" ng-change="aclUpdate(acl)" />
|
||||
<label for="checkbox-permission-{{ acl.id }}-edit"><?php p($l->t('Edit')); ?></label>
|
||||
</span>
|
||||
<span class="shareOption" ng-if="boardservice.canManage()">
|
||||
<input type="checkbox" class="permissions checkbox" id="checkbox-permission-{{ acl.id }}-share" ng-model="acl.permissionShare" ng-change="aclUpdate(acl)" />
|
||||
<label for="checkbox-permission-{{ acl.id }}-share"><?php p($l->t('Share')); ?></label>
|
||||
</span>
|
||||
<span class="shareOption"ng-if="boardservice.canManage()">
|
||||
<input type="checkbox" class="permissions checkbox" id="checkbox-permission-{{ acl.id }}-manage" ng-model="acl.permissionManage" ng-change="aclUpdate(acl)" />
|
||||
<label for="checkbox-permission-{{ acl.id }}-manage"><?php p($l->t('Manage')); ?></label>
|
||||
</span>
|
||||
</span>
|
||||
<a ng-if="boardservice.canManage()" ng-click="aclDelete(acl)"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span class="hidden-visually"><?php p($l->t('Discard share')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="!boardservice.canShare()">
|
||||
<?php p($l->t('Sharing has been disabled for your account.')); ?>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="collaborationResources"></div>
|
||||
|
||||
</div>
|
||||
<div id="board-detail-labels" class="tab commentsTabView" ng-if="params.tab==1">
|
||||
|
||||
<ul class="labels">
|
||||
<li ng-repeat="label in boardservice.getCurrent().labels">
|
||||
<span class="label-title" ng-style="{'background-color':'#{{label.color}}','color':'{{ label.color|textColorFilter }}'}" ng-if="!label.edit">
|
||||
<span ng-if="label.title">{{ label.title }}</span><i ng-if="!label.title"><br /></i>
|
||||
</span>
|
||||
<div class="label-edit" ng-if="label.edit">
|
||||
<div ng-style="{'background-color':'#{{label.color}}','color':'{{ textColor(label.color) }}','width':'100%'}">
|
||||
<form ng-submit="labelUpdate(label)">
|
||||
<input type="text" ng-model="label.title" class="input-inline" ng-style="{'background-color':'#{{label.color}}','color':'{{ label.color|textColorFilter }}'}" autofocus-on-insert maxlength="100"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in defaultColors" ng-style="{'background-color':'#{{ c }}'}" ng-click="label=setColor(label,c);" ng-class="{'selected': (c == label.color) }"><br /></div>
|
||||
<label class="colorselect-label{{ label.color | iconWhiteFilter }} color" ng-style="getCustomBackground(label.hashedColor)" ng-init="label.hashedColor='#' + label.color">
|
||||
<input class="color" type="color" ng-model="label.hashedColor" value="#{{label.color}}" ng-change="label=setHashedColor(label)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<a ng-if="boardservice.canManage() && label.edit" ng-click="labelUpdate(label)" class="icon" title="<?php p($l->t('Update tag')); ?>"><i class="icon icon-checkmark" ></i><span class="hidden-visually"><?php p($l->t('Update tag')); ?></span></a>
|
||||
<a ng-if="boardservice.canManage() && !label.edit" ng-click="labelUpdateBefore(label); label.edit=true" class="icon" title="<?php p($l->t('Edit tag')); ?>"><i class="icon icon-rename"></i><span class="hidden-visually"><?php p($l->t('Edit tag')); ?></span></a>
|
||||
<a ng-if="boardservice.canManage()" ng-click="labelDelete(label)" class="icon" title="<?php p($l->t('Delete tag')); ?>"><i class="icon icon-delete" ></i><span class="hidden-visually"><?php p($l->t('Delete tag')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="status.createLabel">
|
||||
<div class="label-edit">
|
||||
<div ng-style="{'background-color':'#{{newLabel.color}}','color':'{{ textColor(newLabel.color) }}','width':'100%'}">
|
||||
<form ng-submit="labelCreate(newLabel)">
|
||||
<input type="text" class="input-inline" ng-model="newLabel.title" ng-style="{'color':'{{ newLabel.color|textColorFilter }}'};" autofocus-on-insert maxlength="100" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in defaultColors" ng-style="{'background-color':'#{{ c }}'}" ng-click="newLabel=setColor(newLabel,c)" ng-class="{'selected': (c == newLabel.color), 'dark': (newBoard.color | textColorFilter) === '#ffffff' }"><br /></div>
|
||||
<label class="colorselect-label{{ newLabel.color | iconWhiteFilter }} color" ng-style="getCustomBackground(newLabel.hashedColor)" ng-init="newLabel.hashedColor='#' + newLabel.color">
|
||||
<input class="color" type="color" ng-model="newLabel.hashedColor" value="#{{newLabel.color}}" ng-change="newLabel=setHashedColor(newLabel)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<a ng-click="labelCreate(newLabel)" class="icon" title="<?php p($l->t('Create')); ?>"><i class="icon icon-checkmark" ></i><span class="hidden-visually"><?php p($l->t('Create')); ?></span></a>
|
||||
<a ng-click="status.createLabel=false" class="icon" title="<?php p($l->t('Close')); ?>"><i class="icon icon-close" ></i><span class="hidden-visually"><?php p($l->t('Close')); ?></span></a>
|
||||
</li>
|
||||
<li ng-if="boardservice.canManage() && !status.createLabel" class="label-create">
|
||||
<a ng-click="status.createLabel=true" class="button"><span class="icon icon-add"></span><br /><span><?php p($l->t('Create a new tag')); ?></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="board-detail-deleted-stacks" class="tab deletedStacksTabView" ng-if="params.tab==2">
|
||||
<h3><?php p($l->t('Deleted stacks')); ?></h3>
|
||||
<ul class='board-detail__deleted-list'>
|
||||
<li class='board-detail__deleted-list__item' ng-repeat="deletedStack in stackservice.deleted">
|
||||
<span class="icon icon-deck"></span>
|
||||
<span class="title">{{deletedStack.title}}</span>
|
||||
<span class="live-relative-timestamp" data-timestamp="{{ deletedStack.deletedAt*1000 }}">{{deletedStack.deletedAt | relativeDateFilter }}</span>
|
||||
<a ng-click="stackUndoDelete(deletedStack)"><span class="icon icon-history"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3><?php p($l->t('Deleted cards')); ?></h3>
|
||||
|
||||
<ul class='board-detail__deleted-list'>
|
||||
<li class='board-detail__deleted-list__item' ng-repeat="deletedCard in cardservice.deleted">
|
||||
<span class="icon icon-deck"></span>
|
||||
<span class="title">{{deletedCard.title}} ({{stackservice.tryAllThenDeleted(deletedCard.stackId).title}})</span>
|
||||
<span class="live-relative-timestamp" data-timestamp="{{ deletedCard.deletedAt*1000 }}">{{deletedCard.deletedAt | relativeDateFilter }}</span>
|
||||
<a ng-click="cardOrCardAndStackUndoDelete(deletedCard)">
|
||||
<span class="icon icon-history"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="board-detail-activity" class="tab activityTabView" ng-if="isTimelineEnabled() && params.tab==4">
|
||||
<activity-component ng-if="boardservice.getCurrent()" type="deck_board" element="boardservice.getCurrent()"></activity-component>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -1,134 +0,0 @@
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div id="emptycontent">
|
||||
<div class="icon-{{ statusservice.icon }}" title="<?php p($l->t('Status')); ?>"><span class="hidden-visually"><?php p($l->t('Status')); ?></span></div>
|
||||
<h2>{{ statusservice.title }}</h2>
|
||||
<p>{{ statusservice.text }}</p></div>
|
||||
</div>
|
||||
<div id="controls">
|
||||
<div class="breadcrumb">
|
||||
<div class="crumb svg last">
|
||||
<a href="#" class="icon-home" title="<?php p($l->t('All Boards')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('All Boards')); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="emptycontent" ng-if="boardservice.sorted.length == 0 && status.filter == 'archived'">
|
||||
<div class="icon-archive"></div>
|
||||
<h2><?php p($l->t('No archived boards to display')); ?></h2>
|
||||
</div>
|
||||
<div id="emptycontent" ng-if="boardservice.sorted.length == 0 && status.filter == 'shared'">
|
||||
<div class="icon-share"></div>
|
||||
<h2> <?php p($l->t('No shared boards to display')); ?> </h2>
|
||||
</div>
|
||||
<div id="boardlist" ng-if="boardservice.sorted.length > 0 || !status.filter">
|
||||
<table width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="cell-board-bullet"></td>
|
||||
<td class="cell-board-title" width="90%"><?php p($l->t('Title')); ?></td>
|
||||
<td class="cell-board-members"><?php p($l->t('Members')); ?></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="b in boardservice.sorted track by b.id" ng-class="{deleted: b.deletedAt > 0}">
|
||||
<td ng-click="gotoBoard(b)">
|
||||
<div class="board-bullet" ng-style="{'background-color':'#'+b.color}"> </div>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-click="gotoBoard(b)" ng-show="!b.status.edit">{{ b.title }}</div>
|
||||
<div class="app-navigation-entry-edit" ng-show="b.status.edit">
|
||||
<form ng-disabled="isAddingList" class="ng-pristine ng-valid" ng-submit="boardUpdate(b)">
|
||||
<input class="edit ng-valid ng-empty" type="text" autofocus-on-insert ng-model="b.title" maxlength="100" ng-model-options="{ debounce: 250 }">
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="b=setColor(b,c)" ng-class="{'selected': (c == b.color) }"></div>
|
||||
<label class="colorselect-label{{ b.color | iconWhiteFilter }} color" ng-style="getCustomBackground(b.hashedColor)" ng-init="b.hashedColor='#' + b.color">
|
||||
<input class="color" type="color" ng-model="b.hashedColor" value="#{{b.color}}" ng-change="b=setHashedColor(b)"/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="assigned-users">
|
||||
<avatar data-contactsmenu data-tooltip data-user="{{ b.owner.uid }}" data-displayname="{{ b.owner.displayname }}"></avatar>
|
||||
<avatar data-contactsmenu data-tooltip data-user="{{ acl.participant.uid }}" data-displayname="{{ acl.participant.displayname }}" ng-repeat="acl in b.acl | limitTo: 7 track by acl.id"></avatar>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="app-popover-menu-utils" ng-if="b.deletedAt == 0" ng-show="!b.status.edit">
|
||||
<button class="icon icon-more button-inline" title="<?php p($l->t('Actions')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('More actions')); ?></span>
|
||||
</button>
|
||||
<div class="popovermenu bubble hidden">
|
||||
<ul>
|
||||
<li ng-click="boardUpdateBegin(b); b.status.edit = true">
|
||||
<a class="menuitem"><span class="icon-rename"></span> <?php p($l->t('Edit board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="boardservice.canManage(b) && !b.archived" ng-click="boardArchive(b)">
|
||||
<a class="menuitem"><span class="icon-archive"></span> <?php p($l->t('Archive board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="boardservice.canManage(b) && b.archived" ng-click="boardUnarchive(b)">
|
||||
<a class="menuitem"><span class="icon-archive"></span> <?php p($l->t('Unarchive board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="boardservice.canManage(b)" ng-click="boardDelete(b)">
|
||||
<a class="menuitem"><span class="icon-delete"></span> <?php p($l->t('Delete board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ui-sref="board.detail({boardId: b.id})">
|
||||
<a class="menuitem"><span class="icon-settings-dark"></span> <?php p($l->t('Show board details')); ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="board-edit-controls" ng-show="b.status.edit">
|
||||
<span class="icon icon-checkmark" ng-click="boardUpdate(b)" title="<?php p($l->t('Update board')); ?>"><span class="hidden-visually"><?php p($l->t('Update board')); ?></span></span>
|
||||
<span class="icon icon-close" ng-click="boardUpdateReset(b)" title="<?php p($l->t('Reset board')); ?>"><span class="hidden-visually"><?php p($l->t('Reset board')); ?></span></span>
|
||||
</div>
|
||||
<div class="app-popover-menu-utils" ng-if="b.deletedAt > 0">
|
||||
<button class="icon icon-history button-inline" ng-click="boardDeleteUndo(b)" title="<?php p($l->t('Undo board deletion - Otherwise the board will be deleted during the next cronjob run.')); ?>"><span class="hidden-visually"><?php p($l->t('Undo board deletion - Otherwise the board will be deleted during the next cronjob run.')); ?></span></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="canCreate && status.filter === '' && !status.addBoard" ng-click="status.addBoard=!status.addBoard" class="board-create">
|
||||
<td><span class="icon icon-add"></span></td>
|
||||
<td colspan="3">
|
||||
<a ng-click="status.addBoard=!status.addBoard"
|
||||
ng-show="!status.addBoard">
|
||||
<?php p($l->t('Create new board')); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-if="status.filter === '' && status.addBoard">
|
||||
<td><span class="icon icon-add"></span></td>
|
||||
<td>
|
||||
<form ng-disabled="isAddingList"
|
||||
class="ng-pristine ng-valid" ng-submit="boardCreate()">
|
||||
<input class="edit ng-valid ng-empty"
|
||||
type="text" placeholder="<?php p($l->t('New board title')); ?>"
|
||||
autofocus-on-insert ng-model="newBoard.title" maxlength="100" ng-model-options="{ debounce: 250 }">
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="selectColor(c);b=setColor(b,c);"ng-class="{'selected': (c == newBoard.color), 'dark': (newBoard.color | textColorFilter) === '#ffffff' }"></div>
|
||||
<label class="colorselect-label{{ newBoard.color | iconWhiteFilter }} color" ng-style="getCustomBackground(newBoard.hashedColor)" ng-init="newBoard.hashedColor='#' + newBoard.color">
|
||||
<input class="color" type="color" ng-model="newBoard.hashedColor" value="#{{newBoard.color}}" ng-change="newBoard=setHashedColor(newBoard)"/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="board-edit-controls">
|
||||
<span class="icon icon-checkmark" ng-click="boardCreate()" title="<?php p($l->t('Create board')); ?>"><span class="hidden-visually"><?php p($l->t('Create board')); ?></span></span>
|
||||
<span class="icon icon-close" ng-click="status.addBoard=!status.addBoard" title="<?php p($l->t('Close')); ?>"><span class="hidden-visually"><?php p($l->t('Close')); ?></span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,47 +0,0 @@
|
||||
<div id="commentsTabView">
|
||||
<div class="newCommentRow comment" data-id="" ng-show="$ctrl.type === 'deck_card'">
|
||||
<div class="authorRow">
|
||||
<div class="avatardiv" avatar ng-attr-user="{{ $ctrl.currentUser.uid }}" ng-attr-displayname="{{ $ctrl.currentUser.displayName }}" ng-attr-size="24"></div>
|
||||
<div class="author currentUser">{{ $ctrl.currentUser.displayName }}</div>
|
||||
</div>
|
||||
<form class="newCommentForm" ng-submit="$ctrl.postComment()">
|
||||
<div ng-contenteditable contenteditable="true" class="message" ng-submit="$ctrl.postComment()" data-placeholder="{{ newCommentString }}" ng-model="$ctrl.$scope.newComment" ng-disabled="$ctrl.status.commentCreateLoading"></div>
|
||||
<input class="submit icon-confirm has-tooltip" type="submit"
|
||||
value="" title="" data-original-title="Post" ng-if="!$ctrl.status.commentCreateLoading">
|
||||
<div class="submitLoading icon-loading-small" ng-if="$ctrl.status.commentCreateLoading"></div>
|
||||
</form>
|
||||
</div>
|
||||
<ul class="activities" infinite-scroll="$ctrl.page()" infinite-scroll-container="'#app-sidebar'" infinite-scroll-disabled="$ctrl.activityservice.running" infinite-scroll-immediate-check="false">
|
||||
<li ng-if="$ctrl.loadingNewer()"><div class="icon-loading-small"></div></li>
|
||||
<li class="activity box" ng-repeat="activity in $ctrl.getActivityStream() track by $index">
|
||||
<div class="activity-icon">
|
||||
<img ng-if="!activity.commentModel" src="{{activity.icon}}" alt="">
|
||||
<div ng-if="activity.commentModel" avatar ng-attr-contactsmenu="true" ng-attr-size="24" ng-attr-user="{{ activity.commentModel.get('actorId') }}" ng-attr-displayname="{{ activity.actorDisplayName }}"></div>
|
||||
</div>
|
||||
<span class="activitytime has-tooltip live-relative-timestamp"
|
||||
data-timestamp="{{ activity.timestamp }}">{{ activity.timestamp/1000 | relativeDateFilter }}</span>
|
||||
<div class="activitysubject" ng-if="!activity.commentModel" bind-html-compile="$ctrl.parseMessage(activity)"></div>
|
||||
<div class="activitysubject commentAuthor" ng-if="activity.commentModel">
|
||||
{{ activity.subject_rich[1].user.name }}
|
||||
<div class="app-popover-menu-utils">
|
||||
<button class="button-inline icon-more ng-pristine ng-valid ng-empty ng-touched" aria-label="Actions"></button>
|
||||
<div class="popovermenu hidden menu-left">
|
||||
<ul>
|
||||
<li><a ng-click="$ctrl.updateComment(activity)" class="menuitem action edit permanent" data-action="edit"><span class="icon icon-rename"></span><span>Edit comment</span></a></li>
|
||||
<li><a ng-click="$ctrl.deleteComment(activity)" class="menuitem action delete permanent" data-action="delete"><span class="icon icon-delete"></span><span>Delete comment</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activitymessage" ng-if="!activity.commentModel" ng-bind-html="activity.message"></div>
|
||||
<div class="activitymessage" ng-if="activity.commentModel && !activity.commentEdit" bind-html-compile="$ctrl.formatMessage(activity)"></div>
|
||||
<form class="newCommentForm" ng-show="activity.commentEdit">
|
||||
<div ng-contenteditable contenteditable="true" class="message" ng-model="activity.commentEdit"></div>
|
||||
<input class="submit icon-confirm has-tooltip" type="submit"
|
||||
value="" title="" data-original-title="Post" ng-click="$ctrl.editComment(activity)">
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li ng-if="$ctrl.loading"><div class="icon-loading-small"></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,55 +0,0 @@
|
||||
<div ng-class="{'attachment-list-wrapper': $ctrl.isFileSelector}">
|
||||
<div class="attachment-list" ng-class="{selector: $ctrl.isFileSelector}">
|
||||
<h3 class="attachment-selector" ng-if="$ctrl.isFileSelector"><?php p($l->t('Select an attachment')); ?> <a class="icon-close" ng-click="$ctrl.abort()"></a></h3>
|
||||
<ul>
|
||||
<li class="attachment"
|
||||
ng-repeat="attachment in $ctrl.fileservice.getProgressItemsForCard($ctrl.cardservice.getCurrent().id)">
|
||||
<a class="fileicon icon-file"></a>
|
||||
<div class="details">
|
||||
<div class="filename">
|
||||
<span class="basename">{{ attachment.file.name }}</span>
|
||||
</div>
|
||||
<progress ng-attr-value="{{ attachment.progress }}" max="100"></progress>
|
||||
</div>
|
||||
<button class="icon icon-close button-inline" ng-click="attachment.cancel()">
|
||||
<span class="hidden-visually"><?php p($l->t('Cancel upload')); ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="attachment"
|
||||
ng-repeat="attachment in $ctrl.cardservice.getCurrent().attachments | filter: {type: 'deck_file'} | orderBy: ['deletedAt', '-lastModified']"
|
||||
ng-class="{deleted: attachment.deletedAt > 0, selector: $ctrl.isFileSelector}"
|
||||
ng-if="!$ctrl.isFileSelector || attachment.deletedAt == 0">
|
||||
<a class="fileicon" ng-style="$ctrl.mimetypeForAttachment(attachment)" ng-href="{{ attachmentUrl(attachment) }}"></a>
|
||||
<div class="details">
|
||||
<a ng-href="{{ $ctrl.attachmentUrl(attachment) }}" target="_blank">
|
||||
<div class="filename">
|
||||
<span class="basename">{{ attachment.extendedData.info.filename}}</span>
|
||||
<span class="extension">.{{ attachment.extendedData.info.extension}}</span>
|
||||
</div>
|
||||
<span class="filesize">{{ attachment.extendedData.filesize | bytes }}</span>
|
||||
<span class="filedate">{{ attachment.lastModified|relativeDateFilter }}</span>
|
||||
<span class="filedate"><?php p($l->t('by')); ?> {{ attachment.createdBy }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="icon icon-history button-inline" ng-click="$ctrl.cardservice.attachmentRemoveUndo(attachment)" ng-if="!$ctrl.isFileSelector && attachment.deletedAt > 0" title="<?php p($l->t('Undo file deletion - Otherwise the file will be deleted during the next cronjob run.')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('Undo file deletion')); ?></span>
|
||||
</button>
|
||||
<button class="icon icon-confirm button-inline" ng-click="$ctrl.select(attachment)" ng-if="$ctrl.isFileSelector">
|
||||
<span class="hidden-visually"><?php p($l->t('Insert the file into the description')); ?></span>
|
||||
</button>
|
||||
<div class="app-popover-menu-utils" ng-if="!$ctrl.isFileSelector && attachment.deletedAt == 0">
|
||||
<button class="button-inline icon icon-more" title="<?php p($l->t('Actions')) ?>"></button>
|
||||
<div class="popovermenu hidden">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="menuitem action action-delete"
|
||||
ng-click="$ctrl.cardservice.attachmentRemove(attachment); $event.stopPropagation();"><span
|
||||
class="icon icon-delete"></span><span><?php p($l->t('Delete attachment')); ?></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,139 +0,0 @@
|
||||
<div nv-file-drop="" uploader="fileservice.uploader" class="drop-zone" options="{cardId: cardservice.getCurrent().id}">
|
||||
<div class="drop-indicator" nv-file-over uploader="fileservice.uploader">
|
||||
<p><?php p($l->t('Drop your files here to upload it to the card')); ?></p>
|
||||
</div>
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div id="emptycontent">
|
||||
<div class="icon-{{ statusservice.icon }}" title="<?php p($l->t('Status')); ?>"><span class="hidden-visually"><?php p($l->t('Status')); ?></span></div>
|
||||
<h2>{{ statusservice.title }}</h2>
|
||||
<p>{{ statusservice.text }}</p></div>
|
||||
</div>
|
||||
{{card=cardservice.getCurrent();""}}
|
||||
<div id="sidebar-header">
|
||||
<a class="icon-close" ui-sref="board" ng-click="sidebar.show=!sidebar.show"> </a>
|
||||
<h3>
|
||||
<!-- TODO: change to textarea elastic //-->
|
||||
<form ng-submit="cardRename(cardservice.getCurrent())">
|
||||
<input class="input-inline" type="text" ng-if="status.cardRename"
|
||||
ng-model="status.renameTitle"
|
||||
ng-blur="cardRename(cardservice.getCurrent())"
|
||||
autofocus-on-insert required maxlength="100">
|
||||
</form>
|
||||
<div ng-click="cardRenameShow()" ng-if="!status.cardRename">
|
||||
{{ cardservice.getCurrent().title }}
|
||||
</div>
|
||||
</h3>
|
||||
<div id="card-dates">
|
||||
<?php p($l->t('Modified:')); ?> <span class="live-relative-timestamp" data-timestamp="{{cardservice.getCurrent().lastModified*1000}}">{{ cardservice.getCurrent().lastModified|relativeDateFilter }}</span>
|
||||
<?php p($l->t('Created:')); ?> <span class="live-relative-timestamp" data-timestamp="{{cardservice.getCurrent().createdAt*1000}}">{{ cardservice.getCurrent().createdAt|relativeDateFilter }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="card-meta" class="card-block">
|
||||
|
||||
<div class="section-wrapper" ng-if="!(boardservice.isArchived() || card.archived) && card.labels">
|
||||
<div class="section-label icon-tag" data-toggle="tooltip" data-placement="right" title="<?php p($l->t('Tags')); ?>"><span class="hidden-visually"><?php p($l->t('Tags')); ?></span></div>
|
||||
<div class="section-details">
|
||||
<ui-select multiple tagging="" ng-model="card.labels" theme="select2"
|
||||
ng-disabled="boardservice.isArchived() || card.archived"
|
||||
title="<?php p($l->t('Choose a tag')); ?>"
|
||||
placeholder="<?php p($l->t('Add a tag')); ?>"
|
||||
on-select="labelAssign($item, $model)"
|
||||
on-remove="labelRemove($item, $model)" ng-disabled="!boardservice.canEdit() || archived">
|
||||
<ui-select-match placeholder="<?php p($l->t('Select tags')); ?>">
|
||||
<span class="select-label" ng-style="labelStyle($item.color)">{{$item.title}} </span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices
|
||||
repeat="label in boardservice.getCurrent().labels | filter:$select.search track by label.id">
|
||||
<span class="choose-label" ng-style="labelStyle(label.color)">{{label.title}}</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-wrapper card-details-assign-users">
|
||||
<div class="section-label icon-user" data-toggle="tooltip" data-placement="right" title="<?php p($l->t('Assign users')); ?>" ng-click="toggleAssignUser()"><span class="hidden-visually"><?php p($l->t('Assign users')); ?></span></div>
|
||||
<div class="section-details" ng-if="cardservice.getCurrent()">
|
||||
<ui-select id="assignUserSelect" class="card-details-assign-user" ng-model="status.assignedUser" ng-show="status.showAssignUser" uis-open-close="assingUserOpenClose(isOpen)"
|
||||
theme="select2"
|
||||
title="<?php p($l->t('Choose a user to assign')); ?>" placeholder="<?php p($l->t('Choose a user to assign')); ?>"
|
||||
on-select="addAssignedUser($item)">
|
||||
<ui-select-match placeholder="<?php p($l->t('Assign this card to a user')); ?>">
|
||||
<span><i class="icon icon-{{$item.type}}"></i> {{ $item.participant.displayname }}</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="acl in boardservice.getUsers() | filter: $select.search | withoutAssignedUsers: cardservice.getCurrent().assignedUsers track by acl.uid">
|
||||
<div class="avatardiv" avatar ng-attr-user="{{ acl.uid }}" ng-attr-displayname="{{ acl.displayname }}" ng-if="boardservice.id"></div><span>{{ acl.displayname }}</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<div class="card-details-assign-users-list">
|
||||
<div class="assigned-user" ng-repeat="user in cardservice.getCurrent().assignedUsers track by user.participant.uid">
|
||||
<avatar ng-attr-contactsmenu ng-attr-tooltip ng-attr-user="{{ user.participant.uid }}" ng-attr-displayname="{{ user.participant.displayname }}" contactsmenudelete ></avatar>
|
||||
</div>
|
||||
<a class="icon-add icon-inline" ng-click="toggleAssignUser()"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section-wrapper">
|
||||
<div class="section-label icon-calendar-dark" data-placement="right" title="<?php p($l->t('Due date')); ?>"><span class="hidden-visually"><?php p($l->t('Due date')); ?></span></div>
|
||||
<div class="section-details duedate">
|
||||
<input class="datepicker-input medium focus" type="text" placeholder="<?php p($l->t('Click to set')); ?>" value="{{ cardservice.getCurrent().duedate | parseDate }}" datepicker="due" ng-disabled="(boardservice.isArchived() || card.archived)" />
|
||||
<input class="timepicker-input medium focus" type="text" placeholder="00:00" ng-disabled="!cardservice.getCurrent().duedate || (boardservice.isArchived() || card.archived)" value="{{ cardservice.getCurrent().duedate | parseTime }}" timepicker="due" />
|
||||
<button class="icon icon-delete button-inline" title="<?php p($l->t('Remove due date')); ?>" ng-if="cardservice.getCurrent().duedate" ng-click="resetDuedate()"><span class="hidden-visually"><?php p($l->t('Remove due date')); ?></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-header-tabbed">
|
||||
<ul class="tabHeaders ng-scope">
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><span class="icon icon-description"></span><a><?php p($l->t('Description')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><span class="icon icon-files-dark"></span><a><?php p($l->t('Attachments')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}" ng-if="isTimelineEnabled()"><span class="icon icon-activity"></span><a><?php p($l->t('Timeline')); ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tabDetails" ng-if="params.tab === 0">
|
||||
<span class="save-indicator saved"><?php p($l->t('Saved')); ?></span>
|
||||
<span class="save-indicator unsaved"><?php p($l->t('Unsaved changes')); ?></span>
|
||||
<div style="flex-grow: 1;"> </div>
|
||||
<input ng-if="status.cardEditDescription" type="button" ng-mousedown="status.continueEdit = true; status.selectAttachment = true;" class="icon-files-dark" data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Insert attachment')); ?>"/>
|
||||
<a href="https://deck.readthedocs.io/en/latest/Markdown/" target="_blank" class="icon icon-help" data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Formatting help')); ?>"><span class="hidden-visually"><?php p($l->t('Formatting help')); ?></span></a>
|
||||
</div>
|
||||
<div class="section-content card-attachments">
|
||||
<div>
|
||||
<label ng-if="params.tab === 1" for="attachment-upload" class="button data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Upload attachment')); ?>">
|
||||
<span class="icon-upload" ng-class="{'icon-loading-small': fileservice.uploader.isUploading}"></span>
|
||||
<?php p($l->t('Upload attachment')); ?>
|
||||
</label>
|
||||
<input id="attachment-upload" type="file" nv-file-select="" uploader="fileservice.uploader" class="hidden" options="{cardId: cardservice.getCurrent().id}"/>
|
||||
</div>
|
||||
<div class="error icon-error" ng-if="fileservice.status"><strong>{{ fileservice.status.error }}</strong><br />{{ fileservice.status.message }}</div>
|
||||
<attachment-list-component ng-if="params.tab === 1 && cardservice.getCurrent() && isArray(cardservice.getCurrent().attachments)" attachments="cardservice.getCurrent().attachments"></attachment-list-component>
|
||||
</div>
|
||||
|
||||
<div class="section-content card-description" ng-if="params.tab === 0">
|
||||
<attachment-list-component
|
||||
ng-if="status.selectAttachment"
|
||||
attachments="cardservice.getCurrent().attachments"
|
||||
is-file-selector="true"
|
||||
on-select="addAttachmentToDescription(attachment)" on-abort="abortAttachmentSelection()">
|
||||
</attachment-list-component>
|
||||
<textarea elastic ng-if="status.cardEditDescription"
|
||||
placeholder="<?php p($l->t('Add a card description…')); ?>"
|
||||
ng-blur="!status.continueEdit && cardUpdate(status.edit)"
|
||||
ng-model="status.edit.description"
|
||||
ng-change="cardEditDescriptionChanged(); updateMarkdown(status.edit.description)"
|
||||
autofocus-on-insert> </textarea>
|
||||
<div class="container" ng-click="clickCardDescription($event)"
|
||||
ng-if="!status.cardEditDescription" ng-animate>
|
||||
<div class="placeholder"
|
||||
ng-if="!description()"><?php p($l->t('Add a card description…')); ?></div>
|
||||
<div id="markdown" ng-bind-html="description()">{{ description() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-content card-activity activityTabView" ng-if="isTimelineEnabled() && params.tab === 2">
|
||||
<activity-component type="deck_card" element="cardservice.getCurrent()"></activity-component>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -1,73 +0,0 @@
|
||||
<ul class="with-icon">
|
||||
|
||||
<li ng-class="{active: status.filter === '' && !boardservice.getCurrent()}"><a ui-sref="list({ filter: ''})" class="icon-deck"><?php p($l->t('All Boards')); ?></a></li>
|
||||
<li ng-class="{active: status.filter === 'archived' || (boardservice.getCurrent() && boardservice.getCurrent().archived)}"><a ui-sref="list({ filter: 'archived' })" class="icon-archive"><?php p($l->t('Archived boards')); ?></a></li>
|
||||
<li ng-class="{active: status.filter === 'shared'}"><a ui-sref="list({ filter: 'shared' })" class="icon-share"><?php p($l->t('Shared boards')); ?></a></li>
|
||||
|
||||
<li class="with-icon with-menu" ng-class="{active: b.id === boardservice.getCurrent().id, editing: b.status.editNavigation}" data-ng-repeat="b in boardservice.sidebar track by b.id" ng-if="b.deletedAt == 0">
|
||||
|
||||
<span class="board-bullet" ng-style="{'background-color': '#' + b.color}"> </span>
|
||||
<a href="#!/board/{{b.id}}/">{{ b.title }}</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-menu-button svg" ng-show="!status.deleteUndo[b.id]"><button class="icon-more" title="<?php p($l->t('Actions')); ?>"><span class="hidden-visually"><?php p($l->t('Actions')); ?></span></button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="app-navigation-entry-menu" ng-show="!b.status.editNavigation">
|
||||
<ul>
|
||||
<li ng-show="boardservice.canManage(b)">
|
||||
<a class="icon-rename" title="<?php p($l->t('Edit board')); ?>" ng-click="b.status.editNavigation=true">
|
||||
<?php p($l->t('Edit board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="boardservice.canManage(b)">
|
||||
<a class="icon-archive" title="<?php p($l->t('Move board to archive')); ?>" ng-click="boardArchive(b)">
|
||||
<?php p($l->t('Archive board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="boardservice.canManage(b)">
|
||||
<a class="icon-delete" title ="<?php p($l->t('Delete board')); ?>" ng-click="boardDelete(b)">
|
||||
<?php p($l->t('Delete board')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li ui-sref="board.detail({boardId: b.id})">
|
||||
<a class="icon-settings-dark">
|
||||
<?php p($l->t('Show board details')); ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="app-navigation-entry-edit">
|
||||
<form ng-disabled="isAddingList" class="ng-pristine ng-valid" ng-submit="boardUpdate(b)">
|
||||
<input class="edit ng-valid ng-empty" type="text" autofocus-on-insert ng-model="b.title" maxlength="100" ng-model-options="{ debounce: 250 }">
|
||||
<input type="submit" value="" class="action icon-checkmark svg">
|
||||
</form>
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="b=setColor(b,c)" ng-class="{'selected': (c == b.color) }"></div>
|
||||
<label class="colorselect-label{{ b.color | iconWhiteFilter }} color" ng-style="getCustomBackground(b.hashedColor)" ng-init="b.hashedColor='#' + b.color">
|
||||
<input class="color" type="color" ng-model="b.hashedColor" ng-value="colorValue(b.color)" ng-change="b=setHashedColor(b)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li ng-class="{editing: status.addBoard}" ng-if="canCreate">
|
||||
<a ng-click="status.addBoard=!status.addBoard" class="icon-add app-navigation-noclose">
|
||||
<?php p($l->t('Create a new board')); ?>
|
||||
</a>
|
||||
<div class="app-navigation-entry-edit" ng-if="status.addBoard">
|
||||
<form ng-disabled="isAddingList" class="ng-pristine ng-valid" ng-submit="boardCreate()">
|
||||
<input class="edit ng-valid ng-empty" type="text" placeholder="<?php p($l->t('New board title')); ?>" autofocus-on-insert ng-model="newBoard.title" maxlength="100" ng-model-options="{ debounce: 250 }">
|
||||
<input type="submit" value="" class="action icon-checkmark svg">
|
||||
</form>
|
||||
<div class="colorselect" ng-controller="ColorPickerController">
|
||||
<div class="color" ng-repeat="c in ::colors" ng-style="{'background-color':'#{{ c }}'}" ng-click="selectColor(c);newBoard=setColor(newBoard,c)" ng-class="{'selected': (c == newBoard.color), 'dark': (newBoard.color | textColorFilter) === '#ffffff' }"><br /></div>
|
||||
<label class="colorselect-label{{ newBoard.color | iconWhiteFilter }} color" ng-style="getCustomBackground(newBoard.hashedColor)" ng-init="newBoard.hashedColor='#' + newBoard.color">
|
||||
<input class="color" type="color" ng-model="newBoard.hashedColor" ng-value="colorValue(newBoard.color)" ng-change="newBoard=setHashedColor(newBoard)"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
@@ -1,21 +0,0 @@
|
||||
<div id="app-settings" ng-if="isAdmin">
|
||||
<div id="app-settings-header">
|
||||
<button class="settings-button" data-apps-slide-toggle="#app-settings-content"><?php p($l->t('Settings')); ?></button>
|
||||
</div>
|
||||
<div id="app-settings-content" class="hidden">
|
||||
<ui-select multiple tagging="" ng-model="groupLimit" theme="select2"
|
||||
title="<?php p($l->t('Limit deck to groups')); ?>"
|
||||
placeholder="<?php p($l->t('Limit deck to groups')); ?>"
|
||||
on-select="groupLimitAdd($item, $model)"
|
||||
on-remove="groupLimitRemove($item, $model)" ng-disabled="groupLimitDisabled">
|
||||
<ui-select-match placeholder="<?php p($l->t('Limit deck to groups')); ?>">
|
||||
<span class="select-label">{{$item.displayname}} </span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices
|
||||
repeat="group in groups | filter: $select.search | limitTo: 3 track by group.id" position="down">
|
||||
<span class="choose-label">{{group.displayname}}</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<p class="hint"><?php p($l->t('Limiting Deck will block users not part of those groups from creating their own boards. Users will still be able to work on boards that have been shared with them.')); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user