Improve bidi & RTL support (#2415)

- Render cell text in its native direction
- Fix context menu positioning
- Fix item box (localizations needed)
- Fix column resizing
- Fix bidi text in collection tree
- Always right-align in RTL, always left-align in LTR.
  I'm going off advice from this excellent guide for RTL website design
  by Ahmad Shadeed: https://rtlstyling.com/posts/rtl-styling#tables
- Join creators in the tree ("Smith and Jones") using a format string to
  support languages like Arabic and Hebrew where there shouldn't be a
  space after the "and".
- Fix tabs
- Fix toolbar on Mac, flip icons on other platforms
This commit is contained in:
Abe Jellinek 2022-11-20 18:23:17 -05:00 committed by GitHub
parent 93bba41dd5
commit 74492e40c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 254 additions and 145 deletions

View file

@ -1,10 +1,10 @@
#zotero-items-toolbar[state=collapsed] #zotero-items-toolbar[state=collapsed]
{ {
margin-left: -8px !important; margin-inline-start: -8px !important;
} }
#zotero-pane toolbarseparator { #zotero-pane toolbarseparator {
margin-left: 7px; margin-inline-start: 7px;
} }
#zotero-tb-sync-stop .toolbarbutton-icon, #zotero-tb-sync-stop .toolbarbutton-icon,
@ -19,14 +19,19 @@
.zotero-tb-button, .zotero-tb-button,
.zotero-tb-button:first-child, .zotero-tb-button:first-child,
.zotero-tb-button:last-child { .zotero-tb-button:last-child {
-moz-margin-start: 0 !important; margin-inline-start: 0 !important;
-moz-margin-end: 3px !important; margin-inline-end: 3px !important;
-moz-padding-end: 10px !important; padding-right: 10px !important;
background: url("chrome://zotero/skin/mac/menubutton-end.png") right center/auto 24px no-repeat; background: url("chrome://zotero/skin/mac/menubutton-end.png") right center/auto 24px no-repeat;
} }
.zotero-tb-button[type=menu] { .zotero-tb-button[type=menu] {
-moz-padding-end: 8px !important; padding-inline-end: 8px !important;
}
.zotero-tb-button > .toolbarbutton-icon {
background: url("chrome://zotero/skin/mac/menubutton-start.png") left center/auto 24px no-repeat;
padding: 4px 4px 4px 11px;
} }
.zotero-tb-button > .toolbarbutton-icon { .zotero-tb-button > .toolbarbutton-icon {
@ -36,12 +41,35 @@
/* For menu buttons, decrease left padding by 1px */ /* For menu buttons, decrease left padding by 1px */
.zotero-tb-button[type=menu] > .toolbarbutton-icon { .zotero-tb-button[type=menu] > .toolbarbutton-icon {
-moz-padding-start: 9px; padding-left: 9px;
max-width: 29px; max-width: 29px;
} }
/* A decent hack: reverse the effective order of the dropmarker and icon in RTL
mode so that the above CSS, which places the menubutton-start.png background
on the toolbarbutton-icon, will make sense. (Otherwise the dropmarker appears
outside the button.) Then just flip the entire thing horizontally! We want to
do that anyway so that the Locate button will point to the left and Sync will
rotate counter-clockwise.
The other way to do this would be to set direction: ltr on buttons in the
RTL toolbar, but that breaks popup positioning. */
#zotero-pane[dir=rtl] .zotero-tb-button > .toolbarbutton-menu-dropmarker {
-moz-box-ordinal-group: 0;
}
#zotero-pane[dir=rtl] .zotero-tb-button > .toolbarbutton-icon {
-moz-box-ordinal-group: 1;
}
#zotero-pane[dir=rtl] .zotero-tb-button,
#zotero-pane[dir=rtl] .zotero-tb-button:first-child,
#zotero-pane[dir=rtl] .zotero-tb-button:last-child {
transform: scaleX(-1);
}
#zotero-collections-toolbar { #zotero-collections-toolbar {
padding-left: 0; padding-inline-start: 0;
} }
.zotero-tb-button:-moz-window-inactive { .zotero-tb-button:-moz-window-inactive {

View file

@ -17,6 +17,10 @@
margin-right: 4px; margin-right: 4px;
} }
#zotero-pane[dir=rtl] .zotero-tb-button > .toolbarbutton-icon {
transform: scaleX(-1);
}
/* Fixes tabs missing styling on (GTK 3.20) Ubuntu 16.10. See https://bugzilla.mozilla.org/show_bug.cgi?id=1306425 */ /* Fixes tabs missing styling on (GTK 3.20) Ubuntu 16.10. See https://bugzilla.mozilla.org/show_bug.cgi?id=1306425 */
tabpanels { tabpanels {
-moz-appearance: none; -moz-appearance: none;

View file

@ -44,6 +44,10 @@
padding-left: 2px; padding-left: 2px;
} }
#zotero-pane[dir=rtl] .zotero-tb-button > .toolbarbutton-icon {
transform: scaleX(-1);
}
#zotero-collections-splitter:not([state=collapsed]), #zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed]), #zotero-items-splitter:not([state=collapsed]),
#zotero-tags-splitter:not([state=collapsed]), #zotero-tags-splitter:not([state=collapsed]),

View file

@ -278,6 +278,7 @@ var CollectionTree = class CollectionTree extends LibraryTree {
let label = document.createElement('span'); let label = document.createElement('span');
label.innerText = treeRow.getName(); label.innerText = treeRow.getName();
label.className = 'cell-text'; label.className = 'cell-text';
label.dir = 'auto';
// Editing input // Editing input
div.classList.toggle('editing', treeRow == this._editing); div.classList.toggle('editing', treeRow == this._editing);

View file

@ -282,7 +282,7 @@ const TabBar = forwardRef(function (props, ref) {
onDragStart={(event) => handleDragStart(event, id, index)} onDragStart={(event) => handleDragStart(event, id, index)}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
> >
<div className="tab-name">{iconBackgroundImage && <div className="tab-name" dir="auto">{iconBackgroundImage &&
<span className="icon-bg" style={{ backgroundImage: iconBackgroundImage }}/>}{title}</div> <span className="icon-bg" style={{ backgroundImage: iconBackgroundImage }}/>}{title}</div>
<div <div
className="tab-close" className="tab-close"
@ -323,6 +323,7 @@ const TabBar = forwardRef(function (props, ref) {
onDragOver={handleTabBarDragOver} onDragOver={handleTabBarDragOver}
onMouseOut={handleTabBarMouseOut} onMouseOut={handleTabBarMouseOut}
onScroll={updateScrollArrows} onScroll={updateScrollArrows}
dir={Zotero.dir}
> >
{tabs.map((tab, index) => renderTab(tab, index))} {tabs.map((tab, index) => renderTab(tab, index))}
</div> </div>

View file

@ -805,8 +805,8 @@ class VirtualizedTable extends React.Component {
const aRect = a.getBoundingClientRect(); const aRect = a.getBoundingClientRect();
const bRect = b.getBoundingClientRect(); const bRect = b.getBoundingClientRect();
const resizingRect = resizing.getBoundingClientRect(); const resizingRect = resizing.getBoundingClientRect();
let offset = aRect.x; let offset = aRect.left;
if (aColumn.dataKey != resizingColumn.dataKey) { if (aColumn.dataKey != resizingColumn.dataKey && !Zotero.rtl) {
offset += resizingRect.width; offset += resizingRect.width;
} }
const widthSum = aRect.width + bRect.width; const widthSum = aRect.width + bRect.width;
@ -1165,6 +1165,10 @@ class VirtualizedTable extends React.Component {
ref: ref => this._topDiv = ref, ref: ref => this._topDiv = ref,
tabIndex: 0, tabIndex: 0,
role: this.props.role, role: this.props.role,
// XUL's chromedir attribute doesn't work with CSS :dir selectors,
// so we'll manually propagate the locale's script direction to the
// table.
dir: Zotero.Locale.defaultScriptDirection(Zotero.locale),
}; };
if (this.props.hide) { if (this.props.hide) {
props.style = { display: "none" }; props.style = { display: "none" };
@ -1592,17 +1596,19 @@ var Columns = class {
} }
}; };
function renderCell(index, data, column) { function renderCell(index, data, column, dir = null) {
column = column || { columnName: "" } column = column || { columnName: "" };
let span = document.createElement('span'); let span = document.createElement('span');
span.className = `cell ${column.className}`; span.className = `cell ${column.className}`;
span.innerText = data; span.innerText = data;
if (dir) span.dir = dir;
return span; return span;
} }
function renderCheckboxCell(index, data, column) { function renderCheckboxCell(index, data, column, dir = null) {
let span = document.createElement('span'); let span = document.createElement('span');
span.className = `cell checkbox ${column.className}`; span.className = `cell checkbox ${column.className}`;
if (dir) span.dir = dir;
span.setAttribute('role', 'checkbox'); span.setAttribute('role', 'checkbox');
span.setAttribute('aria-checked', data); span.setAttribute('aria-checked', data);
if (data) { if (data) {

View file

@ -922,7 +922,7 @@
// Comma // Comma
var comma = document.createElement("span"); var comma = document.createElement("span");
comma.textContent = ','; comma.textContent = Zotero.getString('punctuation.comma');
comma.className = 'comma'; comma.className = 'comma';
firstlast.appendChild(comma); firstlast.appendChild(comma);
@ -1447,7 +1447,25 @@
} }
valueElement.textContent = valueText; valueElement.textContent = valueText;
// Attempt to make bidi things work automatically:
// If we have text to work off of, let the layout engine try to guess the text direction
if (valueText) {
valueElement.dir = 'auto';
}
// If not, assume it follows the locale's direction
else {
valueElement.dir = Zotero.dir;
}
// Regardless, align the text in the label consistently, following the locale's direction
if (Zotero.rtl) {
valueElement.style.textAlign = 'right';
}
else {
valueElement.style.textAlign = 'left';
}
if (isMultiline) { if (isMultiline) {
valueElement.classList.add('multiline'); valueElement.classList.add('multiline');
} }
@ -1677,6 +1695,9 @@
t.style.mozBoxFlex = 1; t.style.mozBoxFlex = 1;
t.setAttribute('fieldname', fieldName); t.setAttribute('fieldname', fieldName);
t.setAttribute('ztabindex', tabindex); t.setAttribute('ztabindex', tabindex);
// We set dir in createValueElement(), so figure out what it was computed as
// and then propagate to the new text field
t.dir = getComputedStyle(elem).direction;
var box = elem.parentNode; var box = elem.parentNode;
box.replaceChild(t, elem); box.replaceChild(t, elem);

View file

@ -38,8 +38,9 @@
event.preventDefault(); event.preventDefault();
let rect = this.getBoundingClientRect(); let rect = this.getBoundingClientRect();
let dir = getComputedStyle(this).direction;
popup.openPopupAtScreen( popup.openPopupAtScreen(
window.screenX + rect.left, window.screenX + (dir == 'rtl' ? rect.right : rect.left),
window.screenY + rect.bottom, window.screenY + rect.bottom,
true true
); );
@ -59,7 +60,7 @@
static get dropmarkerFragment() { static get dropmarkerFragment() {
let frag = document.importNode( let frag = document.importNode(
MozXULElement.parseXULToFragment(` MozXULElement.parseXULToFragment(`
<image src="chrome://zotero/skin/searchbar-dropmarker${Zotero.hiDPISuffix}.png" width="7" height="4"/> <image src="chrome://zotero/skin/searchbar-dropmarker${Zotero.hiDPISuffix}.png" width="7" height="4" class="toolbarbutton-menu-dropmarker"/>
`), `),
true true
); );

View file

@ -1373,17 +1373,6 @@ var ItemTree = class ItemTree extends LibraryTree {
var creatorSortCache = {}; var creatorSortCache = {};
// Regexp to extract the whole string up to an optional "and" or "et al."
var andEtAlRegExp = new RegExp(
// Extract the beginning of the string in non-greedy mode
"^.+?"
// up to either the end of the string, "et al." at the end of string
+ "(?=(?: " + Zotero.getString('general.etAl').replace('.', '\\.') + ")?$"
// or ' and '
+ "| " + Zotero.getString('general.and').replace('.', '\\.') + " "
+ ")"
);
function creatorSort(a, b) { function creatorSort(a, b) {
var itemA = a.ref; var itemA = a.ref;
var itemB = b.ref; var itemB = b.ref;
@ -1401,24 +1390,12 @@ var ItemTree = class ItemTree extends LibraryTree {
var sortStringB = itemB[prop]; var sortStringB = itemB[prop];
if (fieldA === undefined) { if (fieldA === undefined) {
let firstCreator = Zotero.Items.getSortTitle(sortStringA); let firstCreator = Zotero.Items.getSortTitle(sortStringA);
if (sortCreatorAsString) { fieldA = firstCreator;
var fieldA = firstCreator;
}
else {
var matches = andEtAlRegExp.exec(firstCreator);
fieldA = matches ? matches[0] : '';
}
creatorSortCache[aItemID] = fieldA; creatorSortCache[aItemID] = fieldA;
} }
if (fieldB === undefined) { if (fieldB === undefined) {
let firstCreator = Zotero.Items.getSortTitle(sortStringB); let firstCreator = Zotero.Items.getSortTitle(sortStringB);
if (sortCreatorAsString) { fieldB = firstCreator;
var fieldB = firstCreator;
}
else {
matches = andEtAlRegExp.exec(firstCreator);
fieldB = matches ? matches[0] : '';
}
creatorSortCache[bItemID] = fieldB; creatorSortCache[bItemID] = fieldB;
} }
@ -2750,6 +2727,7 @@ var ItemTree = class ItemTree extends LibraryTree {
} }
let textSpanAriaLabel = [textWithFullStop, itemTypeAriaLabel, tagAriaLabel, retractedAriaLabel].join(' '); let textSpanAriaLabel = [textWithFullStop, itemTypeAriaLabel, tagAriaLabel, retractedAriaLabel].join(' ');
textSpan.className = "cell-text"; textSpan.className = "cell-text";
textSpan.dir = 'auto';
textSpan.setAttribute('aria-label', textSpanAriaLabel); textSpan.setAttribute('aria-label', textSpanAriaLabel);
span.append(twisty, icon, retracted, ...tagSpans, textSpan); span.append(twisty, icon, retracted, ...tagSpans, textSpan);

View file

@ -1723,7 +1723,9 @@ Zotero.Items = function() {
if (matches.length === 2) { if (matches.length === 2) {
let a = matches[0]; let a = matches[0];
let b = matches[1]; let b = matches[1];
return a.lastName + " " + Zotero.getString('general.and') + " " + b.lastName; // \u2068 FIRST STRONG ISOLATE: Isolates the directionality of characters that follow
// \u2069 POP DIRECTIONAL ISOLATE: Pops the above isolation
return Zotero.getString('general.andJoiner', [`\u2068${a.lastName}\u2069`, `\u2068${b.lastName}\u2069`]);
} }
if (matches.length >= 3) { if (matches.length >= 3) {
return matches[0].lastName + " " + Zotero.getString('general.etAl'); return matches[0].lastName + " " + Zotero.getString('general.etAl');
@ -1786,8 +1788,8 @@ Zotero.Items = function() {
var contributorCreatorTypeID = Zotero.CreatorTypes.getID('contributor'); var contributorCreatorTypeID = Zotero.CreatorTypes.getID('contributor');
/* This whole block is to get the firstCreator */ /* This whole block is to get the firstCreator */
var localizedAnd = Zotero.getString('general.and'); var localizedAnd = Zotero.getString('general.andJoiner').replace(/%S/g, '%s');
var localizedEtAl = Zotero.getString('general.etAl'); var localizedEtAl = Zotero.getString('general.etAl');
var sql = "COALESCE(" + var sql = "COALESCE(" +
// First try for primary creator types // First try for primary creator types
"CASE (" + "CASE (" +
@ -1804,16 +1806,21 @@ Zotero.Items = function() {
"WHERE itemID=O.itemID AND primaryField=1" + "WHERE itemID=O.itemID AND primaryField=1" +
") " + ") " +
"WHEN 2 THEN (" + "WHEN 2 THEN (" +
"SELECT " + "SELECT PRINTF(" +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " + `'${localizedAnd}'` +
"LEFT JOIN itemTypeCreatorTypes ITCT " + ", " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " + // \u2068 FIRST STRONG ISOLATE: Isolates the directionality of characters that follow
"WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" + // \u2069 POP DIRECTIONAL ISOLATE: Pops the above isolation
" || ' " + localizedAnd + " ' || " + "(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators IC NATURAL JOIN creators " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " + "LEFT JOIN itemTypeCreatorTypes ITCT " +
"LEFT JOIN itemTypeCreatorTypes ITCT " + "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " + "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
"WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" + ", " +
"(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators IC NATURAL JOIN creators " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
"ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
"WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
")" +
") " + ") " +
"ELSE (" + "ELSE (" +
"SELECT " + "SELECT " +
@ -1836,14 +1843,17 @@ Zotero.Items = function() {
`WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID}` + `WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID}` +
") " + ") " +
"WHEN 2 THEN (" + "WHEN 2 THEN (" +
"SELECT " + "SELECT PRINTF(" +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators " + `'${localizedAnd}'` +
`WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID} ` + ", " +
"ORDER BY orderIndex LIMIT 1)" + "(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators NATURAL JOIN creators " +
" || ' " + localizedAnd + " ' || " + `WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID} ` +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators " + "ORDER BY orderIndex LIMIT 1)" +
`WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID} ` + ", " +
"ORDER BY orderIndex LIMIT 1,1) " + "(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators NATURAL JOIN creators " +
`WHERE itemID=O.itemID AND creatorTypeID=${editorCreatorTypeID} ` +
"ORDER BY orderIndex LIMIT 1,1) " +
")" +
") " + ") " +
"ELSE (" + "ELSE (" +
"SELECT " + "SELECT " +
@ -1865,14 +1875,17 @@ Zotero.Items = function() {
`WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID}` + `WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID}` +
") " + ") " +
"WHEN 2 THEN (" + "WHEN 2 THEN (" +
"SELECT " + "SELECT PRINTF(" +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators " + `'${localizedAnd}'` +
`WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID} ` + ", " +
"ORDER BY orderIndex LIMIT 1)" + "(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators NATURAL JOIN creators " +
" || ' " + localizedAnd + " ' || " + `WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID} ` +
"(SELECT lastName FROM itemCreators NATURAL JOIN creators " + "ORDER BY orderIndex LIMIT 1)" +
`WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID} ` + ", " +
"ORDER BY orderIndex LIMIT 1,1) " + "(SELECT '\u2068' || lastName || '\u2069' FROM itemCreators NATURAL JOIN creators " +
`WHERE itemID=O.itemID AND creatorTypeID=${contributorCreatorTypeID} ` +
"ORDER BY orderIndex LIMIT 1,1) " +
")" +
") " + ") " +
"ELSE (" + "ELSE (" +
"SELECT " + "SELECT " +

View file

@ -1628,7 +1628,9 @@ class EditorInstanceUtilities {
else if (authors.length === 2) { else if (authors.length === 2) {
let a = authors[0].family || authors[0].literal; let a = authors[0].family || authors[0].literal;
let b = authors[1].family || authors[1].literal; let b = authors[1].family || authors[1].literal;
str = a + ' ' + Zotero.getString('general.and') + ' ' + b; // \u2068 FIRST STRONG ISOLATE: Isolates the directionality of characters that follow
// \u2069 POP DIRECTIONAL ISOLATE: Pops the above isolation
str = Zotero.getString('general.andJoiner', [`\u2068${a}\u2069`, `\u2068${b}\u2069`]);
} }
else if (authors.length >= 3) { else if (authors.length >= 3) {
str = (authors[0].family || authors[0].literal) + ' ' + Zotero.getString('general.etAl'); str = (authors[0].family || authors[0].literal) + ' ' + Zotero.getString('general.etAl');

View file

@ -2775,6 +2775,11 @@ var ZoteroPane = new function()
return this.itemsView.getSortDirection(); return this.itemsView.getSortDirection();
} }
function openPopup(popup, clientX, clientY) {
popup.openPopupAtScreen(clientX + 1, clientY + 1, true);
}
/** /**
@ -2782,9 +2787,9 @@ var ZoteroPane = new function()
*/ */
this.onCollectionsContextMenuOpen = async function (event, x, y) { this.onCollectionsContextMenuOpen = async function (event, x, y) {
await ZoteroPane.buildCollectionContextMenu(); await ZoteroPane.buildCollectionContextMenu();
x = x || event.screenX; x = x || event.clientX;
y = y || event.screenY; y = y || event.clientY;
document.getElementById('zotero-collectionmenu').openPopupAtScreen(x + 1, y + 1, true); openPopup(document.getElementById('zotero-collectionmenu'), x, y);
}; };
@ -2793,9 +2798,9 @@ var ZoteroPane = new function()
*/ */
this.onItemsContextMenuOpen = async function (event, x, y) { this.onItemsContextMenuOpen = async function (event, x, y) {
await ZoteroPane.buildItemContextMenu(); await ZoteroPane.buildItemContextMenu();
x = x || event.screenX; x = x || event.clientX;
y = y || event.screenY; y = y || event.clientY;
document.getElementById('zotero-itemmenu').openPopupAtScreen(x + 1, y + 1, true); openPopup(document.getElementById('zotero-itemmenu'), x, y);
}; };

View file

@ -35,6 +35,7 @@ general.notNow = Not Now
general.passed = Passed general.passed = Passed
general.failed = Failed general.failed = Failed
general.and = and general.and = and
general.andJoiner = %S and %S
general.etAl = et al. general.etAl = et al.
general.accessDenied = Access Denied general.accessDenied = Access Denied
general.permissionDenied = Permission Denied general.permissionDenied = Permission Denied
@ -116,6 +117,7 @@ punctuation.closingQMark = ”
punctuation.colon = : punctuation.colon = :
punctuation.colon.withString = %S: punctuation.colon.withString = %S:
punctuation.ellipsis = punctuation.ellipsis =
punctuation.comma = ,
install.quickStartGuide = Zotero Quick Start Guide install.quickStartGuide = Zotero Quick Start Guide
install.quickStartGuide.message.welcome = Welcome to Zotero! install.quickStartGuide.message.welcome = Welcome to Zotero!

View file

@ -207,12 +207,12 @@
} }
#zotero-collections-toolbar { #zotero-collections-toolbar {
margin-right: 10px; /* Set to width of splitter for visual aesthetics */ margin-inline-end: 10px; /* Set to width of splitter for visual aesthetics */
padding-left: 2px; padding-inline-start: 2px;
} }
#zotero-items-toolbar { #zotero-items-toolbar {
margin-right: 10px; margin-inline-end: 10px;
} }
.zotero-tb-button { .zotero-tb-button {
@ -223,18 +223,18 @@
} }
.zotero-tb-button:first-child { .zotero-tb-button:first-child {
margin-left: 0 !important; margin-inline-start: 0 !important;
} }
.zotero-tb-button:last-child { .zotero-tb-button:last-child {
margin-right: 0 !important; margin-inline-end: 0 !important;
} }
.zotero-tb-button[type="menu"] { .zotero-tb-button[type="menu"] {
padding-left: 3px; padding-inline-start: 3px;
padding-right: 3px; padding-inline-end: 3px;
margin-left: 4px; margin-inline-start: 4px;
margin-right: 2px; margin-inline-end: 2px;
} }
.zotero-tb-button:hover:active { .zotero-tb-button:hover:active {
@ -480,7 +480,7 @@
#zotero-tb-sync-stop #zotero-tb-sync-stop
{ {
list-style-image: url(chrome://zotero/skin/control_stop_blue.png); list-style-image: url(chrome://zotero/skin/control_stop_blue.png);
margin-right: 0; margin-inline-end: 0;
} }
#zotero-tb-sync-progress #zotero-tb-sync-progress
@ -488,7 +488,7 @@
min-width: 50px; min-width: 50px;
width: 50px; width: 50px;
height: 10px; height: 10px;
margin-left: 0; margin-inline-start: 0;
} }
#zotero-tb-sync-progress-tooltip-progress { #zotero-tb-sync-progress-tooltip-progress {
@ -532,7 +532,7 @@
/* Sync error panel */ /* Sync error panel */
#zotero-sync-error-panel, #zotero-sync-reminder-panel { #zotero-sync-error-panel, #zotero-sync-reminder-panel {
margin-right: 0; margin-inline-end: 0;
} }
#zotero-sync-error-panel .error-header, #zotero-sync-reminder-panel .header { #zotero-sync-error-panel .error-header, #zotero-sync-reminder-panel .header {
@ -561,9 +561,9 @@
#zotero-tb-sync { #zotero-tb-sync {
list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png); list-style-image: url(chrome://zotero/skin/arrow_rotate_static.png);
margin-left: -6px; margin-inline-start: -6px;
margin-right: -2px; margin-inline-end: -2px;
padding-right: 6px; padding-inline-end: 6px;
} }
#zotero-tb-sync .toolbarbutton-icon { #zotero-tb-sync .toolbarbutton-icon {
@ -606,7 +606,7 @@
} }
.zotero-box { .zotero-box {
margin-left: 5px; margin-inline-start: 5px;
} }
.zotero-box-icon { .zotero-box-icon {
@ -615,7 +615,7 @@
} }
.zotero-box-label { .zotero-box-label {
margin-left: 3px !important; margin-inline-start: 3px !important;
} }
#item-box-container { #item-box-container {

View file

@ -10,7 +10,7 @@
#prefs-search-container { #prefs-search-container {
height: 40px; height: 40px;
padding-right: 8px; padding-inline-end: 8px;
} }
#prefs-search { #prefs-search {
@ -113,8 +113,7 @@ groupbox > label > h2, groupbox > * > label > h2 {
width: 0; width: 0;
height: 0; height: 0;
left: calc(50% - 3px); left: calc(50% - 3px);
border-right: 6px solid transparent; border-inline: 6px solid transparent;
border-left: 6px solid transparent;
} }
.search-tooltip::before { .search-tooltip::before {
@ -199,11 +198,11 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
/* General pane */ /* General pane */
#zotero-prefpane-general .statusLine { #zotero-prefpane-general .statusLine {
margin-left: .75em; margin-inline-start: .75em;
} }
.indented-pref { .indented-pref {
margin-left: 2em; margin-inline-start: 2em;
} }
.fileHandler-menu .menulist-icon { .fileHandler-menu .menulist-icon {
@ -230,28 +229,26 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
#zotero-prefpane-sync .form-grid > hbox #zotero-prefpane-sync .form-grid > hbox
{ {
margin-left: 4px; margin-inline-start: 4px;
} }
#zotero-prefpane-sync .form-grid > hbox label:first-child, #zotero-prefpane-sync .form-grid > hbox label:first-child,
#zotero-prefpane-sync .form-grid > hbox menulist:first-child #zotero-prefpane-sync .form-grid > hbox menulist:first-child
{ {
margin-left: 0; margin-inline: 0;
margin-right: 0;
} }
#zotero-prefpane-sync .form-grid > hbox textbox #zotero-prefpane-sync .form-grid > hbox textbox
{ {
margin-left: 3px; margin-inline: 3px;
margin-right: 3px;
} }
#zotero-prefpane-sync .form-grid > hbox label:last-child #zotero-prefpane-sync .form-grid > hbox label:last-child
{ {
margin-left: 0; margin-inline-start: 0;
margin-right: 10px; margin-inline-end: 10px;
} }
#zotero-prefpane-sync #sync-auth-button { #zotero-prefpane-sync #sync-auth-button {
margin-left: 0; margin-inline-start: 0;
} }
#zotero-prefpane-sync #sync-status-indicator #zotero-prefpane-sync #sync-status-indicator
@ -280,34 +277,34 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
.storage-settings-download-options .storage-settings-download-options
{ {
margin-left: 40px; margin-inline-start: 40px;
} }
#storage-verify, #storage-abort, #storage-clean #storage-verify, #storage-abort, #storage-clean
{ {
margin-left: 0; margin-inline-start: 0;
min-width: 8em; min-width: 8em;
} }
#storage-terms label #storage-terms label
{ {
margin-left: 0; margin-inline-start: 0;
font-size: .9em; font-size: .9em;
} }
#storage-terms label:first-child #storage-terms label:first-child
{ {
margin-right: .25em; margin-inline-end: .25em;
} }
#storage-terms label[class=zotero-text-link] #storage-terms label[class=zotero-text-link]
{ {
margin-right: 0; margin-inline-end: 0;
} }
/* Reset tab */ /* Reset tab */
#sync-reset-form { #sync-reset-form {
margin-left: 1em; margin-inline-start: 1em;
} }
#reset-sync-warning { #reset-sync-warning {
@ -325,7 +322,7 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
#sync-reset-library-menu { #sync-reset-library-menu {
width: 14em; width: 14em;
margin-left: .25em; margin-inline-start: .25em;
font-size: 15px; font-size: 15px;
height: 1.6em; height: 1.6em;
} }
@ -365,7 +362,7 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
} }
#sync-reset-radiogroup > div radio .radio-check { #sync-reset-radiogroup > div radio .radio-check {
margin-right: 1.05em; margin-inline-end: 1.05em;
} }
#sync-reset-radiogroup > div[disabled] span { #sync-reset-radiogroup > div[disabled] span {
@ -446,7 +443,7 @@ description label[class=zotero-text-link], label[class=zotero-text-link]
/* Shortcut Keys pane */ /* Shortcut Keys pane */
#zotero-prefpane-advanced-keys-tab input #zotero-prefpane-advanced-keys-tab input
{ {
margin-left: -1px; margin-inline-start: -1px;
} }
/* Advanced pane */ /* Advanced pane */

View file

@ -7,7 +7,8 @@
.zotero-clicky-minus, .zotero-clicky-plus { .zotero-clicky-minus, .zotero-clicky-plus {
color: transparent !important; color: transparent !important;
padding: 0 !important; padding: 0 !important;
margin: 0 5px 0 0 !important; margin: 0 !important;
margin-inline-end: 5px !important;
width: 18px; width: 18px;
height: 18px; height: 18px;
} }

View file

@ -40,9 +40,10 @@ td > input {
th > label { th > label {
margin-top: 1px !important; margin-top: 1px !important;
margin-bottom: 1px !important; margin-bottom: 1px !important;
-moz-margin-start: 1px !important; -moz-box-pack: start;
-moz-margin-end: 5px !important; margin-inline-start: 1px !important;
padding: 0 2px 0 2px; margin-inline-end: 5px !important;
padding: 0 2px;
} }
td > [fieldname] { td > [fieldname] {
@ -74,6 +75,7 @@ td > [fieldname] {
min-height: 1.5em !important; min-height: 1.5em !important;
padding: 0 !important; padding: 0 !important;
margin: 0 !important; margin: 0 !important;
margin-inline-end: 5px !important;
max-height: 1.5em !important; max-height: 1.5em !important;
flex: 1; flex: 1;
@ -122,8 +124,8 @@ th {
align-self: stretch; align-self: stretch;
font-weight: normal; font-weight: normal;
text-align: right; text-align: right;
margin-left: 5px !important; margin-inline-start: 5px !important;
margin-right: 2px !important; margin-inline-end: 2px !important;
} }
#more-creators-label #more-creators-label
@ -149,7 +151,7 @@ row label
.creator-type-label, .creator-type-value { .creator-type-label, .creator-type-value {
-moz-box-align: center; -moz-box-align: center;
-moz-box-pack: end; -moz-box-pack: right;
} }
.creator-name-box { .creator-name-box {
@ -171,8 +173,9 @@ row label
.creator-type-label > label .creator-type-label > label
{ {
margin: 1px 4px 1px 0 !important; margin: 1px 0 !important;
padding-right: 2px !important; margin-inline-end: 4px !important;
padding-inline-end: 2px !important;
} }
.creator-type-dropmarker { .creator-type-dropmarker {
@ -185,17 +188,21 @@ row label
} }
.creator-name-box, .date-box > span { .creator-name-box, .date-box > span {
margin: 1px 0 1px 0; margin: 1px 0 !important;
margin-inline-start: 1px !important;
} }
.comma { .comma {
margin: 1px 3px 1px 0 !important; margin: 1px 0 !important;
margin-inline-end: 3px !important;
} }
#zotero-date-field-status #zotero-date-field-status
{ {
color: #666; color: #666;
padding: 0 10px 0 1px !important; padding: 0 !important;
padding-inline-start: 1px !important;
padding-inline-end: 10px !important;
white-space: nowrap; white-space: nowrap;
} }
@ -205,7 +212,8 @@ row label
max-width: 27px !important; max-width: 27px !important;
min-width: 27px !important; min-width: 27px !important;
height: 14px; height: 14px;
margin: 0 5px 0 0 !important; margin: 0 !important;
margin-inline-end: 5px !important;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-position: center !important; background-position: center !important;
border-width: 0 !important; border-width: 0 !important;

View file

@ -144,7 +144,15 @@
.tab-close { .tab-close {
position: absolute; position: absolute;
right: 6px;
&:dir(ltr) {
right: 6px;
}
&:dir(rtl) {
left: 6px;
}
top: 6px; top: 6px;
width: 16px; width: 16px;
height: 16px; height: 16px;

View file

@ -44,7 +44,6 @@
cursor: default; cursor: default;
white-space: nowrap; white-space: nowrap;
flex-grow: 1; flex-grow: 1;
flex-shrink: 1;
box-sizing: border-box; box-sizing: border-box;
&.primary { &.primary {
@ -56,7 +55,7 @@
} }
.cell-text { .cell-text {
flex-shrink: 1; flex-grow: 1;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
margin-inline-start: 5px; margin-inline-start: 5px;
@ -64,6 +63,23 @@
.twisty + .cell-text, .spacer-twisty + .cell-text { .twisty + .cell-text, .spacer-twisty + .cell-text {
margin-inline-start: 0; margin-inline-start: 0;
margin-inline-end: 0;
}
// Put the margin on the other side if the directionality of the
// .cell-text is the opposite of that of the table
&:dir(ltr) .cell-text:dir(rtl),
&:dir(rtl) .cell-text:dir(ltr) {
margin-inline-start: 0;
margin-inline-end: 6px;
}
&:dir(ltr) .cell-text {
text-align: left;
}
&:dir(rtl) .cell-text {
text-align: right;
} }
} }

View file

@ -1,4 +1,7 @@
input { input
padding: 1px 2px 1px 1px; {
margin: -1px 0 -1px 0; padding: 1px 0;
padding-inline-start: 1px;
padding-inline-end: 2px;
margin: -1px 0;
} }

View file

@ -24,8 +24,11 @@ th > label, .creator-type-label, #more-creators-label {
max-height: 7px; max-height: 7px;
} }
td > input, .creator-name-box > input { td > input {
margin: -1px 5px -2.5px 0; margin-top: -1px;
margin-bottom: -2.5px;
margin-inline-start: 0;
margin-inline-end: 5px;
padding: 0; padding: 0;
} }

View file

@ -26,6 +26,9 @@
background-image: linear-gradient(#ddd, #ddd); background-image: linear-gradient(#ddd, #ddd);
background-size: 1px 80%; background-size: 1px 80%;
background-position: left; background-position: left;
&:dir(rtl) {
background-position: right;
}
background-repeat: no-repeat; background-repeat: no-repeat;
height: 100%; height: 100%;
} }

View file

@ -9,7 +9,7 @@ row > vbox
row vbox[fieldname] row vbox[fieldname]
{ {
margin-left: 1px; margin-inline-start: 1px;
} }
.creator-type-label image { .creator-type-label image {
@ -17,12 +17,16 @@ row vbox[fieldname]
} }
input { input {
padding: 2px 2px 2px 2px; padding: 2px;
margin: -1px 0 -1px 1px; margin: -1px 0;
margin-inline-start: 1px;
} }
#item-type-menu #item-type-menu
{ {
padding: 0 0 0 1px !important; padding: 0 !important;
margin: 0 5px 0 1px !important; padding-inline-start: 1px !important;
margin: 0 !important;
margin-inline-start: 1px !important;
margin-inline-end: 5px !important;
} }

View file

@ -1116,7 +1116,7 @@ describe("Zotero.Items", function () {
} }
] ]
), ),
'B ' + Zotero.getString('general.and') + ' D', Zotero.getString('general.andJoiner', ['B', 'D']),
creatorType creatorType
); );
} }
@ -1186,7 +1186,7 @@ describe("Zotero.Items", function () {
} }
] ]
), ),
'D ' + Zotero.getString('general.and') + ' H', Zotero.getString('general.andJoiner', ['D', 'H']),
creatorType creatorType
); );
} }