Resolve merge conflicts by accepting PR #6131 changes

Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-02-07 16:30:08 +00:00
parent dc0b68ee80
commit 97dd5d2064
257 changed files with 9483 additions and 14103 deletions

View file

@ -6,75 +6,80 @@
font-weight: bold;
}
.attachment-gallery {
display: flex;
flex-direction: column;
display: grid;
grid-auto-flow: row;
}
.attachment-item {
display: flex;
flex-direction: row;
display: grid;
grid-template-columns: 10ch auto;
align-items: center;
margin-top: 16px;
grid-template-rows: repeat(auto-fit, minmax(1.5lh, auto));
justify-content: stretch;
gap: 2ch;
padding: 2ch;
border-radius: 0.6ch;
}
.attachment-item:hover {
background: #e0e0e0;
}
.attachment-thumbnail-container {
display: block;
width: 150px;
min-width: 150px;
max-height: 150px;
padding-right: 16px;
.attachment-details-container {
display: flex;
flex: 1;
}
.attachment-thumbnail-container {
display: flex;
flex: 1;
position: relative;
}
.attachment-thumbnail {
max-width: 150px;
max-height: 150px;
min-height: 2em;
/* more deterministic outcome */
aspect-ratio: 1/1;
object-fit: cover;
max-width: 100%;
cursor: pointer;
border-radius: 0.4ch;
}
.attachment-thumbnail-text {
min-height: 2em;
display: flex;
align-items: center;
justify-content: center;
font-size: 2em;
cursor: pointer;
flex: 1;
text-align: center;
border-radius: 2px;
border: 1px solid #ccc;
border-radius: 5px;
}
.attachment-details-container {
display: block;
flex-grow: 1;
}
.attachment-details {
display: flex;
justify-content: space-between;
margin-right: 25px; /* Make sure the icons are not to far to the right */
flex: 1;
gap: 0.5ch;
align-items: center;
}
.attachment-actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.5ch;
}
.attachment-actions a {
margin-left: 16px;
}
.attachment-actions a:first-child {
margin-left: 0;
body.mobile-mode .attachment-actions {
flex-direction: column;
gap: 0;
}
.add-attachment {
border: 1px dashed #555;
border-radius: .5ch;
cursor: pointer;
aspect-ratio: 1/1;
height: 1.5lh;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #555;
border-radius: 5px;
padding: 10px;
cursor: pointer;
margin-top: 16px;
}
.icon {
font-size: 1.5em;
cursor: pointer;
margin-left: 10px;
}
.icon:hover {
color: #666;
@ -95,26 +100,25 @@
height: 100%;
}
#viewer-top-bar {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
padding: 16px;
display: grid;
grid-template-columns: 1fr auto;
justify-content: center;
justify-items: center;
font-size: 2rem;
padding: 0.3lh 0.5ch;
}
#attachment-name {
color: white;
font-size: 1.5em;
max-width: calc(
100% - 50px
); /* Make sure the name does not overlap the close button */
text-overflow: ellipsis;
overflow: hidden;
}
#viewer-close {
color: white;
cursor: pointer;
font-size: 4em;
position: absolute;
right: 50px;
top: 16px;
font-size: 2em;
}
/* Upload progress indicators for drag-and-drop uploads */
@ -122,30 +126,24 @@
.card-details-upload-progress {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 12px;
margin: 8px 0;
font-size: 14px;
border-radius: 0.4ch;
}
.upload-progress-header {
display: flex;
align-items: center;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
}
.upload-progress-header i {
margin-right: 8px;
color: #007bff;
}
.upload-progress-item {
display: flex;
flex-direction: column;
margin-bottom: 8px;
padding: 8px;
background: white;
border-radius: 3px;
border: 1px solid #dee2e6;
@ -158,22 +156,17 @@
.upload-progress-filename {
font-weight: 500;
margin-bottom: 4px;
color: #495057;
word-break: break-all;
}
.upload-progress-bar {
width: 100%;
height: 6px;
background: #e9ecef;
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
}
.upload-progress-fill {
height: 100%;
background: linear-gradient(90deg, #007bff, #0056b3);
transition: width 0.3s ease;
border-radius: 3px;
@ -187,7 +180,6 @@
.upload-progress-success {
display: flex;
align-items: center;
font-size: 12px;
font-weight: 500;
}
@ -199,47 +191,6 @@
color: #28a745;
}
.upload-progress-error i,
.upload-progress-success i {
margin-right: 4px;
}
/* Minicard specific styles */
.minicard-upload-progress {
margin: 4px 0;
padding: 8px;
font-size: 12px;
}
.minicard-upload-progress .upload-progress-item {
padding: 6px;
margin-bottom: 6px;
}
.minicard-upload-progress .upload-progress-filename {
font-size: 11px;
}
/* Card details specific styles */
.card-details-upload-progress {
margin: 12px 0;
padding: 16px;
}
.card-details-upload-progress .upload-progress-header {
font-size: 16px;
margin-bottom: 12px;
}
.card-details-upload-progress .upload-progress-item {
padding: 12px;
margin-bottom: 10px;
}
.card-details-upload-progress .upload-progress-filename {
font-size: 14px;
}
/* Drag over state for minicards */
.minicard.is-dragging-over {
border: 2px dashed #007bff !important;
@ -256,7 +207,6 @@
color: white;
cursor: pointer;
align-self: center;
margin: 0 20px;
}
#prev-attachment {
font-size: 4em;
@ -322,7 +272,6 @@
position: absolute;
bottom: 2.2em;
font-size: 1.6em;
padding: 16px;
}
#prev-attachment {
left: 0;
@ -356,19 +305,10 @@
margin-top: 20%;
width: 100%;
}
.attachment-thumbnail-container {
width: 100px;
min-width: 100px;
}
.attachment-thumbnail {
max-width: 100px;
}
.attachment-details {
flex-direction: column;
margin-right: 0px;
}
.attachment-actions {
flex-direction: row;
margin-top: 10px;
}
}

View file

@ -49,15 +49,11 @@ template(name="attachmentViewer")
i.fa.fa-caret-right#next-attachment
template(name="attachmentGallery")
if canModifyCard
a.add-attachment.js-add-attachment
i.fa.fa-plus
.attachment-gallery
if canModifyCard
a.attachment-item.add-attachment.js-add-attachment
i.fa.fa-plus
each attachments
.attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}")
.attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
if link

View file

@ -1,6 +1,5 @@
.card-date {
display: block;
border-radius: 4px;
padding: 1px 3px;
background-color: #dbdbdb;
}
@ -106,6 +105,10 @@
background-color: #e6c200;
}
.date a:has(time) {
text-decoration: none;
}
.card-date.end-date {
background-color: #ffb3b3; /* Light red for end */
color: #000; /* Black text for end */
@ -139,6 +142,6 @@
}
.customfield-date {
display: block;
border-radius: 4px;
border-radius: 0.4ch;
padding: 1px 3px;
}

View file

@ -1,24 +1,24 @@
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import {
formatDateTime,
formatDate,
import {
formatDateTime,
formatDate,
formatDateByUserPreference,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar,
diff
} from '/imports/lib/dateUtils';
@ -143,7 +143,7 @@ class CardReceivedDate extends CardDate {
const startAt = this.data().getStart();
const theDate = this.date.get();
const now = this.now.get();
// Received date logic: if received date is after start, due, or end dates, it's overdue
if (
(startAt && isAfter(theDate, startAt)) ||
@ -187,7 +187,7 @@ class CardStartDate extends CardDate {
const endAt = this.data().getEnd();
const theDate = this.date.get();
const now = this.now.get();
// Start date logic: if start date is after due or end dates, it's overdue
if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) {
classes += 'overdue';
@ -230,7 +230,7 @@ class CardDueDate extends CardDate {
const endAt = this.data().getEnd();
const theDate = this.date.get();
const now = this.now.get();
// If there's an end date and it's before the due date, task is completed early
if (endAt && isBefore(endAt, theDate)) {
classes += 'completed-early';
@ -242,7 +242,7 @@ class CardDueDate extends CardDate {
// Due date logic based on current time
else {
const daysDiff = diff(theDate, now, 'days');
if (daysDiff < 0) {
// Due date is in the past - overdue
classes += 'overdue';
@ -254,7 +254,7 @@ class CardDueDate extends CardDate {
classes += 'not-due';
}
}
return classes;
}
@ -286,7 +286,7 @@ class CardEndDate extends CardDate {
let classes = 'end-date ';
const dueAt = this.data().getDue();
const theDate = this.date.get();
if (!dueAt) {
// No due date set - just show as completed
classes += 'completed';
@ -371,7 +371,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
template() {
return 'minicardReceivedDate';
}
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
@ -383,7 +383,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
template() {
return 'minicardStartDate';
}
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
@ -395,7 +395,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
template() {
return 'minicardDueDate';
}
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
@ -407,7 +407,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
template() {
return 'minicardEndDate';
}
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
@ -419,7 +419,7 @@ CardCustomFieldDate.register('cardCustomFieldDate');
template() {
return 'minicardCustomFieldDate';
}
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';

View file

@ -1,16 +1,12 @@
.new-description {
position: relative;
margin: 0 0 20px 0;
flex: 1;
}
.new-description.is-open .helper {
display: inline-block;
}
.new-description.is-open textarea {
min-height: 100px;
.new-description textarea {
min-height: 1lh;
color: #4d4d4d;
cursor: auto;
overflow: hidden;
word-wrap: break-word;
overflow-wrap: break-word;
}
.new-description .too-long {
margin-top: 8px;
@ -19,9 +15,6 @@
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
height: 36px;
margin: 4px 4px 6px 0;
padding: 9px 11px;
width: 100%;
}
.new-description textarea:hover,
@ -39,16 +32,12 @@
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
color: #8c8c8c;
height: 36px;
margin: 4px 4px 6px 0;
width: 92%;
}
.description-item:hover {
background: #e0e0e0;
}
.description-item.add-description {
display: flex;
margin: 5px;
}
.description-item.add-description a {
display: block;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,25 +2,25 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { DatePicker } from '/client/lib/datepicker';
import {
formatDateTime,
formatDate,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
import {
formatDateTime,
formatDate,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
} from '/imports/lib/dateUtils';
import Cards from '/models/cards';
import Boards from '/models/boards';
@ -35,6 +35,7 @@ import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlane
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
import { handleFileUpload } from './attachments';
import uploadProgressManager from '../../lib/uploadProgressManager';
import PopupComponent from '../main/popup';
const subManager = new SubsManager();
const { calculateIndexData } = Utils;
@ -60,19 +61,8 @@ BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Utils.getCurrentBoard();
this.isLoaded = new ReactiveVar(false);
this.dep = new Tracker.Dependency();
if (this.parentComponent() && this.parentComponent().parentComponent()) {
const boardBody = this.parentComponent().parentComponent();
//in Miniview parent is Board, not BoardBody.
if (boardBody !== null) {
// Only show overlay in mobile mode, not in desktop mode
const isMobile = Utils.getMobileMode();
if (isMobile) {
boardBody.showOverlay.set(true);
}
boardBody.mouseHasEnterCardDetails = false;
}
}
this.calculateNextPeak();
Meteor.subscribe('unsaved-edits');
@ -85,6 +75,18 @@ BlazeComponent.extendComponent({
// });
},
onRendered() {
const boardOverlay = document.getElementsByClassName('board-overlay')?.[0];
this.boardBody = BlazeComponent.getComponentForElement(boardOverlay);
if (this.boardBody) {
this.boardBody.mouseHasEnterCardDetails = false;
}
const isMobile = Utils.getMobileMode();
if (isMobile && Session.get('currentCard')) {
//this.boardBody?.showOverlay.set(true);
}
},
isWatching() {
const card = this.currentData();
if (!card || typeof card.findWatcher !== 'function') return false;
@ -95,8 +97,8 @@ BlazeComponent.extendComponent({
return ReactiveCache.getCurrentUser().hasCustomFieldsGrid();
},
cardMaximized() {
this.dep.depend();
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
},
@ -175,6 +177,11 @@ BlazeComponent.extendComponent({
},
onRendered() {
// #FIXME hackish; if accepted tweak static funcs
if (this.cardMaximized()) {
PopupComponent.maximize({target: this.firstNode()});
}
if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) {
// Send Webhook but not create Activities records ---
const card = this.currentData();
@ -209,11 +216,11 @@ BlazeComponent.extendComponent({
}
const $checklistsDom = this.$('.card-checklist-items');
const sortableSelector = Utils.isMiniScreen() ? '.checklist-handle' : '.checklist-title';
$checklistsDom.sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.checklist-title',
handle: sortableSelector,
items: '.js-checklist',
placeholder: 'checklist placeholder',
distance: 7,
@ -282,6 +289,8 @@ BlazeComponent.extendComponent({
return ReactiveCache.getCurrentUser()?.isBoardMember();
}
// Disable sorting if the current user is not a board member
this.autorun(() => {
const disabled = !userIsMember();
@ -289,10 +298,7 @@ BlazeComponent.extendComponent({
$checklistsDom.data('uiSortable') ||
$checklistsDom.data('sortable')
) {
$checklistsDom.sortable('option', 'disabled', disabled);
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$checklistsDom.sortable({ handle: '.checklist-handle' });
}
$checklistsDom.sortable('option', 'handle', sortableSelector);
}
if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
$subtasksDom.sortable('option', 'disabled', disabled);
@ -301,11 +307,7 @@ BlazeComponent.extendComponent({
},
onDestroyed() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not board body.
if (parentComponent === null) return;
parentComponent.showOverlay.set(false);
this.boardBody?.showOverlay.set(false);
},
events() {
@ -332,59 +334,11 @@ BlazeComponent.extendComponent({
},
'mousedown .js-card-drag-handle'(event) {
event.preventDefault();
const $card = $(event.target).closest('.card-details');
const startX = event.clientX;
const startY = event.clientY;
const startLeft = $card.offset().left;
const startTop = $card.offset().top;
const onMouseMove = (e) => {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
$card.css({
left: startLeft + deltaX + 'px',
top: startTop + deltaY + 'px'
});
};
const onMouseUp = () => {
$(document).off('mousemove', onMouseMove);
$(document).off('mouseup', onMouseUp);
};
$(document).on('mousemove', onMouseMove);
$(document).on('mouseup', onMouseUp);
PopupComponent.toFront(event);
},
'mousedown .js-card-title-drag-handle'(event) {
// Allow dragging from title for ReadOnly users
// Don't interfere with text selection
if (event.target.tagName === 'A' || $(event.target).closest('a').length > 0) {
return; // Don't drag if clicking on links
}
'click .js-card-send-to-back'(event) {
event.preventDefault();
const $card = $(event.target).closest('.card-details');
const startX = event.clientX;
const startY = event.clientY;
const startLeft = $card.offset().left;
const startTop = $card.offset().top;
const onMouseMove = (e) => {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
$card.css({
left: startLeft + deltaX + 'px',
top: startTop + deltaY + 'px'
});
};
const onMouseUp = () => {
$(document).off('mousemove', onMouseMove);
$(document).off('mouseup', onMouseUp);
};
$(document).on('mousemove', onMouseMove);
$(document).on('mouseup', onMouseUp);
PopupComponent.toBack(event);
},
'click .js-close-card-details'() {
// Get board ID from either the card data or current board in session
@ -392,26 +346,21 @@ BlazeComponent.extendComponent({
const boardId = (card && card.boardId) || Utils.getCurrentBoard()._id;
const cardId = card && card._id;
if (boardId) {
// In desktop mode, remove from openCards array
const isMobile = Utils.getMobileMode();
if (!isMobile && cardId) {
const openCards = Session.get('openCards') || [];
const filtered = openCards.filter(id => id !== cardId);
Session.set('openCards', filtered);
// If this was the current card, clear it
if (Session.get('currentCard') === cardId) {
Session.set('currentCard', null);
}
// Don't navigate away in desktop mode - just close the card
return;
if (boardId && cardId) {
const openCards = Session.get('openCards') || [];
const filtered = openCards.filter(id => id !== cardId);
// If this was the current card, clear it
if (openCards.length === filtered.length) {
Session.set('currentCard', null);
}
else {
Session.set('currentCard', filtered[0]);
}
Session.set('openCards', filtered);
// Mobile mode: Clear the current card session to close the card
Session.set('currentCard', null);
// Navigate back to board without card
// Navigate back to board without card: must be done at the time of writing
// otherwise the route for the card is disabled until another
// card is opened
const board = ReactiveCache.getBoard(boardId);
if (board) {
FlowRouter.go('board', {
@ -434,34 +383,6 @@ BlazeComponent.extendComponent({
Meteor.call('changeDateFormat', dateFormat);
},
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
// Mobile: switch to desktop popup view (maximize)
'click .js-mobile-switch-to-desktop'(event) {
event.preventDefault();
// Switch global mode to desktop so the card appears as desktop popup
Utils.setMobileMode(false);
},
'click .js-card-zoom-in'(event) {
event.preventDefault();
const current = Utils.getCardZoom();
const newZoom = Math.min(3.0, current + 0.1);
Utils.setCardZoom(newZoom);
},
'click .js-card-zoom-out'(event) {
event.preventDefault();
const current = Utils.getCardZoom();
const newZoom = Math.max(0.5, current - 0.1);
Utils.setCardZoom(newZoom);
},
'click .js-card-mobile-desktop-toggle'(event) {
event.preventDefault();
const currentMode = Utils.getMobileMode();
Utils.setMobileMode(!currentMode);
},
'click .js-card-mobile-desktop-toggle'(event) {
event.preventDefault();
const currentMode = Utils.getMobileMode();
Utils.setMobileMode(!currentMode);
},
async 'submit .js-card-description'(event) {
event.preventDefault();
const description = this.currentComponent().getValue();
@ -525,7 +446,7 @@ BlazeComponent.extendComponent({
'click .js-add-members': Popup.open('cardMembers'),
'click .js-assignee': Popup.open('cardAssignee'),
'click .js-add-assignees': Popup.open('cardAssignees'),
'click .js-add-labels': Popup.open('cardLabels'),
'click .js-add-labels'(event) {Popup.open('cardLabels')(event, { dataContextIfCurrentDataIsUndefined: this.currentData() })},
'click .js-received-date': Popup.open('editCardReceivedDate'),
'click .js-start-date': Popup.open('editCardStartDate'),
'click .js-due-date': Popup.open('editCardDueDate'),
@ -534,12 +455,10 @@ BlazeComponent.extendComponent({
'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'mouseenter .js-card-details'() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody.
if (parentComponent === null) return;
parentComponent.showOverlay.set(true);
parentComponent.mouseHasEnterCardDetails = true;
if (this.boardBody) {
this.boardBody.showOverlay.set(true);
this.boardBody.mouseHasEnterCardDetails = true;
}
},
'mousedown .js-card-details'() {
Session.set('cardDetailsIsDragging', false);
@ -560,13 +479,13 @@ BlazeComponent.extendComponent({
'click #toggleCustomFieldsGridButton'() {
Meteor.call('toggleCustomFieldsGrid');
},
'click .js-maximize-card-details'() {
'click .js-maximize-card-details'(e) {
PopupComponent.maximize(e);
Meteor.call('toggleCardMaximized');
autosize($('.card-details'));
},
'click .js-minimize-card-details'() {
'click .js-minimize-card-details'(e) {
PopupComponent.minimize(e);
Meteor.call('toggleCardMaximized');
autosize($('.card-details'));
},
'click .js-vote'(e) {
const forIt = $(e.target).hasClass('js-vote-positive');
@ -737,16 +656,6 @@ Template.cardDetails.helpers({
return uploadProgressManager.getUploadCountForCard(this._id);
}
});
Template.cardDetailsPopup.onDestroyed(() => {
Session.delete('popupCardId');
Session.delete('popupCardBoardId');
});
Template.cardDetailsPopup.helpers({
popupCard() {
const ret = Utils.getPopupCard();
return ret;
},
});
BlazeComponent.extendComponent({
template() {
@ -883,9 +792,7 @@ Template.cardDetailsActionsPopup.events({
'click .js-toggle-watch-card'() {
const currentCard = this;
const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
if (!err && ret) Popup.close();
});
Meteor.call('watch', 'card', currentCard._id, level)
},
'click .js-toggle-show-list-on-minicard'() {
const currentCard = this;
@ -896,9 +803,6 @@ Template.cardDetailsActionsPopup.events({
});
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-edit-card-title'));
},
events() {
return [
{
@ -979,10 +883,6 @@ const filterMembers = (filterTerm) => {
return members;
}
Template.editCardRequesterForm.onRendered(function () {
autosize(this.$('.js-edit-card-requester'));
});
Template.editCardRequesterForm.events({
'keydown .js-edit-card-requester'(event) {
// If enter key was pressed, submit the data
@ -992,10 +892,6 @@ Template.editCardRequesterForm.events({
},
});
Template.editCardAssignerForm.onRendered(function () {
autosize(this.$('.js-edit-card-assigner'));
});
Template.editCardAssignerForm.events({
'keydown .js-edit-card-assigner'(event) {
// If enter key was pressed, submit the data
@ -1469,13 +1365,13 @@ BlazeComponent.extendComponent({
'DD/MM/YYYY HH:mm',
'DD-MM-YYYY HH:mm'
];
let parsedDate = null;
for (const format of formats) {
parsedDate = parseDate(dateString, [format], true);
if (parsedDate) break;
}
// Fallback to native Date parsing
if (!parsedDate) {
parsedDate = new Date(dateString);
@ -1721,13 +1617,13 @@ BlazeComponent.extendComponent({
'DD/MM/YYYY HH:mm',
'DD-MM-YYYY HH:mm'
];
let parsedDate = null;
for (const format of formats) {
parsedDate = parseDate(dateString, [format], true);
if (parsedDate) break;
}
// Fallback to native Date parsing
if (!parsedDate) {
parsedDate = new Date(dateString);
@ -1905,9 +1801,6 @@ EscapeActions.register(
() => {
return !Session.equals('currentCard', null);
},
{
noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
},
);
Template.cardAssigneesPopup.onCreated(function () {
@ -1985,3 +1878,16 @@ Template.cardAssigneePopup.events({
},
'click .js-edit-profile': Popup.open('editProfile'),
});
Template.cardDetailsPopup.helpers({
popupArgs() {
return {
name: "cardDetails",
showHeader: false,
closeDOMs: ["click .js-close-card-details"],
followDOM: ".card-details",
handleDOM: ".card-header-middle",
closeVar: "currentCard"
}
},
});

View file

@ -1,6 +1,6 @@
.card-time {
display: block;
border-radius: 4px;
border-radius: 0.4ch;
padding: 1px 3px;
color: #fff;
background-color: #dbdbdb;

View file

@ -4,7 +4,7 @@
textarea.js-add-checklist-item,
textarea.js-edit-checklist-item {
overflow: hidden;
word-wrap: break-word;
overflow-wrap: break-word;
resize: none;
height: 34px;
}
@ -13,7 +13,7 @@ textarea.js-edit-checklist-item {
.js-convert-checklist-item-to-card {
color: #8c8c8c;
text-decoration: underline;
word-wrap: break-word;
overflow-wrap: break-word;
float: right;
padding-top: 6px;
}
@ -25,6 +25,7 @@ textarea.js-edit-checklist-item {
.checklists-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.checklist-progress-bar-container {
display: flex;
@ -35,7 +36,7 @@ textarea.js-edit-checklist-item {
margin-right: 10px;
}
.checklist-progress-bar-container .checklist-progress-bar {
width: 80%;
flex: 1;
height: 10px;
background-color: #e0e0e0;
border-radius: 16px;
@ -47,19 +48,29 @@ textarea.js-edit-checklist-item {
border-radius: 16px;
height: 100%;
}
.checklist-title {
padding: 10px;
.checklist-controls {
display: flex;
gap: 0.25lh;
}
.checklist-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.checklist-title .checkbox {
float: left;
width: 30px;
height: 30px;
font-size: 18px;
line-height: 30px;
}
.checklist-title .title {
font-size: 18px;
line-height: 25px;
.checklist-title p, .title {
font-size: 1em;
line-height: 1;
margin: 0;
}
.checklist-title .checklist-stat {
margin: 0 0.5em;
@ -79,29 +90,31 @@ textarea.js-edit-checklist-item {
bottom: -600px;
right: 0;
}
.checklist {
background: #f7f7f7;
padding: 0.5lh;
margin: 0.5lh 0;
background-color: #f7f7f7;
}
.checklist.placeholder {
background: #ccc;
border-radius: 2px;
}
.checklist.ui-sortable-helper {
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
transform: rotate(4deg);
cursor: grabbing;
}
.checklist-item {
margin: 0 0 0 0.1em;
line-height: 18px;
font-size: 1.1em;
margin-top: 3px;
display: flex;
background: #f7f7f7;
gap: 0.25lh;
opacity: 1;
transition: height 0ms 400ms, opacity 400ms 0ms;
height: auto;
overflow: hidden;
align-items: center;
min-height: 1.5lh;
padding: 0 1ch;
}
.checklist-item.is-checked.invisible {
opacity: 0;
@ -114,26 +127,21 @@ textarea.js-edit-checklist-item {
background: #ccc;
border-radius: 2px;
}
.checklist-item.ui-sortable-helper {
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
transform: rotate(4deg);
cursor: grabbing;
}
.checklist-item:hover {
background-color: #ebebeb;
}
.checklist-item .check-box-container {
padding-right: 10px;
}
.checklist-item .check-box {
margin: 0.1em 0 0 0;
}
.checklist-item .check-box.is-checked {
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
border-bottom: 0.2ch solid #3cb500;
border-right: 0.2ch solid #3cb500;
}
.checklist-item .item-title {
display: flex;
justify-content: start;
flex: 1;
cursor: grab;
}
.checklist-item .item-title.is-checked {
color: #8c8c8c;
@ -141,27 +149,18 @@ textarea.js-edit-checklist-item {
text-decoration: line-through;
}
.checklist-item .item-title .viewer p {
margin-bottom: 2px;
display: block;
word-wrap: break-word;
display: flex;
overflow-wrap: break-word;
max-width: 420px;
}
.checklist-item span.fa.checklistitem-handle {
padding-top: 2px;
padding-right: 10px;
}
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
margin: 0 0 0.5em 1.33em;
padding: 12px 0 0 0;
}
.add-checklist-item {
margin: 0.2em 0 0.5em 1.33em;
}
.add-checklist-item.js-open-inlined-form,
.add-checklist.js-open-inlined-form {
display: block;
width: 50%;
display: inline-block;
}
.add-checklist-item.js-open-inlined-form:hover,
.add-checklist.js-open-inlined-form:hover {
@ -169,25 +168,13 @@ textarea.js-edit-checklist-item {
color: #222;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.add-checklist-top {
/* more space to checklists title */
padding-left: 20px;
/* + is easier clickable */
padding-right: 20px;
}
.add-checklist-top.js-open-inlined-form:hover {
background: #dbdbdb;
color: #222;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
}
.card-details-item-title {
/* max width for adding checklist at top */
width: 100%;
}
.checklist-details-menu {
float: right;
padding: 6px 10px 6px 10px;
}
.edit-controls label.toggle-label {
margin-left: 2px;
}

View file

@ -36,26 +36,31 @@ template(name="checklistDetail")
+editChecklistItemForm(checklist = checklist)
else
.checklist-title
span
if canModifyCard
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
if canModifyCard
h4.title.js-open-inlined-form.is-editable
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
h4.title
if canModifyCard
a.js-open-inlined-form.is-editable(title="{{_ 'moveChecklistPopup-title'}}")
+viewer
= checklist.title
else
+viewer
= checklist.title
else
h4.title
+viewer
.checklist-controls
if canModifyCard
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
if isMiniScreen
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
+viewer
= checklist.title
if $gt finishedPercent 0
.checklist-progress-bar-container
.checklist-progress-text {{finishedPercent}}%
.checklist-progress-bar
//- jumps where checking the first item is not comfortable;
//- so try to show it anytime. also, it helps to separate the checklists.
.checklist-progress-bar-container
.checklist-progress-text {{finishedPercent}}%
.checklist-progress-bar
if $gt finishedPercent 0
.checklist-progress(style="width:{{finishedPercent}}%")
else
.checklist-progress(style="visibility:hidden")
+checklistItems(checklist = checklist card = card)
template(name="checklistDeletePopup")
@ -64,7 +69,7 @@ template(name="checklistDeletePopup")
template(name="addChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
span.copied-tooltip.copied-tooltip-hidden {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
@ -73,16 +78,12 @@ template(name="addChecklistItemForm")
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
span.toggle-switch-desc
| {{_ 'newLineNewItem'}}
if $eq position 'top'
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
| {{_ 'originOrder'}}
template(name="editChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
span.copied-tooltip.copied-tooltip-hidden {{_ 'copied'}}
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item'
= item.title
@ -99,13 +100,6 @@ template(name="editChecklistItemForm")
| {{_ 'convertChecklistItemToCardPopup-title'}}
template(name="checklistItems")
if checklist.items.length
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus
.checklist-items.js-checklist-items
each item in checklist.items
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
@ -118,14 +112,15 @@ template(name="checklistItems")
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}" role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
if isTouchScreenOrShowDesktopDragHandles
if isMiniScreen
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
@ -141,16 +136,16 @@ template(name="checklistActionsPopup")
li
a.js-delete-checklist.delete-checklist
i.fa.fa-trash
| {{_ "delete"}} ...
| {{_ "delete"}}
a.js-move-checklist.move-checklist
i.fa.fa-arrow-right
| {{_ "moveChecklist"}} ...
| {{_ "moveChecklist"}}
a.js-copy-checklist.copy-checklist
i.fa.fa-copy
| {{_ "copyChecklist"}} ...
| {{_ "copyChecklist"}}
a.js-hide-checked-checklist-items
i.fa.fa-eye-slash
| {{_ "hideCheckedChecklistItems"}} ...
| {{_ "hideCheckedChecklistItems"}}
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
if checklist.hideCheckedChecklistItems
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
@ -158,7 +153,7 @@ template(name="checklistActionsPopup")
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
a.js-hide-all-checklist-items
i.fa.fa-ban
| 🚫
| {{_ "hideAllChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
if checklist.hideAllChecklistItems

View file

@ -7,7 +7,7 @@ import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwim
const subManager = new SubsManager();
const { calculateIndexData, capitalize } = Utils;
function initSorting(items) {
function initSorting(items, handleSelector) {
items.sortable({
tolerance: 'pointer',
helper: 'clone',
@ -16,6 +16,7 @@ function initSorting(items) {
appendTo: 'parent',
distance: 7,
placeholder: 'checklist-item placeholder',
handle: handleSelector,
scroll: true,
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
@ -48,8 +49,9 @@ function initSorting(items) {
BlazeComponent.extendComponent({
onRendered() {
const self = this;
this.handleSelector = Utils.isMiniScreen() ? 'span.fa.checklistitem-handle' : '.item-title';
self.itemsDom = this.$('.js-checklist-items');
initSorting(self.itemsDom);
initSorting(self.itemsDom, this.handleSelector);
self.itemsDom.mousedown(function (evt) {
evt.stopPropagation();
});
@ -63,11 +65,9 @@ BlazeComponent.extendComponent({
const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$(self.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
});
}
$(self.itemsDom).sortable({
handle: this.handleSelector,
});
}
});
},

View file

@ -1,20 +1,20 @@
.card-label {
border: 1px solid #000;
border-radius: 4px;
border-radius: 0.4ch;
color: #fff;
display: inline-block;
font-weight: 700;
font-size: 13px;
margin-right: 4px;
margin-bottom: 5px;
padding: 3px 8px;
max-width: 210px;
min-width: 8px;
word-wrap: break-word;
min-height: 18px;
vertical-align: middle;
white-space: initial;
overflow: initial;
font-size: 0.9em;
display: flex;
/* prefer not using padding/margin but let outer grids
position/size labels (see e.g. minicards), otherwise we get
inconsistencies */
align-self: stretch;
justify-content: center;
align-items: center;
text-align: center;
padding: 0 0.5ch;
height: var(--label-height);
min-width: 8ch;
}
.card-label:hover {
color: #fff;
@ -34,6 +34,7 @@
}
.card-label p {
margin: 0px;
--overflow-lines: 1;
}
.palette-colors {
display: flex;
@ -138,37 +139,22 @@
.card-label-indigo {
background-color: #4b0082;
}
.edit-label .card-label,
.create-label .card-label {
float: left;
height: 25px;
margin: 0px 3% 7px 0px;
width: 10.5%;
max-width: 10.5%;
cursor: pointer;
}
.edit-labels input[type="text"] {
margin: 4px 0 6px 38px;
width: 243px;
}
.edit-labels .card-label {
height: 30px;
left: 0;
padding: 1px 5px;
position: absolute;
top: 0;
width: 24px;
}
.edit-labels .labels-static .card-label {
line-height: 30px;
margin-bottom: 4px;
position: relative;
top: auto;
left: 0;
width: 260px;
}
.edit-labels-pop-over {
margin-bottom: 8px;
display: grid;
/* so that inner elements, align nicely */
grid-template-columns: 1fr;
gap: 0.1lh;
>li {
display: flex;
flex-direction: row-reverse;
gap: 1ch;
align-items: center;
}
.card-label-selectable {
flex: 1;
display: flex;
gap: 1ch;
}
}
.edit-labels-pop-over .card-label .viewer p {
margin: 0;
@ -176,34 +162,6 @@
.edit-labels-pop-over .shortcut {
display: inline-block;
}
.card-label-selectable {
border-radius: 3px;
cursor: pointer;
margin: 0;
margin-bottom: 3px;
width: 190px;
min-height: 18px;
padding: 8px;
position: relative;
transition: margin-right 0.1s;
}
.card-label-selectable .card-label-selectable-icon {
position: absolute;
top: 8px;
right: -20px;
}
.card-label-selectable.active:hover,
.card-label-selectable.active,
.card-label-selectable.active.selected:hover,
.card-label-selectable.active.selected {
padding-right: 32px;
}
.card-label-selectable.active:hover .card-label-selectable-icon,
.card-label-selectable.active .card-label-selectable-icon,
.card-label-selectable.active.selected:hover .card-label-selectable-icon,
.card-label-selectable.active.selected .card-label-selectable-icon {
right: 6px;
}
.card-label-selectable.selected,
.card-label-selectable:hover {
opacity: 0.8;
@ -212,24 +170,6 @@
.active .card-label-selectable:hover {
margin-right: 0;
}
.active .card-label-selectable .card-label-selectable-icon {
right: 8px;
}
.card-label-edit-button {
border-radius: 3px;
float: right;
padding: 8px;
}
.card-label-edit-button:hover {
background: #dbdbdb;
}
ul.edit-labels-pop-over span.label-handle {
padding-right: 10px;
display: inline-block;
width: 1.2em;
text-align: center;
color: #999;
}
ul.edit-labels-pop-over span.label-handle + .card-label {
max-width: 180px;
}
}

View file

@ -1,10 +1,32 @@
.minicard-body {
display: flex;
flex-direction: column;
padding: 0 1ch 0.2lh 1ch;
gap: 0.2lh;
}
.minicard-wrapper {
cursor: pointer;
position: relative;
display: flex;
align-items: center;
margin-bottom: 1.2vh;
height: min-content;
}
.minicard-header {
display: flex;
align-items: center;
padding: 0 1ch;
gap: 1ch;
}
.minicard > hr {
margin: 0;
}
.minicard-add-form {
width: auto;
}
.minicard-wrapper.placeholder {
background: #ccc;
border-radius: 1.2vw;
@ -28,32 +50,25 @@
.minicard-wrapper .multi-selection-checkbox + .minicard {
margin-left: 1vw;
}
@media only screen {
.minicard {
padding: 0.8vh 1vw 0.3vh;
position: relative;
flex: 1;
flex-wrap: wrap;
background-color: #fff;
min-height: 2.5vh;
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
border-radius: 0.3vw;
color: #4d4d4d;
overflow: hidden;
transition: transform 0.2s, border-radius 0.2s;
}
.minicard {
display: grid;
grid-auto-flow: row;
grid-template-rows: min-content 1fr auto auto;
gap: 0.4lh;
background-color: #fff;
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
border-radius: 0.3vw;
color: #4d4d4d;
overflow: hidden;
transition: transform 0.2s, border-radius 0.2s;
flex: 1;
}
.minicard-details-menu-with-handle {
float: right;
padding-left: 0.7vw;
font-size: clamp(14px, 3vw, 18px);
padding: 0;
z-index: 1;
}
.minicard-details-menu {
float: right;
font-size: clamp(14px, 3vw, 18px);
padding-left: 0.7vw;
.minicard-actions-right {
justify-content: end;
display: flex;
align-items: end;
gap: .5lh;
}
@media print {
.minicard-details-menu,
@ -76,7 +91,6 @@
transform: translateX(1.5vw);
border-bottom-right-radius: 0;
border-top-right-radius: 0;
z-index: 25;
box-shadow: -0.3vw 0.2vh 0.3vh rgba(0,0,0,0.2);
}
.minicard:hover:not(.minicard-composer),
@ -96,20 +110,30 @@
margin: 0.8vh -1vw 0.8vh -1vw;
border-radius: top 0.3vw;
}
.minicard .minicard-labels {
float: none;
margin-right: 6vw;
}
.minicard .minicard-labels .minicard-label {
width: clamp(12px, 1.5vw, 16px);
height: clamp(12px, 1.5vw, 16px);
border-radius: 0.3vw;
margin-right: 0.4vw;
margin-bottom: 0.4vh;
}
.minicard .minicard-labels-no-text {
display: flex;
flex-wrap: wrap;
.minicard {
.minicard-labels, .dates {
display: grid;
grid-auto-rows: min-content;
justify-content: stretch;
font-size: 0.8em;
grid-auto-rows: minmax(1.3lh, auto);
}
.minicard-labels {
grid-template-columns: repeat(auto-fill, minmax(12ch, auto));
gap: 0.2lh 0.5ch;
}
.minicard-labels-no-text {
grid-template-columns: repeat(auto-fill, 4ch);
grid-template-rows: 4ch;
font-size: 0.4em;
.minicard-label {
border-radius: 1ch;
}
}
.dates {
height: min-content;
grid-template-columns: repeat(auto-fit, minmax(15ch, auto));
}
}
.minicard .minicard-custom-fields {
display: block;
@ -121,26 +145,22 @@
.minicard .minicard-custom-field-item {
flex-grow: 1;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 13vw;
margin-right: 0.5vw;
}
.minicard .minicard-custom-field-item-fullwidth {
flex-grow: 1;
display: block;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
margin-right: 0.5vw;
}
.minicard .handle {
width: clamp(20px, 2.5vw, 28px);
height: clamp(20px, 2.5vw, 28px);
position: absolute;
right: 0vw;
top: 4vh;
display: none;
z-index: 1;
}
@media only screen {
.minicard .handle {
display: block;
@ -154,58 +174,34 @@
text-align: center;
}
.minicard .minicard-title {
margin-right: 1.5vw;
display: flex;
max-width: 100%;
flex: 1;
cursor: grab;
.viewer {
--overflow-lines: 2;
}
}
.minicard .minicard-title .card-number {
color: #b3b3b3;
display: inline-block;
margin-right: 0.7vw;
}
@media only screen {
.minicard .minicard-title p:last-child {
margin-bottom: 0;
}
.minicard .minicard-title .viewer {
display: block;
word-wrap: break-word;
}
}
.minicard .dates {
display: flex;
flex-direction: row;
flex-wrap: wrap;
position: relative;
z-index: 5;
margin-right: 6vw;
clear: both;
}
.minicard .date {
margin-right: 0.4vw;
display: flex;
&>a {
display: flex;
justify-content: center;
flex: 1;
align-items: center;
text-align: center;
align-self: stretch;
}
}
/* Unicode icons for minicard dates - matching cardDate.css */
.minicard .card-date.end-date time::before {
content: "🏁"; /* Finish flag - represents end/completion */
}
.minicard .card-date.due-date time::before {
content: "⏰"; /* Alarm clock - represents due/deadline */
}
.minicard .card-date.start-date time::before {
content: "🚀"; /* Rocket - represents start/launch */
}
.minicard .card-date.received-date time::before {
content: "📥"; /* Inbox tray - represents received/incoming */
}
.minicard .card-date time::before {
font-size: inherit;
margin-right: 0.3em;
display: inline-block;
}
/* Date type specific colors for minicards - matching cardDate.css */
.minicard .card-date.received-date {
background-color: #dbdbdb; /* Grey for received - same as base card-date */
background-color: #d3d3d3; /* Grey for received - a bit darker than base card-date */
}
.minicard .card-date.received-date:hover,
@ -311,102 +307,134 @@
background-color: #1976d2 !important;
}
.minicard .minicard-badges-and-creator {
display: flex;
flex-direction: row-reverse;
justify-content: end;
gap: 0 0.5ch;;
}
.minicard-people-grid {
display: grid;
grid-template-columns: 1fr auto;
grid-auto-rows: auto;
}
.minicard-people-wrapper {
display: flex;
justify-content: end;
gap: 0.1lh;
}
.minicard .badges {
float: left;
margin-top: 1vh;
display: flex;
align-items: center;
gap: 1.5ch;
color: #808080;
/* this avoid padding-ish at the bottom of the card */
font-size: 0.8rem;
}
.minicard .badges:empty {
display: none;
}
.minicard .badges .badge {
float: left;
margin-right: 1.5vw;
margin-bottom: 0.4vh;
font-size: 0.9em;
}
.minicard .badges .badge.is-finished {
background: #3cb500;
padding: 0 0.4vw;
padding: 0.3lh 0.8ch;
border-radius: 0.4vw;
color: #fff;
&, .fa {
color: #fff;
}
}
.minicard .badges .badge:last-of-type {
margin-right: 0;
}
.minicard .badges .badge .badge-icon,
.minicard .badges .badge .badge-text {
vertical-align: middle;
}
.minicard .badges .badge .badge-icon.badge-comment,
.minicard .badges .badge .badge-text.badge-comment {
margin-bottom: 0.1rem;
}
.minicard .badges .badge .badge-text {
font-size: 0.9em;
padding-left: 0.3vw;
line-height: 1.2;
}
.minicard .badges .badge .check-list-text {
padding-left: 0px;
line-height: 1.1;
}
.minicard .minicard-members,
.minicard .minicard-assignees,
.minicard .minicard-creator {
float: right;
margin-left: 0.7vw;
margin-bottom: 0.5vh;
}
.minicard .minicard-members .member,
.minicard .minicard-assignees .member,
.minicard .minicard-creator .member {
float: right;
display: flex;
border-radius: 50%;
height: clamp(24px, 3.5vw, 32px);
width: clamp(24px, 3.5vw, 32px);
margin-bottom: 0.5vh;
font-size: 0.8em;
margin-bottom: 0.2lh;
}
.minicard .minicard-members .assignee,
.minicard .minicard-assignees .assignee,
.minicard .minicard-creator .assignee {
float: right;
border-radius: 50%;
height: clamp(24px, 3.5vw, 32px);
width: clamp(24px, 3.5vw, 32px);
.minicard .minicard-assignees .member {
border: 2px solid rgb(180, 87, 87);
}
.minicard .minicard-members + .badges,
.minicard .minicard-assignees + .badges,
.minicard .minicard-creator + .badges {
margin-top: 0.7vh;
.minicard .minicard-creator .member {
border: 2px solid #7fd67f;
}
.minicard .minicard-members .member {
border: 2px solid #5a5ac6;
}
.minicard .minicard-assignees {
border-bottom: 1px solid #f00;
}
.minicard .minicard-creator {
border-bottom: 1px solid #008000;
display: flex;
}
.minicard .minicard-members:empty,
.minicard .minicard-assignees:empty {
display: none;
}
.minicard .minicard-description {
padding: 0.8vh 0 0 1vw;
color: #000;
background-color: #eee;
width: 100%;
margin-bottom: 0.3vh;
margin-left: -0.5vw;
border-radius: 0.4vw;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
padding: 0.5lh 1ch;
--overflow-lines: 2;
.viewer {
font-size: 0.9em;
ul {
padding-bottom: 0;
}
}
}
.minicard .minicard-description p {
margin: 0;
}
.minicard-composer {
display: flex;
flex-direction: column;
.minicard-composer-icons {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2lh;
}
.minicard-bottom {
display: flex;
justify-content: end;
align-items: center;
gap: 1ch;
.minicard-composer-icons {
display: flex;
flex: 1;
flex-wrap: wrap;
flex-direction: row-reverse;
}
.add-controls {
display: flex;
align-self: start;
}
textarea {
display: flex;
flex: 1;
}
}
}
.minicard.minicard-composer {
margin-bottom: 1.3vh;
flex-wrap: wrap;
flex: 1;
align-self: stretch;
gap: 0.3lh;
padding: 0.5lh 1ch;
position: relative;
}
.minicard.minicard-composer textarea.minicard-composer-textarea,
.minicard.minicard-composer textarea.minicard-composer-textarea:focus {
resize: none;
@ -415,11 +443,11 @@
box-shadow: none;
height: auto;
margin: 0;
padding: 0;
max-height: 22vh;
min-height: 5vh;
margin-bottom: 2.5vh;
padding: 1ch;
min-height: 5lh;
overflow-y: auto;
flex: 1;
width: 100%;
}
.parent-prefix {
color: #b3b3b3;
@ -734,30 +762,12 @@
/* List name display on minicard */
.minicard-list-name {
font-size: 0.75em;
font-size: inherit;
color: #8c8c8c;
margin-top: 0.2vh;
display: flex;
align-items: center;
gap: 0.3vw;
}
/* Checklist display on minicard */
.minicard-checklist {
width: 100%;
margin-top: 0.5vh;
margin-bottom: 0.5vh;
padding: 0.3vh 0.5vw;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.3vw;
border: 1px solid #e0e0e0;
}
.minicard-checklist .checklist-header {
padding: 0 0.5ch;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.3vh;
gap: 0.5ch;
}
.minicard-checklist .checklist-title {

View file

@ -1,226 +1,236 @@
template(name="minicard")
if isSelected
+cardDetailsPopup(this)
.minicard.nodragscroll(
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if canMoveCard
if isTouchScreenOrShowDesktopDragHandles
.handle
i.fa.fa-arrows
if canModifyCard
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
.dates
if getReceived
.date
+minicardReceivedDate
if getStart
.date
+minicardStartDate
if getDue
.date
+minicardDueDate
if getEnd
+minicardEndDate
if allowsReceivedDate
if getReceived
.date.viewer
+minicardReceivedDate
if allowsStartDate
if getStart
.date.viewer
+minicardStartDate
if allowsDueDate
if getDue
.date.viewer
+minicardDueDate
if allowsEndDate
if getEnd
.date.viewer
+minicardEndDate
if getSpentTime
.date
.date.viewer
+cardSpentTime
if cover
if currentBoard.allowsCoverAttachmentOnMinicard
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
.minicard-header
.minicard-title
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
| {{ parentString ' > ' }}
if $eq 'prefix-with-parent' currentBoard.presentParentTask
.parent-prefix
| {{ parentCardName }}
if isLinkedBoard
a.js-linked-link
span.linked-icon
i.fa.fa-folder
else if isLinkedCard
a.js-linked-link
span.linked-icon
i.fa.fa-id-card
if getArchived
span.linked-icon.linked-archived
i.fa.fa-archive
+viewer
if allowsCardNumber
span.card-number
| ##{getCardNumber}&thinsp;
= getTitle
div.minicard-actions-right
if canModifyCard
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
if isMiniScreen
if canMoveCard
.handle
i.fa.fa-arrows
hr
.minicard-body
if cover
if allowsCoverAttachmentOnMinicard
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
// Upload progress indicator for drag-and-drop uploads
if hasActiveUploads
.minicard-upload-progress
.upload-progress-header
i.fa.fa-upload
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
.upload-progress-filename {{file.name}}
.upload-progress-bar
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
i.fa.fa-warning
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
i.fa.fa-check
span {{_ 'upload-completed'}}
//- Upload progress indicator for drag-and-drop uploads
if hasActiveUploads
.minicard-upload-progress
.upload-progress-header
i.fa.fa-upload
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
.upload-progress-filename {{file.name}}
.upload-progress-bar
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
i.fa.fa-warning
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
i.fa.fa-check
span {{_ 'upload-completed'}}
.minicard-title
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
| {{ parentString ' > ' }}
if $eq 'prefix-with-parent' currentBoard.presentParentTask
.parent-prefix
| {{ parentCardName }}
if isLinkedBoard
a.js-linked-link
span.linked-icon
i.fa.fa-folder
else if isLinkedCard
a.js-linked-link
span.linked-icon
i.fa.fa-id-card
if getArchived
span.linked-icon.linked-archived
i.fa.fa-archive
+viewer
if currentBoard.allowsCardNumber
span.card-number
| ##{getCardNumber}
= getTitle
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-custom-fields
each customFieldsWD
if definition.showOnCard
if trueValue
.minicard-custom-field
// If there is custom field label, show label at left,
// and value at right
if definition.showLabelOnMiniCard
.minicard-custom-field-item
+viewer
= definition.name
.minicard-custom-field-item
if $eq definition.type "currency"
.minicard-custom-fields
each customFieldsWD
if definition.showOnCard
if trueValue
.minicard-custom-field
// If there is custom field label, show label at left,
// and value at right
if definition.showLabelOnMiniCard
.minicard-custom-field-item
+viewer
= formattedCurrencyCustomFieldValue(definition)
else if $eq definition.type "date"
.date
+minicardCustomFieldDate
else if $eq definition.type "checkbox"
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
else if $eq definition.type "stringtemplate"
+viewer
= formattedStringtemplateCustomFieldValue(definition)
else
+viewer
= trueValue
else
// If there is no custom field label,
// show value full width
.minicard-custom-field-item-fullwidth
if $eq definition.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(definition)
else if $eq definition.type "date"
.date
+minicardCustomFieldDate
else if $eq definition.type "checkbox"
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
else if $eq definition.type "stringtemplate"
+viewer
= formattedStringtemplateCustomFieldValue(definition)
else
+viewer
= trueValue
= definition.name
.minicard-custom-field-item
if $eq definition.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(definition)
else if $eq definition.type "date"
.date
+minicardCustomFieldDate
else if $eq definition.type "checkbox"
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
else if $eq definition.type "stringtemplate"
+viewer
= formattedStringtemplateCustomFieldValue(definition)
else
+viewer
= trueValue
else
// If there is no custom field label,
// show value full width
.minicard-custom-field-item-fullwidth
if $eq definition.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(definition)
else if $eq definition.type "date"
.date
+minicardCustomFieldDate
else if $eq definition.type "checkbox"
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
else if $eq definition.type "stringtemplate"
+viewer
= formattedStringtemplateCustomFieldValue(definition)
else
+viewer
= trueValue
.minicard-people-grid
if allowsAssignee
if getAssignees
.minicard-people-wrapper
.minicard-assignees.js-minicard-assignees
each getAssignees
+userAvatar(userId=this)
if showAssignee
if getAssignees
.minicard-assignees.js-minicard-assignees
each getAssignees
+userAvatar(userId=this)
if allowsMembers
if getMembers
.minicard-people-wrapper
.minicard-members.js-minicard-members
each getMembers
+userAvatar(userId=this)
if showMembers
if getMembers
.minicard-members.js-minicard-members
each getMembers
+userAvatar(userId=this)
.minicard-badges-and-creator
if allowsCreatorOnMinicard
.minicard-creator
+userAvatar(userId=this.userId noRemove=true)
if showCreatorOnMinicard
.minicard-creator
+userAvatar(userId=this.userId noRemove=true)
.badges
if canModifyCard
if comments.length
.badge(title="{{_ 'card-comments-title' comments.length }}")
span.badge-icon.badge-comment.badge-text
i.fa.fa-comment-o
= ' '
= comments.length
//span.badge-comment.badge-text
//| {{_ 'comment'}}
if getDescription
unless currentBoard.allowsDescriptionTextOnMinicard
.badge.badge-state-image-only(title=getDescription)
span.badge-icon
i.fa.fa-file-text-o
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon(class="{{#if voteState}}text-green{{/if}}")
i.fa.fa-thumbs-up
span.badge-text {{ voteCountPositive }}
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}")
i.fa.fa-thumbs-down
span.badge-text {{ voteCountNegative }}
if getPokerQuestion
.badge.badge-state-image-only(title=getPokerQuestion)
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}")
i.fa.fa-check-square
if expiredPoker
span.badge-text {{ getPokerEstimation }}
if attachments.length
if currentBoard.allowsBadgeAttachmentOnMinicard
.badge
span.badge-icon
i.fa.fa-paperclip
span.badge-text= attachments.length
if allSubtasks.count
.badge
span.badge-icon
i.fa.fa-globe
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if currentBoard.allowsCardSortingByNumber
if currentBoard.allowsCardSortingByNumberOnMinicard
.badge
span.badge-icon
i.fa.fa-sort-numeric-asc
span.badge-text.check-list-sort {{ sort }}
if shouldShowChecklistAtMinicard
each shouldShowChecklistAtMinicard
+minicardChecklist(checklist=. card=..)
if currentBoard.allowsDescriptionTextOnMinicard
.badges
if canModifyCard
if allowsComments
if comments.length
.badge(title="{{_ 'card-comments-title' comments.length }}")
span.badge-icon.badge-comment.badge-text
i.fa.fa-comment-o
= ' '
= comments.length
//span.badge-comment.badge-text
//| {{_ 'comment'}}
if getDescription
unless allowsDescriptionTextOnMinicard
.badge.badge-state-image-only(title=getDescription)
span.badge-icon
i.fa.fa-file-text-o
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon(class="{{#if voteState}}text-green{{/if}}")
i.fa.fa-thumbs-up
span.badge-text {{ voteCountPositive }}
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}")
i.fa.fa-thumbs-down
span.badge-text {{ voteCountNegative }}
if getPokerQuestion
.badge.badge-state-image-only(title=getPokerQuestion)
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}")
i.fa.fa-check-square
if expiredPoker
span.badge-text {{ getPokerEstimation }}
if attachments.length
if allowsBadgeAttachmentOnMinicard
.badge
span.badge-icon
i.fa.fa-paperclip
span.badge-text= attachments.length
if checklists.length
if allowsChecklists
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
span.badge-icon
i.fa.fa-check
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
if allSubtasks.count
if allowsSubtasks
.badge
span.badge-icon
i.fa.fa-globe
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if allowsCardSortingByNumber
if allowsCardSortingByNumberOnMinicard
.badge
span.badge-icon
i.fa.fa-sort-numeric-asc
span.badge-text.check-list-sort {{ sort }}
if shouldShowListOnMinicard
.minicard-list-name
span
i.fa.fa-list
span
| {{ listName }}
if $eq 'subtext-with-full-path' presentParentTask
.parent-subtext
| {{ parentString ' > ' }}
if $eq 'subtext-with-parent' presentParentTask
.parent-subtext
| {{ parentCardName }}
if allowsDescriptionTextOnMinicard
if getDescription
.minicard-description
+viewer
| {{ getDescription }}
if shouldShowListOnMinicard
.minicard-list-name
i.fa.fa-list
| {{ listName }}
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
.parent-subtext
| {{ parentString ' > ' }}
if $eq 'subtext-with-parent' currentBoard.presentParentTask
.parent-subtext
| {{ parentCardName }}
template(name="editCardSortOrderPopup")
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
input.js-edit-card-sort-popup(type='text' value=sort dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
template(name="minicardChecklist")
.minicard-checklist
.checklist-header
.checklist-title= checklist.title
if canModifyCard
a.checklist-menu.js-open-checklist-menu(title="{{_ 'checklistActionsPopup-title'}}")
i.fa.fa-bars
each visibleItems
+checklistItemDetail(item = . checklist = checklist card = card)
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}

View file

@ -13,6 +13,24 @@ BlazeComponent.extendComponent({
return 'minicard';
},
onRendered() {
// cannot be done with CSS because newlines
// rendered by the JADE engine count as non empty
// and some "empty" divs are nested
// this is not very robust and could probably be
// done with a helper, but it could be in fact worse
// because we would need to to if (allowsX() && X() && ...)
const body = $(this.find('.minicard-body'));
if (!body) {return}
let emptyChildren;
do {
emptyChildren = body.find('*').filter((_, e) => !e.classList.contains('fa') && $(e).html().trim().length === 0).remove();
} while (emptyChildren.length > 0)
if (body.html().trim().length === 0) {
body.parent().find('hr:has(+ .minicard-body)').remove();
}
},
formattedCurrencyCustomFieldValue(definition) {
const customField = this.data()
.customFieldsWD()
@ -39,46 +57,14 @@ BlazeComponent.extendComponent({
return ret;
},
showCreatorOnMinicard() {
// cache "board" to reduce the mini-mongodb access
const board = this.data().board();
let ret = false;
if (board) {
ret = board.allowsCreatorOnMinicard ?? false;
}
return ret;
},
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
},
showMembers() {
// cache "board" to reduce the mini-mongodb access
const board = this.data().board();
let ret = false;
if (board) {
ret =
board.allowsMembers === null ||
board.allowsMembers === undefined ||
board.allowsMembers
;
}
return ret;
},
showAssignee() {
// cache "board" to reduce the mini-mongodb access
const board = this.data().board();
let ret = false;
if (board) {
ret =
board.allowsAssignee === null ||
board.allowsAssignee === undefined ||
board.allowsAssignee
;
}
return ret;
isSelected() {
const card = this.currentData();
return Session.get('currentCard') === card._id;
},
/** opens the card label popup only if clicked onto a label
@ -87,6 +73,8 @@ BlazeComponent.extendComponent({
*/
cardLabelsPopup(event) {
if (this.find('.js-card-label:hover')) {
event.preventDefault();
event.stopPropagation();
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()});
}
},
@ -203,7 +191,7 @@ BlazeComponent.extendComponent({
visibleItems() {
const checklist = this.currentData().checklist || this.currentData();
const items = checklist.items();
return items.filter(item => {
// Hide finished items if hideCheckedChecklistItems is true
if (item.isFinished && checklist.hideCheckedChecklistItems) {
@ -254,33 +242,8 @@ Template.minicard.helpers({
},
shouldShowListOnMinicard() {
// Show list name if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific card has the setting enabled
const currentBoard = this.board();
if (!currentBoard) return false;
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
return Utils.allowsShowLists();
},
shouldShowChecklistAtMinicard() {
// Return checklists that should be shown on minicard
const currentBoard = this.board();
if (!currentBoard) return [];
const checklists = this.checklists();
const visibleChecklists = [];
checklists.forEach(checklist => {
// Show checklist if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific checklist has the setting enabled
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
visibleChecklists.push(checklist);
}
});
return visibleChecklists;
}
});
BlazeComponent.extendComponent({

View file

@ -18,7 +18,8 @@
font-weight: bold;
}
.result-card-context-list {
margin-bottom: 0.7rem;
display: flex;
gap: 0.2ch;
}
.result-card-block-wrapper {
display: inline-block;

View file

@ -14,17 +14,10 @@ BlazeComponent.extendComponent({
onReady() {
Session.set('popupCardId', cardId);
Session.set('popupCardBoardId', boardId);
this_.cardDetailsPopup(evt);
},
});
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() {
return [
{

View file

@ -4,19 +4,14 @@
textarea.js-add-subtask-item,
textarea.js-edit-subtask-item {
overflow: hidden;
word-wrap: break-word;
overflow-wrap: break-word;
resize: none;
height: 34px;
}
.delete-text,
.subtask-title .js-delete-subtask,
.subtask-title .js-view-subtask,
.js-delete-subtask-item {
color: #8c8c8c;
text-decoration: underline;
word-wrap: break-word;
float: right;
padding-top: 6px;
}
.delete-text:hover,
.subtask-title .js-delete-subtask:hover,
@ -28,11 +23,11 @@ textarea.js-edit-subtask-item {
float: left;
width: 30px;
height: 30px;
font-size: 18px;
line-height: 30px;
}
.subtask-title .title {
font-size: 18px;
line-height: 25px;
}
.subtask-title .subtasks-stat {
@ -133,7 +128,7 @@ textarea.js-edit-subtask-item {
margin: 0.1em 0 0 0;
}
.subtasks-item .check-box.is-checked {
border-bottom: 2px solid #3cb500;
border-bottom: 0.2ch solid #3cb500;
border-right: 2px solid #3cb500;
}
/* Unicode checkbox icons styling */
@ -165,16 +160,4 @@ body.grey-icons-enabled .subtasks-item .check-box-unicode {
}
.subtasks-item .item-title .viewer p {
margin-bottom: 2px;
}
.js-delete-subtask-item {
margin: 0 0 0.5em 1.33em;
padding: 12px 0 0 0;
}
.add-subtask-item {
margin: 0.2em 0 0.5em 1.33em;
display: inline-block;
}
.subtask-details-menu {
float: right;
padding: 6px 10px 6px 10px;
}
}

View file

@ -4,12 +4,20 @@ template(name="subtasks")
| {{_ 'subtasks'}}
if currentUser.isBoardAdmin
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+subtaskDeleteDialog(subtask = subtaskToDelete)
.card-subtasks-items
each subtask in currentCard.subtasks
+subtaskDetail(subtask = subtask)
if currentCard.subtasks
.card-subtasks-items
each subtask in currentCard.subtasks
.subtask-container
+subtaskDetail(subtask = subtask)
if canModifyCard
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
| ☰
if currentUser.isBoardAdmin
a.js-delete-subtask-item
| ❌
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-subtask" cardId = cardId)
@ -24,9 +32,6 @@ template(name="subtaskDetail")
+editSubtaskItemForm(subtask = subtask)
else
.subtask-title
span
if canModifyCard
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
if canModifyCard
h2.title.js-open-inlined-form.is-editable
+viewer
@ -37,13 +42,13 @@ template(name="subtaskDetail")
= subtask.title
template(name="addSubtaskItemForm")
textarea.js-add-subtask-item(rows='1' autofocus dir="auto")
textarea.js-add-subtask-item(rows='1' dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form
template(name="editSubtaskItemForm")
textarea.js-edit-subtask-item(rows='1' autofocus dir="auto")
textarea.js-edit-subtask-item(rows='1' dir="auto")
if $eq type 'item'
= item.title
else
@ -52,9 +57,6 @@ template(name="editSubtaskItemForm")
button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form
span(title=createdAt) {{ moment createdAt }}
if canModifyCard
if currentUser.isBoardAdmin
a.js-delete-subtask-item {{_ "delete"}}...
template(name="subtasksItems")
.subtasks-items.js-subtasks-items
@ -100,4 +102,3 @@ template(name="subtaskActionsPopup")
a.js-delete-subtask.delete-subtask
i.fa.fa-trash
| {{_ "delete"}} ...