mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 15:45:01 -05:00
Separate card and minicard custom field visibility.
Allow checkbox values to toggle both ways from the reactive field context. Add board settings that show custom fields on opened cards by default and hide them on minicards by default, with independent menu controls for each surface. Thanks to xet7 !
This commit is contained in:
1 parent
269351e852
commit
1d4ffd4080
11 files changed
+112
-13
No files matched your search
@@ -134,12 +134,12 @@ Template['cardCustomField-checkbox'].events({
|
||||
async 'click .js-card-custom-field-checkbox .check-box-container'(event, tpl) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const sourceCard = tpl.card?.getRealCard?.() || tpl.card;
|
||||
const storedField = (sourceCard?.customFields || []).find(
|
||||
field => field?._id === tpl.customFieldId,
|
||||
);
|
||||
if (!sourceCard || !storedField) return;
|
||||
const value = !Boolean(storedField.value);
|
||||
// This template context is rebuilt from customFieldsWD whenever the saved
|
||||
// value changes. Using the Card object captured at onCreated kept the old
|
||||
// value after the first click, so a second click tried to save true again.
|
||||
const currentField = Template.currentData();
|
||||
if (!currentField || currentField._id !== tpl.customFieldId) return;
|
||||
const value = !Boolean(currentField.value);
|
||||
try {
|
||||
await Meteor.callAsync(
|
||||
'setCardCustomFieldCheckbox', tpl.card.getRealId(), tpl.customFieldId, value);
|
||||
|
||||
@@ -446,7 +446,7 @@ template(name="cardDetails")
|
||||
were removed with the move, and the sidebar view is opened by nothing.
|
||||
The FIELDS below still come from `customFieldsWD`, so an empty section
|
||||
is an empty section, not a phantom row.
|
||||
if canModifyCard
|
||||
if canShowCustomFieldsOnCard
|
||||
//- ONE header for the whole group, outside the loop: a card with six
|
||||
custom fields is one Custom Fields section, not six sections that
|
||||
happen to share a caret and draw six rules. A group like the others,
|
||||
|
||||
@@ -550,6 +550,10 @@ Template.cardDetails.onDestroyed(function () {
|
||||
});
|
||||
|
||||
Template.cardDetails.helpers({
|
||||
canShowCustomFieldsOnCard() {
|
||||
const board = this?.board?.();
|
||||
return Utils.canModifyCard(this) && board?.allowsCustomFields !== false;
|
||||
},
|
||||
stickers() {
|
||||
const card = Template.currentData();
|
||||
return card && typeof card.getStickers === 'function' ? card.getStickers() : [];
|
||||
|
||||
@@ -132,10 +132,10 @@ template(name="minicard")
|
||||
if hiddenMinicardLabelText
|
||||
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
|
||||
|
||||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
if definition.showOnCard
|
||||
if trueValue
|
||||
if showCustomFieldsOnMinicard
|
||||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
if definition.showOnCard
|
||||
.minicard-custom-field
|
||||
// If there is custom field label, show label at left,
|
||||
// and value at right
|
||||
|
||||
@@ -34,6 +34,10 @@ function getMinicardFlag(board, onMinicardField, legacyField, defaultValue) {
|
||||
// });
|
||||
|
||||
Template.minicard.helpers({
|
||||
showCustomFieldsOnMinicard() {
|
||||
const board = this.board();
|
||||
return board?.allowsCustomFieldsOnMinicard === true;
|
||||
},
|
||||
// True exactly when the drag handle is rendered (see minicard.jade): the user
|
||||
// may move the card AND drag handles are on. On a coarse pointer the handle is
|
||||
// a 48px strip down the leading edge, so the card's own content needs matching
|
||||
|
||||
@@ -477,6 +477,17 @@ template(name="boardCardSettingsPopup")
|
||||
i.fa.fa-file-text-o
|
||||
| {{_ 'labels'}}
|
||||
| {{_ 'custom-field-text'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-custom-fields(title="{{_ 'custom-fields'}}" class="{{#if allowsCustomFields}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCustomFields}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-custom-fields-on-minicard(title="{{_ 'custom-fields'}}" class="{{#if allowsCustomFieldsOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCustomFieldsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-list-alt
|
||||
| {{_ 'custom-fields'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
|
||||
|
||||
@@ -1700,6 +1700,14 @@ Template.boardCardSettingsPopup.helpers({
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return getMinicardSetting(currentBoard, 'allowsLabelsOnMinicard', 'allowsLabels', true);
|
||||
},
|
||||
allowsCustomFields() {
|
||||
const board = ReactiveCache.getBoard(Session.get('currentBoard'));
|
||||
return board ? board.allowsCustomFields !== false : true;
|
||||
},
|
||||
allowsCustomFieldsOnMinicard() {
|
||||
const board = ReactiveCache.getBoard(Session.get('currentBoard'));
|
||||
return board?.allowsCustomFieldsOnMinicard === true;
|
||||
},
|
||||
allowsShowListsOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
@@ -2001,6 +2009,20 @@ Template.boardCardSettingsPopup.events({
|
||||
const newValue = !tpl.currentBoard.allowsLabelsOnMinicard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsLabelsOnMinicard: newValue } });
|
||||
},
|
||||
'click .js-field-has-custom-fields'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const currentValue = tpl.currentBoard.allowsCustomFields !== false;
|
||||
Boards.update(tpl.currentBoard._id, {
|
||||
$set: { allowsCustomFields: !currentValue },
|
||||
});
|
||||
},
|
||||
'click .js-field-has-custom-fields-on-minicard'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const currentValue = tpl.currentBoard.allowsCustomFieldsOnMinicard === true;
|
||||
Boards.update(tpl.currentBoard._id, {
|
||||
$set: { allowsCustomFieldsOnMinicard: !currentValue },
|
||||
});
|
||||
},
|
||||
'click .js-field-has-card-show-lists-on-minicard'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsShowListsOnMinicard;
|
||||
|
||||
@@ -604,6 +604,19 @@ Boards.attachSchema(
|
||||
defaultValue: true,
|
||||
},
|
||||
|
||||
allowsCustomFields: {
|
||||
/** Show assigned custom fields on opened cards? */
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
},
|
||||
allowsCustomFieldsOnMinicard: {
|
||||
/** Show assigned custom fields on minicards? Opt-in by default. */
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
allowsChecklistCountBadgeOnMinicard: {
|
||||
/**
|
||||
* Does the board show the compact checklist item-count badge
|
||||
|
||||
@@ -28,8 +28,8 @@ test('opened-card checkbox saves its value independently of visibility', () => {
|
||||
const at = client.indexOf("async 'click .js-card-custom-field-checkbox .check-box-container'");
|
||||
const body = client.slice(at, client.indexOf('\n },', at));
|
||||
assert.match(body, /await Meteor\.callAsync\(\s*'setCardCustomFieldCheckbox'/);
|
||||
assert.match(body, /sourceCard\?\.customFields/);
|
||||
assert.match(body, /!Boolean\(storedField\.value\)/);
|
||||
assert.match(body, /Template\.currentData\(\)/);
|
||||
assert.match(body, /!Boolean\(currentField\.value\)/);
|
||||
assert.match(body, /event\.stopPropagation\(\)/);
|
||||
assert.doesNotMatch(body, /tpl\.card\.setCustomField/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
const boards = read('models/boards.js');
|
||||
const sidebar = read('client/components/sidebar/sidebar.js');
|
||||
const sidebarTemplate = read('client/components/sidebar/sidebar.jade');
|
||||
const details = read('client/components/cards/cardDetails.js');
|
||||
const detailsTemplate = read('client/components/cards/cardDetails.jade');
|
||||
const minicard = read('client/components/cards/minicard.js');
|
||||
const minicardTemplate = read('client/components/cards/minicard.jade');
|
||||
|
||||
test('custom fields default on for cards and off for minicards', () => {
|
||||
const cardSetting = boards.slice(boards.indexOf('allowsCustomFields: {'));
|
||||
assert.match(cardSetting.slice(0, cardSetting.indexOf('},') + 2),
|
||||
/defaultValue: true/);
|
||||
const miniSetting = boards.slice(boards.indexOf('allowsCustomFieldsOnMinicard: {'));
|
||||
assert.match(miniSetting.slice(0, miniSetting.indexOf('},') + 2),
|
||||
/defaultValue: false/);
|
||||
assert.match(details, /board\?\.allowsCustomFields !== false/);
|
||||
assert.match(minicard, /board\?\.allowsCustomFieldsOnMinicard === true/);
|
||||
});
|
||||
|
||||
test('card and minicard menus expose independent Custom Fields rows', () => {
|
||||
assert.match(sidebarTemplate, /js-field-has-custom-fields/);
|
||||
assert.match(sidebarTemplate, /js-field-has-custom-fields-on-minicard/);
|
||||
assert.match(sidebarTemplate, /\{\{_ 'custom-fields'\}\}/);
|
||||
assert.match(sidebar, /'click \.js-field-has-custom-fields'/);
|
||||
assert.match(sidebar, /\$set: \{ allowsCustomFields: !currentValue \}/);
|
||||
assert.match(sidebar, /'click \.js-field-has-custom-fields-on-minicard'/);
|
||||
assert.match(sidebar,
|
||||
/\$set: \{ allowsCustomFieldsOnMinicard: !currentValue \}/);
|
||||
});
|
||||
|
||||
test('each card surface is gated only by its matching setting', () => {
|
||||
assert.match(detailsTemplate, /if canShowCustomFieldsOnCard[\s\S]*each customFieldsWD/);
|
||||
assert.match(minicardTemplate,
|
||||
/if showCustomFieldsOnMinicard[\s\S]*each customFieldsWD/);
|
||||
});
|
||||
@@ -309,6 +309,9 @@ test.describe('Cards – operations', () => {
|
||||
db.updateOne('boards', { _id: source.boardId }, {
|
||||
$set: { labels: [{ _id: labelId, name: 'Source Label', color: 'green' }] },
|
||||
});
|
||||
db.updateOne('boards', { _id: board.boardId }, {
|
||||
$set: { allowsCustomFieldsOnMinicard: true },
|
||||
});
|
||||
db.insertOne('customFields', {
|
||||
_id: customFieldId,
|
||||
boardIds: [source.boardId],
|
||||
|
||||
Reference in new issue
Block a user