diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js
index b9d629ce28..334063d28b 100644
--- a/chrome/content/zotero/bibliography.js
+++ b/chrome/content/zotero/bibliography.js
@@ -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();
}
diff --git a/chrome/content/zotero/elements/itemBox.js b/chrome/content/zotero/elements/itemBox.js
index 5223afff36..bad3ac7549 100644
--- a/chrome/content/zotero/elements/itemBox.js
+++ b/chrome/content/zotero/elements/itemBox.js
@@ -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
diff --git a/chrome/content/zotero/elements/paneHeader.js b/chrome/content/zotero/elements/paneHeader.js
index 25807d2f3a..a2ff4895b8 100644
--- a/chrome/content/zotero/elements/paneHeader.js
+++ b/chrome/content/zotero/elements/paneHeader.js
@@ -26,30 +26,31 @@
"use strict";
{
+ let htmlDoc = document.implementation.createHTMLDocument();
+
class PaneHeader extends ItemPaneSectionElementBase {
content = MozXULElement.parseXULToFragment(`
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
`, ['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);
+ }
+ }
}
diff --git a/chrome/content/zotero/preferences/preferences.xhtml b/chrome/content/zotero/preferences/preferences.xhtml
index fd87d09535..7c7c1fb2c1 100644
--- a/chrome/content/zotero/preferences/preferences.xhtml
+++ b/chrome/content/zotero/preferences/preferences.xhtml
@@ -54,6 +54,7 @@
+
diff --git a/chrome/content/zotero/preferences/preferences_general.js b/chrome/content/zotero/preferences/preferences_general.js
index a817b114c4..6e6e451c7b 100644
--- a/chrome/content/zotero/preferences/preferences_general.js
+++ b/chrome/content/zotero/preferences/preferences_general.js
@@ -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(() => {
diff --git a/chrome/content/zotero/preferences/preferences_general.xhtml b/chrome/content/zotero/preferences/preferences_general.xhtml
index 2698f907f8..78f6e5e5f8 100644
--- a/chrome/content/zotero/preferences/preferences_general.xhtml
+++ b/chrome/content/zotero/preferences/preferences_general.xhtml
@@ -44,6 +44,39 @@
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js
index 95b057efc6..278086892f 100644
--- a/chrome/content/zotero/xpcom/style.js
+++ b/chrome/content/zotero/xpcom/style.js
@@ -507,7 +507,6 @@ Zotero.Styles = new function() {
* Populate menulist with locales
*
* @param {xul:menulist} menulist
- * @return {Promise}
*/
this.populateLocaleList = function (menulist) {
if (!_initialized) {
diff --git a/chrome/locale/en-US/zotero/preferences.ftl b/chrome/locale/en-US/zotero/preferences.ftl
index 514e331ce9..da95aa5309 100644
--- a/chrome/locale/en-US/zotero/preferences.ftl
+++ b/chrome/locale/en-US/zotero/preferences.ftl
@@ -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
diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl
index ea2f29d153..d45419d662 100644
--- a/chrome/locale/en-US/zotero/zotero.ftl
+++ b/chrome/locale/en-US/zotero/zotero.ftl
@@ -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
diff --git a/chrome/skin/default/zotero/16/universal/merge.svg b/chrome/skin/default/zotero/16/universal/merge.svg
index 359026ac0c..039f06f388 100644
--- a/chrome/skin/default/zotero/16/universal/merge.svg
+++ b/chrome/skin/default/zotero/16/universal/merge.svg
@@ -1,3 +1,3 @@
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
index eb2c6d4b10..398ad00348 100644
--- a/defaults/preferences/zotero.js
+++ b/defaults/preferences/zotero.js
@@ -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);
diff --git a/scss/elements/_duplicatesMergePane.scss b/scss/elements/_duplicatesMergePane.scss
index 7f2bc53eaf..9788bac92b 100644
--- a/scss/elements/_duplicatesMergePane.scss
+++ b/scss/elements/_duplicatesMergePane.scss
@@ -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
diff --git a/scss/elements/_editableText.scss b/scss/elements/_editableText.scss
index f6e52331ad..fddc68cc90 100644
--- a/scss/elements/_editableText.scss
+++ b/scss/elements/_editableText.scss
@@ -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:
diff --git a/scss/elements/_itemBox.scss b/scss/elements/_itemBox.scss
index 7e33f2d7b2..2c4855ebe9 100644
--- a/scss/elements/_itemBox.scss
+++ b/scss/elements/_itemBox.scss
@@ -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
*/
diff --git a/scss/elements/_paneHeader.scss b/scss/elements/_paneHeader.scss
index b4c5046ae1..b823b11dd4 100644
--- a/scss/elements/_paneHeader.scss
+++ b/scss/elements/_paneHeader.scss
@@ -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 {
diff --git a/scss/preferences/_general.scss b/scss/preferences/_general.scss
index e8c69a70dc..db4e45a88f 100644
--- a/scss/preferences/_general.scss
+++ b/scss/preferences/_general.scss
@@ -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;
diff --git a/test/tests/itemPaneTest.js b/test/tests/itemPaneTest.js
index 2fab81c051..523462a597 100644
--- a/test/tests/itemPaneTest.js
+++ b/test/tests/itemPaneTest.js
@@ -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* () {