Bidi improvements and fixes (#4534)
This commit is contained in:
parent
98b3745a20
commit
181afb92ac
5 changed files with 141 additions and 21 deletions
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue