Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
['name' => 'contacts#direct', 'url' => '/direct/contact/{contact}', 'verb' => 'GET'],
['name' => 'contacts#directcircle', 'url' => '/direct/circle/{singleId}', 'verb' => 'GET'],
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#joinInvitation', 'url' => '/join/{invitationCode}', 'verb' => 'GET'],
['name' => 'page#index', 'url' => '/{group}', 'verb' => 'GET', 'postfix' => 'group'],
['name' => 'page#index', 'url' => '/{group}/{contact}', 'verb' => 'GET', 'postfix' => 'group.contact'],
['name' => 'social_api#update_contact', 'url' => '/api/v1/social/avatar/{network}/{addressbookId}/{contactId}', 'verb' => 'PUT'],
Expand Down
13 changes: 13 additions & 0 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,17 @@ public function index(): TemplateResponse {

return new TemplateResponse(Application::APP_ID, 'main');
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function joinInvitation(string $invitationCode): TemplateResponse {
$this->initialStateService->provideInitialState(Application::APP_ID, 'invitationCode', $invitationCode);

Util::addStyle(Application::APP_ID, 'contacts-join-invitation');
Util::addScript(Application::APP_ID, 'contacts-join-invitation');

return new TemplateResponse(Application::APP_ID, 'join-invitation');
}
}
56 changes: 2 additions & 54 deletions src/components/CircleDetails/CircleConfigs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,25 @@

<template>
<ul>
<li v-for="(configs, title) in PUBLIC_CIRCLE_CONFIG" :key="title" class="circle-config">
<li v-for="(config, title) in PUBLIC_CIRCLE_CONFIG" :key="title" class="circle-config">
<ContentHeading class="circle-config__title">
{{ title }}
</ContentHeading>

<ul class="circle-config__list">
<CheckboxRadioSwitch
v-for="(label, config) in configs"
:key="'circle-config' + config"
:model-value="isChecked(config)"
:loading="loading === config"
:disabled="loading !== false"
wrapper-element="li"
@update:model-value="onChange(config, $event)">
{{ label }}
</CheckboxRadioSwitch>
</ul>
<component :is="config.component" v-bind="config.props" :circle="circle" />
</li>
</ul>
</template>

<script>
import { showError } from '@nextcloud/dialogs'
import { NcCheckboxRadioSwitch as CheckboxRadioSwitch } from '@nextcloud/vue'
import ContentHeading from './ContentHeading.vue'
import Circle from '../../models/circle.ts'
import { PUBLIC_CIRCLE_CONFIG } from '../../models/constants.ts'
import { CircleEdit, editCircle } from '../../services/circles.ts'

export default {
name: 'CircleConfigs',

components: {
CheckboxRadioSwitch,
ContentHeading,
},

Expand All @@ -52,45 +37,8 @@ export default {
data() {
return {
PUBLIC_CIRCLE_CONFIG,

loading: false,
}
},

methods: {
isChecked(config) {
return (this.circle.config & config) !== 0
},

/**
* On toggle, add or remove the config bitwise
*
* @param {CircleConfig} config the circle config to manage
* @param {boolean} checked checked or not
*/
async onChange(config, checked) {
this.logger.debug(`Circle config ${config} is set to ${checked}`)

this.loading = config
const prevConfig = this.circle.config
if (checked) {
config = prevConfig | config
} else {
config = prevConfig & ~config
}

try {
const circleData = await editCircle(this.circle.id, CircleEdit.Config, config)
// eslint-disable-next-line vue/no-mutating-props
this.circle.config = circleData.config
} catch (error) {
console.error('Unable to edit circle config', prevConfig, config, error)
showError(t('contacts', 'An error happened during the config change'))
} finally {
this.loading = false
}
},
},
}
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!--
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<ul class="circle-config__list">
<NcCheckboxRadioSwitch
v-for="(label, config) in configs"
:key="'circle-config' + config"
:model-value="isChecked(config)"
:loading="loading === config"
:disabled="loading !== false"
wrapper-element="li"
@update:model-value="onChange(config, $event)">
{{ label }}
</NcCheckboxRadioSwitch>
</ul>
</template>

<script>
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
import Circle from '../../../models/circle.ts'
import { CircleEdit, editCircle } from '../../../services/circles.ts'

export default {
name: 'CircleConfigCheckboxesList',

components: {
NcCheckboxRadioSwitch,
},

props: {
circle: {
type: Circle,
required: true,
},

configs: {
type: Object,
required: true,
},
},

data() {
return {
loading: false,
}
},

methods: {
isChecked(config) {
return (this.circle.config & config) !== 0
},

/**
* On toggle, add or remove the config bitwise
*
* @param {CircleConfig} config the circle config to manage
* @param {boolean} checked checked or not
*/
async onChange(config, checked) {
this.logger.debug(`Circle config ${config} is set to ${checked}`)

this.loading = config
const prevConfig = this.circle.config
if (checked) {
config = prevConfig | config
} else {
config = prevConfig & ~config
}

try {
const circleData = await editCircle(this.circle.id, CircleEdit.Config, config)
// eslint-disable-next-line vue/no-mutating-props
this.circle.config = circleData.config
} catch (error) {
console.error('Unable to edit circle config', prevConfig, config, error)
showError(t('contacts', 'An error happened during the config change'))
} finally {
this.loading = false
}
},
},
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!--
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<NcActions :inline="3" force-name variant="tertiary">
<NcActionButton
v-if="!invitationUrl"
@click="createInvitationLink()">
<template #icon>
<LinkPlus :size="20" />
</template>
{{ t('contacts', 'Create link') }}
</NcActionButton>
<template v-else>
<NcActionLink
:href="invitationUrl"
:icon="copyLinkIcon"
@click.stop.prevent="copyToClipboard(invitationUrl)">
{{ copyButtonText }}
</NcActionLink>

<NcActionButton
@click="confirm(
t('contacts', 'This action will make it impossible to join the team using the current link. Do we really want to change the link?'),
() => createInvitationLink(),
)">
<template #icon>
<Autorenew :size="20" />
</template>
{{ t('contacts', 'Reset link') }}
</NcActionButton>

<NcActionButton
@click="confirm(
t('contacts', 'This action will make it impossible to join the team using the current link. Do we really want to delete the link?'),
() => revokeInvitationLink(),
)">
<template #icon>
<LinkOff :size="20" />
</template>
{{ t('contacts', 'Reject link') }}
</NcActionButton>
</template>
</NcActions>
</template>

<script>
import { generateUrl, getBaseUrl } from '@nextcloud/router'
import { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'
import Autorenew from 'vue-material-design-icons/Autorenew.vue'
import LinkOff from 'vue-material-design-icons/LinkOff.vue'
import LinkPlus from 'vue-material-design-icons/LinkPlus.vue'
import CopyToClipboardMixin from '../../../mixins/CopyToClipboardMixin.js'
import Circle from '../../../models/circle.ts'

export default {
name: 'InvitationLink',
components: {
LinkOff,
Autorenew,
LinkPlus,
NcActions,
NcActionLink,
NcActionButton,
},

mixins: [CopyToClipboardMixin],
props: {
circle: {
type: Circle,
required: true,
},
},

computed: {
invitationUrl() {
if (!this.circle.invitationCode) {
return null
}

return getBaseUrl() + generateUrl(
'apps/contacts/join/{invitationCode}',
{ invitationCode: this.circle.invitationCode.match(/.{1,4}/g).join('-') },
)
},

copyButtonText() {
if (this.copied) {
return this.copySuccess
? t('contacts', 'Copied')
: t('contacts', 'Could not copy')
}
return t('contacts', 'Copy link')
},
},

methods: {
async createInvitationLink() {
const circleId = this.circle.id
await this.$store.dispatch('createInvitationLink', { circleId })

await this.copyToClipboard(this.invitationUrl)
},

async revokeInvitationLink() {
const circleId = this.circle.id
await this.$store.dispatch('revokeInvitationLink', { circleId })
},

confirm(message, action) {
if (window.confirm(message)) {
action()
}
},
},
}
</script>
Loading
Loading