This commit is contained in:
Lauri Ojansivu committed 2026-10-14 14:04:07 +03:00
1 parent 331d1e347b
commit a6b2938e1c
8 files changed
+130 -2

No files matched your search

+10 -2
View File
@@ -1,5 +1,6 @@
import { isSubmitKey } from '/models/lib/editorSubmitKey';
import { titleSortIndexes } from '/models/lib/multilineTitles';
import { titleTagText } from '/models/lib/titleTags';
import { titlesFromComposer } from '/client/components/forms/multilineTitleChoice';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
@@ -996,6 +997,12 @@ Template.addCardForm.onRendered(function () {
autosize($textarea);
// #1273: a per-user choice, off by default.
const keepTitleTags = () => {
const user = ReactiveCache.getCurrentUser();
return !!(user && typeof user.hasKeepTitleTags === 'function' && user.hasKeepTitleTags());
};
$textarea.escapeableTextComplete(
[
// User mentions
@@ -1022,7 +1029,8 @@ Template.addCardForm.onRendered(function () {
},
replace(user) {
toggleValueInReactiveArray(tpl.members, user._id);
return '';
// #1273: Member Settings can keep the "@member" text in the title.
return titleTagText('member', user, keepTitleTags());
},
index: 1,
},
@@ -1056,7 +1064,7 @@ Template.addCardForm.onRendered(function () {
},
replace(label) {
toggleValueInReactiveArray(tpl.labels, label._id);
return '';
return titleTagText('label', label, keepTitleTags());
},
index: 1,
},
+7
View File
@@ -312,6 +312,13 @@ template(name="changeSettingsPopup")
| {{_ 'show-desktop-drag-handles'}}
if isShowDesktopDragHandles
i.fa.fa-check
//- #1273: keep the #label / @member text in a new card's title.
li
a.js-toggle-keep-title-tags(title="{{_ 'keep-title-tags-description'}}")
i.fa.fa-hashtag
| {{_ 'keep-title-tags'}}
if isKeepTitleTags
i.fa.fa-check
li
a.js-toggle-submit-on-enter(title="{{_ 'submit-on-enter-description'}}")
i.fa.fa-level-down
+7
View File
@@ -462,6 +462,10 @@ Template.changeLanguagePopup.events({
});
Template.changeSettingsPopup.helpers({
isKeepTitleTags() {
const currentUser = ReactiveCache.getCurrentUser();
return currentUser ? currentUser.hasKeepTitleTags() : false;
},
isSubmitOnEnter() {
const currentUser = ReactiveCache.getCurrentUser();
return currentUser ? currentUser.hasSubmitOnEnter() : false;
@@ -517,6 +521,9 @@ Template.changeSettingsPopup.events({
window.localStorage.setItem('showDesktopDragHandles', show ? 'true' : 'false');
}
},
'click .js-toggle-keep-title-tags'() {
if (ReactiveCache.getCurrentUser()) Meteor.call('toggleKeepTitleTags');
},
'click .js-toggle-submit-on-enter'() {
// Saved to the user profile (Member Settings only; requires a logged-in user).
if (ReactiveCache.getCurrentUser()) {
+19
View File
@@ -0,0 +1,19 @@
'use strict';
// #1273: what a new card's title keeps when a #label or @member is picked
// while it is typed. By default the "#label" / "@member" text is replaced by
// the label or member itself (nothing is left in the title); with the Member
// Settings switch on, the text stays too, as it was typed. Pure:
// tests/titleTags.test.cjs.
function titleTagText(kind, item, keep) {
if (!keep || !item) return '';
if (kind === 'member') return item.username ? `@${item.username} ` : '';
if (kind === 'label') {
const name = item.name || item.color;
return name ? `#${name} ` : '';
}
return '';
}
module.exports = { titleTagText };
+14
View File
@@ -447,6 +447,15 @@ Users.attachSchema(
type: Boolean,
optional: true,
},
'profile.keepTitleTags': {
/**
* #1273: when a #label or @member is picked while typing a new card's
* title, keep the "#label" / "@member" text in the title too. Off by
* default: the text is replaced by the label or member, as before.
*/
type: Boolean,
optional: true,
},
'profile.checklistDingSound': {
/**
* #5427: per-user preference - play a short synthesized "ding" when a
@@ -2094,6 +2103,11 @@ Users.helpers({
return profile.submitOnEnter || false;
},
// #1273: keep the #label / @member text in a new card's title.
hasKeepTitleTags() {
return !!((this.profile || {}).keepTitleTags);
},
// #5427: does this user want a short "ding" sound when a checklist item is
// checked off? Off by default.
hasChecklistDingSound() {
@@ -2079,6 +2079,14 @@
{
"key": "activity-recovery-bulk-result",
"since": "2026-10-14"
},
{
"key": "keep-title-tags",
"since": "2026-10-14"
},
{
"key": "keep-title-tags-description",
"since": "2026-10-14"
}
]
}
+9
View File
@@ -954,6 +954,15 @@ Meteor.methods({
// Per-user "submit editors on plain Enter" preference (Member Settings).
// Off by default; when on, plain Enter submits and Shift+Enter is a newline.
// #1273: keep the #label / @member text in a new card's title (Member
// Settings; off by default).
async toggleKeepTitleTags() {
if (!this.userId) throw new Meteor.Error('not-logged-in', 'User must be logged in');
const user = await Users.findOneAsync(this.userId);
if (!user) throw new Meteor.Error('user-not-found', 'User not found');
await Users.updateAsync(this.userId, { $set: { 'profile.keepTitleTags': !((user.profile || {}).keepTitleTags) } });
},
async toggleSubmitOnEnter() {
if (!this.userId) throw new Meteor.Error('not-logged-in', 'User must be logged in');
const user = await Users.findOneAsync(this.userId);
+56
View File
@@ -0,0 +1,56 @@
'use strict';
// #1273: a Member Settings switch keeps the "#label" / "@member" text in a
// new card's title when the label or member is picked from the composer's
// autocomplete. Off (the default) keeps today's behaviour: the text goes,
// and the card gets the label or member.
// Run: node tests/titleTags.test.cjs
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { titleTagText } = require('../models/lib/titleTags.js');
const ROOT = path.join(__dirname, '..');
const read = file => fs.readFileSync(path.join(ROOT, file), 'utf8');
// Positive: with the switch on, the typed text stays.
assert.equal(titleTagText('member', { username: 'xet7' }, true), '@xet7 ');
assert.equal(titleTagText('label', { name: 'Bug', color: 'red' }, true), '#Bug ');
assert.equal(titleTagText('label', { name: '', color: 'red' }, true), '#red ', 'an unnamed label is its colour');
console.log(' ok - on: the #label / @member text stays in the title');
// Negative: off (or unset) removes it, as before; nothing odd is ever inserted.
for (const keep of [false, undefined, null, 0]) {
assert.equal(titleTagText('member', { username: 'xet7' }, keep), '');
assert.equal(titleTagText('label', { name: 'Bug' }, keep), '');
}
assert.equal(titleTagText('member', {}, true), '', 'no "@undefined"');
assert.equal(titleTagText('label', {}, true), '', 'no "#undefined"');
assert.equal(titleTagText('label', null, true), '');
assert.equal(titleTagText('other', { name: 'x' }, true), '');
console.log(' ok - off, or with nothing to name, nothing is left in the title');
// Wiring: both composer strategies use it, with the user's setting.
const body = read('client/components/lists/listBody.js');
assert.match(body, /toggleValueInReactiveArray\(tpl\.members, user\._id\);[\s\S]{0,200}return titleTagText\('member', user, keepTitleTags\(\)\);/);
assert.match(body, /toggleValueInReactiveArray\(tpl\.labels, label\._id\);[\s\S]{0,200}return titleTagText\('label', label, keepTitleTags\(\)\);/);
assert.match(body, /const keepTitleTags = \(\) => \{[\s\S]{0,200}user\.hasKeepTitleTags\(\)/);
console.log(' ok - both autocomplete strategies follow the setting');
// The setting: schema, helper, an own-profile toggle, and the UI (UI guard).
const users = read('models/users.js');
assert.match(users, /'profile\.keepTitleTags': \{[\s\S]{0,400}type: Boolean,\s*optional: true,/);
assert.match(users, /hasKeepTitleTags\(\) \{\s*return !!\(\(this\.profile \|\| \{\}\)\.keepTitleTags\);/);
const server = read('server/models/users.js');
assert.match(server, /async toggleKeepTitleTags\(\) \{\s*if \(!this\.userId\) throw[\s\S]{0,300}Users\.updateAsync\(this\.userId, \{ \$set: \{ 'profile\.keepTitleTags':/,
'only the signed-in user\'s own profile is changed');
const jade = read('client/components/users/userHeader.jade');
assert.match(jade, /a\.js-toggle-keep-title-tags\(title="\{\{_ 'keep-title-tags-description'\}\}"\)[\s\S]{0,120}\{\{_ 'keep-title-tags'\}\}\s*\n\s*if isKeepTitleTags\s*\n\s*i\.fa\.fa-check/);
const js = read('client/components/users/userHeader.js');
assert.match(js, /'click \.js-toggle-keep-title-tags'\(\) \{\s*if \(ReactiveCache\.getCurrentUser\(\)\) Meteor\.call\('toggleKeepTitleTags'\);/);
const en = JSON.parse(read('imports/i18n/data/en.i18n.json'));
assert.ok(en['keep-title-tags'] && en['keep-title-tags-description']);
console.log(' ok - Member Settings has the switch, off by default');
console.log('\ntitleTags: all tests passed');