mirror of https://github.com/nextcloud/server
chore(files_reminders): upgrade to 28 APIs
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
parent
72ffd4999a
commit
cf6c921376
|
@ -51,11 +51,11 @@
|
|||
}"
|
||||
:close-after-click="!isMenu(action.id)"
|
||||
:data-cy-files-list-row-action="action.id"
|
||||
:is-menu="isMenu(action.id)"
|
||||
:title="action.title?.([source], currentView)"
|
||||
@click="onActionClick(action)">
|
||||
<template #icon>
|
||||
<ChevronRightIcon v-if="isMenu(action.id)" />
|
||||
<NcLoadingIcon v-else-if="loading === action.id" :size="18" />
|
||||
<NcLoadingIcon v-if="loading === action.id" :size="18" />
|
||||
<NcIconSvgWrapper v-else :svg="action.iconSvgInline([source], currentView)" />
|
||||
</template>
|
||||
{{ actionDisplayName(action) }}
|
||||
|
@ -63,22 +63,22 @@
|
|||
</template>
|
||||
|
||||
<!-- Submenu actions list-->
|
||||
<template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu]">
|
||||
<template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu?.id]">
|
||||
<!-- Back to top-level button -->
|
||||
<NcActionButton class="files-list__row-action-back" @click="openedSubmenu = ''">
|
||||
<NcActionButton class="files-list__row-action-back" @click="openedSubmenu = null">
|
||||
<template #icon>
|
||||
<ArrowLeftIcon />
|
||||
</template>
|
||||
{{ t('files', 'Back') }}
|
||||
{{ actionDisplayName(openedSubmenu) }}
|
||||
</NcActionButton>
|
||||
<NcActionSeparator />
|
||||
|
||||
<!-- Submenu actions -->
|
||||
<NcActionButton v-for="action in enabledSubmenuActions[openedSubmenu]"
|
||||
<NcActionButton v-for="action in enabledSubmenuActions[openedSubmenu?.id]"
|
||||
:key="action.id"
|
||||
:class="`files-list__row-action-${action.id}`"
|
||||
class="files-list__row-action--submenu"
|
||||
:close-after-click="true"
|
||||
:close-after-click="false /* never close submenu, just go back */"
|
||||
:data-cy-files-list-row-action="action.id"
|
||||
:title="action.title?.([source], currentView)"
|
||||
@click="onActionClick(action)">
|
||||
|
@ -152,7 +152,7 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
openedSubmenu: '',
|
||||
openedSubmenu: null as FileAction | null,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -267,10 +267,10 @@ export default Vue.extend({
|
|||
return action.displayName([this.source], this.currentView)
|
||||
},
|
||||
|
||||
async onActionClick(action) {
|
||||
async onActionClick(action, isSubmenu = false) {
|
||||
// If the action is a submenu, we open it
|
||||
if (this.enabledSubmenuActions[action.id]) {
|
||||
this.openedSubmenu = action.id
|
||||
this.openedSubmenu = action
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -299,6 +299,11 @@ export default Vue.extend({
|
|||
// Reset the loading marker
|
||||
this.$emit('update:loading', '')
|
||||
Vue.set(this.source, 'status', undefined)
|
||||
|
||||
// If that was a submenu, we just go back after the action
|
||||
if (isSubmenu) {
|
||||
this.openedSubmenu = null
|
||||
}
|
||||
}
|
||||
},
|
||||
execDefaultAction(event) {
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
alt=""
|
||||
class="files-list__row-icon-preview"
|
||||
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
|
||||
loading="lazy"
|
||||
:src="previewUrl"
|
||||
@error="backgroundFailed = true"
|
||||
@load="backgroundFailed = false">
|
||||
|
|
|
@ -63,39 +63,3 @@ registerRecentView()
|
|||
|
||||
// Register preview service worker
|
||||
registerPreviewServiceWorker()
|
||||
|
||||
registerFileAction(new FileAction({
|
||||
id: 'menu',
|
||||
displayName: () => 'Menu',
|
||||
iconSvgInline: () => MenuIcon,
|
||||
exec: async () => true,
|
||||
}))
|
||||
|
||||
registerFileAction(new FileAction({
|
||||
id: 'submenu1',
|
||||
displayName: () => 'Submenu 1',
|
||||
iconSvgInline: () => MenuIcon,
|
||||
exec: async () => alert('Hello 1'),
|
||||
parent: 'menu',
|
||||
}))
|
||||
registerFileAction(new FileAction({
|
||||
id: 'submenu2',
|
||||
displayName: () => 'Submenu 2',
|
||||
iconSvgInline: () => MenuIcon,
|
||||
exec: async () => alert('Hello 2'),
|
||||
parent: 'menu',
|
||||
}))
|
||||
registerFileAction(new FileAction({
|
||||
id: 'submenu3',
|
||||
displayName: () => 'Submenu 3',
|
||||
iconSvgInline: () => MenuIcon,
|
||||
exec: async () => alert('Hello 3'),
|
||||
parent: 'menu',
|
||||
}))
|
||||
registerFileAction(new FileAction({
|
||||
id: 'submenu4',
|
||||
displayName: () => 'Submenu 4',
|
||||
iconSvgInline: () => MenuIcon,
|
||||
exec: async () => alert('Hello 4'),
|
||||
parent: 'menu',
|
||||
}))
|
||||
|
|
|
@ -36,8 +36,6 @@ if (!window.OCA.Files) {
|
|||
Object.assign(window.OCA.Files, { Sidebar: new Sidebar() })
|
||||
Object.assign(window.OCA.Files.Sidebar, { Tab })
|
||||
|
||||
console.debug('OCA.Files.Sidebar initialized')
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
const contentElement = document.querySelector('body > .content')
|
||||
|| document.querySelector('body > #content')
|
||||
|
|
|
@ -47,6 +47,6 @@ class LoadAdditionalScriptsListener implements IEventListener {
|
|||
return;
|
||||
}
|
||||
|
||||
Util::addScript(Application::APP_ID, 'main');
|
||||
Util::addInitScript(Application::APP_ID, 'init');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { FileAction, Node } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import CalendarClockSvg from '@mdi/svg/svg/calendar-clock.svg?raw'
|
||||
|
||||
import { SET_REMINDER_MENU_ID } from './setReminderMenuAction'
|
||||
import { pickCustomDate } from '../services/customPicker'
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'set-reminder-custom',
|
||||
displayName: () => t('files', 'Set custom reminder'),
|
||||
title: () => t('files_reminders', 'Set reminder at custom date & time'),
|
||||
iconSvgInline: () => CalendarClockSvg,
|
||||
|
||||
enabled: () => true,
|
||||
parent: SET_REMINDER_MENU_ID,
|
||||
|
||||
async exec(file: Node) {
|
||||
pickCustomDate(file)
|
||||
return null
|
||||
},
|
||||
|
||||
// After presets
|
||||
order: 22,
|
||||
})
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { FileAction } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import AlarmSvg from '@mdi/svg/svg/alarm.svg?raw'
|
||||
|
||||
export const SET_REMINDER_MENU_ID = 'set-reminder-menu'
|
||||
|
||||
export const action = new FileAction({
|
||||
id: SET_REMINDER_MENU_ID,
|
||||
displayName: () => t('files', 'Set reminder'),
|
||||
iconSvgInline: () => AlarmSvg,
|
||||
|
||||
enabled: () => true,
|
||||
|
||||
async exec() {
|
||||
return null
|
||||
},
|
||||
|
||||
order: 20,
|
||||
})
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
// TODO: remove when/if the actions API supports a separator
|
||||
// This the last preset action, so we need to add a separator
|
||||
.files-list__row-action-set-reminder-3 {
|
||||
margin-bottom: 13px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
margin: 3px 10px 3px 15px;
|
||||
border-bottom: 1px solid var(--color-border-dark);
|
||||
cursor: default;
|
||||
display: flex;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import { FileAction } from '@nextcloud/files'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
import { DateTimePreset, getDateString, getDateTime, getVerboseDateString } from '../shared/utils'
|
||||
import { logger } from '../shared/logger'
|
||||
import { SET_REMINDER_MENU_ID } from './setReminderMenuAction'
|
||||
import { setReminder } from '../services/reminderService'
|
||||
import './setReminderSuggestionActions.scss'
|
||||
|
||||
interface ReminderOption {
|
||||
dateTimePreset: DateTimePreset
|
||||
label: string
|
||||
ariaLabel: string
|
||||
dateString?: string
|
||||
action?: () => Promise<void>
|
||||
}
|
||||
|
||||
const laterToday: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.LaterToday,
|
||||
label: t('files_reminders', 'Later today'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for later today'),
|
||||
}
|
||||
|
||||
const tomorrow: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.Tomorrow,
|
||||
label: t('files_reminders', 'Tomorrow'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for tomorrow'),
|
||||
}
|
||||
|
||||
const thisWeekend: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.ThisWeekend,
|
||||
label: t('files_reminders', 'This weekend'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for this weekend'),
|
||||
}
|
||||
|
||||
const nextWeek: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.NextWeek,
|
||||
label: t('files_reminders', 'Next week'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for next week'),
|
||||
}
|
||||
|
||||
// Generate the default preset actions
|
||||
export const actions = [laterToday, tomorrow, thisWeekend, nextWeek].map((option): FileAction|null => {
|
||||
const dateTime = getDateTime(option.dateTimePreset)
|
||||
if (!dateTime) {
|
||||
return null
|
||||
}
|
||||
|
||||
return new FileAction({
|
||||
id: `set-reminder-${option.dateTimePreset}`,
|
||||
displayName: () => `${option.label} - ${getDateString(dateTime)}`,
|
||||
title: () => `${option.ariaLabel} – ${getVerboseDateString(dateTime)}`,
|
||||
|
||||
// Empty svg to hide the icon
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
|
||||
enabled: () => true,
|
||||
parent: SET_REMINDER_MENU_ID,
|
||||
|
||||
async exec(node: Node) {
|
||||
// Can't really happen, but just in case™
|
||||
if (!node.fileid) {
|
||||
logger.error('Failed to set reminder, missing file id')
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
return null
|
||||
}
|
||||
|
||||
// Set the reminder
|
||||
try {
|
||||
await setReminder(node.fileid, dateTime)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: node.basename }))
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
// Silent success as we display our own notification
|
||||
return null
|
||||
},
|
||||
|
||||
order: 21,
|
||||
})
|
||||
}).filter(Boolean) as FileAction[]
|
|
@ -0,0 +1,199 @@
|
|||
<!--
|
||||
- @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcModal v-if="opened"
|
||||
:out-transition="true"
|
||||
size="small"
|
||||
@close="onClose">
|
||||
<form class="custom-reminder-modal" @submit.prevent="setCustom">
|
||||
<h2 class="custom-reminder-modal__title">
|
||||
{{ title }}
|
||||
</h2>
|
||||
|
||||
<NcDateTimePickerNative id="set-custom-reminder"
|
||||
v-model="customDueDate"
|
||||
:label="label"
|
||||
:min="nowDate"
|
||||
:required="true"
|
||||
type="datetime-local"
|
||||
@input="onInput" />
|
||||
|
||||
<NcNoteCard v-if="isValid" type="info">
|
||||
{{ t('files_reminders', 'We will remind you of this file') }}
|
||||
<NcDateTime :timestamp="customDueDate" />
|
||||
</NcNoteCard>
|
||||
|
||||
<NcNoteCard v-else type="error">
|
||||
{{ t('files_reminders', 'Please choose a valid date & time') }}
|
||||
</NcNoteCard>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="custom-reminder-modal__buttons">
|
||||
<!-- Cancel pick -->
|
||||
<NcButton @click="onClose">
|
||||
{{ t('files_reminders', 'Cancel') }}
|
||||
</NcButton>
|
||||
|
||||
<!-- Set reminder -->
|
||||
<NcButton :disabled="!isValid" native-type="submit" type="primary">
|
||||
{{ t('files_reminders', 'Set reminder') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</form>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import Vue from 'vue'
|
||||
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcDateTime from '@nextcloud/vue/dist/Components/NcDateTime.js'
|
||||
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
|
||||
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
|
||||
|
||||
import { getDateString, getInitialCustomDueDate } from '../shared/utils.ts'
|
||||
import { logger } from '../shared/logger.ts'
|
||||
import { setReminder } from '../services/reminderService.ts'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SetCustomReminderModal',
|
||||
|
||||
components: {
|
||||
NcButton,
|
||||
NcDateTime,
|
||||
NcDateTimePickerNative,
|
||||
NcModal,
|
||||
NcNoteCard,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
node: undefined as Node | undefined,
|
||||
opened: false,
|
||||
isValid: true,
|
||||
|
||||
customDueDate: getInitialCustomDueDate() as '' | Date,
|
||||
nowDate: new Date(),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
fileId(): number {
|
||||
return this.node.fileid
|
||||
},
|
||||
|
||||
fileName(): string {
|
||||
return this.node.basename
|
||||
},
|
||||
|
||||
title() {
|
||||
return t('files_reminders', 'Set reminder for "{fileName}"', { fileName: this.fileName })
|
||||
},
|
||||
|
||||
label(): string {
|
||||
return t('files_reminders', 'Set reminder at custom date & time')
|
||||
},
|
||||
|
||||
clearAriaLabel(): string {
|
||||
return t('files_reminders', 'Clear reminder')
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
getDateString,
|
||||
|
||||
/**
|
||||
* Open the modal to set a custom reminder
|
||||
* and reset the state.
|
||||
* @param node The node to set a reminder for
|
||||
*/
|
||||
async open(node: Node): Promise<void> {
|
||||
this.node = node
|
||||
this.isValid = true
|
||||
this.opened = true
|
||||
this.customDueDate = getInitialCustomDueDate()
|
||||
this.nowDate = new Date()
|
||||
|
||||
// Focus the input and show the picker after the animation
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById('set-custom-reminder') as HTMLInputElement
|
||||
input.focus()
|
||||
input.showPicker()
|
||||
}, 300)
|
||||
},
|
||||
|
||||
async setCustom(): Promise<void> {
|
||||
// Handle input cleared
|
||||
if (this.customDueDate === '') {
|
||||
showError(t('files_reminders', 'Please choose a valid date & time'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await setReminder(this.fileId, this.customDueDate)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: this.fileName }))
|
||||
this.onClose()
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
},
|
||||
|
||||
onClose(): void {
|
||||
this.opened = false
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
onInput(): void {
|
||||
const input = document.getElementById('set-custom-reminder') as HTMLInputElement
|
||||
this.isValid = input.checkValidity()
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-reminder-modal {
|
||||
margin: 30px;
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 30px;
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,272 +0,0 @@
|
|||
<!--
|
||||
- @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcActions class="actions-secondary-vue"
|
||||
:open.sync="open">
|
||||
<NcActionButton @click="$emit('back')">
|
||||
<template #icon>
|
||||
<ArrowLeft :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Back') }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionButton v-if="Boolean(dueDate)"
|
||||
:aria-label="clearAriaLabel"
|
||||
@click="clear">
|
||||
<template #icon>
|
||||
<CloseCircleOutline :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Clear reminder') }} – {{ getDateString(dueDate) }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionSeparator />
|
||||
|
||||
<NcActionButton v-for="({ label, ariaLabel, dateString, action }) in options"
|
||||
:key="label"
|
||||
:aria-label="ariaLabel"
|
||||
@click="action">
|
||||
{{ label }} – {{ dateString }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionSeparator />
|
||||
|
||||
<NcActionInput type="datetime-local"
|
||||
is-native-picker
|
||||
:min="now"
|
||||
v-model="customDueDate">
|
||||
<template #icon>
|
||||
<CalendarClock :size="20" />
|
||||
</template>
|
||||
</NcActionInput>
|
||||
|
||||
<NcActionButton :aria-label="customAriaLabel"
|
||||
@click="setCustom">
|
||||
<template #icon>
|
||||
<Check :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Set custom reminder') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { type PropType } from 'vue'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
|
||||
|
||||
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
|
||||
import CalendarClock from 'vue-material-design-icons/CalendarClock.vue'
|
||||
import Check from 'vue-material-design-icons/Check.vue'
|
||||
import CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'
|
||||
|
||||
import { clearReminder, setReminder } from '../services/reminderService.ts'
|
||||
import {
|
||||
DateTimePreset,
|
||||
getDateString,
|
||||
getDateTime,
|
||||
getInitialCustomDueDate,
|
||||
getVerboseDateString,
|
||||
} from '../shared/utils.ts'
|
||||
import { logger } from '../shared/logger.ts'
|
||||
|
||||
import type { FileAttributes } from '../shared/types.ts'
|
||||
|
||||
interface ReminderOption {
|
||||
dateTimePreset: DateTimePreset
|
||||
label: string
|
||||
ariaLabel: string
|
||||
dateString?: string
|
||||
action?: () => Promise<void>
|
||||
}
|
||||
|
||||
const laterToday: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.LaterToday,
|
||||
label: t('files_reminders', 'Later today'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for later today'),
|
||||
}
|
||||
|
||||
const tomorrow: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.Tomorrow,
|
||||
label: t('files_reminders', 'Tomorrow'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for tomorrow'),
|
||||
}
|
||||
|
||||
const thisWeekend: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.ThisWeekend,
|
||||
label: t('files_reminders', 'This weekend'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for this weekend'),
|
||||
}
|
||||
|
||||
const nextWeek: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.NextWeek,
|
||||
label: t('files_reminders', 'Next week'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for next week'),
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SetReminderActions',
|
||||
|
||||
components: {
|
||||
ArrowLeft,
|
||||
CalendarClock,
|
||||
Check,
|
||||
CloseCircleOutline,
|
||||
NcActionButton,
|
||||
NcActionInput,
|
||||
NcActions,
|
||||
NcActionSeparator,
|
||||
},
|
||||
|
||||
props: {
|
||||
file: {
|
||||
type: Object as PropType<FileAttributes>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
dueDate: {
|
||||
type: Date as PropType<null | Date>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
open: true,
|
||||
now: new Date(),
|
||||
customDueDate: getInitialCustomDueDate() as '' | Date,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
open(isOpen) {
|
||||
if (!isOpen) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
fileId(): number {
|
||||
return this.file.id
|
||||
},
|
||||
|
||||
fileName(): string {
|
||||
return this.file.name
|
||||
},
|
||||
|
||||
clearAriaLabel(): string {
|
||||
return `${t('files_reminders', 'Clear reminder')} – ${getVerboseDateString(this.dueDate as Date)}`
|
||||
},
|
||||
|
||||
customAriaLabel(): null | string {
|
||||
if (this.customDueDate === '') {
|
||||
return null
|
||||
}
|
||||
return `${t('files_reminders', 'Set reminder at custom date & time')} – ${getVerboseDateString(this.customDueDate)}`
|
||||
},
|
||||
|
||||
options(): ReminderOption[] {
|
||||
const computeOption = (option: ReminderOption): null | ReminderOption => {
|
||||
const dateTime = getDateTime(option.dateTimePreset)
|
||||
if (!dateTime) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...option,
|
||||
ariaLabel: `${option.ariaLabel} – ${getVerboseDateString(dateTime)}`,
|
||||
dateString: getDateString(dateTime),
|
||||
action: () => this.set(dateTime),
|
||||
}
|
||||
}
|
||||
|
||||
const options = [
|
||||
laterToday,
|
||||
tomorrow,
|
||||
thisWeekend,
|
||||
nextWeek,
|
||||
]
|
||||
return options
|
||||
.map(computeOption)
|
||||
.filter(Boolean) as ReminderOption[]
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
getDateString,
|
||||
|
||||
async set(dueDate: Date): Promise<void> {
|
||||
try {
|
||||
await setReminder(this.fileId, dueDate)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: this.fileName }))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
},
|
||||
|
||||
async setCustom(): Promise<void> {
|
||||
// Handle input cleared
|
||||
if (this.customDueDate === '') {
|
||||
showError(t('files_reminders', 'Please choose a valid date & time'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await setReminder(this.fileId, this.customDueDate)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: this.fileName }))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
},
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
await clearReminder(this.fileId)
|
||||
showSuccess(t('files_reminders', 'Reminder cleared'))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to clear reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to clear reminder'))
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions-secondary-vue {
|
||||
display: block !important;
|
||||
float: right !important;
|
||||
padding: 5px 0 0 4px !important;
|
||||
pointer-events: none !important; // prevent activation of file row
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { registerFileAction } from '@nextcloud/files'
|
||||
import { action as menuAction } from './actions/setReminderMenuAction'
|
||||
import { actions as suggestionActions } from './actions/setReminderSuggestionActions'
|
||||
import { action as customAction } from './actions/setReminderCustomAction'
|
||||
|
||||
registerFileAction(menuAction)
|
||||
registerFileAction(customAction)
|
||||
suggestionActions.forEach((action) => registerFileAction(action))
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue, { type ComponentInstance } from 'vue'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
import SetReminderActionsComponent from './components/SetReminderActions.vue'
|
||||
|
||||
import { getReminder } from './services/reminderService.js'
|
||||
import { logger } from './shared/logger.js'
|
||||
|
||||
import type { FileAttributes } from './shared/types.js'
|
||||
|
||||
interface FileContext {
|
||||
[key: string]: any
|
||||
$file: JQuery<HTMLTableRowElement>
|
||||
fileInfoModel: {
|
||||
[key: string]: any
|
||||
attributes: FileAttributes
|
||||
}
|
||||
}
|
||||
|
||||
interface EventPayload {
|
||||
el: HTMLDivElement
|
||||
context: FileContext
|
||||
}
|
||||
|
||||
const handleOpen = async (payload: EventPayload) => {
|
||||
const fileId = payload.context.fileInfoModel.attributes.id
|
||||
const menuEl = payload.context.$file[0].querySelector('.fileactions .action-menu') as HTMLLinkElement
|
||||
const linkEl = payload.el.querySelector('.action-setreminder-container .action-setreminder') as HTMLLinkElement
|
||||
|
||||
let dueDate: null | Date = null
|
||||
let error: null | any = null
|
||||
try {
|
||||
dueDate = (await getReminder(fileId)).dueDate
|
||||
} catch (e) {
|
||||
error = e
|
||||
logger.error(`Failed to load reminder for file with id: ${fileId}`, { error })
|
||||
}
|
||||
|
||||
linkEl.addEventListener('click', (_event) => {
|
||||
if (error) {
|
||||
showError(t('files_reminders', 'Failed to load reminder'))
|
||||
throw Error()
|
||||
}
|
||||
|
||||
const mountPoint = document.createElement('div')
|
||||
const SetReminderActions = Vue.extend(SetReminderActionsComponent)
|
||||
|
||||
const origDisplay = menuEl.style.display
|
||||
menuEl.style.display = 'none'
|
||||
menuEl.insertAdjacentElement('afterend', mountPoint)
|
||||
|
||||
const propsData = {
|
||||
file: payload.context.fileInfoModel.attributes,
|
||||
dueDate,
|
||||
}
|
||||
const actions = (new SetReminderActions({ propsData }) as ComponentInstance)
|
||||
.$mount(mountPoint)
|
||||
|
||||
const cleanUp = () => {
|
||||
actions.$destroy() // destroy popper
|
||||
actions.$el.remove() // remove action menu button
|
||||
menuEl.style.display = origDisplay
|
||||
}
|
||||
|
||||
actions.$once('back', () => {
|
||||
cleanUp()
|
||||
menuEl.click() // reopen original actions menu
|
||||
})
|
||||
|
||||
actions.$once('close', () => {
|
||||
cleanUp()
|
||||
})
|
||||
}, {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
subscribe('files:action-menu:opened', handleOpen)
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import Vue from 'vue'
|
||||
|
||||
import SetCustomReminderModal from '../components/SetCustomReminderModal.vue'
|
||||
|
||||
const View = Vue.extend(SetCustomReminderModal)
|
||||
const mount = document.createElement('div')
|
||||
mount.id = 'set-custom-reminder-modal'
|
||||
document.body.appendChild(mount)
|
||||
|
||||
// Create a new Vue instance and mount it to our modal container
|
||||
const CustomReminderModal = new View({
|
||||
name: 'SetCustomReminderModal',
|
||||
el: mount,
|
||||
})
|
||||
|
||||
export const pickCustomDate = async (node: Node): Promise<void> => {
|
||||
console.debug('CustomReminderModal', mount, CustomReminderModal)
|
||||
|
||||
CustomReminderModal.open(node)
|
||||
|
||||
// Wait for the modal to close
|
||||
return new Promise((resolve) => {
|
||||
CustomReminderModal.$on('close', resolve)
|
||||
})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -46,7 +46,7 @@
|
|||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^5.0.0-beta.6",
|
||||
"@nextcloud/event-bus": "^3.1.0",
|
||||
"@nextcloud/files": "^3.0.0-beta.26",
|
||||
"@nextcloud/files": "^3.0.0-beta.27",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^2.1.0",
|
||||
"@nextcloud/logger": "^2.5.0",
|
||||
|
|
|
@ -60,7 +60,7 @@ module.exports = {
|
|||
init: path.join(__dirname, 'apps/files_external/src', 'init.ts'),
|
||||
},
|
||||
files_reminders: {
|
||||
main: path.join(__dirname, 'apps/files_reminders/src', 'main.ts'),
|
||||
init: path.join(__dirname, 'apps/files_reminders/src', 'init.ts'),
|
||||
},
|
||||
files_sharing: {
|
||||
additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'),
|
||||
|
|
Loading…
Reference in New Issue