Right top User Settings / Grey Icons. Also fixed Change Language popup.

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu 2025-12-23 01:31:02 +02:00
parent 1808ea7384
commit 300b653ea3
15 changed files with 237 additions and 149 deletions

View file

@ -109,7 +109,7 @@ template(name="boardHeaderBar")
| ❌
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
| 🔍
span.emoji-icon 🔍
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(

View file

@ -647,6 +647,13 @@
font-size: 25px;
color: #fff;
}
/* Prevent Grey Icons from affecting checkmarks in background color list */
body.grey-icons-enabled .checkmark-no-grey {
filter: none !important;
-webkit-filter: none !important;
}
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
.board-list.mobile-view {
height: calc(100vh - 120px);

View file

@ -8,18 +8,26 @@ template(name="boardList")
ul.menu
li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}")
a.js-select-menu(data-type="starred")
span.menu-label ⭐ {{_ 'allboards.starred'}}
span.menu-label
span.emoji-icon ⭐
| {{_ 'allboards.starred'}}
span.menu-count {{menuItemCount 'starred'}}
li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}")
a.js-select-menu(data-type="templates")
span.menu-label 📋 {{_ 'allboards.templates'}}
span.menu-label
span.emoji-icon 📋
| {{_ 'allboards.templates'}}
span.menu-count {{menuItemCount 'templates'}}
li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}")
a.js-select-menu(data-type="remaining")
span.menu-label 📂 {{_ 'allboards.remaining'}}
span.menu-label
span.emoji-icon 📂
| {{_ 'allboards.remaining'}}
span.menu-count {{menuItemCount 'remaining'}}
.workspaces-header
span 🗂️ {{_ 'allboards.workspaces'}}
span
span.emoji-icon 🗂️
| {{_ 'allboards.workspaces'}}
a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") +
// Workspaces tree
+workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId)
@ -43,44 +51,49 @@ template(name="boardList")
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
span 🔍
span.emoji-icon 🔍
input#filterBtn(type="button" value="{{_ 'filter'}}")
button#resetBtn.filter-reset-btn
span.reset-icon ❌
span.reset-icon
span.emoji-icon ❌
span {{_ 'filter-clear'}}
// Right boards grid
.boards-right-grid
.boards-path-header
.path-left
span.path-icon {{currentMenuPath.icon}}
span.path-icon.emoji-icon {{currentMenuPath.icon}}
span.path-text {{currentMenuPath.text}}
if BoardMultiSelection.isActive
span.multiselection-hint 📌 {{_ 'multi-selection-active'}}
span.multiselection-hint
span.emoji-icon 📌
| {{_ 'multi-selection-active'}}
.path-right
if canModifyBoards
if hasBoardsSelected
button.js-archive-selected-boards.board-header-btn
span 📦
span.emoji-icon 📦
span {{_ 'archive-board'}}
button.js-duplicate-selected-boards.board-header-btn
span 📋
span.emoji-icon 📋
span {{_ 'duplicate-board'}}
a.board-header-btn.js-multiselection-activate(
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
| ☑️
span.emoji-icon ☑️
if BoardMultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
span.emoji-icon
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
li.js-add-board
if isSelectedMenu 'templates'
a.board-list-item.label(title="{{_ 'add-template-container'}}")
| {{_ 'add-template-container'}}
span.emoji-icon
| {{_ 'add-template-container'}}
else
a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}}
span.emoji-icon
| {{_ 'add-board'}}
each boards
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
if isInvited
@ -93,7 +106,8 @@ template(name="boardList")
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
p.board-list-item-desc {{_ 'just-invited'}}
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
@ -103,7 +117,9 @@ template(name="boardList")
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}") ↕️
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon ↕️
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
@ -116,17 +132,20 @@ template(name="boardList")
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
| ⏱️
span.emoji-icon ⏱️
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
else
.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}") ↕️
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon ↕️
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
@ -151,11 +170,12 @@ template(name="boardList")
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
| ⏱️
span.emoji-icon ⏱️
a.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
template(name="boardListHeaderBar")
h1 {{_ title }}
@ -174,16 +194,19 @@ template(name="workspaceTree")
each nodes
li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true")
.workspace-node-content
span.workspace-drag-handle ↕️
span.workspace-drag-handle
span.emoji-icon ↕️
a.js-select-workspace(data-id="{{id}}")
span.workspace-icon
if icon
+viewer
= icon
else
| 📁
span.emoji-icon 📁
span.workspace-name= name
a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}") ✏️
a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}")
span.emoji-icon ✏️
span.workspace-count {{workspaceCount id}}
a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") +
if children

View file

@ -232,20 +232,18 @@ BlazeComponent.extendComponent({
},
boards() {
let query = {
// { type: 'board' },
// { type: { $in: ['board','template-container'] } },
$and: [
{ archived: false },
{ type: { $in: ['board', 'template-container'] } },
{ $or: [] },
{ title: { $not: { $regex: /^\^.*\^$/ } } }
]
};
const membershipOrs = [];
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
if (FlowRouter.getRouteName() === 'home') {
query.$and[2].$or.push({ 'members.userId': Meteor.userId() });
membershipOrs.push({ 'members.userId': Meteor.userId() });
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
query.$and.push({ 'permission': 'private' });
@ -260,7 +258,7 @@ BlazeComponent.extendComponent({
// }
//query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}});
query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } });
membershipOrs.push({ 'orgs.orgId': { $in: orgsIds } });
}
let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || '';
@ -270,8 +268,11 @@ BlazeComponent.extendComponent({
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
// }
//query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}});
query.$and[2].$or.push({ 'teams.teamId': { $in: teamsIds } });
membershipOrs.push({ 'teams.teamId': { $in: teamsIds } });
}
if (membershipOrs.length) {
query.$and.splice(2, 0, { $or: membershipOrs });
}
}
else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) {
query = {
@ -545,15 +546,18 @@ BlazeComponent.extendComponent({
const query = {
$and: [
{ archived: false },
{ type: 'board' },
{ $or: [] }
{ type: 'board' }
]
};
const ors = [];
if (selectedTeamsValues.length > 0) {
query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } });
ors.push({ 'teams.teamId': { $in: selectedTeamsValues } });
}
if (selectedOrgsValues.length > 0) {
query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
ors.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
}
if (ors.length) {
query.$and.push({ $or: ors });
}
let filteredBoards = ReactiveCache.getBoards(query, {});

View file

@ -29,7 +29,7 @@ template(name="cardDetails")
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
| 🔗
span.emoji-icon 🔗
span.copied-tooltip {{_ 'copied'}}
else
unless isPopup
@ -43,7 +43,7 @@ template(name="cardDetails")
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
| 🔗
span.emoji-icon 🔗
span.copied-tooltip {{_ 'copied'}}
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
@ -767,7 +767,7 @@ template(name="cardDetailsActionsPopup")
ul.pop-over-list
li
a.js-more
| 🔗
span.emoji-icon 🔗
| {{_ 'cardMorePopup-title'}}
template(name="exportCardPopup")

View file

@ -94,24 +94,24 @@
}
/* Admin edit popups: use full height */
.pop-over[data-popup="editUser"],
.pop-over[data-popup="editOrg"],
.pop-over[data-popup="editTeam"] {
.pop-over[data-popup="editUserPopup"],
.pop-over[data-popup="editOrgPopup"],
.pop-over[data-popup="editTeamPopup"] {
height: calc(100vh - 20px) !important;
max-height: calc(100vh - 20px) !important;
}
.pop-over[data-popup="editUser"] .content-wrapper,
.pop-over[data-popup="editOrg"] .content-wrapper,
.pop-over[data-popup="editTeam"] .content-wrapper {
.pop-over[data-popup="editUserPopup"] .content-wrapper,
.pop-over[data-popup="editOrgPopup"] .content-wrapper,
.pop-over[data-popup="editTeamPopup"] .content-wrapper {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
overflow-y: auto !important;
}
.pop-over[data-popup="editUser"] .content-container,
.pop-over[data-popup="editOrg"] .content-container,
.pop-over[data-popup="editTeam"] .content-container {
.pop-over[data-popup="editUserPopup"] .content-container,
.pop-over[data-popup="editOrgPopup"] .content-container,
.pop-over[data-popup="editTeamPopup"] .content-container {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
}
@ -123,7 +123,7 @@
}
/* Specific styling for language popup list */
.pop-over[data-popup="changeLanguage"] .pop-over-list {
.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
max-height: none;
overflow: visible;
height: auto;
@ -131,46 +131,69 @@
}
/* Ensure content div in language popup contains all items */
.pop-over[data-popup="changeLanguage"] .content {
.pop-over[data-popup="changeLanguagePopup"] .content {
height: auto;
min-height: 100%;
/* Remove forced min-height to avoid top gap */
display: flex;
flex-direction: column;
}
/* Allow dynamic height for Change Language popup */
.pop-over[data-popup="changeLanguage"] .content-wrapper {
max-height: inherit; /* Use dynamic height from JavaScript */
}
.pop-over[data-popup="changeLanguage"] .content-container {
max-height: inherit; /* Use dynamic height from JavaScript */
/* Ensure hidden stack pages truly take no space */
.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
min-height: 0 !important;
height: 0 !important;
padding: 0 !important;
margin: 0 !important;
visibility: hidden !important;
}
/* Make language popup extend to bottom of browser window */
.pop-over[data-popup="changeLanguage"] {
height: calc(100vh - 30px);
min-height: 300px;
/* Adjust positioning to move popup 30px higher */
transform: translateY(-30px);
.pop-over[data-popup="changeLanguagePopup"] {
position: fixed !important;
bottom: 0 !important;
top: auto !important;
left: auto !important;
right: 20px !important;
width: auto !important;
max-width: 450px !important;
height: 100vh !important;
max-height: 100vh !important;
min-height: 300px !important;
display: flex !important;
flex-direction: column !important;
margin: 0 !important;
}
.pop-over[data-popup="changeLanguage"] .content-wrapper {
height: calc(100% - 50px); /* Subtract header height more precisely */
min-height: 250px;
overflow-y: auto;
max-height: none; /* Remove any max-height constraints */
display: flex;
flex-direction: column;
/* Allow dynamic height for Change Language popup */
.pop-over[data-popup="changeLanguagePopup"] .header {
flex-shrink: 0 !important;
height: auto !important;
}
.pop-over[data-popup="changeLanguage"] .content-container {
height: auto; /* Let content determine height */
min-height: 250px;
max-height: none; /* Remove any max-height constraints */
flex: 1;
display: flex;
flex-direction: column;
.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
flex: 1 !important;
overflow-y: auto !important;
overflow-x: hidden !important;
min-height: 0 !important;
max-height: none !important;
height: auto !important;
width: 100% !important;
}
.pop-over[data-popup="changeLanguagePopup"] .content-container {
height: auto !important;
max-height: none !important;
flex: 1 !important;
display: flex !important;
flex-direction: column !important;
width: 100% !important;
}
.pop-over[data-popup="changeLanguagePopup"] .content {
height: auto !important;
max-height: none !important;
padding-bottom: 50px !important;
width: 100% !important;
}
/* Date popup sizing for native HTML inputs */

View file

@ -2,6 +2,7 @@
class="{{#unless title}}miniprofile{{/unless}}"
class=currentBoard.colorClass
class="{{#unless title}}no-title{{/unless}}"
data-popup="{{popupName}}"
style="left:{{offset.left}}px; top:{{offset.top}}px;{{#if offset.maxHeight}} max-height:{{offset.maxHeight}}px;{{/if}}")
.header
a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}")

View file

@ -8,27 +8,27 @@ template(name="adminReports")
ul
li
a.js-report-broken(data-id="report-broken")
| 🔗
span.emoji-icon 🔗
| {{_ 'broken-cards'}}
li
a.js-report-files(data-id="report-files")
| 📎
span.emoji-icon 📎
| {{_ 'filesReportTitle'}}
li
a.js-report-rules(data-id="report-rules")
|
span.emoji-icon
| {{_ 'rulesReportTitle'}}
li
a.js-report-boards(data-id="report-boards")
|
span.emoji-icon
| {{_ 'boardsReportTitle'}}
li
a.js-report-cards(data-id="report-cards")
|
span.emoji-icon
| {{_ 'cardsReportTitle'}}
.main-body

View file

@ -48,7 +48,7 @@ template(name="people")
option(value="inactive") {{_ 'admin-people-filter-inactive'}}
option(value="admin") Admin
button#unlockAllUsers.unlock-all-btn
| 🔓
span.emoji-icon 🔓
| {{_ 'accounts-lockout-unlock-all'}}
.ext-box-right
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
@ -58,7 +58,7 @@ template(name="people")
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
else if lockedUsersSetting.get
span
span.text-red 🔒
span.emoji-icon.text-red 🔒
unless isMiniScreen
| {{_ 'accounts-lockout-locked-users'}}
@ -79,7 +79,7 @@ template(name="people")
| {{_ 'people'}}
li
a.js-locked-users-menu(data-id="locked-users-setting")
span.text-red 🔒
span.emoji-icon.text-red 🔒
| {{_ 'accounts-lockout-locked-users'}}
.main-body
if loading.get
@ -247,9 +247,9 @@ template(name="peopleRow")
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
td.account-status
if isUserLocked
span.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
span.text-red.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
else
span.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
td.account-active-status
if userData.loginDisabled
span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") 🚫

View file

@ -6,87 +6,87 @@ template(name="setting")
.content-title.ext-box
if isGeneralSetting
span
| 🔑
span.emoji-icon 🔑
| {{_ 'registration'}}
else if isEmailSetting
span
| ✉️
span.emoji-icon ✉️
| {{_ 'email'}}
else if isAccountSetting
span
| 👥
span.emoji-icon 👥
| {{_ 'accounts'}}
else if isTableVisibilityModeSetting
span
| 👁️
span.emoji-icon 👁️
| {{_ 'tableVisibilityMode'}}
else if isAnnouncementSetting
span
| 📢
span.emoji-icon 📢
| {{_ 'admin-announcement'}}
else if isAccessibilitySetting
span
|
span.emoji-icon
| {{_ 'accessibility'}}
else if isLayoutSetting
span
| 🔗
span.emoji-icon 🔗
| {{_ 'layout'}}
else if isWebhookSetting
span
| 🌐
span.emoji-icon 🌐
| {{_ 'global-webhook'}}
else if isAttachmentSettings
span
| 📎
span.emoji-iconpan.emoji-icon 📎
| {{_ 'attachments'}}
else if isCronSettings
span
|
span.emoji-icon
| {{_ 'cron'}}
.content-body
.side-menu
ul
li(class="{{#if isGeneralSetting}}active{{/if}}")
a.js-setting-menu(data-id="registration-setting")
| 🔑
span.emoji-icon 🔑
| {{_ 'registration'}}
unless isSandstorm
li(class="{{#if isEmailSetting}}active{{/if}}")
a.js-setting-menu(data-id="email-setting")
| ✉️
span.emoji-icon ✉️
| {{_ 'email'}}
li(class="{{#if isAccountSetting}}active{{/if}}")
a.js-setting-menu(data-id="account-setting")
| 👥
span.emoji-icon 👥
| {{_ 'accounts'}}
li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}")
a.js-setting-menu(data-id="tableVisibilityMode-setting")
| 👁️
span.emoji-icon 👁️
| {{_ 'tableVisibilityMode'}}
li(class="{{#if isAnnouncementSetting}}active{{/if}}")
a.js-setting-menu(data-id="announcement-setting")
| 📢
span.emoji-icon 📢
| {{_ 'admin-announcement'}}
li(class="{{#if isAccessibilitySetting}}active{{/if}}")
a.js-setting-menu(data-id="accessibility-setting")
|
span.emoji-icon
| {{_ 'accessibility'}}
li(class="{{#if isLayoutSetting}}active{{/if}}")
a.js-setting-menu(data-id="layout-setting")
| 🔗
span.emoji-icon 🔗
| {{_ 'layout'}}
li(class="{{#if isWebhookSetting}}active{{/if}}")
a.js-setting-menu(data-id="webhook-setting")
| 🌐
span.emoji-icon 🌐
| {{_ 'global-webhook'}}
li(class="{{#if isAttachmentSettings}}active{{/if}}")
a.js-setting-menu(data-id="attachment-settings")
| 📎
span.emoji-icon 📎
| {{_ 'attachments'}}
li(class="{{#if isCronSettings}}active{{/if}}")
a.js-setting-menu(data-id="cron-settings")
|
span.emoji-icon
| {{_ 'cron'}}
.main-body
if isLoading

View file

@ -5,31 +5,31 @@ template(name="settingHeaderBar")
.setting-header-btns.left
if currentUser
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
| ⚙️
span.emoji-icon ⚙️
span {{_ 'settings'}}
a.setting-header-btn.people(href="{{pathFor 'people'}}")
| 👥
span.emoji-icon 👥
span {{_ 'people'}}
a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}")
| 📋
span.emoji-icon 📋
span {{_ 'reports'}}
a.setting-header-btn.informations(href="{{pathFor 'attachments'}}")
| 📎
span.emoji-icon 📎
span {{_ 'attachments'}}
a.setting-header-btn.informations(href="{{pathFor 'translation'}}")
| 🔤
span.emoji-icon 🔤
span {{_ 'translation'}}
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
span.emoji-icon
span {{_ 'info'}}
else
a.setting-header-btn.js-log-in(
title="{{_ 'log-in'}}")
| 🚪
span.emoji-icon 🚪
span {{_ 'log-in'}}

View file

@ -155,7 +155,7 @@ template(name="boardChangeColorPopup")
span.background-box(class="board-color-{{this}}")
span {{this}}
if isSelected
|
span.checkmark-no-grey
template(name="boardChangeBackgroundImagePopup")
form
@ -575,7 +575,8 @@ template(name="boardMenuPopup")
if currentUser.isBoardAdmin
li
a.js-open-rules-view(title="{{_ 'rules'}}")
| ✨
span.emoji-icon
| ✨
| {{_ 'rules'}}
if currentUser.isBoardAdmin
li
@ -637,7 +638,8 @@ template(name="boardMenuPopup")
// | {{_ 'delete-duplicate-lists'}}
li
a.js-archive-board
| ➡️📦
span.emoji-icon
| ➡️📦
| {{_ 'archive-board'}}
template(name="exportBoard")

View file

@ -1,6 +1,33 @@
.unicode-icon {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.8;
display: inline-block;
line-height: 1;
}
/* Greyscale for explicitly-marked emoji when feature is enabled */
body.grey-icons-enabled .emoji-icon {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.85;
display: inline-block;
}
/* When grey icons are enabled, also grey common UI badges and toggles */
body.grey-icons-enabled .card-date,
body.grey-icons-enabled .mobile-icon,
body.grey-icons-enabled .desktop-icon {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.85;
}
/* Grey card minibadges (icons + text + backgrounds) */
body.grey-icons-enabled .minicard .badges .badge,
body.grey-icons-enabled .minicard .badges .badge .badge-icon,
body.grey-icons-enabled .minicard .badges .badge .badge-text {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.9;
}

View file

@ -1,10 +1,12 @@
Meteor.startup(() => {
const greyscaleIcons = [
'🔼', '❌', '🏷️', '📅', '📥', '🚀', '👤', '👥', '✍️', '📋', '✏️', '🌐', '📎', '📝', '📋', '📜', '🏠', '🔒', '🔕', '🃏',
'⏰', '🛒', '🔢', '✅', '❌', '👁️', '👍', '📋', '🕐', '🎨',
'⏰', '🛒', '🔢', '✅', '❌', '👁️', '👍', '👎', '📋', '🕐', '🎨',
'📤', '⬆️', '⬇️', '➡️', '📦',
'⬅️', '↕️', '🔽', '🔍', '▼', '🏊',
'🔔', '⚙️', '🖼️', '🔑', '🚪', '◀️', '⌨️', '👥', '🏷️', '✅', '🚫'
'🔔', '⚙️', '🖼️', '🔑', '🚪', '◀️', '⌨️', '👥', '🏷️', '✅', '🚫', '☑️', '💬',
// Mobile/Desktop toggle + calendar
'📱', '🖥️', '🗓️'
];
const EXCLUDE_SELECTOR = '.header-user-bar-avatar, .avatar-initials, script, style';
@ -34,43 +36,40 @@ Meteor.startup(() => {
parent.replaceChild(span, textNode);
}
function processNode(root) {
function wrapSubtree(root) {
try {
if (!root) return;
if (root.nodeType === Node.TEXT_NODE) {
wrapTextNodeOnce(root.parentNode, root);
return;
// Walk only within this subtree for text nodes
const walker = document.createTreeWalker(
root.nodeType === Node.ELEMENT_NODE ? root : root.parentNode || document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
if (!node || !node.nodeValue) return NodeFilter.FILTER_REJECT;
const parent = node.parentNode;
if (!parent || isExcluded(parent)) return NodeFilter.FILTER_REJECT;
if (parent.closest && parent.closest('.unicode-icon')) return NodeFilter.FILTER_REJECT;
const txt = node.nodeValue.trim();
if (!txt || txt.length > 3) return NodeFilter.FILTER_REJECT;
return greyscaleIcons.includes(txt) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
},
},
false,
);
const toWrap = [];
while (walker.nextNode()) {
toWrap.push(walker.currentNode);
}
if (root.nodeType !== Node.ELEMENT_NODE) return;
if (isExcluded(root)) return;
// Fast path: only check direct text children first
const children = Array.from(root.childNodes);
for (const child of children) {
if (child.nodeType === Node.TEXT_NODE) {
wrapTextNodeOnce(root, child);
}
}
// If element is small, also scan one level deeper to catch common structures
if (children.length <= 20) {
for (const child of children) {
if (child.nodeType === Node.ELEMENT_NODE && !isExcluded(child)) {
for (const gchild of Array.from(child.childNodes)) {
if (gchild.nodeType === Node.TEXT_NODE) wrapTextNodeOnce(child, gchild);
}
}
}
for (const textNode of toWrap) {
wrapTextNodeOnce(textNode.parentNode, textNode);
}
} catch (_) {}
}
function processInitial() {
// Process only frequently used UI containers to avoid full-page walks
const roots = [
document.body,
document.querySelector('#header-user-bar'),
...Array.from(document.querySelectorAll('.pop-over, .pop-over-list, .board-header, .card-details, .sidebar-content')),
].filter(Boolean);
roots.forEach(processNode);
const roots = [document.body].filter(Boolean);
roots.forEach(wrapSubtree);
}
function startObserver() {
@ -80,8 +79,8 @@ Meteor.startup(() => {
for (const m of mutations) {
if (m.type !== 'childList') continue;
m.addedNodes && m.addedNodes.forEach((n) => {
// Avoid scanning huge subtrees repeatedly by limiting depth
processNode(n);
// Process only within the newly added subtree
wrapSubtree(n);
});
}
});
@ -98,6 +97,7 @@ Meteor.startup(() => {
function enableGrey() {
if (enabled) return;
enabled = true;
try { document.body.classList.add('grey-icons-enabled'); } catch (_) {}
Meteor.defer(processInitial);
startObserver();
}
@ -106,6 +106,7 @@ Meteor.startup(() => {
if (!enabled) return;
enabled = false;
stopObserver();
try { document.body.classList.remove('grey-icons-enabled'); } catch (_) {}
// unwrap existing
document.querySelectorAll('span.unicode-icon').forEach((span) => {
const txt = document.createTextNode(span.textContent || '');

View file

@ -175,7 +175,7 @@ template(name="changeLanguagePopup")
each languages
li(class="{{# if isCurrentLanguage}}active{{/if}}")
a.js-set-language
| {{languageFlag}}
span.emoji-icon {{languageFlag}}
| {{name}}
if isCurrentLanguage
| ✅