Bidi improvements and fixes (#4534)

This commit is contained in:
Abe Jellinek 2024-08-08 10:50:28 -06:00 committed by Dan Stillman
parent 98b3745a20
commit 181afb92ac
5 changed files with 141 additions and 21 deletions

View file

@ -26,6 +26,16 @@
"use strict"; "use strict";
{ {
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"BIDI_BROWSER_UI",
"bidi.browser.ui",
false
);
class EditableText extends XULElementBase { class EditableText extends XULElementBase {
_input; _input;
@ -43,7 +53,8 @@
'nowrap', 'nowrap',
'autocomplete', 'autocomplete',
'min-lines', 'min-lines',
'max-lines' 'max-lines',
'dir'
]; ];
static get _textMeasurementSpan() { static get _textMeasurementSpan() {
@ -128,8 +139,8 @@
} }
set value(value) { set value(value) {
this.setAttribute('value', value || '');
this.resetTextDirection(); this.resetTextDirection();
this.setAttribute('value', value || '');
} }
get initialValue() { get initialValue() {
@ -163,15 +174,21 @@
} }
} }
get dir() {
return this.getAttribute('dir');
}
set dir(dir) {
this.setAttribute('dir', dir);
}
get ref() { get ref() {
return this._input; return this._input;
} }
resetTextDirection() { resetTextDirection() {
this._textDirection = null; this._textDirection = null;
if (this._input) { this._input?.removeAttribute('dir');
this._input.dir = null;
}
} }
sizeToContent = () => { sizeToContent = () => {
@ -297,13 +314,32 @@
} }
// Set text direction automatically if user has enabled bidi utilities // Set text direction automatically if user has enabled bidi utilities
if ((!this._input.dir || this._input.dir === 'auto') && Zotero.Prefs.get('bidi.browser.ui', true)) { if ((!this._input.dir || this._input.dir === 'auto') && lazy.BIDI_BROWSER_UI) {
if (!this._textDirection) { // If we haven't already guessed (or been given) a direction,
this._textDirection = window.windowUtils.getDirectionFromText(this._input.value) === Ci.nsIDOMWindowUtils.DIRECTION_RTL // see if we can guess from the text
? 'rtl' if (!this.dir) {
: 'ltr'; switch (window.windowUtils.getDirectionFromText(this._input.value)) {
case Ci.nsIDOMWindowUtils.DIRECTION_RTL:
this.dir = 'rtl';
break;
case Ci.nsIDOMWindowUtils.DIRECTION_LTR:
this.dir = 'ltr';
break;
case Ci.nsIDOMWindowUtils.DIRECTION_NOT_SET:
default:
this.dir = 'auto';
break;
}
}
// If all we have is 'auto' but the input has no text, use the
// app locale
if (this.dir === 'auto' && !this._input.value) {
this._input.dir = Zotero.dir;
}
// Otherwise, use the direction we guessed/were given
else {
this._input.dir = this.dir;
} }
this._input.dir = this._textDirection;
} }
} }

View file

@ -26,6 +26,16 @@
"use strict"; "use strict";
{ {
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"BIDI_BROWSER_UI",
"bidi.browser.ui",
false
);
class ItemBox extends ItemPaneSectionElementBase { class ItemBox extends ItemPaneSectionElementBase {
constructor() { constructor() {
super(); super();
@ -1512,17 +1522,15 @@
valueElement.value = valueText; valueElement.value = valueText;
// Attempt to make bidi things work automatically: if (lazy.BIDI_BROWSER_UI) {
// If we have text to work off of, let the layout engine try to guess the text direction // Attempt to guess text direction automatically
if (valueText) { let language = this.item.getField('language');
valueElement.dir = 'auto'; valueElement.dir = Zotero.ItemFields.getDirection(
} this.item.itemTypeID, fieldName, language
// 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 // Regardless, align the text in unfocused fields consistently, following the locale's direction
if (Zotero.rtl) { if (Zotero.rtl) {
valueElement.style.textAlign = 'right'; valueElement.style.textAlign = 'right';
} }

View file

@ -35,6 +35,15 @@ const { getCSSIcon, getCSSItemTypeIcon } = Icons;
const { COLUMNS } = require("zotero/itemTreeColumns"); const { COLUMNS } = require("zotero/itemTreeColumns");
const { Cc, Ci, Cu, ChromeUtils } = require('chrome'); const { Cc, Ci, Cu, ChromeUtils } = require('chrome');
const { OS } = ChromeUtils.importESModule("chrome://zotero/content/osfile.mjs"); const { OS } = ChromeUtils.importESModule("chrome://zotero/content/osfile.mjs");
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"BIDI_BROWSER_UI",
"bidi.browser.ui",
false
);
/** /**
* @typedef {import("./itemTreeColumns.jsx").ItemTreeColumnOptions} ItemTreeColumnOptions * @typedef {import("./itemTreeColumns.jsx").ItemTreeColumnOptions} ItemTreeColumnOptions
@ -2851,7 +2860,11 @@ 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'; if (lazy.BIDI_BROWSER_UI) {
textSpan.dir = Zotero.ItemFields.getDirection(
item.itemTypeID, column.dataKey, item.getField('language')
);
}
textSpan.setAttribute('aria-label', textSpanAriaLabel); textSpan.setAttribute('aria-label', textSpanAriaLabel);
if (Zotero.Prefs.get('ui.tagsAfterTitle')) { if (Zotero.Prefs.get('ui.tagsAfterTitle')) {

View file

@ -418,6 +418,68 @@ Zotero.ItemFields = new function() {
} }
/**
* Guess the text direction of a field, using the item's language field if available.
*
* @param {number} itemTypeID
* @param {string | number} field
* @param {string} [itemLanguage]
* @returns {'auto' | 'ltr' | 'rtl'}
*/
this.getDirection = function (itemTypeID, field, itemLanguage) {
field = this.getName(this.getBaseIDFromTypeAndField(itemTypeID, field) || field);
switch (field) {
// Certain fields containing IDs, numbers, and data: always LTR
case 'ISBN':
case 'ISSN':
case 'DOI':
case 'url':
case 'callNumber':
case 'volume':
case 'numberOfVolumes':
case 'issue':
case 'runningTime':
case 'number':
case 'versionNumber':
case 'applicationNumber':
case 'priorityNumbers':
case 'codeNumber':
case 'pages':
case 'numPages':
case 'seriesNumber':
case 'edition':
case 'citationKey':
case 'language':
case 'extra':
return 'ltr';
// Primary fields: follow app locale
case 'dateAdded':
case 'dateModified':
case 'accessDate':
return Zotero.dir;
// Everything else: guess based on the language if we have one; otherwise auto
default:
if (itemLanguage) {
let languageCode = Zotero.Utilities.Item.languageToISO6391(itemLanguage);
try {
let locale = new Intl.Locale(languageCode).maximize();
// https://www.w3.org/International/questions/qa-scripts#directions
// TODO: Remove this once Fx supports Intl.Locale#getTextInfo()
if (['Adlm', 'Arab', 'Aran', 'Rohg', 'Hebr', 'Mand', 'Mend', 'Nkoo', 'Hung', 'Samr', 'Syrc', 'Thaa', 'Yezi']
.includes(locale.script)) {
return 'rtl';
}
}
catch (e) {
Zotero.logError(e);
}
return 'ltr';
}
return 'auto';
}
};
/** /**
* Check whether a field is valid, throwing an exception if not * Check whether a field is valid, throwing an exception if not
* (since it should never actually happen) * (since it should never actually happen)

View file

@ -93,6 +93,7 @@ editable-text {
&:read-only, &:not(:focus) { &:read-only, &:not(:focus) {
appearance: none; appearance: none;
background: transparent; background: transparent;
text-align: inherit;
} }
&:hover:not(:read-only, :focus) { &:hover:not(:read-only, :focus) {