Item pane header customization (#3791)
This commit is contained in:
parent
8278140492
commit
59b1d75b98
17 changed files with 664 additions and 112 deletions
|
@ -79,7 +79,7 @@ var Zotero_File_Interface_Bibliography = new function() {
|
|||
}
|
||||
|
||||
// See note in style.js
|
||||
if (!Zotero.Styles.initialized) {
|
||||
if (!Zotero.Styles.initialized()) {
|
||||
// Initialize styles
|
||||
yield Zotero.Styles.init();
|
||||
}
|
||||
|
|
|
@ -439,14 +439,7 @@
|
|||
}
|
||||
|
||||
get _ignoreFields() {
|
||||
let value = ['title', 'abstractNote']
|
||||
.flatMap(field => [
|
||||
field,
|
||||
...(Zotero.ItemFields.getTypeFieldsFromBase(field, true) || [])
|
||||
]);
|
||||
// Cache the result
|
||||
Object.defineProperty(this, '_ignoreFields', { value });
|
||||
return value;
|
||||
return ['abstractNote'];
|
||||
}
|
||||
|
||||
get _linkMenu() {
|
||||
|
@ -696,7 +689,7 @@
|
|||
var button = document.createXULElement("toolbarbutton");
|
||||
button.className = 'zotero-field-version-button zotero-clicky-merge';
|
||||
button.setAttribute('type', 'menu');
|
||||
button.setAttribute('wantdropmarker', true);
|
||||
button.setAttribute('data-l10n-id', 'itembox-button-merge');
|
||||
|
||||
var popup = button.appendChild(document.createXULElement("menupopup"));
|
||||
|
||||
|
@ -760,8 +753,13 @@
|
|||
|
||||
// Creator rows
|
||||
|
||||
// Place, in order of preference, after type or at beginning
|
||||
let field = this._infoTable.querySelector('[fieldname="itemType"]');
|
||||
// Place, in order of preference, after title, after type,
|
||||
// or at beginning
|
||||
var titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
||||
var field = this._infoTable.querySelector(`[fieldname="${Zotero.ItemFields.getName(titleFieldID)}"]`);
|
||||
if (!field) {
|
||||
field = this._infoTable.querySelector('[fieldName="itemType"]');
|
||||
}
|
||||
if (field) {
|
||||
this._beforeRow = field.parentNode.nextSibling;
|
||||
}
|
||||
|
@ -2069,6 +2067,13 @@
|
|||
var fieldName = label.getAttribute('fieldname');
|
||||
this._modifyField(fieldName, newValue);
|
||||
|
||||
if (Zotero.ItemFields.isFieldOfBase(fieldName, 'title')) {
|
||||
let shortTitleVal = this.item.getField('shortTitle');
|
||||
if (newValue.toLowerCase().startsWith(shortTitleVal.toLowerCase())) {
|
||||
this._modifyField('shortTitle', newValue.substring(0, shortTitleVal.length));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.saveOnEdit) {
|
||||
// If a field is open, blur it, which will trigger a save and cause
|
||||
// the saveTx() to be a no-op
|
||||
|
|
|
@ -26,30 +26,31 @@
|
|||
"use strict";
|
||||
|
||||
{
|
||||
let htmlDoc = document.implementation.createHTMLDocument();
|
||||
|
||||
class PaneHeader extends ItemPaneSectionElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="head">
|
||||
<html:div class="title">
|
||||
<editable-text />
|
||||
</html:div>
|
||||
|
||||
<html:div class="menu-button">
|
||||
<toolbarbutton
|
||||
class="zotero-tb-button expand-button"
|
||||
tooltiptext="&zotero.toolbar.openURL.label;"
|
||||
type="menu"
|
||||
wantdropmarker="true"
|
||||
tabindex="0">
|
||||
<menupopup onpopupshowing="Zotero_LocateMenu.buildLocateMenu(this)"/>
|
||||
</toolbarbutton>
|
||||
</html:div>
|
||||
</html:div>
|
||||
<html:div class="custom-head">
|
||||
<html:div class="title">
|
||||
<editable-text />
|
||||
</html:div>
|
||||
|
||||
<html:div class="creator-year" />
|
||||
|
||||
<html:div class="bib-entry" />
|
||||
|
||||
<popupset>
|
||||
<menupopup class="secondary-popup">
|
||||
<menuitem data-l10n-id="text-action-copy" />
|
||||
<menuseparator />
|
||||
<menu data-l10n-id="item-pane-header-view-as">
|
||||
<menupopup class="view-as-popup" />
|
||||
</menu>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<html:div class="custom-head"/>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
|
||||
showInFeeds = true;
|
||||
|
||||
_item = null;
|
||||
|
||||
_titleFieldID = null;
|
||||
|
@ -65,9 +66,51 @@
|
|||
|
||||
init() {
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'paneHeader');
|
||||
this._prefsObserverIDs = [
|
||||
Zotero.Prefs.registerObserver('itemPaneHeader', () => {
|
||||
// TEMP?: _forceRenderAll() doesn't do anything if the section is hidden, so un-hide first
|
||||
this.hidden = false;
|
||||
this._forceRenderAll();
|
||||
}),
|
||||
Zotero.Prefs.registerObserver('itemPaneHeader.bibEntry.style', () => this._forceRenderAll()),
|
||||
Zotero.Prefs.registerObserver('itemPaneHeader.bibEntry.locale', () => this._forceRenderAll()),
|
||||
];
|
||||
|
||||
this.titleField = this.querySelector('.title editable-text');
|
||||
this.menuButton = this.querySelector('.menu-button');
|
||||
this.title = this.querySelector('.title');
|
||||
this.titleField = this.title.querySelector('editable-text');
|
||||
this.creatorYear = this.querySelector('.creator-year');
|
||||
this.bibEntry = this.querySelector('.bib-entry');
|
||||
this.bibEntry.attachShadow({ mode: 'open' });
|
||||
|
||||
// Context menu for non-editable information (creator/year and bib entry)
|
||||
this.secondaryPopup = this.querySelector('.secondary-popup');
|
||||
this.secondaryPopup.firstElementChild.addEventListener('command', () => this._handleSecondaryCopy());
|
||||
|
||||
this.creatorYear.addEventListener('contextmenu', (event) => {
|
||||
if (this.item) {
|
||||
this.secondaryPopup.openPopupAtScreen(event.screenX + 1, event.screenY + 1, true);
|
||||
}
|
||||
});
|
||||
|
||||
this.bibEntryContent = document.createElement('div');
|
||||
this.bibEntryContent.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
this.bibEntryContent.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
if (event.target.matches('a[href]')) {
|
||||
Zotero.launchURL(event.target.href);
|
||||
}
|
||||
});
|
||||
this.bibEntryContent.addEventListener('contextmenu', (event) => {
|
||||
if (this._item && Zotero.Styles.initialized()) {
|
||||
this.secondaryPopup.openPopupAtScreen(event.screenX + 1, event.screenY + 1, true);
|
||||
}
|
||||
});
|
||||
this.bibEntry.shadowRoot.append(this.bibEntryContent);
|
||||
|
||||
this.viewAsPopup = this.querySelector('.view-as-popup');
|
||||
this.viewAsPopup.addEventListener('popupshowing', () => this._buildViewAsMenu(this.viewAsPopup));
|
||||
|
||||
this._bibEntryCache = new LRUCache();
|
||||
|
||||
this.titleField.addEventListener('change', () => this.save());
|
||||
this.titleField.ariaLabel = Zotero.getString('itemFields.title');
|
||||
|
@ -81,6 +124,17 @@
|
|||
this._setTransformedValue(newValue);
|
||||
},
|
||||
});
|
||||
|
||||
menupopup.append(document.createXULElement('menuseparator'));
|
||||
|
||||
let viewAsMenu = document.createXULElement('menu');
|
||||
viewAsMenu.setAttribute('data-l10n-id', 'item-pane-header-view-as');
|
||||
viewAsMenu.setAttribute('type', 'menu');
|
||||
let viewAsPopup = document.createXULElement('menupopup');
|
||||
this._buildViewAsMenu(viewAsPopup);
|
||||
viewAsMenu.append(viewAsPopup);
|
||||
menupopup.append(viewAsMenu);
|
||||
|
||||
this.ownerDocument.querySelector('popupset').append(menupopup);
|
||||
menupopup.addEventListener('popuphidden', () => menupopup.remove());
|
||||
menupopup.openPopupAtScreen(event.screenX + 1, event.screenY + 1, true);
|
||||
|
@ -89,9 +143,18 @@
|
|||
|
||||
destroy() {
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
for (let id of this._prefsObserverIDs) {
|
||||
Zotero.Prefs.unregisterObserver(id);
|
||||
}
|
||||
}
|
||||
|
||||
notify(action, type, ids) {
|
||||
if (action == 'modify' || action == 'delete') {
|
||||
for (let id of ids) {
|
||||
this._bibEntryCache.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (action == 'modify' && this.item && ids.includes(this.item.id)) {
|
||||
this._forceRenderAll();
|
||||
}
|
||||
|
@ -102,15 +165,15 @@
|
|||
this._item.setField(this._titleFieldID, newValue);
|
||||
let shortTitleVal = this._item.getField('shortTitle');
|
||||
if (newValue.toLowerCase().startsWith(shortTitleVal.toLowerCase())) {
|
||||
this._item.setField('shortTitle', newValue.substr(0, shortTitleVal.length));
|
||||
this._item.setField('shortTitle', newValue.substring(0, shortTitleVal.length));
|
||||
}
|
||||
await this._item.saveTx();
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.item) {
|
||||
this.item.setField(this._titleFieldID, this.titleField.value);
|
||||
await this.item.saveTx();
|
||||
if (this._item) {
|
||||
this._item.setField(this._titleFieldID, this.titleField.value);
|
||||
await this._item.saveTx();
|
||||
}
|
||||
this._forceRenderAll();
|
||||
}
|
||||
|
@ -121,29 +184,189 @@
|
|||
await this.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.item) {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
if (this._isAlreadyRendered()) return;
|
||||
|
||||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
||||
let headerMode = Zotero.Prefs.get('itemPaneHeader');
|
||||
if (this._item.isAttachment()) {
|
||||
headerMode = 'title';
|
||||
}
|
||||
|
||||
let title = this.item.getField(this._titleFieldID);
|
||||
// If focused, update the value that will be restored on Escape;
|
||||
// otherwise, update the displayed value
|
||||
if (this.titleField.focused) {
|
||||
this.titleField.initialValue = title;
|
||||
if (headerMode === 'none') {
|
||||
this.hidden = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this.titleField.value = title;
|
||||
|
||||
this.hidden = false;
|
||||
this.title.hidden = true;
|
||||
this.creatorYear.hidden = true;
|
||||
this.bibEntry.hidden = true;
|
||||
|
||||
if (headerMode === 'bibEntry') {
|
||||
if (!Zotero.Styles.initialized()) {
|
||||
this.bibEntryContent.textContent = Zotero.getString('general.loading');
|
||||
this.bibEntry.classList.add('loading');
|
||||
this.bibEntry.hidden = false;
|
||||
Zotero.Styles.init().then(() => this._forceRenderAll());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._renderBibEntry()) {
|
||||
this.bibEntry.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to Title/Creator/Year if style is not found
|
||||
headerMode = 'titleCreatorYear';
|
||||
}
|
||||
this.titleField.readOnly = !this.editable;
|
||||
if (this._titleFieldID) {
|
||||
this.titleField.placeholder = Zotero.ItemFields.getLocalizedString(this._titleFieldID);
|
||||
|
||||
if (headerMode === 'title' || headerMode === 'titleCreatorYear') {
|
||||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this._item.itemTypeID, 'title');
|
||||
|
||||
let title = this.item.getField(this._titleFieldID);
|
||||
// If focused, update the value that will be restored on Escape;
|
||||
// otherwise, update the displayed value
|
||||
if (this.titleField.focused) {
|
||||
this.titleField.initialValue = title;
|
||||
}
|
||||
else {
|
||||
this.titleField.value = title;
|
||||
}
|
||||
this.titleField.readOnly = !this.editable;
|
||||
if (this._titleFieldID) {
|
||||
this.titleField.placeholder = Zotero.ItemFields.getLocalizedString(this._titleFieldID);
|
||||
}
|
||||
this.title.hidden = false;
|
||||
}
|
||||
this.menuButton.hidden = !this.item.isRegularItem() && !this.item.isAttachment();
|
||||
|
||||
if (headerMode === 'titleCreatorYear') {
|
||||
let firstCreator = this._item.getField('firstCreator');
|
||||
let year = this._item.getField('year');
|
||||
let creatorYearString = '';
|
||||
if (firstCreator) {
|
||||
creatorYearString += firstCreator;
|
||||
}
|
||||
if (year) {
|
||||
creatorYearString += ` (${year})`;
|
||||
}
|
||||
|
||||
if (creatorYearString) {
|
||||
this.creatorYear.textContent = creatorYearString;
|
||||
this.creatorYear.hidden = false;
|
||||
}
|
||||
else {
|
||||
this.creatorYear.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make title field padding tighter if creator/year is visible below it
|
||||
this.titleField.toggleAttribute('tight',
|
||||
headerMode === 'titleCreatorYear' && !this.creatorYear.hidden);
|
||||
}
|
||||
|
||||
_renderBibEntry() {
|
||||
let style = Zotero.Styles.get(Zotero.Prefs.get('itemPaneHeader.bibEntry.style'));
|
||||
if (!style) {
|
||||
Zotero.warn('Style not found: ' + Zotero.Prefs.get('itemPaneHeader.bibEntry.style'));
|
||||
return false;
|
||||
}
|
||||
let locale = Zotero.Prefs.get('itemPaneHeader.bibEntry.locale');
|
||||
|
||||
// Create engine if not cached (first run with this style)
|
||||
if (this._cslEngineStyleID !== style.styleID || this._cslEngineLocale !== locale) {
|
||||
this._cslEngine = style.getCiteProc(locale, 'html');
|
||||
this._cslEngineStyleID = style.styleID;
|
||||
this._cslEngineLocale = locale;
|
||||
this._bibEntryCache.clear();
|
||||
}
|
||||
|
||||
// Create bib entry if not cached (first run on this item or item data has changed)
|
||||
if (!this._bibEntryCache.has(this._item.id)) {
|
||||
// Force refresh items - without this, entries won't change when item data changes
|
||||
this._cslEngine.updateItems([]);
|
||||
this._bibEntryCache.set(this._item.id,
|
||||
Zotero.Cite.makeFormattedBibliographyOrCitationList(this._cslEngine,
|
||||
[this._item], 'html', false));
|
||||
}
|
||||
|
||||
htmlDoc.body.innerHTML = this._bibEntryCache.get(this._item.id);
|
||||
// Remove .loading (added above if styles weren't yet initialized)
|
||||
this.bibEntry.classList.remove('loading');
|
||||
// Remove existing children and *then* append new ones to avoid "scripts are blocked internally"
|
||||
// error in log
|
||||
this.bibEntryContent.replaceChildren();
|
||||
this.bibEntryContent.append(...htmlDoc.body.childNodes);
|
||||
|
||||
let body = this.bibEntryContent.querySelector('.csl-bib-body');
|
||||
if (!body) {
|
||||
Zotero.debug('No .csl-bib-body found in bib entry');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove any custom indentation/line height set by the style
|
||||
body.style.marginLeft = body.style.marginRight = '';
|
||||
body.style.textIndent = '';
|
||||
body.style.lineHeight = '';
|
||||
|
||||
if (style.categories === 'numeric') {
|
||||
// Remove number from entry if present
|
||||
let number = body.querySelector('.csl-entry > .csl-left-margin:first-child');
|
||||
if (number) {
|
||||
let followingContent = number.nextElementSibling;
|
||||
if (followingContent?.classList.contains('csl-right-inline')) {
|
||||
followingContent.classList.remove('csl-right-inline');
|
||||
followingContent.style = '';
|
||||
}
|
||||
number.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_handleSecondaryCopy() {
|
||||
let selectedMode = Zotero.Prefs.get('itemPaneHeader');
|
||||
if (selectedMode === 'titleCreatorYear') {
|
||||
Zotero.Utilities.Internal.copyTextToClipboard(this.creatorYear.textContent);
|
||||
}
|
||||
else if (selectedMode === 'bibEntry') {
|
||||
Zotero_File_Interface.copyItemsToClipboard(
|
||||
[this._item],
|
||||
Zotero.Prefs.get('itemPaneHeader.bibEntry.style'),
|
||||
Zotero.Prefs.get('itemPaneHeader.bibEntry.locale'),
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_buildViewAsMenu(menupopup) {
|
||||
menupopup.replaceChildren();
|
||||
|
||||
let selectedMode = Zotero.Prefs.get('itemPaneHeader');
|
||||
for (let headerMode of ['title', 'titleCreatorYear', 'bibEntry']) {
|
||||
let menuitem = document.createXULElement('menuitem');
|
||||
menuitem.setAttribute('data-l10n-id', 'item-pane-header-' + headerMode);
|
||||
menuitem.setAttribute('type', 'radio');
|
||||
menuitem.setAttribute('checked', headerMode === selectedMode);
|
||||
menuitem.addEventListener('command', () => {
|
||||
Zotero.Prefs.set('itemPaneHeader', headerMode);
|
||||
});
|
||||
menupopup.append(menuitem);
|
||||
}
|
||||
|
||||
menupopup.append(document.createXULElement('menuseparator'));
|
||||
|
||||
let moreOptionsMenuitem = document.createXULElement('menuitem');
|
||||
moreOptionsMenuitem.setAttribute('data-l10n-id', 'item-pane-header-more-options');
|
||||
moreOptionsMenuitem.addEventListener('command', () => {
|
||||
Zotero.Utilities.Internal.openPreferences('zotero-prefpane-general');
|
||||
});
|
||||
menupopup.append(moreOptionsMenuitem);
|
||||
}
|
||||
|
||||
renderCustomHead(callback) {
|
||||
|
@ -159,4 +382,46 @@
|
|||
}
|
||||
}
|
||||
customElements.define("pane-header", PaneHeader);
|
||||
|
||||
/**
|
||||
* Simple LRU cache that stores bibliography entries for the 100 most recently viewed items.
|
||||
*/
|
||||
class LRUCache {
|
||||
static CACHE_SIZE = 100;
|
||||
|
||||
_map = new Map();
|
||||
|
||||
clear() {
|
||||
this._map.clear();
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (!this._map.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
let value = this._map.get(key);
|
||||
// Maps are sorted by insertion order, so delete and add back at the end
|
||||
this._map.delete(key);
|
||||
this._map.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this._map.delete(key);
|
||||
// Delete the first (= inserted earliest) elements until we're under CACHE_SIZE
|
||||
while (this._map.size >= this.constructor.CACHE_SIZE) {
|
||||
this._map.delete(this._map.keys().next().value);
|
||||
}
|
||||
this._map.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
return this._map.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
<linkset>
|
||||
<html:link rel="localization" href="branding/brand.ftl"/>
|
||||
<html:link rel="localization" href="zotero.ftl"/>
|
||||
<html:link rel="localization" href="preferences.ftl"/>
|
||||
</linkset>
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ Zotero_Preferences.General = {
|
|||
}
|
||||
|
||||
this.refreshLocale();
|
||||
this._initItemPaneHeaderUI();
|
||||
this.updateAutoRenameFilesUI();
|
||||
this._updateFileHandlerUI();
|
||||
this._initEbookFontFamilyMenu();
|
||||
|
@ -136,6 +137,83 @@ Zotero_Preferences.General = {
|
|||
Zotero.Utilities.Internal.quitZotero(true);
|
||||
}
|
||||
},
|
||||
|
||||
_initItemPaneHeaderUI() {
|
||||
let pane = document.querySelector('#zotero-prefpane-general');
|
||||
let headerMenu = document.querySelector('#item-pane-header-menulist');
|
||||
let styleMenu = document.querySelector('#item-pane-header-style-menu');
|
||||
|
||||
this._updateItemPaneHeaderStyleUI();
|
||||
pane.addEventListener('showing', () => this._updateItemPaneHeaderStyleUI());
|
||||
|
||||
// menulists stop responding to clicks if we replace their items while
|
||||
// they're closing. Yield back to the event loop before updating to
|
||||
// avoid this.
|
||||
let updateUI = () => {
|
||||
setTimeout(() => {
|
||||
this._updateItemPaneHeaderStyleUI();
|
||||
});
|
||||
};
|
||||
headerMenu.addEventListener('command', updateUI);
|
||||
styleMenu.addEventListener('command', updateUI);
|
||||
},
|
||||
|
||||
_updateItemPaneHeaderStyleUI: Zotero.Utilities.Internal.serial(async function () {
|
||||
let optionsContainer = document.querySelector('#item-pane-header-bib-entry-options');
|
||||
let styleMenu = document.querySelector('#item-pane-header-style-menu');
|
||||
let localeMenu = document.querySelector('#item-pane-header-locale-menu');
|
||||
|
||||
optionsContainer.hidden = Zotero.Prefs.get('itemPaneHeader') !== 'bibEntry';
|
||||
if (optionsContainer.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Zotero.Styles.initialized()) {
|
||||
let menus = [styleMenu, localeMenu];
|
||||
for (let menu of menus) {
|
||||
menu.selectedItem = null;
|
||||
menu.setAttribute('label', Zotero.getString('general.loading'));
|
||||
menu.disabled = true;
|
||||
}
|
||||
await Zotero.Styles.init();
|
||||
for (let menu of menus) {
|
||||
menu.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
let currentStyle = Zotero.Styles.get(styleMenu.value);
|
||||
let currentLocale = Zotero.Prefs.get('itemPaneHeader.bibEntry.locale');
|
||||
|
||||
styleMenu.menupopup.replaceChildren();
|
||||
for (let style of Zotero.Styles.getVisible()) {
|
||||
let menuitem = document.createXULElement('menuitem');
|
||||
menuitem.label = style.title;
|
||||
menuitem.value = style.styleID;
|
||||
styleMenu.menupopup.append(menuitem);
|
||||
}
|
||||
|
||||
if (currentStyle) {
|
||||
if (currentStyle.styleID !== styleMenu.value) {
|
||||
// Style has been renamed
|
||||
styleMenu.value = currentStyle.styleID;
|
||||
}
|
||||
|
||||
if (!localeMenu.menupopup.childElementCount) {
|
||||
Zotero.Styles.populateLocaleList(localeMenu);
|
||||
}
|
||||
Zotero.Styles.updateLocaleList(localeMenu, currentStyle, currentLocale);
|
||||
}
|
||||
else {
|
||||
// Style is unknown/removed - show placeholder
|
||||
let shortName = styleMenu.value.replace('http://www.zotero.org/styles/', '');
|
||||
let missingLabel = await document.l10n.formatValue(
|
||||
'preferences-item-pane-header-missing-style',
|
||||
{ shortName }
|
||||
);
|
||||
styleMenu.selectedItem = null;
|
||||
styleMenu.setAttribute('label', missingLabel);
|
||||
}
|
||||
}),
|
||||
|
||||
updateAutoRenameFilesUI: function () {
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -44,6 +44,39 @@
|
|||
<menupopup onpopuphidden="Zotero_Preferences.General.onLocaleChange()"/>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-item-pane-header"/>
|
||||
<menulist id="item-pane-header-menulist" preference="extensions.zotero.itemPaneHeader" native="true">
|
||||
<menupopup>
|
||||
<menuitem data-l10n-id="item-pane-header-none" value="none"/>
|
||||
<menuitem data-l10n-id="item-pane-header-title" value="title"/>
|
||||
<menuitem data-l10n-id="item-pane-header-titleCreatorYear" value="titleCreatorYear"/>
|
||||
<menuitem data-l10n-id="item-pane-header-bibEntry" value="bibEntry"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<vbox id="item-pane-header-bib-entry-options" class="indented-pref">
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-item-pane-header-style"/>
|
||||
<menulist
|
||||
id="item-pane-header-style-menu"
|
||||
preference="extensions.zotero.itemPaneHeader.bibEntry.style"
|
||||
native="true"
|
||||
flex="1"
|
||||
><menupopup/></menulist>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label data-l10n-id="preferences-item-pane-header-locale"/>
|
||||
<menulist
|
||||
id="item-pane-header-locale-menu"
|
||||
preference="extensions.zotero.itemPaneHeader.bibEntry.locale"
|
||||
native="true"
|
||||
><menupopup/></menulist>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-file-handling-groupbox">
|
||||
|
|
|
@ -507,7 +507,6 @@ Zotero.Styles = new function() {
|
|||
* Populate menulist with locales
|
||||
*
|
||||
* @param {xul:menulist} menulist
|
||||
* @return {Promise}
|
||||
*/
|
||||
this.populateLocaleList = function (menulist) {
|
||||
if (!_initialized) {
|
||||
|
|
|
@ -33,6 +33,11 @@ preferences-color-scheme-light =
|
|||
preferences-color-scheme-dark =
|
||||
.label = Dark
|
||||
|
||||
preferences-item-pane-header = Item Pane Header:
|
||||
preferences-item-pane-header-style = Header Citation Style:
|
||||
preferences-item-pane-header-locale = Header Language:
|
||||
preferences-item-pane-header-missing-style = Missing style: <{ $shortName }>
|
||||
|
||||
preferences-advanced-language-and-region-title = Language and Region
|
||||
preferences-advanced-enable-bidi-ui =
|
||||
.label = Enable bidirectional text editing utilities
|
||||
|
|
|
@ -114,6 +114,8 @@ item-button-view-online =
|
|||
|
||||
itembox-button-options =
|
||||
.tooltiptext = Open Context Menu
|
||||
itembox-button-merge =
|
||||
.aria-label = Select Version
|
||||
|
||||
reader-use-dark-mode-for-content =
|
||||
.label = Use Dark Mode for Content
|
||||
|
@ -494,3 +496,16 @@ quicksearch-input =
|
|||
.aria-label = Quick Search
|
||||
.placeholder = { $placeholder }
|
||||
.aria-description = { $placeholder }
|
||||
|
||||
item-pane-header-view-as =
|
||||
.label = View As
|
||||
item-pane-header-none =
|
||||
.label = None
|
||||
item-pane-header-title =
|
||||
.label = Title
|
||||
item-pane-header-titleCreatorYear =
|
||||
.label = Title, Creator, Year
|
||||
item-pane-header-bibEntry =
|
||||
.label = Bibliography Entry
|
||||
item-pane-header-more-options =
|
||||
.label = More Options
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9.29298V2.707L5.707 5L5 4.293L8.5 0.792999L12 4.293L11.293 5L9 2.707V9.29284L15 15.2929L14.2929 16L8.50008 10.2071L2.70711 16L2 15.2929L8 9.29298Z" fill="context-fill"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1H15V3H9V1ZM8 1C8 0.447715 8.44771 0 9 0H15C15.5523 0 16 0.447715 16 1V3C16 3.55228 15.5523 4 15 4H9C8.44771 4 8 3.55228 8 3V1ZM9 7H15V9H9V7ZM8 7C8 6.44772 8.44771 6 9 6H15C15.5523 6 16 6.44772 16 7V9C16 9.55228 15.5523 10 15 10H9C8.44771 10 8 9.55228 8 9V7ZM15 13H9V15H15V13ZM9 12C8.44771 12 8 12.4477 8 13V15C8 15.5523 8.44771 16 9 16H15C15.5523 16 16 15.5523 16 15V13C16 12.4477 15.5523 12 15 12H9ZM7 2C5.34315 2 4 3.34315 4 5V6C4 7.10457 3.10457 8 2 8H1H-1V9H1H2C3.10457 9 4 9.89543 4 11V12C4 13.6569 5.34315 15 7 15V14C5.89543 14 5 13.1046 5 12V11C5 10.2316 4.71115 9.53076 4.2361 9H7V8H4.23611C4.71115 7.46924 5 6.76835 5 6V5C5 3.89543 5.89543 3 7 3V2Z" fill="context-fill"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 838 B |
|
@ -74,6 +74,9 @@ pref("extensions.zotero.sortCreatorAsString", false);
|
|||
|
||||
pref("extensions.zotero.uiDensity", "comfortable");
|
||||
|
||||
pref("extensions.zotero.itemPaneHeader", "title");
|
||||
pref("extensions.zotero.itemPaneHeader.bibEntry.style", "http://www.zotero.org/styles/apa");
|
||||
pref("extensions.zotero.itemPaneHeader.bibEntry.locale", "");
|
||||
|
||||
//Tag Selector
|
||||
pref("extensions.zotero.tagSelector.showAutomatic", true);
|
||||
|
|
|
@ -1,20 +1,37 @@
|
|||
duplicates-merge-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
groupbox {
|
||||
margin: 8px 0 0 0;
|
||||
min-height: 0;
|
||||
&, #zotero-duplicates-merge-version-select, #zotero-duplicates-merge-item-box-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#zotero-duplicates-merge-button
|
||||
{
|
||||
font-size: 13px;
|
||||
padding-top: 9px;
|
||||
|
||||
> groupbox {
|
||||
// Override default margin/padding that breaks our styles here
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-inline: 8px;
|
||||
min-height: 0;
|
||||
|
||||
> :is(description, label, button) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#zotero-duplicates-merge-field-select {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
#zotero-duplicates-merge-item-box-container {
|
||||
overflow-y: auto;
|
||||
padding: 0 8px;
|
||||
flex: 1;
|
||||
padding-inline: 8px;
|
||||
overflow-y: scroll;
|
||||
border-top: var(--material-border-quinary);
|
||||
|
||||
collapsible-section > .head {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show duplicates date list item as selected even when not focused
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
@include comfortable {
|
||||
--editable-text-padding-inline: 4px;
|
||||
--editable-text-padding-block: 4px;
|
||||
|
||||
--editable-text-tight-padding-inline: 4px;
|
||||
--editable-text-tight-padding-block: 2px;
|
||||
}
|
||||
|
||||
@include compact {
|
||||
--editable-text-padding-inline: 4px;
|
||||
--editable-text-padding-block: 1px;
|
||||
|
||||
--editable-text-tight-padding-inline: 3px;
|
||||
--editable-text-tight-padding-block: 1px;
|
||||
}
|
||||
|
||||
editable-text {
|
||||
|
@ -14,15 +20,8 @@ editable-text {
|
|||
--max-visible-lines: 1;
|
||||
|
||||
&[tight] {
|
||||
@include comfortable {
|
||||
--editable-text-padding-inline: 4px;
|
||||
--editable-text-padding-block: 2px;
|
||||
}
|
||||
|
||||
@include compact {
|
||||
--editable-text-padding-inline: 3px;
|
||||
--editable-text-padding-block: 1px;
|
||||
}
|
||||
--editable-text-padding-inline: var(--editable-text-tight-padding-inline);
|
||||
--editable-text-padding-block: var(--editable-text-tight-padding-block);
|
||||
}
|
||||
|
||||
// Fun auto-sizing approach from CSSTricks:
|
||||
|
|
|
@ -14,6 +14,10 @@ item-box {
|
|||
|
||||
#info-table {
|
||||
@include meta-table;
|
||||
|
||||
.meta-row .zotero-field-version-button {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.creator-type-label, #more-creators-label {
|
||||
|
@ -169,12 +173,6 @@ item-box {
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
/* Merge pane in duplicates view */
|
||||
.zotero-field-version-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retraction box
|
||||
*/
|
||||
|
|
|
@ -1,46 +1,44 @@
|
|||
pane-header {
|
||||
display: flex;
|
||||
&:not([hidden]) {
|
||||
display: flex;
|
||||
}
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 6px 8px;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
gap: 4px;
|
||||
max-height: 25%;
|
||||
overflow-y: auto;
|
||||
scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-background);
|
||||
scrollbar-gutter: stable;
|
||||
|
||||
.title {
|
||||
margin-top: calc(0px - var(--editable-text-padding-block));
|
||||
flex: 1 1 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.333;
|
||||
|
||||
.title {
|
||||
align-self: center;
|
||||
margin-top: calc(0px - var(--editable-text-padding-block));
|
||||
padding: 2px 0px 1px 0px;
|
||||
flex: 1 1 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.333;
|
||||
|
||||
editable-text {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.menu-button toolbarbutton {
|
||||
@include svgicon-menu("go-to", "universal", "20");
|
||||
editable-text {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
align-self: start;
|
||||
.creator-year {
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
|
||||
.menu-button toolbarbutton {
|
||||
@include svgicon-menu("go-to", "universal", "20");
|
||||
--width-focus-border: 2px;
|
||||
@include focus-ring;
|
||||
.bib-entry {
|
||||
line-height: 1.5;
|
||||
|
||||
&.loading {
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.creator-year, .bib-entry {
|
||||
// Set padding to match editable-text in tight mode, plus 1px for border
|
||||
padding-inline: calc(var(--editable-text-tight-padding-inline) + 1px);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.custom-head {
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
height: 16px;
|
||||
}
|
||||
|
||||
#item-pane-header-locale-menu {
|
||||
min-width: 12em;
|
||||
}
|
||||
|
||||
@media (-moz-platform: windows) {
|
||||
button, menulist, radio, checkbox, input {
|
||||
margin-block: 4px;
|
||||
|
|
|
@ -9,6 +9,138 @@ describe("Item pane", function () {
|
|||
after(function () {
|
||||
win.close();
|
||||
});
|
||||
|
||||
describe("Item pane header", function () {
|
||||
let itemData = {
|
||||
itemType: 'book',
|
||||
title: 'Birds - A Primer of Ornithology (Teach Yourself Books)',
|
||||
creators: [{
|
||||
creatorType: 'author',
|
||||
lastName: 'Hyde',
|
||||
firstName: 'George E.'
|
||||
}]
|
||||
};
|
||||
|
||||
before(async function () {
|
||||
await Zotero.Styles.init();
|
||||
});
|
||||
|
||||
after(function () {
|
||||
Zotero.Prefs.clear('itemPaneHeader');
|
||||
Zotero.Prefs.clear('itemPaneHeader.bibEntry.style');
|
||||
Zotero.Prefs.clear('itemPaneHeader.bibEntry.locale');
|
||||
});
|
||||
|
||||
it("should be hidden when set to None mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'none');
|
||||
await createDataObject('item', itemData);
|
||||
assert.isTrue(doc.querySelector('pane-header').hidden);
|
||||
});
|
||||
|
||||
it("should show title when set to Title mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'title');
|
||||
let item = await createDataObject('item', itemData);
|
||||
|
||||
assert.isFalse(doc.querySelector('pane-header .title').hidden);
|
||||
assert.isTrue(doc.querySelector('pane-header .creator-year').hidden);
|
||||
assert.isTrue(doc.querySelector('pane-header .bib-entry').hidden);
|
||||
|
||||
assert.equal(doc.querySelector('pane-header .title editable-text').value, item.getField('title'));
|
||||
});
|
||||
|
||||
it("should show title/creator/year when set to Title/Creator/Year mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'titleCreatorYear');
|
||||
let item = await createDataObject('item', itemData);
|
||||
item.setField('date', '1962-05-01');
|
||||
await item.saveTx();
|
||||
|
||||
assert.isTrue(doc.querySelector('pane-header .bib-entry').hidden);
|
||||
assert.isFalse(doc.querySelector('pane-header .title').hidden);
|
||||
assert.isFalse(doc.querySelector('pane-header .creator-year').hidden);
|
||||
|
||||
assert.equal(doc.querySelector('pane-header .title editable-text').value, item.getField('title'));
|
||||
let creatorYearText = doc.querySelector('pane-header .creator-year').textContent;
|
||||
assert.include(creatorYearText, 'Hyde');
|
||||
assert.include(creatorYearText, '1962');
|
||||
});
|
||||
|
||||
it("should show bib entry when set to Bibliography Entry mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'bibEntry');
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/apa');
|
||||
await createDataObject('item', itemData);
|
||||
|
||||
assert.isFalse(doc.querySelector('pane-header .bib-entry').hidden);
|
||||
assert.isTrue(doc.querySelector('pane-header .title').hidden);
|
||||
assert.isTrue(doc.querySelector('pane-header .creator-year').hidden);
|
||||
|
||||
let bibEntry = doc.querySelector('pane-header .bib-entry').shadowRoot.firstElementChild.textContent;
|
||||
assert.equal(bibEntry.trim(), 'Hyde, G. E. (n.d.). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
});
|
||||
|
||||
it("should update bib entry on item change when set to Bibliography Entry mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'bibEntry');
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/apa');
|
||||
let item = await createDataObject('item', itemData);
|
||||
|
||||
let bibEntryElem = doc.querySelector('pane-header .bib-entry').shadowRoot.firstElementChild;
|
||||
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, G. E. (n.d.). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
|
||||
item.setField('date', '1962-05-01');
|
||||
await item.saveTx();
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, G. E. (1962). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
|
||||
item.setCreators([
|
||||
{
|
||||
creatorType: 'author',
|
||||
lastName: 'Smith',
|
||||
firstName: 'John'
|
||||
}
|
||||
]);
|
||||
await item.saveTx();
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Smith, J. (1962). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
|
||||
item.setField('title', 'Birds');
|
||||
await item.saveTx();
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Smith, J. (1962). Birds.');
|
||||
});
|
||||
|
||||
it("should update bib entry on style change when set to Bibliography Entry mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'bibEntry');
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/apa');
|
||||
await createDataObject('item', itemData);
|
||||
|
||||
let bibEntryElem = doc.querySelector('pane-header .bib-entry').shadowRoot.firstElementChild;
|
||||
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, G. E. (n.d.). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/chicago-author-date');
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, George E. n.d. Birds - A Primer of Ornithology (Teach Yourself Books).');
|
||||
});
|
||||
|
||||
it("should update bib entry on locale change when set to Bibliography Entry mode", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'bibEntry');
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/apa');
|
||||
await createDataObject('item', itemData);
|
||||
|
||||
let bibEntryElem = doc.querySelector('pane-header .bib-entry').shadowRoot.firstElementChild;
|
||||
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, G. E. (n.d.). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.locale', 'de-DE');
|
||||
assert.equal(bibEntryElem.textContent.trim(), 'Hyde, G. E. (o. J.). Birds—A Primer of Ornithology (Teach Yourself Books).');
|
||||
});
|
||||
|
||||
it("should fall back to Title/Creator/Year when citation style is missing", async function () {
|
||||
Zotero.Prefs.set('itemPaneHeader', 'bibEntry');
|
||||
Zotero.Prefs.set('itemPaneHeader.bibEntry.style', 'http://www.zotero.org/styles/an-id-that-does-not-match-any-citation-style');
|
||||
await createDataObject('item', itemData);
|
||||
|
||||
assert.isTrue(doc.querySelector('pane-header .bib-entry').hidden);
|
||||
assert.isFalse(doc.querySelector('pane-header .title').hidden);
|
||||
assert.isFalse(doc.querySelector('pane-header .creator-year').hidden);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Info pane", function () {
|
||||
it("should refresh on item update", function* () {
|
||||
|
|
Loading…
Reference in a new issue