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;
this.append(span);
let size = span.getBoundingClientRect();
let inlinePadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-inline');
let blockPadding = getComputedStyle(this).getPropertyValue('--editable-text-padding-block');
this.style['max-width'] = `calc(${size.width}px + 2*${inlinePadding} + 2*${blockPadding})`;
this.style['max-width'] = `calc(${size.width}px)`;
this.querySelector("span").remove();
};

File diff suppressed because it is too large Load diff

View file

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

View file

@ -9,6 +9,13 @@ zotero-tabs-menu-filter =
zotero-tabs-menu-close-button =
.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 =
.label = Open {
$attachmentType ->
@ -22,6 +29,13 @@ item-menu-viewAttachment =
[window] New Window
*[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 =
.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.defaultLastName = last
pane.item.defaultFullName = full name
pane.item.switchFieldMode.one = Switch to single field
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.switchFieldMode.one = Switch to Single Field
pane.item.switchFieldMode.two = Switch to Two Fields
pane.item.notes.allNotes = All Notes
pane.item.notes.untitled = Untitled 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 */
.zotero-clicky-minus, .zotero-clicky-plus {
margin: 0 !important;
margin: 0;
width: 20px;
height: 20px;
border-radius: 2px;
@ -29,11 +29,33 @@
.zotero-clicky-minus {
@include svgicon-menu("minus-circle", "universal", "16");
border: 0px !important;
margin: 0;
}
.zotero-clicky-plus {
@include svgicon-menu("plus-circle", "universal", "16");
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 {

View file

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

View file

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

View file

@ -1,5 +1,9 @@
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 {
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) {
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
// colors. Ideally, we only want to do this on Windows 11, but
// Windows 11 can't currently be specifically targeted.
@ -36,7 +19,6 @@ item-box {
#item-type-menu
{
padding: 0 !important;
padding-inline-start: 1px !important;
margin: 0 !important;
margin-inline-start: 1px !important;

View file

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

View file

@ -16,14 +16,14 @@ describe("Item pane", function () {
var id = yield item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelectorAll('[fieldname="title"]')[1];
assert.equal(label.textContent, '');
var label = itemBox.querySelectorAll('[fieldname="series"]')[1];
assert.equal(label.value, '');
item.setField('title', 'Test');
item.setField('series', 'Test');
yield item.saveTx();
label = itemBox.querySelectorAll('[fieldname="title"]')[1];
assert.equal(label.textContent, 'Test');
label = itemBox.querySelectorAll('[fieldname="series"]')[1];
assert.equal(label.value, 'Test');
yield Zotero.Items.erase(id);
})
@ -41,10 +41,10 @@ describe("Item pane", function () {
await item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('[fieldname="creator-0-lastName"]')
var parent = label.parentNode;
var lastName = itemBox.querySelector('#itembox-field-value-creator-0-lastName');
var parent = lastName.closest(".creator-type-value");
assert.property(parent, 'oncontextmenu');
assert.isFunction(label.parentNode.oncontextmenu);
assert.isFunction(parent.oncontextmenu);
var menupopup = itemBox.querySelector('#zotero-creator-transform-menu');
// Fake a right-click
@ -73,13 +73,61 @@ describe("Item pane", function () {
await item.saveTx();
var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('[fieldname="creator-0-lastName"]')
var firstlast = label.closest('.creator-name-box');
firstlast.oncontextmenu(new MouseEvent('click', { bubbles: true, button: 2 }));
var label = itemBox.querySelector('#itembox-field-value-creator-0-lastName');
var firstlast = label.closest('.creator-type-value');
firstlast.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, button: 2 }));
var menuitem = doc.getElementById('creator-transform-swap-names');
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),
@ -119,9 +167,7 @@ describe("Item pane", function () {
var item = await createDataObject('item');
var itemBox = doc.getElementById('zotero-editpane-item-box');
var label = itemBox.querySelector('div[fieldname="accessDate"].zotero-clicky');
label.click();
var textbox = itemBox.querySelector('input[fieldname="accessDate"]');
var textbox = itemBox.querySelector('[fieldname="accessDate"]');
textbox.value = 'now';
// Blur events don't necessarily trigger if window doesn't have focus
itemBox.hideEditor(textbox);
@ -148,11 +194,11 @@ describe("Item pane", function () {
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"]'));
assert.equal(
itemBox.querySelector('div[fieldname="creator-0-lastName"]').getAttribute('fieldMode'),
itemBox.querySelector('[fieldname="creator-0-lastName"]').getAttribute('fieldMode'),
'1'
);
});