itemBox redesign

- Table structure rewrite to use grid layout instead
of the <table> component so that screen readers can see the content
- Added icons to mirror right-click actions
- Moved creator actions to the options menu
- Drag-drop to reorder creators
- Using editable-text instead of clicky component
- Consolidated autocomplete logic in one function
- Added @focus-ring to all components of the itemBox
- Fields are focusable and navigatable via keyboard in non-edit modes
- General refactoring to consolidate stylesheets across platforms
and remove code that's not more used (mainly related to handling old
clicky text component).
- Retractions panel background set for --material-background instead
of light pink in dark mode.
This commit is contained in:
Bogdan Abaev 2023-12-21 15:10:04 -05:00 committed by Dan Stillman
parent 553d1f6b3c
commit 13de06cd52
13 changed files with 1099 additions and 1093 deletions

View file

@ -139,9 +139,7 @@
span.innerText = this.value; span.innerText = this.value;
this.append(span); this.append(span);
let size = span.getBoundingClientRect(); let size = span.getBoundingClientRect();
let inlinePadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-inline'); this.style['max-width'] = `calc(${size.width}px)`;
let blockPadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-block');
this.style['max-width'] = `calc(${size.width}px + 2*${inlinePadding} + 2*${blockPadding})`;
this.querySelector("span").remove(); this.querySelector("span").remove();
}; };

File diff suppressed because it is too large Load diff

View file

@ -351,7 +351,7 @@
itemID: this._item.id || '' itemID: this._item.id || ''
}; };
valueElement.autocomplete = { valueElement.autocomplete = {
ignoreBlurWhileSearching: true, ignoreBlurWhileSearching: false,
popup: 'PopupAutoComplete', popup: 'PopupAutoComplete',
search: 'zotero', search: 'zotero',
searchParam: JSON.stringify(params), searchParam: JSON.stringify(params),

View file

@ -9,6 +9,13 @@ zotero-tabs-menu-filter =
zotero-tabs-menu-close-button = zotero-tabs-menu-close-button =
.tooltiptext = Close Tab .tooltiptext = Close Tab
item-creator-moveDown =
.label = Move Down
item-creator-moveToTop =
.label = Move to Top
item-creator-moveUp =
.label = Move Up
item-menu-viewAttachment = item-menu-viewAttachment =
.label = Open { .label = Open {
$attachmentType -> $attachmentType ->
@ -22,6 +29,13 @@ item-menu-viewAttachment =
[window] New Window [window] New Window
*[other] Reader *[other] Reader
} }
itembox-button-openLink =
.title = Open Link
.aria-label = Open Link
itembox-button-options =
.aria-label = Open Context Menu
.title = Open Context Menu
import-window = import-window =
.title = Import .title = Import

View file

@ -411,11 +411,8 @@ pane.item.changeType.text = Are you sure you want to change the item type?\n\nT
pane.item.defaultFirstName = first pane.item.defaultFirstName = first
pane.item.defaultLastName = last pane.item.defaultLastName = last
pane.item.defaultFullName = full name pane.item.defaultFullName = full name
pane.item.switchFieldMode.one = Switch to single field pane.item.switchFieldMode.one = Switch to Single Field
pane.item.switchFieldMode.two = Switch to two fields pane.item.switchFieldMode.two = Switch to Two Fields
pane.item.creator.moveToTop = Move to Top
pane.item.creator.moveUp = Move Up
pane.item.creator.moveDown = Move Down
pane.item.notes.allNotes = All Notes pane.item.notes.allNotes = All Notes
pane.item.notes.untitled = Untitled Note pane.item.notes.untitled = Untitled Note
pane.item.notes.delete.confirm = Are you sure you want to delete this note? pane.item.notes.delete.confirm = Are you sure you want to delete this note?

View file

@ -15,7 +15,7 @@
/* Minus and plus buttons with clicky glow effect */ /* Minus and plus buttons with clicky glow effect */
.zotero-clicky-minus, .zotero-clicky-plus { .zotero-clicky-minus, .zotero-clicky-plus {
margin: 0 !important; margin: 0;
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
@ -29,11 +29,33 @@
.zotero-clicky-minus { .zotero-clicky-minus {
@include svgicon-menu("minus-circle", "universal", "16"); @include svgicon-menu("minus-circle", "universal", "16");
border: 0px !important; border: 0px !important;
margin: 0;
} }
.zotero-clicky-plus { .zotero-clicky-plus {
@include svgicon-menu("plus-circle", "universal", "16"); @include svgicon-menu("plus-circle", "universal", "16");
border: 0px !important; border: 0px !important;
margin: 0;
}
.zotero-clicky-grippy {
@include svgicon-menu("grip", "universal", "16");
margin: 0;
}
.zotero-clicky-options {
@include svgicon-menu("options", "universal", "16");
margin: 0;
}
.zotero-clicky-open-link {
@include svgicon-menu("open-link", "universal", "16");
margin: 0;
}
.zotero-clicky-merge {
@include svgicon-menu("merge", "universal", "16");
margin: 0;
} }
.zotero-clicky-cross { .zotero-clicky-cross {

View file

@ -40,9 +40,11 @@ editable-text {
span { span {
visibility: hidden; visibility: hidden;
margin: 1px; margin: 0;
border: 1px solid transparent;
width: fit-content; width: fit-content;
white-space: nowrap; white-space: nowrap;
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
} }
&:not([nowrap])::after { &:not([nowrap])::after {
@ -83,7 +85,7 @@ editable-text {
&:hover:not(:read-only, :focus) { &:hover:not(:read-only, :focus) {
border-radius: 5px; border-radius: 5px;
border: 1px solid var(--fill-tertiary); background-color: var(--fill-quinary);
box-shadow: 0 0 0 1px var(--fill-quinary); box-shadow: 0 0 0 1px var(--fill-quinary);
} }

View file

@ -1,100 +1,115 @@
item-box { item-box {
display: flex;
flex-direction: column;
}
item-box .body {
--row-height: 1.5em;
display: flex; display: flex;
min-width: 0; min-width: 0;
width: 100%; width: 100%;
#item-box { #item-box {
display: flex;
flex-direction: column;
margin-top: 8px;
align-items: start;
width: 100%; width: 100%;
} }
#info-table { #info-table {
display: grid; display: grid;
grid-template-columns: max-content 1fr; grid-template-columns: max-content 1fr;
// Make sure rows are at least --row-height tall, but no taller than their tallest child column-gap: 10px;
// This lets rows expand for multi-line editors row-gap: 2px;
grid-auto-rows: minmax(var(--row-height), max-content); width: inherit;
align-items: center;
width: 100%;
} }
tr { .meta-row {
display: contents; display: grid;
} grid-template-columns: subgrid;
grid-column: span 2;
padding-inline-start: 8px;
padding-inline-end: 8px;
td { .meta-data {
width: 0;
min-width: 100%;
display: flex; display: flex;
min-width: 0; toolbarbutton {
align-self: stretch; margin-inline-start: 4px;
}
}
editable-text {
flex: 1; // stretch value field as much as possible
max-width: 100%; // stay within .meta-data when the itemBox is narrow
.input {
// keep input within editable-text when the itemBox is narrow
width: calc(100% - 2*var(--editable-text-padding-inline) - 1px)
}
// keep multiline fields as tall as they have to be unless they're focused
&[multiline] textarea:not(:focus) {
min-height: 1em;
}
}
.meta-label {
display: flex;
font-weight: normal;
text-align: end;
&[fieldname^="creator"] {
justify-content: space-between;
align-items: center; align-items: center;
margin-inline-end: 5px; }
> label {
margin-top: 2px;
@include comfortable {
margin-top: 3px;
}
}
} }
td > input, .creator-name-box > input { .key {
align-self: center;
// Fields have 3px borders; cancel them out
margin-top: -3px;
margin-bottom: -3px;
margin-inline-start: 0;
padding: 0;
}
td > input {
margin-inline-end: 5px;
}
th > label {
margin-top: 1px !important;
margin-bottom: 1px !important;
-moz-box-pack: start;
margin-inline-start: 1px !important;
margin-inline-end: 5px !important;
padding: 0 2px;
}
td > [fieldname] {
width: 100%; width: 100%;
} }
.value { toolbarbutton {
min-height: 14px; @include focus-ring;
// needed to have the outline appear on all platforms
-moz-appearance: none;
align-self: center; align-self: center;
padding-inline: 2px; // Make all buttons tigher to not stretch the rows
height: auto;
width: auto;
padding: 1px;
}
} }
.value:not(.multiline) { .meta-label > label, .creator-type-label, #more-creators-label {
white-space: nowrap; color: var(--fill-secondary);
overflow: hidden;
text-overflow: ellipsis;
} }
.value.multiline { // All icons that are by default hidden
white-space: pre-line; #info-table .show-on-hover {
visibility: hidden;
} }
/*td > vbox > description .drag-hidden-creator {
{ opacity: 0;
margin: 0 !important; }
}*/ // On hover of the meta-row, reveal all hidden icons
// unless there's .noHover class which keeps everything hidden
#info-table .meta-row:not(.noHover):hover .show-on-hover,
#info-table .meta-row:focus-within .show-on-hover {
visibility: visible;
}
#item-type-menu { #item-type-menu {
height: 1.5em !important; @include focus-ring;
min-height: 1.5em !important; margin: 0;
padding: 0 2px !important;
margin: 0 !important;
margin-inline-end: 5px !important; margin-inline-end: 5px !important;
max-height: 1.5em !important;
flex: 1; flex: 1;
padding-inline-start: 5px;
// Same padding as editable-text
@include comfortable {
padding-top: 3px;
padding-bottom: 3px;
}
&::part(dropmarker) { &::part(dropmarker) {
display: none; display: none;
@ -103,10 +118,13 @@ item-box .body {
&::part(label) { &::part(label) {
margin-inline-start: 0; margin-inline-start: 0;
} }
&[disabled] {
background: none;
}
} }
#item-type-menu:not(:hover):not(:active) { #item-type-menu:not(:hover):not(:active) {
border: var(--material-border-transparent);
background-color: transparent; background-color: transparent;
} }
@ -119,128 +137,95 @@ item-box .body {
padding: 0 !important; padding: 0 !important;
} }
/* DEBUG: this doesn't seem to work, unfortunately
label[singleField=false]:after
{
content:",";
}
*/
/*textbox .textbox-input-box
{
margin: 0;
}*/
textarea { textarea {
font: inherit; font: inherit;
resize: none; resize: none;
} }
/* metadata field names */
th {
display: flex;
height: var(--row-height);
align-self: start;
align-items: center;
justify-content: end;
font-weight: normal;
margin-inline-start: 5px !important;
margin-inline-end: 0 !important;
}
#more-creators-label #more-creators-label
{ {
font-weight: bold; font-weight: bold;
} }
/*row > label
{
border: 1px solid transparent;
}
row label
{
-moz-user-focus: ignore;
}*/
.pointer:hover, .pointer:hover > label { .pointer:hover, .pointer:hover > label {
cursor: pointer !important; cursor: pointer !important;
} }
/* creator type menu */ /* creator type menu */
.creator-type-label {
@include focus-ring;
display: inline-block;
// undo the padding so that the name is pushed to the end
margin-inline-end: -4px;
padding-left: 4px;
padding-right: 4px;
&:hover, &:focus {
.creator-type-dropmarker {
visibility: visible;
}
}
}
.creator-type-label, .creator-type-value { .creator-type-label, .creator-type-value {
-moz-box-align: center; -moz-box-align: center;
align-items: center;
display: flex;
}
.creator-type-value > .zotero-clicky {
// Some spacing between creator buttons
margin-right: 2px;
} }
.creator-name-box { .creator-name-box {
flex: 1; flex: 1;
min-width: 0;
display: flex; display: flex;
align-items: baseline; editable-text input {
min-width: 0;
& > input {
flex: 1;
min-width: 60%;
} }
& > div { // Margin adjusted by inline padding to have 4px between first and last name
flex-shrink: 1; *[fieldMode="0"]:first-child {
min-width: 10px; margin-inline-end: calc(max(0px, 4px - var(--editable-text-padding-inline)));
} }
// Add comma when the last name is not focused
& > div:first-child[fieldMode="0"] { *[fieldMode="0"]:first-child:not(.focused) {
// Cancel out padding before comma position: relative;
margin-inline-end: -3px; &::after {
content: ",";
position: absolute;
right: 0;
bottom: var(--editable-text-padding-block);
} }
} }
.creator-type-label > label
{
margin: 1px 0 !important;
margin-inline-end: 4px !important;
padding-inline-end: 2px !important;
} }
.creator-type-dropmarker { .creator-type-dropmarker {
display: inline-block; display: inline-block;
margin: 0 1em 1px; background-image: url('chrome://zotero/skin/16/universal/chevron-12.svg');
background-image: url('chrome://zotero/skin/arrow-down.gif'); background-size: contain;
background-size: cover; width: 8px;
width: 11px; height: 8px;
height: 6px; background-repeat: no-repeat;
} padding-right: 4px;
align-self: center;
.creator-name-box, .date-box > span { visibility: hidden;
margin: 1px 0 !important;
margin-inline-start: 1px !important;
} }
.comma { .comma {
margin: 1px 0 !important; margin-inline-end: calc(4px - var(--editable-text-padding-inline));
align-self: center;
} }
#zotero-date-field-status #zotero-date-field-status
{ {
color: #666; color: var(--fill-secondary);
padding: 0 !important; padding: 0 !important;
padding-inline-start: 1px !important; padding-inline-start: 5px !important;
padding-inline-end: 10px !important; padding-inline-end: 1px !important;
white-space: nowrap; white-space: nowrap;
} align-self: center;
.zotero-field-toggle
{
width: 27px !important;
max-width: 27px !important;
min-width: 27px !important;
height: 14px;
margin: 0 !important;
margin-inline-end: 5px !important;
background-repeat: no-repeat !important;
background-position: center !important;
border-width: 0 !important;
border-radius: 4px !important;
} }
/* Merge pane in duplicates view */ /* Merge pane in duplicates view */
@ -267,7 +252,7 @@ item-box .body {
} }
#retraction-details { #retraction-details {
background: #fbf0f0; @include light-dark(background, #fbf0f0, var(--material-background));
padding: .5em 1.5em; padding: .5em 1.5em;
margin-top: 0; margin-top: 0;
margin-bottom: 1em; margin-bottom: 1em;

View file

@ -1,5 +1,9 @@
item-box { item-box {
.zotero-clicky {
min-height: 17px; #item-type-menu {
padding: 1px;
@include comfortable {
padding: 2px;
}
} }
} }

View file

@ -1,32 +0,0 @@
item-box {
scrollbox
{
padding-top: 3px;
}
th > label, .creator-type-label, #more-creators-label {
color: #7f7f7f;
}
/*.zotero-field-toggle .toolbarbutton-text
{
visibility: hidden;
}
.zotero-field-toggle .toolbarbutton-icon
{
margin: 0px !important;
}*/
.creator-type-dropmarker {
margin: 1px .2em 1px;
background-image: url('chrome://zotero/skin/mac/arrow-down.png');
max-width: 7px;
max-height: 7px;
}
}
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
@media (min-resolution: 1.25dppx) {
item-box .creator-type-dropmarker { background-image: url('chrome://zotero/skin/mac/arrow-down@2x.png'); }
}

View file

@ -1,24 +1,7 @@
item-box { item-box {
row > hbox,
row > vbox
{
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
row vbox[fieldname]
{
margin-inline-start: 1px;
}
.creator-type-label image {
margin-bottom: 0;
}
@media (-moz-platform: windows-win10) { @media (-moz-platform: windows-win10) {
td > input, td > textarea, .creator-name-box > input { .meta-data > input, .meta-data > textarea, .creator-name-box > input {
// Give text fields a consistent, native-ish look using Windows 11 // Give text fields a consistent, native-ish look using Windows 11
// colors. Ideally, we only want to do this on Windows 11, but // colors. Ideally, we only want to do this on Windows 11, but
// Windows 11 can't currently be specifically targeted. // Windows 11 can't currently be specifically targeted.
@ -36,7 +19,6 @@ item-box {
#item-type-menu #item-type-menu
{ {
padding: 0 !important;
padding-inline-start: 1px !important; padding-inline-start: 1px !important;
margin: 0 !important; margin: 0 !important;
margin-inline-start: 1px !important; margin-inline-start: 1px !important;

View file

@ -13,4 +13,3 @@
// Elements // Elements
@import "mac/elements/attachmentBox"; @import "mac/elements/attachmentBox";
@import "mac/elements/itemBox";

View file

@ -16,14 +16,14 @@ describe("Item pane", function () {
var id = yield item.saveTx(); var id = yield item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box'); var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelectorAll('[fieldname="title"]')[1]; var label = itemBox.querySelectorAll('[fieldname="series"]')[1];
assert.equal(label.textContent, ''); assert.equal(label.value, '');
item.setField('title', 'Test'); item.setField('series', 'Test');
yield item.saveTx(); yield item.saveTx();
label = itemBox.querySelectorAll('[fieldname="title"]')[1]; label = itemBox.querySelectorAll('[fieldname="series"]')[1];
assert.equal(label.textContent, 'Test'); assert.equal(label.value, 'Test');
yield Zotero.Items.erase(id); yield Zotero.Items.erase(id);
}) })
@ -41,10 +41,10 @@ describe("Item pane", function () {
await item.saveTx(); await item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box'); var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('[fieldname="creator-0-lastName"]') var lastName = itemBox.querySelector('#itembox-field-value-creator-0-lastName');
var parent = label.parentNode; var parent = lastName.closest(".creator-type-value");
assert.property(parent, 'oncontextmenu'); assert.property(parent, 'oncontextmenu');
assert.isFunction(label.parentNode.oncontextmenu); assert.isFunction(parent.oncontextmenu);
var menupopup = itemBox.querySelector('#zotero-creator-transform-menu'); var menupopup = itemBox.querySelector('#zotero-creator-transform-menu');
// Fake a right-click // Fake a right-click
@ -73,14 +73,62 @@ describe("Item pane", function () {
await item.saveTx(); await item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box'); var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('[fieldname="creator-0-lastName"]') var label = itemBox.querySelector('#itembox-field-value-creator-0-lastName');
var firstlast = label.closest('.creator-name-box'); var firstlast = label.closest('.creator-type-value');
firstlast.oncontextmenu(new MouseEvent('click', { bubbles: true, button: 2 })); firstlast.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, button: 2 }));
var menuitem = doc.getElementById('creator-transform-swap-names'); var menuitem = doc.getElementById('creator-transform-swap-names');
assert.isTrue(menuitem.hidden); assert.isTrue(menuitem.hidden);
}); });
it("should reorder creators", async function () {
var item = new Zotero.Item('book');
item.setCreators([
{
lastName: "One",
creatorType: "author"
},
{
lastName: "Two",
creatorType: "author"
},
{
lastName: "Three",
creatorType: "author"
}
]);
await item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box');
// Move One to the last spot
itemBox.moveCreator(0, null, 3);
await waitForItemEvent('modify');
let thirdLastName = itemBox.querySelector("[fieldname='creator-2-lastName']").value;
assert.equal(thirdLastName, "One");
// Move One to the second spot
itemBox.moveCreator(2, null, 1);
await waitForItemEvent('modify');
let secondLastname = itemBox.querySelector("[fieldname='creator-1-lastName']").value;
assert.equal(secondLastname, "One");
// Move Two down
itemBox.moveCreator(0, 'down');
await waitForItemEvent('modify');
secondLastname = itemBox.querySelector("[fieldname='creator-1-lastName']").value;
let firstLastName = itemBox.querySelector("[fieldname='creator-0-lastName']").value;
assert.equal(secondLastname, "Two");
assert.equal(firstLastName, "One");
// Move Three up
itemBox.moveCreator(2, 'up');
await waitForItemEvent('modify');
secondLastname = itemBox.querySelector("[fieldname='creator-1-lastName']").value;
thirdLastName = itemBox.querySelector("[fieldname='creator-2-lastName']").value;
assert.equal(secondLastname, "Three");
assert.equal(thirdLastName, "Two");
});
// Note: This issue applies to all context menus in the item box (text transform, name swap), // Note: This issue applies to all context menus in the item box (text transform, name swap),
// though the others aren't tested. This might go away with the XUL->HTML transition. // though the others aren't tested. This might go away with the XUL->HTML transition.
@ -119,9 +167,7 @@ describe("Item pane", function () {
var item = await createDataObject('item'); var item = await createDataObject('item');
var itemBox = doc.getElementById('zotero-editpane-item-box'); var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('div[fieldname="accessDate"].zotero-clicky'); var textbox = itemBox.querySelector('[fieldname="accessDate"]');
label.click();
var textbox = itemBox.querySelector('input[fieldname="accessDate"]');
textbox.value = 'now'; textbox.value = 'now';
// Blur events don't necessarily trigger if window doesn't have focus // Blur events don't necessarily trigger if window doesn't have focus
itemBox.hideEditor(textbox); itemBox.hideEditor(textbox);
@ -148,11 +194,11 @@ describe("Item pane", function () {
let itemBox = doc.getElementById('zotero-editpane-item-box'); let itemBox = doc.getElementById('zotero-editpane-item-box');
itemBox.querySelector('div[fieldname="creator-0-lastName"]').click(); itemBox.querySelector('[fieldname="creator-0-lastName"]').click();
itemBox.hideEditor(itemBox.querySelector('input[fieldname="creator-0-lastName"]')); itemBox.hideEditor(itemBox.querySelector('input[fieldname="creator-0-lastName"]'));
assert.equal( assert.equal(
itemBox.querySelector('div[fieldname="creator-0-lastName"]').getAttribute('fieldMode'), itemBox.querySelector('[fieldname="creator-0-lastName"]').getAttribute('fieldMode'),
'1' '1'
); );
}); });