feat(editors): redesign editors
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
|
@ -37,11 +37,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hide the submit button for the title, because it does not trigger a save */
|
// We use our custom header layout for the sidebar editor
|
||||||
.app-sidebar-header__mainname-form {
|
.app-sidebar-header__info {
|
||||||
button {
|
display: none !important;
|
||||||
display: none;
|
}
|
||||||
}
|
|
||||||
|
.app-sidebar-header__description {
|
||||||
|
// Close button should be aligned with calendar picker (header)
|
||||||
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-invitee-list-empty-message,
|
.editor-invitee-list-empty-message,
|
||||||
|
@ -228,6 +231,18 @@
|
||||||
.property-title-time-picker {
|
.property-title-time-picker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
margin-left: -5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
&__time-pickers,
|
&__time-pickers,
|
||||||
&__all-day {
|
&__all-day {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -235,10 +250,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__time-pickers {
|
&__time-pickers {
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
.mx-datepicker {
|
.mx-datepicker {
|
||||||
width: 49%;
|
flex: 1 auto;
|
||||||
|
|
||||||
.mx-input-append {
|
.mx-input-append {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
|
@ -246,16 +263,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--readonly {
|
&--readonly {
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
.property-title-time-picker-read-only-wrapper {
|
.property-title-time-picker-read-only-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 50%;
|
|
||||||
margin: 3px 3px 3px 0;
|
|
||||||
padding: 8px 7px;
|
padding: 8px 7px;
|
||||||
background-color: var(--color-main-background);
|
background-color: var(--color-main-background);
|
||||||
color: var(--color-main-text);
|
color: var(--color-main-text);
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
&--start-date {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--end-date {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@ -275,22 +300,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1500px) {
|
|
||||||
&__time-pickers {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-datepicker {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.property-title-time-picker-read-only-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__all-day {
|
&__all-day {
|
||||||
justify-content: flex-start;
|
padding-left: 3px;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
// Reduce the height just a little bit (from 44px) to save some space
|
||||||
|
.checkbox-radio-switch__label {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.datetime-picker-inline-icon {
|
.datetime-picker-inline-icon {
|
||||||
|
@ -421,7 +438,6 @@
|
||||||
&__summary {
|
&__summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
|
@ -432,7 +448,7 @@
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
flex: 1 auto;
|
flex: 1 auto;
|
||||||
padding: 0 7px;
|
padding: 8px 7px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -519,7 +535,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
&__icon,
|
&__icon,
|
||||||
&__info {
|
&__info {
|
||||||
|
@ -536,6 +551,7 @@
|
||||||
&__info {
|
&__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +584,6 @@
|
||||||
div {
|
div {
|
||||||
width: calc(100% - 8px); /* for typical (thin) scrollbar size */
|
width: calc(100% - 8px); /* for typical (thin) scrollbar size */
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
margin: 3px 3px 3px 0;
|
|
||||||
padding: 8px 7px;
|
padding: 8px 7px;
|
||||||
background-color: var(--color-main-background);
|
background-color: var(--color-main-background);
|
||||||
color: var(--color-main-text);
|
color: var(--color-main-text);
|
||||||
|
@ -577,26 +592,31 @@
|
||||||
word-break: break-word; /* allows breaking on long URLs */
|
word-break: break-word; /* allows breaking on long URLs */
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.linkified {
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: ' ↗';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--readonly-calendar-picker {
|
&--readonly-calendar-picker {
|
||||||
|
|
||||||
div.calendar-picker-option {
|
div.calendar-picker-option {
|
||||||
margin: 3px 3px 3px 0;
|
|
||||||
padding: 8px 7px;
|
padding: 8px 7px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.property-text,
|
||||||
|
.property-select,
|
||||||
|
.property-color,
|
||||||
|
.property-select-multiple,
|
||||||
|
.property-title,
|
||||||
|
.property-repeat,
|
||||||
|
.resource-capacity,
|
||||||
|
.resource-room-type {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.property-select,
|
.property-select,
|
||||||
.property-select-multiple {
|
.property-select-multiple {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -611,21 +631,46 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-color {
|
.property-color {
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
// Align with other (text based) fields
|
||||||
|
margin: 3px 0 3px 7px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__color-preview {
|
&__color-preview {
|
||||||
border-radius: var(--border-radius);
|
$size: 44px;
|
||||||
height: 34px !important;
|
width: $size !important;
|
||||||
width: 34px !important;
|
height: $size !important;
|
||||||
margin: 0;
|
border-radius: $size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-text {
|
.property-text {
|
||||||
|
&__icon {
|
||||||
|
// Prevent icon misalignment on vertically growing inputs
|
||||||
|
height: unset;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--readonly {
|
||||||
|
.property-text__icon {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
|
&--readonly {
|
||||||
|
// Reduce line height but still keep first row aligned to the icon
|
||||||
|
line-height: 1;
|
||||||
|
padding-top: calc(var(--default-line-height) / 2 - 0.5lh);
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
@ -655,12 +700,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix weird height
|
||||||
|
&__input {
|
||||||
|
max-height: 44px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-title {
|
.property-title {
|
||||||
&__input,
|
&__input, input {
|
||||||
&__input input {
|
font-weight: bold;
|
||||||
font-size: 20px;
|
}
|
||||||
|
|
||||||
|
&__input--readonly {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize gaps between all properties. We use outer margins between each row so a padding
|
||||||
|
// around inputs (from core) is not required.
|
||||||
|
.property-title,
|
||||||
|
.property-title-time-picker {
|
||||||
|
input {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,16 +730,19 @@
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.illustration-header {
|
}
|
||||||
max-height: 150px;
|
|
||||||
height: 150px;
|
.event-popover .event-popover__inner {
|
||||||
width: 100%;
|
.event-popover__response-buttons {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.illustration-header svg {
|
.property-text,
|
||||||
width: 100%;
|
.property-title-time-picker {
|
||||||
height: 150px;
|
&__icon {
|
||||||
padding: 8px 8px 0 8px;
|
margin: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,27 +770,21 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
width: 480px;
|
width: 480px;
|
||||||
padding: 5px 8px;
|
padding: 5px 10px 10px 10px;
|
||||||
|
|
||||||
.empty-content {
|
.empty-content {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
padding: 50px 0;
|
padding: 50px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.illustration-header {
|
.property-title-time-picker:not(.property-title-time-picker--readonly) {
|
||||||
height: 100px;
|
margin-bottom: 12px;
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
background-color: var(--color-background-dark);
|
|
||||||
// There is probably a more elegant solution for this
|
|
||||||
margin: -5px 0 5px -8px;
|
|
||||||
width: 496px;
|
|
||||||
border-top-left-radius: var(--border-radius);
|
|
||||||
border-top-right-radius: var(--border-radius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-title-time-picker {
|
.event-popover__invitees {
|
||||||
margin-bottom: 12px;
|
.avatar-participation-status__text {
|
||||||
|
bottom: 22px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-popover__buttons {
|
.event-popover__buttons {
|
||||||
|
@ -824,7 +883,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 20px;
|
|
||||||
|
// Only apply the margin if at least one button is being rendered
|
||||||
|
&:not(:empty) {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-search-list-item,
|
.resource-search-list-item,
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
cursor: text;
|
cursor: text;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-top: 1px !important;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -19,7 +18,14 @@
|
||||||
max-height: 16em;
|
max-height: 16em;
|
||||||
max-height: calc(100vh - 500px);
|
max-height: calc(100vh - 500px);
|
||||||
|
|
||||||
a.linkified::after {
|
a.linkified {
|
||||||
content: ' ↗';
|
text-decoration: underline;
|
||||||
|
|
||||||
|
// Prevent misalignment when a linkified line starts with a link, e.g. in the location field
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ↗';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -8,15 +8,15 @@
|
||||||
<div class="attachments-summary">
|
<div class="attachments-summary">
|
||||||
<div class="attachments-summary-inner">
|
<div class="attachments-summary-inner">
|
||||||
<Paperclip :size="20" />
|
<Paperclip :size="20" />
|
||||||
<div v-if="attachments.length > 0">
|
<div v-if="attachments.length > 0" class="attachments-summary-inner-label">
|
||||||
{{ n('calendar', '{count} attachment', '{count} attachments', attachments.length, { count: attachments.length }) }}
|
{{ n('calendar', '{count} attachment', '{count} attachments', attachments.length, { count: attachments.length }) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else class="attachments-summary-inner-label">
|
||||||
{{ t('calendar', 'No attachments') }}
|
{{ t('calendar', 'No attachments') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NcActions>
|
<NcActions v-if="!isReadOnly">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Plus :size="20" />
|
<Plus :size="20" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,9 +35,10 @@
|
||||||
</NcActions>
|
</NcActions>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="attachments.length > 0">
|
<div v-if="attachments.length > 0">
|
||||||
<ul class="attachments-list-item">
|
<ul class="attachments-list">
|
||||||
<NcListItem v-for="attachment in attachments"
|
<NcListItem v-for="attachment in attachments"
|
||||||
:key="attachment.path"
|
:key="attachment.path"
|
||||||
|
class="attachments-list-item"
|
||||||
:force-display-actions="true"
|
:force-display-actions="true"
|
||||||
:name="getBaseName(attachment.fileName)"
|
:name="getBaseName(attachment.fileName)"
|
||||||
@click="openFile(attachment.uri)">
|
@click="openFile(attachment.uri)">
|
||||||
|
@ -45,7 +46,8 @@
|
||||||
<img :src="getPreview(attachment)" class="attachment-icon">
|
<img :src="getPreview(attachment)" class="attachment-icon">
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<NcActionButton @click="deleteAttachmentFromEvent(attachment)">
|
<NcActionButton v-if="!isReadOnly"
|
||||||
|
@click="deleteAttachmentFromEvent(attachment)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Close :size="20" />
|
<Close :size="20" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -217,13 +219,37 @@ export default {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
margin-right: 10px;
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments-summary-inner-label {
|
||||||
|
padding: 0 7px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachments-list-item {
|
.attachments-list {
|
||||||
margin: 0 -8px;
|
margin: 0 -8px;
|
||||||
|
|
||||||
|
.attachments-list-item {
|
||||||
|
// Reduce height to 44px
|
||||||
|
:deep(.list-item) {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
:deep(.list-item-content__wrapper) {
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.list-item-content) {
|
||||||
|
// Align text with other properties
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.line-one__title) {
|
||||||
|
font-weight: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#attachments .empty-content {
|
#attachments .empty-content {
|
||||||
|
@ -240,8 +266,8 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.attachment-icon {
|
.attachment-icon {
|
||||||
width: 40px;
|
width: 24px;
|
||||||
height: auto;
|
height: 24px;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2024 Richard Steinmetz <richard@steinmetz.cloud>
|
||||||
|
-
|
||||||
|
- @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||||
|
-
|
||||||
|
- @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 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 General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="calendar-picker-header"
|
||||||
|
:class="{ 'calendar-picker-header--readonly': isReadOnly }">
|
||||||
|
<NcActions type="tertiary"
|
||||||
|
class="calendar-picker-header__picker"
|
||||||
|
:class="{ 'calendar-picker-header__picker--fix-width': isReadOnly && value && value.isSharedWithMe }"
|
||||||
|
:menu-name="value.displayName"
|
||||||
|
:force-name="true"
|
||||||
|
:disabled="isDisabled">
|
||||||
|
<template #icon>
|
||||||
|
<div class="calendar-picker-header__icon">
|
||||||
|
<div class="calendar-picker-header__icon__dot"
|
||||||
|
:style="{ 'background-color': value.color }" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<NcActionButton v-for="calendar in calendars"
|
||||||
|
:key="calendar.id"
|
||||||
|
:close-after-click="true"
|
||||||
|
@click="$emit('update:value', calendar)">
|
||||||
|
<template #icon>
|
||||||
|
<div class="calendar-picker-header__icon">
|
||||||
|
<div class="calendar-picker-header__icon__dot"
|
||||||
|
:style="{ 'background-color': calendar.color }" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
{{ calendar.displayName }}
|
||||||
|
</NcActionButton>
|
||||||
|
</NcActions>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NcActions, NcActionButton } from '@nextcloud/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CalendarPickerHeader',
|
||||||
|
components: {
|
||||||
|
NcActions,
|
||||||
|
NcActionButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
calendars: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isDisabled() {
|
||||||
|
return this.isReadOnly || this.calendars.length < 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.event-popover {
|
||||||
|
.calendar-picker-header {
|
||||||
|
width: calc(100% - 79px);
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: -9px;
|
||||||
|
|
||||||
|
.button-vue__text {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-picker-header--readonly button .button-vue__text {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar {
|
||||||
|
.calendar-picker-header {
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: -14px;
|
||||||
|
|
||||||
|
.button-vue__text {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-picker-header--readonly button .button-vue__text {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.calendar-picker-header {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&__picker {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// For some reason the NcActions component behaves weirdly when a calendar is shared
|
||||||
|
// read-only with the user. This is an ugly workaround to fix the width of the button.
|
||||||
|
&--fix-width {
|
||||||
|
width: unset;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(button.button-vue) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep full opacity for disabled buttons
|
||||||
|
&:disabled, :deep(:disabled) {
|
||||||
|
opacity: 1 !important;
|
||||||
|
filter: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
|
||||||
|
&__dot {
|
||||||
|
$dot-size: 16px;
|
||||||
|
width: $dot-size;
|
||||||
|
height: $dot-size;
|
||||||
|
border-radius: $dot-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,7 +21,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="invitation-response-buttons">
|
<div class="invitation-response-buttons"
|
||||||
|
:class="{ 'invitation-response-buttons--grow': growHorizontally }">
|
||||||
<NcButton v-if="!isAccepted"
|
<NcButton v-if="!isAccepted"
|
||||||
type="primary"
|
type="primary"
|
||||||
class="invitation-response-buttons__button"
|
class="invitation-response-buttons__button"
|
||||||
|
@ -87,6 +88,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
growHorizontally: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -165,11 +170,16 @@ export default {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.invitation-response-buttons {
|
.invitation-response-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
justify-content: flex-end;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
&__button {
|
&--grow {
|
||||||
flex: 1 auto;
|
width: 100%;
|
||||||
|
|
||||||
|
.invitation-response-buttons__button {
|
||||||
|
flex: 1 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,7 +24,13 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="!hideIfEmpty || !isListEmpty" class="invitees-list">
|
||||||
|
<div v-if="showHeader" class="invitees-list__header">
|
||||||
|
<AccountMultipleIcon :size="20" />
|
||||||
|
<b>{{ t('calendar', 'Attendees') }}</b>
|
||||||
|
{{ statusHeader }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<InviteesListSearch v-if="!isReadOnly && !isSharedWithMe && hasUserEmailAddress"
|
<InviteesListSearch v-if="!isReadOnly && !isSharedWithMe && hasUserEmailAddress"
|
||||||
:already-invited-emails="alreadyInvitedEmails"
|
:already-invited-emails="alreadyInvitedEmails"
|
||||||
:organizer="calendarObjectInstance.organizer"
|
:organizer="calendarObjectInstance.organizer"
|
||||||
|
@ -32,22 +38,24 @@
|
||||||
<OrganizerListItem v-if="hasOrganizer"
|
<OrganizerListItem v-if="hasOrganizer"
|
||||||
:is-read-only="isReadOnly || isSharedWithMe"
|
:is-read-only="isReadOnly || isSharedWithMe"
|
||||||
:organizer="calendarObjectInstance.organizer" />
|
:organizer="calendarObjectInstance.organizer" />
|
||||||
<InviteesListItem v-for="invitee in inviteesWithoutOrganizer"
|
<InviteesListItem v-for="invitee in limitedInviteesWithoutOrganizer"
|
||||||
:key="invitee.email"
|
:key="invitee.email"
|
||||||
:attendee="invitee"
|
:attendee="invitee"
|
||||||
:is-read-only="isReadOnly || isSharedWithMe"
|
:is-read-only="isReadOnly || isSharedWithMe"
|
||||||
:organizer-display-name="organizerDisplayName"
|
:organizer-display-name="organizerDisplayName"
|
||||||
:members="invitee.members"
|
:members="invitee.members"
|
||||||
@remove-attendee="removeAttendee" />
|
@remove-attendee="removeAttendee" />
|
||||||
<NoAttendeesView v-if="isReadOnly && isListEmpty"
|
<div v-if="limit > 0 && inviteesWithoutOrganizer.length > limit"
|
||||||
:message="noInviteesMessage" />
|
class="invitees-list__more">
|
||||||
<NoAttendeesView v-if="!isReadOnly && isListEmpty && hasUserEmailAddress"
|
{{ n('calendar', '%n more guest', '%n more guests', inviteesWithoutOrganizer.length - limit) }}
|
||||||
:message="noInviteesMessage" />
|
</div>
|
||||||
<NoAttendeesView v-if="isSharedWithMe"
|
<NoAttendeesView v-if="isReadOnly && isSharedWithMe && !hideErrors"
|
||||||
:message="noOwnerMessage" />
|
:message="noOwnerMessage" />
|
||||||
<OrganizerNoEmailError v-if="!isReadOnly && isListEmpty && !hasUserEmailAddress" />
|
<NoAttendeesView v-else-if="isReadOnly && isListEmpty && hasUserEmailAddress"
|
||||||
|
:message="noInviteesMessage" />
|
||||||
|
<OrganizerNoEmailError v-else-if="!isReadOnly && isListEmpty && !hasUserEmailAddress && !hideErrors" />
|
||||||
|
|
||||||
<div class="invitees-list-button-group">
|
<div v-if="!hideButtons" class="invitees-list-button-group">
|
||||||
<NcButton v-if="isCreateTalkRoomButtonVisible"
|
<NcButton v-if="isCreateTalkRoomButtonVisible"
|
||||||
class="invitees-list-button-group__button"
|
class="invitees-list-button-group__button"
|
||||||
:disabled="isCreateTalkRoomButtonDisabled"
|
:disabled="isCreateTalkRoomButtonDisabled"
|
||||||
|
@ -86,6 +94,7 @@ import {
|
||||||
showError,
|
showError,
|
||||||
} from '@nextcloud/dialogs'
|
} from '@nextcloud/dialogs'
|
||||||
import { organizerDisplayName, removeMailtoPrefix } from '../../../utils/attendee.js'
|
import { organizerDisplayName, removeMailtoPrefix } from '../../../utils/attendee.js'
|
||||||
|
import AccountMultipleIcon from 'vue-material-design-icons/AccountMultiple.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InviteesList',
|
name: 'InviteesList',
|
||||||
|
@ -97,6 +106,7 @@ export default {
|
||||||
InviteesListItem,
|
InviteesListItem,
|
||||||
InviteesListSearch,
|
InviteesListSearch,
|
||||||
OrganizerListItem,
|
OrganizerListItem,
|
||||||
|
AccountMultipleIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
|
@ -111,11 +121,32 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
showHeader: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hideIfEmpty: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
hideButtons: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
hideErrors: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
creatingTalkRoom: false,
|
creatingTalkRoom: false,
|
||||||
showFreeBusyModel: false,
|
showFreeBusyModel: false,
|
||||||
|
recentAttendees: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -146,8 +177,12 @@ export default {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* All invitees except the organizer.
|
||||||
|
*
|
||||||
|
* @return {object[]}
|
||||||
|
*/
|
||||||
inviteesWithoutOrganizer() {
|
inviteesWithoutOrganizer() {
|
||||||
|
|
||||||
if (!this.calendarObjectInstance.organizer) {
|
if (!this.calendarObjectInstance.organizer) {
|
||||||
return this.invitees
|
return this.invitees
|
||||||
}
|
}
|
||||||
|
@ -171,6 +206,25 @@ export default {
|
||||||
return attendee.uri !== this.calendarObjectInstance.organizer.uri
|
return attendee.uri !== this.calendarObjectInstance.organizer.uri
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* All invitees except the organizer limited by the limit prop.
|
||||||
|
* If the limit prop is 0 all invitees except the organizer are returned.
|
||||||
|
*
|
||||||
|
* @return {object[]}
|
||||||
|
*/
|
||||||
|
limitedInviteesWithoutOrganizer() {
|
||||||
|
const filteredInvitees = this.inviteesWithoutOrganizer
|
||||||
|
|
||||||
|
if (this.limit) {
|
||||||
|
const limit = this.hasOrganizer ? this.limit - 1 : this.limit
|
||||||
|
return filteredInvitees
|
||||||
|
// Push newly added attendees to the top of the list
|
||||||
|
.toSorted((a, b) => this.recentAttendees.indexOf(b.uri) - this.recentAttendees.indexOf(a.uri))
|
||||||
|
.slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredInvitees
|
||||||
|
},
|
||||||
isOrganizer() {
|
isOrganizer() {
|
||||||
return this.calendarObjectInstance.organizer !== null
|
return this.calendarObjectInstance.organizer !== null
|
||||||
&& this.$store.getters.getCurrentUserPrincipal !== null
|
&& this.$store.getters.getCurrentUserPrincipal !== null
|
||||||
|
@ -221,6 +275,18 @@ export default {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
statusHeader() {
|
||||||
|
if (!this.isReadOnly) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.t('calendar', '{invitedCount} invited, {confirmedCount} confirmed', {
|
||||||
|
invitedCount: this.inviteesWithoutOrganizer.length,
|
||||||
|
confirmedCount: this.inviteesWithoutOrganizer
|
||||||
|
.filter((attendee) => attendee.participationStatus === 'ACCEPTED')
|
||||||
|
.length,
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addAttendee({ commonName, email, calendarUserType, language, timezoneId, member }) {
|
addAttendee({ commonName, email, calendarUserType, language, timezoneId, member }) {
|
||||||
|
@ -237,6 +303,7 @@ export default {
|
||||||
organizer: this.$store.getters.getCurrentUserPrincipal,
|
organizer: this.$store.getters.getCurrentUserPrincipal,
|
||||||
member,
|
member,
|
||||||
})
|
})
|
||||||
|
this.recentAttendees.push(email)
|
||||||
},
|
},
|
||||||
removeAttendee(attendee) {
|
removeAttendee(attendee) {
|
||||||
// Remove attendee from participating group
|
// Remove attendee from participating group
|
||||||
|
@ -256,6 +323,7 @@ export default {
|
||||||
calendarObjectInstance: this.calendarObjectInstance,
|
calendarObjectInstance: this.calendarObjectInstance,
|
||||||
attendee,
|
attendee,
|
||||||
})
|
})
|
||||||
|
this.recentAttendees = this.recentAttendees.filter((a) => a.uri !== attendee.email)
|
||||||
},
|
},
|
||||||
openFreeBusy() {
|
openFreeBusy() {
|
||||||
this.showFreeBusyModel = true
|
this.showFreeBusyModel = true
|
||||||
|
@ -304,19 +372,35 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.invitees-list-button-group {
|
.invitees-list {
|
||||||
display: flex;
|
margin-top: 12px;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invitees-list-button-group__button {
|
&__header {
|
||||||
flex: 1 0 200px;
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 5px 5px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .button-vue__text {
|
&__more {
|
||||||
white-space: unset !important;
|
padding: 15px 0 0 46px;
|
||||||
overflow: unset !important;
|
font-weight: bold;
|
||||||
text-overflow: unset !important;
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invitees-list-button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
flex: 1 0 200px;
|
||||||
|
|
||||||
|
:deep(.button-vue__text) {
|
||||||
|
white-space: unset !important;
|
||||||
|
overflow: unset !important;
|
||||||
|
text-overflow: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,7 +21,9 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="display" class="property-select">
|
<div v-if="display"
|
||||||
|
class="property-select"
|
||||||
|
:class="{ 'property-select--readonly': isReadOnly }">
|
||||||
<div class="property-select__input"
|
<div class="property-select__input"
|
||||||
:class="{ 'property-select__input--readonly-calendar-picker': isReadOnly }">
|
:class="{ 'property-select__input--readonly-calendar-picker': isReadOnly }">
|
||||||
<CalendarPicker v-if="!isReadOnly"
|
<CalendarPicker v-if="!isReadOnly"
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="property-color">
|
<div class="property-color" :class="{ 'property-color--readonly': isReadOnly }">
|
||||||
<component :is="icon"
|
<component :is="icon"
|
||||||
:size="20"
|
:size="20"
|
||||||
:name="readableName"
|
:name="readableName"
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="display" class="property-select">
|
<div v-if="display"
|
||||||
|
class="property-select"
|
||||||
|
:class="{ 'property-select--readonly': isReadOnly }">
|
||||||
<component :is="icon"
|
<component :is="icon"
|
||||||
:size="20"
|
:size="20"
|
||||||
:name="readableName"
|
:name="readableName"
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="display" class="property-text">
|
<div v-if="display"
|
||||||
|
class="property-text"
|
||||||
|
:class="{ 'property-text--readonly': isReadOnly }">
|
||||||
<component :is="icon"
|
<component :is="icon"
|
||||||
:size="20"
|
:size="20"
|
||||||
:name="readableName"
|
:name="readableName"
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="property-title">
|
<div class="property-title" :class="{ 'property-title--readonly': isReadOnly }">
|
||||||
<div class="property-title__input"
|
<div class="property-title__input"
|
||||||
:class="{ 'property-title__input--readonly': isReadOnly }">
|
:class="{ 'property-title__input--readonly': isReadOnly }">
|
||||||
<input v-if="!isReadOnly"
|
<input v-if="!isReadOnly"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
-
|
-
|
||||||
- @author Georg Ehrke <oc.list@georgehrke.com>
|
- @author Georg Ehrke <oc.list@georgehrke.com>
|
||||||
- @author Jakob Röhrl <jakob.roehrl@web.de>
|
- @author Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
- @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||||
-
|
-
|
||||||
- @license AGPL-3.0-or-later
|
- @license AGPL-3.0-or-later
|
||||||
-
|
-
|
||||||
|
@ -23,7 +24,12 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="property-title-time-picker">
|
<div class="property-title-time-picker"
|
||||||
|
:class="{ 'property-title-time-picker--readonly': isReadOnly }">
|
||||||
|
<CalendarIcon v-if="isReadOnly"
|
||||||
|
class="property-title-time-picker__icon"
|
||||||
|
:size="20" />
|
||||||
|
|
||||||
<div v-if="!isReadOnly"
|
<div v-if="!isReadOnly"
|
||||||
class="property-title-time-picker__time-pickers">
|
class="property-title-time-picker__time-pickers">
|
||||||
<DatePicker :date="startDate"
|
<DatePicker :date="startDate"
|
||||||
|
@ -46,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isReadOnly"
|
<div v-if="isReadOnly"
|
||||||
class="property-title-time-picker__time-pickers property-title-time-picker__time-pickers--readonly">
|
class="property-title-time-picker__time-pickers property-title-time-picker__time-pickers--readonly">
|
||||||
<div class="property-title-time-picker-read-only-wrapper">
|
<div class="property-title-time-picker-read-only-wrapper property-title-time-picker-read-only-wrapper--start-date">
|
||||||
<div class="property-title-time-picker-read-only-wrapper__label">
|
<div class="property-title-time-picker-read-only-wrapper__label">
|
||||||
{{ formattedStart }}
|
{{ formattedStart }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,28 +61,26 @@
|
||||||
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
|
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
|
||||||
:size="20" />
|
:size="20" />
|
||||||
</div>
|
</div>
|
||||||
<div class="property-title-time-picker-read-only-wrapper">
|
<template v-if="!isAllDayOneDayEvent">
|
||||||
<div class="property-title-time-picker-read-only-wrapper__label">
|
<div>-</div>
|
||||||
{{ formattedEnd }}
|
<div class="property-title-time-picker-read-only-wrapper property-title-time-picker-read-only-wrapper--end-date">
|
||||||
|
<div class="property-title-time-picker-read-only-wrapper__label">
|
||||||
|
{{ formattedEnd }}
|
||||||
|
</div>
|
||||||
|
<IconTimezone v-if="!isAllDay"
|
||||||
|
:title="endTimezone"
|
||||||
|
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
|
||||||
|
:size="20" />
|
||||||
</div>
|
</div>
|
||||||
<IconTimezone v-if="!isAllDay"
|
</template>
|
||||||
:name="endTimezone"
|
|
||||||
:class="{ 'highlighted-timezone-icon': highlightStartTimezone }"
|
|
||||||
:size="20" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!isReadOnly" class="property-title-time-picker__all-day">
|
<div v-if="!isReadOnly" class="property-title-time-picker__all-day">
|
||||||
<input id="allDay"
|
<NcCheckboxRadioSwitch :checked="isAllDay"
|
||||||
:checked="isAllDay"
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox"
|
|
||||||
:disabled="!canModifyAllDay"
|
:disabled="!canModifyAllDay"
|
||||||
@change="toggleAllDay">
|
@update:checked="toggleAllDay">
|
||||||
<label v-tooltip="allDayTooltip"
|
|
||||||
for="allDay">
|
|
||||||
{{ $t('calendar', 'All day') }}
|
{{ $t('calendar', 'All day') }}
|
||||||
</label>
|
</NcCheckboxRadioSwitch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -85,13 +89,17 @@
|
||||||
import moment from '@nextcloud/moment'
|
import moment from '@nextcloud/moment'
|
||||||
import DatePicker from '../../Shared/DatePicker.vue'
|
import DatePicker from '../../Shared/DatePicker.vue'
|
||||||
import IconTimezone from 'vue-material-design-icons/Web.vue'
|
import IconTimezone from 'vue-material-design-icons/Web.vue'
|
||||||
|
import CalendarIcon from 'vue-material-design-icons/Calendar.vue'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PropertyTitleTimePicker',
|
name: 'PropertyTitleTimePicker',
|
||||||
components: {
|
components: {
|
||||||
DatePicker,
|
DatePicker,
|
||||||
IconTimezone,
|
IconTimezone,
|
||||||
|
CalendarIcon,
|
||||||
|
NcCheckboxRadioSwitch,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
/**
|
/**
|
||||||
|
@ -195,16 +203,10 @@ export default {
|
||||||
*/
|
*/
|
||||||
formattedStart() {
|
formattedStart() {
|
||||||
if (this.isAllDay) {
|
if (this.isAllDay) {
|
||||||
return this.$t('calendar', 'from {startDate}', {
|
return moment(this.startDate).locale(this.locale).format('ll')
|
||||||
startDate: moment(this.startDate).locale(this.locale).format('L'),
|
|
||||||
endDate: moment(this.endDate).locale(this.locale).format('L'),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('calendar', 'from {startDate} at {startTime}', {
|
return moment(this.startDate).locale(this.locale).format('lll')
|
||||||
startDate: moment(this.startDate).locale(this.locale).format('L'),
|
|
||||||
startTime: moment(this.startDate).locale(this.locale).format('LT'),
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -212,15 +214,10 @@ export default {
|
||||||
*/
|
*/
|
||||||
formattedEnd() {
|
formattedEnd() {
|
||||||
if (this.isAllDay) {
|
if (this.isAllDay) {
|
||||||
return this.$t('calendar', 'to {endDate}', {
|
return moment(this.endDate).locale(this.locale).format('ll')
|
||||||
endDate: moment(this.endDate).locale(this.locale).format('L'),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('calendar', 'to {endDate} at {endTime}', {
|
return moment(this.endDate).locale(this.locale).format('lll')
|
||||||
endDate: moment(this.endDate).locale(this.locale).format('L'),
|
|
||||||
endTime: moment(this.endDate).locale(this.locale).format('LT'),
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
|
@ -234,6 +231,17 @@ export default {
|
||||||
highlightEndTimezone() {
|
highlightEndTimezone() {
|
||||||
return this.endTimezone !== this.userTimezone
|
return this.endTimezone !== this.userTimezone
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* True if the event is an all day event, starts and ends on the same date
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isAllDayOneDayEvent() {
|
||||||
|
return this.isAllDay
|
||||||
|
&& this.startDate.getDate() === this.endDate.getDate()
|
||||||
|
&& this.startDate.getMonth() === this.endDate.getMonth()
|
||||||
|
&& this.startDate.getFullYear() === this.endDate.getFullYear()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="property-repeat">
|
<div class="property-repeat" :class="{ 'property-repeat--readonly': isReadOnly }">
|
||||||
<div class="property-repeat__summary">
|
<div class="property-repeat__summary">
|
||||||
<RepeatIcon class="property-repeat__summary__icon"
|
<RepeatIcon class="property-repeat__summary__icon"
|
||||||
:name="$t('calendar', 'Repeat')"
|
:name="$t('calendar', 'Repeat')"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{{ recurrenceRule | formatRecurrenceRule(locale) }}
|
{{ recurrenceRule | formatRecurrenceRule(locale) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ $t('calendar', 'No recurrence') }}
|
{{ $t('calendar', 'Does not repeat') }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|