Notes/Tags/Related redesign
|
@ -938,45 +938,30 @@ var ZoteroContextPane = new function () {
|
|||
div.className = 'zotero-view-item';
|
||||
main.append(div);
|
||||
|
||||
let createSection = (pane, showAdd = false) => {
|
||||
let section = document.createXULElement('collapsible-section');
|
||||
section.dataset.pane = pane;
|
||||
section.setAttribute('data-l10n-id', 'section-' + pane);
|
||||
section.toggleAttribute('show-add', showAdd);
|
||||
return section;
|
||||
};
|
||||
|
||||
// Info
|
||||
var itemBoxContainer = createSection('info');
|
||||
var itemBox = new (customElements.get('item-box'));
|
||||
itemBoxContainer.append(itemBox);
|
||||
itemBox.setAttribute('data-pane', 'info');
|
||||
div.append(itemBox);
|
||||
|
||||
// Abstract
|
||||
var abstractBoxContainer = createSection('abstract');
|
||||
var abstractBox = new (customElements.get('abstract-box'));
|
||||
abstractBox.className = 'zotero-editpane-abstract';
|
||||
abstractBoxContainer.append(abstractBox);
|
||||
abstractBox.setAttribute('data-pane', 'abstract');
|
||||
div.append(abstractBox);
|
||||
|
||||
// TODO: Attachments
|
||||
|
||||
// Tags
|
||||
var tagsBoxContainer = createSection('tags', true);
|
||||
var tagsBox = new (customElements.get('tags-box'));
|
||||
tagsBox.className = 'zotero-editpane-tags';
|
||||
tagsBoxContainer.append(tagsBox);
|
||||
tagsBox.setAttribute('data-pane', 'tags');
|
||||
div.append(tagsBox);
|
||||
|
||||
// Related
|
||||
var relatedBoxContainer = createSection('related', true);
|
||||
var relatedBox = new (customElements.get('related-box'));
|
||||
relatedBox.className = 'zotero-editpane-related';
|
||||
relatedBox.addEventListener('click', (event) => {
|
||||
if (event.originalTarget.closest('.zotero-clicky')) {
|
||||
Zotero_Tabs.select('zotero-pane');
|
||||
}
|
||||
});
|
||||
relatedBoxContainer.append(relatedBox);
|
||||
|
||||
div.append(itemBoxContainer, abstractBoxContainer, tagsBoxContainer, relatedBoxContainer);
|
||||
relatedBox.setAttribute('data-pane', 'related');
|
||||
div.append(relatedBox);
|
||||
|
||||
// item-pane-sidenav
|
||||
var sidenav = document.createXULElement('item-pane-sidenav');
|
||||
|
|
|
@ -50,6 +50,9 @@ Services.scriptloader.loadSubScript('chrome://zotero/content/elements/editableTe
|
|||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/itemPaneSidenav.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/abstractBox.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/collapsibleSection.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/attachmentsBox.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/attachmentRow.js', this);
|
||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/annotationRow.js', this);
|
||||
|
||||
// Fix missing property bug that breaks arrow key navigation between <tab>s
|
||||
{
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
{
|
||||
class AbstractBox extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<editable-text multiline="true" data-l10n-id="abstract-field" />
|
||||
<collapsible-section data-l10n-id="section-abstract" data-pane="abstract">
|
||||
<html:div class="body">
|
||||
<editable-text multiline="true" data-l10n-id="abstract-field" />
|
||||
</html:div>
|
||||
</collapsible-section>
|
||||
`);
|
||||
|
||||
_item = null;
|
||||
|
@ -83,8 +87,10 @@
|
|||
}
|
||||
|
||||
async blurOpenField() {
|
||||
this.abstractField.blur();
|
||||
await this.save();
|
||||
if (this.abstractField?.matches(':focus-within')) {
|
||||
this.abstractField.blur();
|
||||
await this.save();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -92,9 +98,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let title = this.item.getField('abstractNote');
|
||||
if (this.abstractField.initialValue !== title) {
|
||||
this.abstractField.value = title;
|
||||
let abstract = this.item.getField('abstractNote');
|
||||
if (!this.abstractField.initialValue || this.abstractField.initialValue !== abstract) {
|
||||
this.abstractField.value = abstract;
|
||||
this.abstractField.initialValue = '';
|
||||
}
|
||||
this.abstractField.readOnly = this._mode == 'view';
|
||||
this.abstractField.setAttribute('aria-label', Zotero.ItemFields.getLocalizedString('abstractNote'));
|
||||
|
|
121
chrome/content/zotero/elements/annotationRow.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2023 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
{
|
||||
class AnnotationRow extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="head">
|
||||
<image class="icon"/>
|
||||
<html:div class="title"/>
|
||||
</html:div>
|
||||
<html:div class="body"/>
|
||||
<html:div class="tags"/>
|
||||
`);
|
||||
|
||||
_annotation = null;
|
||||
|
||||
_mode = null;
|
||||
|
||||
_listenerAdded = false;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['annotation-id'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
switch (name) {
|
||||
case 'annotation-id':
|
||||
this._annotation = Zotero.Items.get(newValue);
|
||||
break;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
get annotation() {
|
||||
return this._annotation;
|
||||
}
|
||||
|
||||
set annotation(annotation) {
|
||||
this._annotation = annotation;
|
||||
this.setAttribute('annotation-id', annotation.id);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._head = this.querySelector('.head');
|
||||
this._title = this.querySelector('.title');
|
||||
this._body = this.querySelector('.body');
|
||||
this._tags = this.querySelector('.tags');
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.initialized) return;
|
||||
|
||||
this._title.textContent = Zotero.getString('pdfReader.page') + ' '
|
||||
+ (this._annotation.annotationPageLabel || '-');
|
||||
|
||||
let type = this._annotation.annotationType;
|
||||
if (type == 'image') {
|
||||
type = 'area';
|
||||
}
|
||||
this.querySelector('.icon').src = 'chrome://zotero/skin/16/universal/annotate-' + type + '.svg';
|
||||
this._body.replaceChildren();
|
||||
|
||||
if (['image', 'ink'].includes(this._annotation.annotationType)) {
|
||||
let imagePath = Zotero.Annotations.getCacheImagePath(this._annotation);
|
||||
if (imagePath) {
|
||||
let img = document.createElement('img');
|
||||
img.src = Zotero.File.pathToFileURI(imagePath);
|
||||
img.draggable = false;
|
||||
this._body.append(img);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._annotation.annotationText) {
|
||||
let text = document.createElement('div');
|
||||
text.classList.add('quote');
|
||||
text.textContent = this._annotation.annotationText;
|
||||
this._body.append(text);
|
||||
}
|
||||
|
||||
if (this._annotation.annotationComment) {
|
||||
let comment = document.createElement('div');
|
||||
comment.classList.add('comment');
|
||||
comment.textContent = this._annotation.annotationComment;
|
||||
this._body.append(comment);
|
||||
}
|
||||
|
||||
let tags = this._annotation.getTags();
|
||||
this._tags.hidden = !tags.length;
|
||||
this._tags.textContent = tags.map(tag => tag.tag).sort(Zotero.localeCompare).join(Zotero.getString('punctuation.comma') + ' ');
|
||||
|
||||
this.style.setProperty('--annotation-color', this._annotation.annotationColor);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('annotation-row', AnnotationRow);
|
||||
}
|
177
chrome/content/zotero/elements/attachmentRow.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2023 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
import { getCSSItemTypeIcon } from 'components/icons';
|
||||
|
||||
{
|
||||
class AttachmentRow extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:div class="head">
|
||||
<html:span class="twisty"/>
|
||||
<html:div class="clicky-item">
|
||||
<html:span class="icon"/>
|
||||
<html:div class="label"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
<html:div class="body"/>
|
||||
`);
|
||||
|
||||
_attachment = null;
|
||||
|
||||
_mode = null;
|
||||
|
||||
_listenerAdded = false;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['attachment-id'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
switch (name) {
|
||||
case 'attachment-id':
|
||||
this._attachment = Zotero.Items.get(newValue);
|
||||
break;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
get open() {
|
||||
if (this.empty) {
|
||||
return false;
|
||||
}
|
||||
return this.hasAttribute('open');
|
||||
}
|
||||
|
||||
set open(val) {
|
||||
val = !!val;
|
||||
let open = this.open;
|
||||
if (open === val || this.empty) return;
|
||||
this.render();
|
||||
let openHeight = this._body.scrollHeight;
|
||||
if (openHeight) {
|
||||
this.style.setProperty('--open-height', `${openHeight}px`);
|
||||
}
|
||||
else {
|
||||
this.style.setProperty('--open-height', 'auto');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-void
|
||||
void getComputedStyle(this).maxHeight; // Force style calculation! Without this the animation doesn't work
|
||||
this.toggleAttribute('open', val);
|
||||
if (!this.dispatchEvent(new CustomEvent('toggle', { bubbles: false, cancelable: true }))) {
|
||||
// Revert
|
||||
this.toggleAttribute('open', open);
|
||||
return;
|
||||
}
|
||||
if (!val && this.ownerDocument?.activeElement && this.contains(this.ownerDocument?.activeElement)) {
|
||||
this.ownerDocument.activeElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
get attachment() {
|
||||
return this._attachment;
|
||||
}
|
||||
|
||||
set attachment(attachment) {
|
||||
this._attachment = attachment;
|
||||
this.setAttribute('attachment-id', attachment.id);
|
||||
}
|
||||
|
||||
get attachmentTitle() {
|
||||
return this._attachment.getField('title');
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return !this._attachment || !this._attachment.numAnnotations();
|
||||
}
|
||||
|
||||
get contextRow() {
|
||||
return this.classList.contains('context');
|
||||
}
|
||||
|
||||
set contextRow(val) {
|
||||
this.classList.toggle('context', !!val);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._head = this.querySelector('.head');
|
||||
this._head.addEventListener('click', this._handleClick);
|
||||
this._head.addEventListener('keydown', this._handleKeyDown);
|
||||
|
||||
this._label = this.querySelector('.label');
|
||||
this._body = this.querySelector('.body');
|
||||
this.open = false;
|
||||
this.render();
|
||||
}
|
||||
|
||||
_handleClick = (event) => {
|
||||
if (event.target.closest('.clicky-item')) {
|
||||
let win = Zotero.getMainWindow();
|
||||
if (win) {
|
||||
win.ZoteroPane.selectItem(this._attachment.id);
|
||||
win.Zotero_Tabs.select('zotero-pane');
|
||||
win.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.open = !this.open;
|
||||
};
|
||||
|
||||
_handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
this.open = !this.open;
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.initialized) return;
|
||||
|
||||
this.querySelector('.icon').replaceWith(getCSSItemTypeIcon(this._attachment.getItemTypeIconName()));
|
||||
this._label.textContent = this._attachment.getField('title');
|
||||
|
||||
this._body.replaceChildren();
|
||||
for (let annotation of this._attachment.getAnnotations()) {
|
||||
let row = document.createXULElement('annotation-row');
|
||||
row.annotation = annotation;
|
||||
this._body.append(row);
|
||||
}
|
||||
|
||||
if (!this._listenerAdded) {
|
||||
this._body.addEventListener('transitionend', () => {
|
||||
this.style.setProperty('--open-height', 'auto');
|
||||
});
|
||||
this._listenerAdded = true;
|
||||
}
|
||||
|
||||
this._head.setAttribute('aria-expanded', this.open);
|
||||
this.toggleAttribute('empty', this.empty);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('attachment-row', AttachmentRow);
|
||||
}
|
164
chrome/content/zotero/elements/attachmentsBox.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2023 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://www.zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
{
|
||||
class AttachmentsBox extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<collapsible-section data-l10n-id="section-attachments" data-pane="attachments" show-add="true">
|
||||
<html:div class="body">
|
||||
</html:div>
|
||||
</collapsible-section>
|
||||
`);
|
||||
|
||||
_item = null;
|
||||
|
||||
_mode = null;
|
||||
|
||||
_inTrash = false;
|
||||
|
||||
get item() {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
set item(item) {
|
||||
if (this._item === item) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._item = item;
|
||||
this._body.replaceChildren();
|
||||
if (item) {
|
||||
for (let attachment of Zotero.Items.get(item.getAttachments())) {
|
||||
this.addRow(attachment);
|
||||
}
|
||||
this.updateCount();
|
||||
}
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this._mode;
|
||||
}
|
||||
|
||||
set mode(mode) {
|
||||
this._mode = mode;
|
||||
}
|
||||
|
||||
get inTrash() {
|
||||
return this._inTrash;
|
||||
}
|
||||
|
||||
set inTrash(inTrash) {
|
||||
this._inTrash = inTrash;
|
||||
for (let row of this._body.children) {
|
||||
row.contextRow = this._isContext(row.attachment);
|
||||
}
|
||||
this.updateCount();
|
||||
}
|
||||
|
||||
init() {
|
||||
this._section = this.querySelector('collapsible-section');
|
||||
this._section.addEventListener('add', this._handleAdd);
|
||||
this._body = this.querySelector('.body');
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
}
|
||||
|
||||
notify(action, type, ids) {
|
||||
if (!this._item) return;
|
||||
|
||||
let itemAttachmentIDs = this._item.getAttachments(true);
|
||||
let attachments = Zotero.Items.get(ids.filter(id => itemAttachmentIDs.includes(id)));
|
||||
if (action == 'add') {
|
||||
for (let attachment of attachments) {
|
||||
this.addRow(attachment);
|
||||
}
|
||||
}
|
||||
else if (action == 'modify') {
|
||||
for (let attachment of attachments) {
|
||||
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
||||
let open = false;
|
||||
if (row) {
|
||||
open = row.open;
|
||||
row.remove();
|
||||
}
|
||||
this.addRow(attachment).open = open;
|
||||
}
|
||||
}
|
||||
else if (action == 'delete') {
|
||||
for (let attachment of attachments) {
|
||||
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
||||
if (row) {
|
||||
row.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateCount();
|
||||
}
|
||||
|
||||
addRow(attachment) {
|
||||
if (attachment.deleted && !this._inTrash) return;
|
||||
|
||||
let row = document.createXULElement('attachment-row');
|
||||
row.attachment = attachment;
|
||||
row.contextRow = this._isContext(attachment);
|
||||
|
||||
let inserted = false;
|
||||
for (let existingRow of this._body.children) {
|
||||
if (Zotero.localeCompare(row.attachmentTitle, existingRow.attachmentTitle) < 0) {
|
||||
continue;
|
||||
}
|
||||
existingRow.before(row);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
if (!inserted) {
|
||||
this._body.append(row);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
updateCount() {
|
||||
let count = this._item.numAttachments(this._inTrash);
|
||||
this._section.setCount(count);
|
||||
}
|
||||
|
||||
_handleAdd = () => {
|
||||
ZoteroPane.addAttachmentFromDialog(false, this._item.id);
|
||||
this._section.empty = false;
|
||||
this._section.open = true;
|
||||
};
|
||||
|
||||
_isContext(attachment) {
|
||||
return this._inTrash && !this._item.deleted && !attachment.deleted;
|
||||
}
|
||||
}
|
||||
customElements.define("attachments-box", AttachmentsBox);
|
||||
}
|
|
@ -50,8 +50,8 @@ class XULElementBase extends XULElement {
|
|||
document.l10n.connectRoot(this.shadowRoot);
|
||||
}
|
||||
|
||||
this.init();
|
||||
this.initialized = true;
|
||||
this.init();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
|
|
@ -36,13 +36,16 @@
|
|||
_listenerAdded = false;
|
||||
|
||||
get open() {
|
||||
if (this.empty) {
|
||||
return false;
|
||||
}
|
||||
return this.hasAttribute('open');
|
||||
}
|
||||
|
||||
set open(val) {
|
||||
val = !!val;
|
||||
let open = this.open;
|
||||
if (open === val) return;
|
||||
if (open === val || this.empty) return;
|
||||
this.render();
|
||||
let openHeight = this._head?.nextSibling?.scrollHeight;
|
||||
if (openHeight) {
|
||||
|
@ -58,11 +61,28 @@
|
|||
if (!this.dispatchEvent(new CustomEvent('toggle', { bubbles: false, cancelable: true }))) {
|
||||
// Revert
|
||||
this.toggleAttribute('open', open);
|
||||
return;
|
||||
}
|
||||
if (!val && this.ownerDocument?.activeElement && this.contains(this.ownerDocument?.activeElement)) {
|
||||
this.ownerDocument.activeElement.blur();
|
||||
}
|
||||
|
||||
this._saveOpenState();
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return this.hasAttribute('empty');
|
||||
}
|
||||
|
||||
set empty(val) {
|
||||
this.toggleAttribute('empty', !!val);
|
||||
}
|
||||
|
||||
setCount(count) {
|
||||
this.setAttribute('data-l10n-args', JSON.stringify({ count }));
|
||||
this.empty = !count;
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
@ -80,7 +100,7 @@
|
|||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['open', 'label', 'show-add'];
|
||||
return ['open', 'empty', 'label', 'show-add'];
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
|
@ -92,7 +112,6 @@
|
|||
throw new Error('data-pane is required');
|
||||
}
|
||||
|
||||
this._restoreOpenState();
|
||||
this.tabIndex = 0;
|
||||
|
||||
this._head = document.createElement('div');
|
||||
|
@ -108,8 +127,7 @@
|
|||
this._addButton = document.createXULElement('toolbarbutton');
|
||||
this._addButton.className = 'add';
|
||||
this._addButton.addEventListener('command', (event) => {
|
||||
// TODO: Is this the best approach?
|
||||
this._head.nextSibling?.dispatchEvent(new CustomEvent('add', { ...event, bubbles: true }));
|
||||
this.dispatchEvent(new CustomEvent('add', { ...event, bubbles: false }));
|
||||
});
|
||||
this._head.append(this._addButton);
|
||||
|
||||
|
@ -118,9 +136,14 @@
|
|||
this._head.append(twisty);
|
||||
|
||||
this.prepend(this._head);
|
||||
this._restoreOpenState();
|
||||
this.render();
|
||||
|
||||
this._notifierID = Zotero.Prefs.registerObserver(`panes.${this.dataset.pane}.open`, this._restoreOpenState.bind(this));
|
||||
|
||||
if (this.hasAttribute('data-l10n-id') && !this.hasAttribute('data-l10n-args')) {
|
||||
this.setAttribute('data-l10n-args', JSON.stringify({ count: 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
|
@ -27,13 +27,9 @@
|
|||
|
||||
{
|
||||
class EditableText extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<html:textarea rows="1" />
|
||||
`);
|
||||
_input;
|
||||
|
||||
_textarea;
|
||||
|
||||
static observedAttributes = ['multiline', 'readonly', 'label'];
|
||||
static observedAttributes = ['multiline', 'readonly', 'placeholder', 'label', 'aria-label', 'value'];
|
||||
|
||||
get multiline() {
|
||||
return this.hasAttribute('multiline');
|
||||
|
@ -51,44 +47,73 @@
|
|||
this.toggleAttribute('readonly', readOnly);
|
||||
}
|
||||
|
||||
// Fluent won't set placeholder on an editable-text for some reason, so we use the label property to store
|
||||
// the placeholder that will be set on the child <textarea> or <input>
|
||||
get placeholder() {
|
||||
return this._textarea.placeholder;
|
||||
return this.label;
|
||||
}
|
||||
|
||||
set placeholder(placeholder) {
|
||||
this._textarea.placeholder = placeholder;
|
||||
this.label = placeholder;
|
||||
}
|
||||
|
||||
// Fluent won't set placeholder on an editable-text for some reason, so we use the label property to store
|
||||
// the placeholder that will be set on the child <textarea>
|
||||
get label() {
|
||||
return this.getAttribute('label');
|
||||
return this.getAttribute('label') || '';
|
||||
}
|
||||
|
||||
set label(label) {
|
||||
this.setAttribute('label', label);
|
||||
this.setAttribute('label', label || '');
|
||||
}
|
||||
|
||||
get ariaLabel() {
|
||||
return this._textarea.getAttribute('aria-label');
|
||||
return this.getAttribute('aria-label') || '';
|
||||
}
|
||||
|
||||
set ariaLabel(ariaLabel) {
|
||||
this._textarea.setAttribute('aria-label', ariaLabel);
|
||||
this.setAttribute('aria-label', ariaLabel);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._textarea.value;
|
||||
return this.getAttribute('value') || '';
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
this._textarea.value = value;
|
||||
this.dataset.value = value;
|
||||
this.render();
|
||||
this.setAttribute('value', value || '');
|
||||
}
|
||||
|
||||
get initialValue() {
|
||||
return this._textarea.dataset.initialValue;
|
||||
return this._input?.dataset.initialValue || '';
|
||||
}
|
||||
|
||||
set initialValue(initialValue) {
|
||||
this._input.dataset.initialValue = initialValue || '';
|
||||
}
|
||||
|
||||
get autocomplete() {
|
||||
let val = this.getAttribute('autocomplete');
|
||||
try {
|
||||
let props = JSON.parse(val);
|
||||
if (typeof props === 'object') {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
set autocomplete(val) {
|
||||
if (val) {
|
||||
this.setAttribute('autocomplete', JSON.stringify(val));
|
||||
}
|
||||
else {
|
||||
this.removeAttribute('autocomplete');
|
||||
}
|
||||
}
|
||||
|
||||
get ref() {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
|
@ -96,46 +121,99 @@
|
|||
}
|
||||
|
||||
init() {
|
||||
this._textarea = this.querySelector('textarea');
|
||||
this._textarea.addEventListener('input', () => {
|
||||
if (!this.multiline) {
|
||||
this._textarea.value = this._textarea.value.replace(/\n/g, ' ');
|
||||
}
|
||||
this.dataset.value = this._textarea.value;
|
||||
});
|
||||
this._textarea.addEventListener('focus', () => {
|
||||
this._textarea.dataset.initialValue = this._textarea.value;
|
||||
});
|
||||
this._textarea.addEventListener('blur', () => {
|
||||
delete this._textarea.dataset.initialValue;
|
||||
});
|
||||
this._textarea.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (!this.multiline || event.shiftKey) {
|
||||
event.preventDefault();
|
||||
this._textarea.blur();
|
||||
}
|
||||
}
|
||||
else if (event.key === 'Escape') {
|
||||
this._textarea.value = this._textarea.dataset.initialValue;
|
||||
this._textarea.blur();
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this._textarea) return;
|
||||
this._textarea.readOnly = this.readOnly;
|
||||
this._textarea.placeholder = this.label;
|
||||
let autocompleteParams = this.autocomplete;
|
||||
let autocompleteEnabled = !this.multiline && !!autocompleteParams;
|
||||
if (!this._input || autocompleteEnabled !== (this._input.constructor.name === 'AutocompleteInput')) {
|
||||
let input;
|
||||
if (autocompleteEnabled) {
|
||||
input = document.createElement('input', { is: 'autocomplete-input' });
|
||||
input.type = 'autocomplete';
|
||||
}
|
||||
else {
|
||||
input = document.createElement('textarea');
|
||||
input.rows = 1;
|
||||
}
|
||||
input.classList.add('input');
|
||||
let handleInput = () => {
|
||||
if (!this.multiline) {
|
||||
this._input.value = this._input.value.replace(/\n/g, ' ');
|
||||
}
|
||||
this.value = this._input.value;
|
||||
};
|
||||
let handleChange = () => {
|
||||
this.value = this._input.value;
|
||||
};
|
||||
input.addEventListener('input', handleInput);
|
||||
input.addEventListener('change', handleChange);
|
||||
input.addEventListener('focus', () => {
|
||||
this.dispatchEvent(new CustomEvent('focus'));
|
||||
this._input.dataset.initialValue = this._input.value;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
this.dispatchEvent(new CustomEvent('blur'));
|
||||
delete this._input.dataset.initialValue;
|
||||
});
|
||||
input.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (this.multiline === event.shiftKey) {
|
||||
event.preventDefault();
|
||||
this._input.blur();
|
||||
}
|
||||
}
|
||||
else if (event.key === 'Escape') {
|
||||
this._input.value = this._input.dataset.initialValue;
|
||||
this._input.blur();
|
||||
}
|
||||
});
|
||||
|
||||
let focused = false;
|
||||
let selectionStart = this._input?.selectionStart;
|
||||
let selectionEnd = this._input?.selectionEnd;
|
||||
let selectionDirection = this._input?.selectionDirection;
|
||||
if (this._input && document.activeElement === this._input) {
|
||||
focused = true;
|
||||
input.dataset.initialValue = this._input?.dataset.initialValue;
|
||||
}
|
||||
if (this._input) {
|
||||
this._input.replaceWith(input);
|
||||
}
|
||||
else {
|
||||
this.append(input);
|
||||
}
|
||||
this._input = input;
|
||||
|
||||
if (focused) {
|
||||
this._input.focus();
|
||||
}
|
||||
if (selectionStart !== undefined && selectionEnd !== undefined) {
|
||||
this._input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
|
||||
}
|
||||
}
|
||||
this._input.readOnly = this.readOnly;
|
||||
this._input.placeholder = this.label;
|
||||
this._input.setAttribute('aria-label', this.ariaLabel);
|
||||
this._input.value = this.value;
|
||||
|
||||
if (autocompleteEnabled) {
|
||||
this._input.setAttribute('autocomplete', 'on');
|
||||
this._input.setAttribute('autocompletepopup', autocompleteParams.popup || '');
|
||||
this._input.setAttribute('autocompletesearch', autocompleteParams.search || '');
|
||||
delete autocompleteParams.popup;
|
||||
delete autocompleteParams.search;
|
||||
Object.assign(this._input, autocompleteParams);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._textarea.focus();
|
||||
focus(options) {
|
||||
this._input?.focus(options);
|
||||
}
|
||||
|
||||
blur() {
|
||||
this._textarea.blur();
|
||||
this._input?.blur();
|
||||
}
|
||||
}
|
||||
customElements.define("editable-text", EditableText);
|
||||
|
|
|
@ -57,42 +57,46 @@
|
|||
this._initialVisibleCreators = 5;
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<div id="item-box" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<menupopup id="creator-type-menu" position="after_start"/>
|
||||
<menupopup id="zotero-creator-transform-menu">
|
||||
<menuitem id="creator-transform-swap-names" label="&zotero.item.creatorTransform.nameSwap;"/>
|
||||
<menuitem id="creator-transform-capitalize" label="&zotero.item.creatorTransform.fixCase;"/>
|
||||
</menupopup>
|
||||
<menupopup id="zotero-doi-menu">
|
||||
<menuitem id="zotero-doi-menu-view-online" label="&zotero.item.viewOnline;"/>
|
||||
<menuitem id="zotero-doi-menu-copy" label="&zotero.item.copyAsURL;"/>
|
||||
</menupopup>
|
||||
<guidance-panel id="zotero-author-guidance" about="authorMenu" position="after_end" x="-25"/>
|
||||
</popupset>
|
||||
<div id="retraction-box" hidden="hidden">
|
||||
<div id="retraction-header">
|
||||
<div id="retraction-header-text"/>
|
||||
<collapsible-section data-l10n-id="section-info" data-pane="info">
|
||||
<html:div class="body">
|
||||
<div id="item-box" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<menupopup id="creator-type-menu" position="after_start"/>
|
||||
<menupopup id="zotero-creator-transform-menu">
|
||||
<menuitem id="creator-transform-swap-names" label="&zotero.item.creatorTransform.nameSwap;"/>
|
||||
<menuitem id="creator-transform-capitalize" label="&zotero.item.creatorTransform.fixCase;"/>
|
||||
</menupopup>
|
||||
<menupopup id="zotero-doi-menu">
|
||||
<menuitem id="zotero-doi-menu-view-online" label="&zotero.item.viewOnline;"/>
|
||||
<menuitem id="zotero-doi-menu-copy" label="&zotero.item.copyAsURL;"/>
|
||||
</menupopup>
|
||||
<guidance-panel id="zotero-author-guidance" about="authorMenu" position="after_end" x="-25"/>
|
||||
</popupset>
|
||||
<div id="retraction-box" hidden="hidden">
|
||||
<div id="retraction-header">
|
||||
<div id="retraction-header-text"/>
|
||||
</div>
|
||||
<div id="retraction-details">
|
||||
<p id="retraction-date"/>
|
||||
|
||||
<dl id="retraction-reasons"/>
|
||||
|
||||
<p id="retraction-notice"/>
|
||||
|
||||
<div id="retraction-links"/>
|
||||
|
||||
<p id="retraction-credit"/>
|
||||
<div id="retraction-hide"><button/></div>
|
||||
</div>
|
||||
</div>
|
||||
<table id="info-table">
|
||||
<tr>
|
||||
<th><label class="key">&zotero.items.itemType;</label></th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="retraction-details">
|
||||
<p id="retraction-date"/>
|
||||
|
||||
<dl id="retraction-reasons"/>
|
||||
|
||||
<p id="retraction-notice"/>
|
||||
|
||||
<div id="retraction-links"/>
|
||||
|
||||
<p id="retraction-credit"/>
|
||||
<div id="retraction-hide"><button/></div>
|
||||
</div>
|
||||
</div>
|
||||
<table id="info-table">
|
||||
<tr>
|
||||
<th><label class="key">&zotero.items.itemType;</label></th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</html:div>
|
||||
</collapsible-section>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
}
|
||||
|
||||
|
@ -960,8 +964,7 @@
|
|||
td.appendChild(toggleButton);
|
||||
|
||||
// Minus (-) button
|
||||
var removeButton = document.createElement('button');
|
||||
removeButton.textContent = "-";
|
||||
var removeButton = document.createXULElement('toolbarbutton');
|
||||
removeButton.setAttribute("class", "zotero-clicky zotero-clicky-minus zotero-focusable");
|
||||
removeButton.setAttribute('ztabindex', tabindex + 4);
|
||||
removeButton.setAttribute('aria-label', Zotero.getString('general.delete'));
|
||||
|
@ -977,8 +980,7 @@
|
|||
td.appendChild(removeButton);
|
||||
|
||||
// Plus (+) button
|
||||
var addButton = document.createElement('button');
|
||||
addButton.textContent = "+";
|
||||
var addButton = document.createXULElement('toolbarbutton');
|
||||
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus zotero-focusable");
|
||||
addButton.setAttribute('ztabindex', tabindex + 5);
|
||||
// If row isn't saved, don't let user add more
|
||||
|
|
|
@ -104,6 +104,11 @@
|
|||
_updateSelectedPane() {
|
||||
let topPane = null;
|
||||
let containerBoundingRect = this.container.getBoundingClientRect();
|
||||
// If not initialized with content, just select the first pane
|
||||
if (containerBoundingRect.height == 0) {
|
||||
this.selectedPane = 'info';
|
||||
return;
|
||||
}
|
||||
for (let box of this._getPanes()) {
|
||||
// Allow a little padding to deal with floating-point imprecision
|
||||
if (box.getBoundingClientRect().top > containerBoundingRect.top + 5) {
|
||||
|
|
|
@ -28,56 +28,32 @@
|
|||
import { getCSSItemTypeIcon } from 'components/icons';
|
||||
|
||||
{
|
||||
class NotesBox extends XULElement {
|
||||
class NotesBox extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<collapsible-section data-l10n-id="section-notes" data-pane="notes">
|
||||
<html:div class="body"/>
|
||||
</collapsible-section>
|
||||
`);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._mode = 'view';
|
||||
this._item = null;
|
||||
this._destroyed = false;
|
||||
this._noteIDs = [];
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<box flex="1" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<div style="flex-grow: 1" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="header">
|
||||
<label id="notes-num"/>
|
||||
<button id="notes-add">&zotero.item.add;</button>
|
||||
</div>
|
||||
<div id="notes-grid" class="grid"/>
|
||||
</div>
|
||||
</box>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._destroyed = false;
|
||||
window.addEventListener("unload", this.destroy);
|
||||
|
||||
let content = document.importNode(this.content, true);
|
||||
this.append(content);
|
||||
|
||||
this._id('notes-add').addEventListener('click', this._handleAdd);
|
||||
this.addEventListener('add', this._handleAdd);
|
||||
|
||||
init() {
|
||||
this._section = this.querySelector('collapsible-section');
|
||||
this._section.addEventListener('add', this._handleAdd);
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'notesBox');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener("unload", this.destroy);
|
||||
this._destroyed = true;
|
||||
|
||||
this._section = null;
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.replaceChildren();
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this._mode;
|
||||
}
|
||||
|
@ -118,38 +94,45 @@ import { getCSSItemTypeIcon } from 'components/icons';
|
|||
}
|
||||
|
||||
this._noteIDs = this._item.getNotes();
|
||||
this._id('notes-add').hidden = this._mode != 'edit';
|
||||
|
||||
let grid = this._id('notes-grid');
|
||||
grid.replaceChildren();
|
||||
let body = this.querySelector('.body');
|
||||
body.replaceChildren();
|
||||
|
||||
let notes = Zotero.Items.get(this._item.getNotes());
|
||||
for (let item of notes) {
|
||||
let id = item.id;
|
||||
let icon = getCSSItemTypeIcon(item.getItemTypeIconName());
|
||||
|
||||
let label = document.createElement("label");
|
||||
label.append(item.getDisplayTitle());
|
||||
let row = document.createElement('div');
|
||||
row.className = 'row';
|
||||
|
||||
let icon = getCSSItemTypeIcon('note');
|
||||
|
||||
let label = document.createElement("span");
|
||||
label.className = 'label';
|
||||
label.append(this._TODO_EXTRACT_noteToTitle(item.getNote(), {
|
||||
maxLength: 0
|
||||
}));
|
||||
|
||||
let box = document.createElement('div');
|
||||
box.addEventListener('click', () => this._handleShowItem(id));
|
||||
box.className = 'box zotero-clicky';
|
||||
box.appendChild(icon);
|
||||
box.appendChild(label);
|
||||
box.className = 'box';
|
||||
box.append(icon, label);
|
||||
|
||||
grid.append(box);
|
||||
row.append(box);
|
||||
|
||||
if (this._mode == 'edit') {
|
||||
let remove = document.createElement("label");
|
||||
remove.addEventListener('click', () => this._handleRemove(id));
|
||||
let remove = document.createXULElement("toolbarbutton");
|
||||
remove.addEventListener('command', () => this._handleRemove(id));
|
||||
remove.className = 'zotero-clicky zotero-clicky-minus';
|
||||
remove.append('-');
|
||||
grid.append(remove);
|
||||
row.append(remove);
|
||||
}
|
||||
|
||||
body.append(row);
|
||||
}
|
||||
|
||||
let num = this._noteIDs.length;
|
||||
this._id('notes-num').replaceChildren(Zotero.getString('pane.item.notes.count', num, num));
|
||||
let count = this._noteIDs.length;
|
||||
this._section.showAdd = this._mode == 'edit';
|
||||
this._section.setCount(count);
|
||||
}
|
||||
|
||||
_handleAdd = (event) => {
|
||||
|
@ -170,6 +153,56 @@ import { getCSSItemTypeIcon } from 'components/icons';
|
|||
_id(id) {
|
||||
return this.querySelector(`#${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Extract this back to utilities_item.js when merging
|
||||
* Return first line (or first maxLength characters) of note content
|
||||
*
|
||||
* @param {String} text
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.stopAtLineBreak] - Stop at <br/> instead of converting to space
|
||||
* @param {Number} [options.maxLength] - Defaults to 120. If set to 0, no limit is applied.
|
||||
* @return {String}
|
||||
*/
|
||||
_TODO_EXTRACT_noteToTitle(text, options = {}) {
|
||||
var maxLength = options.maxLength;
|
||||
if (maxLength === undefined) {
|
||||
maxLength = 120;
|
||||
}
|
||||
else if (maxLength === 0) {
|
||||
maxLength = Infinity;
|
||||
}
|
||||
|
||||
var origText = text;
|
||||
text = text.trim();
|
||||
// Add line breaks after block elements
|
||||
text = text.replace(/(<\/(h\d|p|div)+>)/g, '$1\n');
|
||||
if (options.stopAtLineBreak) {
|
||||
text = text.replace(/<br\s*\/?>/g, '\n');
|
||||
}
|
||||
else {
|
||||
text = text.replace(/<br\s*\/?>/g, ' ');
|
||||
}
|
||||
text = Zotero.Utilities.unescapeHTML(text);
|
||||
|
||||
// If first line is just an opening HTML tag, remove it
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// <blockquote>
|
||||
// <p>Foo</p>
|
||||
// </blockquote>
|
||||
if (/^<[^>\n]+[^\/]>\n/.test(origText)) {
|
||||
text = text.trim();
|
||||
}
|
||||
|
||||
var t = text.substring(0, maxLength);
|
||||
var ln = t.indexOf("\n");
|
||||
if (ln > -1 && ln < maxLength) {
|
||||
t = t.substring(0, ln);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
customElements.define("notes-box", NotesBox);
|
||||
}
|
||||
|
|
|
@ -128,8 +128,10 @@
|
|||
}
|
||||
|
||||
async blurOpenField() {
|
||||
this.titleField.blur();
|
||||
await this.save();
|
||||
if (this.titleField?.matches(':focus-within')) {
|
||||
this.titleField.blur();
|
||||
await this.save();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -140,8 +142,9 @@
|
|||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
||||
|
||||
let title = this.item.getField(this._titleFieldID);
|
||||
if (this.titleField.initialValue !== title) {
|
||||
if (!this.titleField.initialValue || this.titleField.initialValue !== title) {
|
||||
this.titleField.value = title;
|
||||
this.titleField.initialValue = '';
|
||||
}
|
||||
this.titleField.readOnly = this._mode == 'view';
|
||||
this.titleField.placeholder = Zotero.ItemFields.getLocalizedString(this._titleFieldID);
|
||||
|
|
|
@ -25,54 +25,32 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { getCSSItemTypeIcon } from 'components/icons';
|
||||
|
||||
{
|
||||
class RelatedBox extends XULElement {
|
||||
class RelatedBox extends XULElementBase {
|
||||
content = MozXULElement.parseXULToFragment(`
|
||||
<collapsible-section data-l10n-id="section-related" data-pane="related">
|
||||
<html:div class="body"/>
|
||||
</collapsible-section>
|
||||
`);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._mode = 'view';
|
||||
this._item = null;
|
||||
this._destroyed = false;
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<box flex="1" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<div style="flex-grow: 1" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="header">
|
||||
<label id="related-num"/>
|
||||
<button id="related-add">&zotero.item.add;</button>
|
||||
</div>
|
||||
<div id="related-grid" class="grid"/>
|
||||
</div>
|
||||
</box>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._destroyed = false;
|
||||
window.addEventListener("unload", this.destroy);
|
||||
|
||||
let content = document.importNode(this.content, true);
|
||||
this.append(content);
|
||||
|
||||
this._id('related-add').addEventListener('click', this.add);
|
||||
this.addEventListener('add', this.add);
|
||||
|
||||
init() {
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
||||
this._section = this.querySelector('collapsible-section');
|
||||
this._section.addEventListener('add', this.add);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener("unload", this.destroy);
|
||||
this._destroyed = true;
|
||||
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.replaceChildren();
|
||||
this.destroy();
|
||||
this._section = null;
|
||||
}
|
||||
|
||||
get mode() {
|
||||
|
@ -126,10 +104,8 @@
|
|||
}
|
||||
|
||||
refresh() {
|
||||
this._id('related-add').hidden = this._mode != 'edit';
|
||||
|
||||
let grid = this._id('related-grid');
|
||||
grid.replaceChildren();
|
||||
let body = this.querySelector('.body');
|
||||
body.replaceChildren();
|
||||
|
||||
if (this._item) {
|
||||
let relatedKeys = this._item.relatedItems;
|
||||
|
@ -144,33 +120,42 @@
|
|||
continue;
|
||||
}
|
||||
let id = relatedItem.id;
|
||||
let icon = document.createElement("img");
|
||||
icon.src = relatedItem.getImageSrc();
|
||||
|
||||
let label = document.createElement("label");
|
||||
let row = document.createElement('div');
|
||||
row.className = 'row';
|
||||
|
||||
let icon = getCSSItemTypeIcon(relatedItem.getItemTypeIconName());
|
||||
|
||||
let label = document.createElement("span");
|
||||
label.className = 'label';
|
||||
label.append(relatedItem.getDisplayTitle());
|
||||
|
||||
let box = document.createElement('div');
|
||||
box.addEventListener('click', () => this._handleShowItem(id));
|
||||
box.className = 'box zotero-clicky';
|
||||
box.className = 'box';
|
||||
box.appendChild(icon);
|
||||
box.appendChild(label);
|
||||
|
||||
grid.append(box);
|
||||
row.append(box);
|
||||
|
||||
if (this._mode == 'edit') {
|
||||
let remove = document.createElement("label");
|
||||
remove.addEventListener('click', () => this._handleRemove(id));
|
||||
let remove = document.createXULElement("toolbarbutton");
|
||||
remove.addEventListener('command', () => this._handleRemove(id));
|
||||
remove.className = 'zotero-clicky zotero-clicky-minus';
|
||||
remove.append('-');
|
||||
grid.append(remove);
|
||||
row.append(remove);
|
||||
}
|
||||
|
||||
body.append(row);
|
||||
}
|
||||
this._updateCount();
|
||||
}
|
||||
|
||||
this._section.showAdd = this._mode == 'edit';
|
||||
}
|
||||
|
||||
add = async () => {
|
||||
this._section.empty = false;
|
||||
this._section.open = true;
|
||||
|
||||
let io = { dataIn: null, dataOut: null, deferred: Zotero.Promise.defer(), itemTreeID: 'related-box-select-item-dialog' };
|
||||
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '',
|
||||
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
||||
|
@ -233,19 +218,7 @@
|
|||
|
||||
_updateCount() {
|
||||
let count = this._item.relatedItems.length;
|
||||
let str = 'pane.item.related.count.';
|
||||
switch (count) {
|
||||
case 0:
|
||||
str += 'zero';
|
||||
break;
|
||||
case 1:
|
||||
str += 'singular';
|
||||
break;
|
||||
default:
|
||||
str += 'plural';
|
||||
break;
|
||||
}
|
||||
this._id('related-num').replaceChildren(Zotero.getString(str, [count]));
|
||||
this._section.setCount(count);
|
||||
}
|
||||
|
||||
_id(id) {
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
this.count = 0;
|
||||
this.clickHandler = null;
|
||||
|
||||
this._lastTabIndex = false;
|
||||
this._tabDirection = null;
|
||||
this._tagColors = [];
|
||||
this._notifierID = null;
|
||||
|
@ -41,34 +40,30 @@
|
|||
this._item = null;
|
||||
|
||||
this.content = MozXULElement.parseXULToFragment(`
|
||||
<box flex="1" tooltip="html-tooltip" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<div id="tags-box" style="flex-grow: 1" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div class="tags-box-header">
|
||||
<label id="count"/>
|
||||
<button id="tags-box-add-button">&zotero.item.add;</button>
|
||||
</div>
|
||||
<ul id="rows" class="tags-box-list"/>
|
||||
</div>
|
||||
</box>
|
||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<tooltip id="html-tooltip" page="true"/>
|
||||
<menupopup id="tags-context-menu">
|
||||
<menuitem id="remove-all-item-tags" label="&zotero.item.tags.removeAll;"/>
|
||||
</menupopup>
|
||||
<!-- Note: autocomplete-input can create this panel by itself, but it appears
|
||||
in the top DOM and is behind the tags box popup -->
|
||||
<panel
|
||||
is="autocomplete-richlistbox-popup"
|
||||
type="autocomplete-richlistbox"
|
||||
id="PopupAutoComplete"
|
||||
role="group"
|
||||
noautofocus="true"
|
||||
hidden="true"
|
||||
overflowpadding="4"
|
||||
norolluponanchor="true"
|
||||
nomaxresults="true"
|
||||
/>
|
||||
</popupset>
|
||||
<collapsible-section data-l10n-id="section-tags" data-pane="tags">
|
||||
<html:div class="body">
|
||||
<html:div id="rows" class="tags-box-list"/>
|
||||
<popupset>
|
||||
<tooltip id="html-tooltip" page="true"/>
|
||||
<menupopup id="tags-context-menu">
|
||||
<menuitem id="remove-all-item-tags" label="&zotero.item.tags.removeAll;"/>
|
||||
</menupopup>
|
||||
<!-- Note: autocomplete-input can create this panel by itself, but it appears
|
||||
in the top DOM and is behind the tags box popup -->
|
||||
<panel
|
||||
is="autocomplete-richlistbox-popup"
|
||||
type="autocomplete-richlistbox"
|
||||
id="PopupAutoComplete"
|
||||
role="group"
|
||||
noautofocus="true"
|
||||
hidden="true"
|
||||
overflowpadding="4"
|
||||
norolluponanchor="true"
|
||||
nomaxresults="true"
|
||||
/>
|
||||
</popupset>
|
||||
</html:div>
|
||||
</collapsible-section>
|
||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||
}
|
||||
|
||||
|
@ -79,23 +74,23 @@
|
|||
let content = document.importNode(this.content, true);
|
||||
this.append(content);
|
||||
|
||||
this._id("tags-box-add-button").addEventListener('click', this._handleAddButtonClick);
|
||||
this.addEventListener('add', this._handleAddButtonClick);
|
||||
this._id("tags-box-add-button").addEventListener('keydown', this._handleAddButtonKeyDown);
|
||||
this._id('tags-box').addEventListener('click', (event) => {
|
||||
if (event.target.id == 'tags-box') {
|
||||
this._section = this.querySelector('collapsible-section');
|
||||
this._section.addEventListener('add', this._handleAddButtonClick);
|
||||
this.addEventListener('click', (event) => {
|
||||
if (event.target === this) {
|
||||
this.blurOpenField();
|
||||
}
|
||||
});
|
||||
|
||||
let removeAllItemTags = this._id('remove-all-item-tags');
|
||||
this._id('remove-all-item-tags').addEventListener('command', this.removeAll);
|
||||
this._id('tags-box').addEventListener('contextmenu', (event) => {
|
||||
this.addEventListener('contextmenu', (event) => {
|
||||
removeAllItemTags.disabled = !this.count;
|
||||
this._id('tags-context-menu').openPopupAtScreen(event.screenX, event.screenY, true);
|
||||
});
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item-tag', 'setting'], 'tagsBox');
|
||||
// Register our observer with priority 101 (after Zotero.Tags) so we get updated tag colors
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item-tag', 'setting'], 'tagsBox', 101);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -105,6 +100,7 @@
|
|||
window.removeEventListener("unload", this.destroy);
|
||||
this._destroyed = true;
|
||||
|
||||
this._section = null;
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
}
|
||||
|
||||
|
@ -130,8 +126,6 @@
|
|||
case 'edit':
|
||||
this.clickable = true;
|
||||
this.editable = true;
|
||||
this.clickHandler = this.showEditor;
|
||||
this.blurHandler = this.hideEditor;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -151,7 +145,6 @@
|
|||
return;
|
||||
}
|
||||
this._item = val;
|
||||
this._lastTabIndex = false;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
|
@ -175,20 +168,7 @@
|
|||
let tagType = data.type;
|
||||
|
||||
if (event == 'add') {
|
||||
var newTabIndex = this.add(tagName, tagType);
|
||||
if (newTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex > newTabIndex) {
|
||||
this._lastTabIndex++;
|
||||
}
|
||||
}
|
||||
this.add(tagName, tagType);
|
||||
}
|
||||
else if (event == 'modify') {
|
||||
let oldTagName = data.old.tag;
|
||||
|
@ -196,20 +176,7 @@
|
|||
this.add(tagName, tagType);
|
||||
}
|
||||
else if (event == 'remove') {
|
||||
var oldTabIndex = this.remove(tagName);
|
||||
if (oldTabIndex == -1) {
|
||||
return;
|
||||
}
|
||||
if (this._tabDirection == -1) {
|
||||
if (this._lastTabIndex > oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
else if (this._tabDirection == 1) {
|
||||
if (this._lastTabIndex >= oldTabIndex) {
|
||||
this._lastTabIndex--;
|
||||
}
|
||||
}
|
||||
this.remove(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,10 +196,10 @@
|
|||
// Cancel field focusing while we're updating
|
||||
this._reloading = true;
|
||||
|
||||
this._id("tags-box-add-button").hidden = !this.editable;
|
||||
|
||||
this._tagColors = Zotero.Tags.getColors(this.item.libraryID);
|
||||
|
||||
let focusedTag = this._id('rows').querySelector('editable-text:focus')?.value;
|
||||
|
||||
let tagRows = this._id('rows');
|
||||
tagRows.replaceChildren();
|
||||
|
||||
|
@ -240,15 +207,32 @@
|
|||
|
||||
// Sort tags alphabetically
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
tags.sort((a, b) => collation.compareString(1, a.tag, b.tag));
|
||||
tags.sort((a, b) => {
|
||||
let aTag = a.tag;
|
||||
let bTag = b.tag;
|
||||
let aHasColor = this._tagColors.has(aTag);
|
||||
let bHasColor = this._tagColors.has(bTag);
|
||||
// Sort colored tags to the top
|
||||
if (aHasColor && !bHasColor) {
|
||||
return -1;
|
||||
}
|
||||
if (!aHasColor && bHasColor) {
|
||||
return 1;
|
||||
}
|
||||
return collation.compareString(1, aTag, bTag);
|
||||
});
|
||||
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
this.addDynamicRow(tags[i], i + 1);
|
||||
}
|
||||
this.updateCount(tags.length);
|
||||
this._section.showAdd = this.editable;
|
||||
|
||||
this._reloading = false;
|
||||
this._focusField();
|
||||
|
||||
if (focusedTag) {
|
||||
this._id('rows').querySelector(`[value="${CSS.escape(focusedTag)}"]`)?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
addDynamicRow(tagData, tabindex, skipAppend) {
|
||||
|
@ -259,7 +243,7 @@
|
|||
tabindex = this._id('rows').childNodes.length + 1;
|
||||
}
|
||||
|
||||
var icon = document.createElement("img");
|
||||
var icon = document.createElement("div");
|
||||
icon.className = "zotero-box-icon";
|
||||
|
||||
// DEBUG: Why won't just this.nextSibling.blur() work?
|
||||
|
@ -270,13 +254,18 @@
|
|||
var label = this.createValueElement(name, tabindex);
|
||||
|
||||
if (this.editable) {
|
||||
var remove = document.createXULElement("label");
|
||||
var remove = document.createXULElement("toolbarbutton");
|
||||
remove.setAttribute('value', '-');
|
||||
remove.setAttribute('class', 'zotero-clicky zotero-clicky-minus');
|
||||
remove.setAttribute('tabindex', -1);
|
||||
}
|
||||
|
||||
var row = document.createElement("li");
|
||||
var row = document.createElement("div");
|
||||
row.classList.add('row');
|
||||
if (name && this._tagColors.has(name)) {
|
||||
row.classList.add('has-color');
|
||||
row.style.setProperty('--tag-color', this._tagColors.get(name).color);
|
||||
}
|
||||
if (isNew) {
|
||||
row.setAttribute('isNew', true);
|
||||
}
|
||||
|
@ -302,7 +291,6 @@
|
|||
var tagName = tagData ? tagData.tag : "";
|
||||
var tagType = (tagData && tagData.type) ? tagData.type : 0;
|
||||
|
||||
var icon = row.firstChild;
|
||||
if (this.editable) {
|
||||
var remove = row.lastChild;
|
||||
}
|
||||
|
@ -312,21 +300,15 @@
|
|||
row.setAttribute('tagType', tagType);
|
||||
|
||||
// Icon
|
||||
var iconFile = 'tag';
|
||||
if (!tagData || tagType == 0) {
|
||||
icon.setAttribute('title', Zotero.getString('pane.item.tags.icon.user'));
|
||||
}
|
||||
else if (tagType == 1) {
|
||||
iconFile += '-automatic';
|
||||
icon.setAttribute('title', Zotero.getString('pane.item.tags.icon.automatic'));
|
||||
}
|
||||
icon.setAttribute('src', `chrome://zotero/skin/${iconFile}${Zotero.hiDPISuffix}.png`);
|
||||
let icon = row.firstChild;
|
||||
icon.title = tagType == 0
|
||||
? Zotero.getString('pane.item.tags.icon.user')
|
||||
: Zotero.getString('pane.item.tags.icon.automatic');
|
||||
|
||||
// "-" button
|
||||
if (this.editable) {
|
||||
remove.setAttribute('disabled', false);
|
||||
remove.addEventListener('click', async (event) => {
|
||||
this._lastTabIndex = false;
|
||||
if (tagData) {
|
||||
let item = this.item;
|
||||
this.remove(tagName);
|
||||
|
@ -354,218 +336,96 @@
|
|||
}
|
||||
|
||||
|
||||
createValueElement(valueText, tabindex) {
|
||||
var valueElement = document.createXULElement("label");
|
||||
createValueElement(valueText) {
|
||||
var valueElement = document.createXULElement("editable-text");
|
||||
valueElement.setAttribute('fieldname', 'tag');
|
||||
valueElement.setAttribute('flex', 1);
|
||||
valueElement.className = 'zotero-box-label';
|
||||
|
||||
if (this.clickable) {
|
||||
if (tabindex) {
|
||||
valueElement.setAttribute('ztabindex', tabindex);
|
||||
}
|
||||
valueElement.addEventListener('click', (event) => {
|
||||
/* Skip right-click on Windows */
|
||||
if (event.button) {
|
||||
return;
|
||||
}
|
||||
this.clickHandler(event.target, 1, valueText);
|
||||
}, false);
|
||||
valueElement.className += ' zotero-clicky';
|
||||
}
|
||||
|
||||
var firstSpace;
|
||||
if (typeof valueText == 'string') {
|
||||
firstSpace = valueText.indexOf(" ");
|
||||
}
|
||||
|
||||
// 29 == arbitrary length at which to chop uninterrupted text
|
||||
if ((firstSpace == -1 && valueText.length > 29) || firstSpace > 29) {
|
||||
valueElement.setAttribute('crop', 'end');
|
||||
valueElement.setAttribute('value', valueText);
|
||||
}
|
||||
else {
|
||||
// Wrap to multiple lines
|
||||
valueElement.appendChild(document.createTextNode(valueText));
|
||||
}
|
||||
|
||||
// Tag color
|
||||
var colorData = this._tagColors.get(valueText);
|
||||
if (colorData) {
|
||||
valueElement.style.color = colorData.color;
|
||||
valueElement.style.fontWeight = 'bold';
|
||||
}
|
||||
|
||||
valueElement.readOnly = !this.editable;
|
||||
valueElement.value = valueText;
|
||||
let params = {
|
||||
fieldName: 'tag',
|
||||
libraryID: this._item.libraryID,
|
||||
itemID: this._item.id || ''
|
||||
};
|
||||
valueElement.autocomplete = {
|
||||
ignoreBlurWhileSearching: true,
|
||||
popup: 'PopupAutoComplete',
|
||||
search: 'zotero',
|
||||
searchParam: JSON.stringify(params),
|
||||
completeSelectedIndex: true
|
||||
};
|
||||
valueElement.addEventListener('blur', this.saveTag);
|
||||
valueElement.addEventListener('keydown', this.handleKeyDown);
|
||||
valueElement.addEventListener('paste', this.handlePaste);
|
||||
return valueElement;
|
||||
}
|
||||
|
||||
showEditor(elem, rows, value) {
|
||||
|
||||
// Blur any active fields
|
||||
/*
|
||||
if (this._dynamicFields) {
|
||||
this._dynamicFields.focus();
|
||||
}
|
||||
*/
|
||||
|
||||
Zotero.debug('Showing editor');
|
||||
|
||||
var fieldName = 'tag';
|
||||
var tabindex = elem.getAttribute('ztabindex');
|
||||
|
||||
var itemID = this._item.id;
|
||||
|
||||
var t = document.createElement(rows > 1 ? 'textarea' : 'input', { is: 'shadow-autocomplete-input' });
|
||||
t.setAttribute('class', 'editable');
|
||||
t.setAttribute('value', value);
|
||||
t.setAttribute('fieldname', fieldName);
|
||||
t.setAttribute('ztabindex', tabindex);
|
||||
t.setAttribute('ignoreblurwhilesearching', 'true');
|
||||
t.setAttribute('autocompletepopup', 'PopupAutoComplete');
|
||||
// Multi-line
|
||||
if (rows > 1) {
|
||||
t.setAttribute('rows', rows);
|
||||
}
|
||||
// Add auto-complete
|
||||
else {
|
||||
t.setAttribute('type', 'autocomplete');
|
||||
t.setAttribute('autocompletesearch', 'zotero');
|
||||
let params = {
|
||||
fieldName: fieldName,
|
||||
libraryID: this.item.libraryID
|
||||
};
|
||||
params.itemID = itemID ? itemID : '';
|
||||
t.setAttribute(
|
||||
'autocompletesearchparam', JSON.stringify(params)
|
||||
);
|
||||
t.setAttribute('completeselectedindex', true);
|
||||
}
|
||||
|
||||
var box = elem.parentNode;
|
||||
box.replaceChild(t, elem);
|
||||
|
||||
t.addEventListener('blur', this.blurHandler);
|
||||
t.addEventListener('keydown', this.handleKeyDown);
|
||||
t.addEventListener('paste', this.handlePaste);
|
||||
|
||||
this._tabDirection = false;
|
||||
this._lastTabIndex = tabindex;
|
||||
|
||||
// Prevent error when clicking between a changed field
|
||||
// and another -- there's probably a better way
|
||||
if (!t.select) {
|
||||
return;
|
||||
}
|
||||
t.select();
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
handleKeyDown = async (event) => {
|
||||
var target = event.target;
|
||||
var focused = document.activeElement;
|
||||
var target = event.currentTarget;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_RETURN:
|
||||
var multiline = target.parentNode.classList.contains('multiline');
|
||||
var empty = target.value == "";
|
||||
if (event.shiftKey) {
|
||||
if (!multiline) {
|
||||
var self = this;
|
||||
setTimeout(function () {
|
||||
var val = target.value;
|
||||
if (val !== "") {
|
||||
val += "\n";
|
||||
}
|
||||
self.makeMultiline(target, val, 6);
|
||||
}, 0);
|
||||
return false;
|
||||
}
|
||||
// Submit
|
||||
}
|
||||
else if (multiline) {
|
||||
return true;
|
||||
if (event.key === 'Enter') {
|
||||
var multiline = target.multiline;
|
||||
var empty = target.value == "";
|
||||
if (event.shiftKey) {
|
||||
if (!multiline) {
|
||||
setTimeout(() => {
|
||||
var val = target.value;
|
||||
if (val !== "") {
|
||||
val += "\n";
|
||||
}
|
||||
this.makeMultiline(target, val);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Submit
|
||||
}
|
||||
else if (multiline) {
|
||||
return;
|
||||
}
|
||||
|
||||
var row = target.closest('li');
|
||||
let blurOnly = false;
|
||||
event.preventDefault();
|
||||
|
||||
// If non-empty last row, only blur, because the open textbox will
|
||||
// be cleared in hideEditor() and remain in place
|
||||
if (row == row.parentNode.lastChild && !empty) {
|
||||
blurOnly = true;
|
||||
}
|
||||
// If empty non-last row, refocus current row
|
||||
else if (row != row.parentNode.lastChild && empty) {
|
||||
var focusField = true;
|
||||
}
|
||||
// If non-empty non-last row, return focus to items pane
|
||||
else {
|
||||
var focusField = false;
|
||||
this._lastTabIndex = false;
|
||||
}
|
||||
var row = target.parentElement;
|
||||
// Not sure why this can happen, but if the event fires on an unmounted node, just ignore it
|
||||
if (!row.parentElement) {
|
||||
return;
|
||||
}
|
||||
let blurOnly = false;
|
||||
let focusField = false;
|
||||
|
||||
await this.blurHandler(event);
|
||||
// If non-empty last row, only blur, because the open textbox will
|
||||
// be cleared in saveTag() and remain in place
|
||||
if (row == row.parentNode.lastChild && !empty) {
|
||||
blurOnly = true;
|
||||
}
|
||||
// If empty non-last row, refocus current row
|
||||
else if (row != row.parentNode.lastChild && empty) {
|
||||
focusField = row.nextElementSibling;
|
||||
}
|
||||
|
||||
if (blurOnly) {
|
||||
return false;
|
||||
}
|
||||
if (focusField) {
|
||||
this._focusField();
|
||||
}
|
||||
// Return focus to items pane
|
||||
else {
|
||||
var tree = document.getElementById('zotero-items-tree');
|
||||
if (tree) {
|
||||
tree.focus();
|
||||
}
|
||||
}
|
||||
await this.blurOpenField();
|
||||
|
||||
return false;
|
||||
|
||||
case event.DOM_VK_ESCAPE:
|
||||
// Reset field to original value
|
||||
target.value = target.getAttribute('value');
|
||||
|
||||
var tagsbox = focused.closest('.editable');
|
||||
|
||||
this._lastTabIndex = false;
|
||||
await this.blurHandler(event);
|
||||
|
||||
if (tagsbox) {
|
||||
tagsbox.closePopup();
|
||||
}
|
||||
|
||||
// TODO: Return focus to items pane
|
||||
if (blurOnly) {
|
||||
return;
|
||||
}
|
||||
if (focusField) {
|
||||
focusField.focus();
|
||||
}
|
||||
// Return focus to items pane
|
||||
else {
|
||||
var tree = document.getElementById('zotero-items-tree');
|
||||
if (tree) {
|
||||
tree.focus();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case event.DOM_VK_TAB:
|
||||
// If already an empty last row, ignore forward tab
|
||||
if (target.value == "" && !event.shiftKey) {
|
||||
var row = Zotero.getAncestorByTagName(target, 'li');
|
||||
if (row == row.parentNode.lastChild) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this._tabDirection = event.shiftKey ? -1 : 1;
|
||||
await this.blurHandler(event);
|
||||
this._focusField();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Intercept paste, check for newlines, and convert textbox
|
||||
// to multiline if necessary
|
||||
handlePaste = (event) => {
|
||||
var textbox = event.target;
|
||||
var textbox = event.currentTarget;
|
||||
var str = event.clipboardData.getData('text');
|
||||
|
||||
var multiline = !!str.trim().match(/\n/);
|
||||
|
@ -577,32 +437,21 @@
|
|||
}
|
||||
};
|
||||
|
||||
makeMultiline(textbox, value, rows) {
|
||||
textbox.parentNode.classList.add('multiline');
|
||||
// If rows not specified, use one more than lines in input
|
||||
if (!rows) {
|
||||
rows = value.match(/\n/g).length + 1;
|
||||
}
|
||||
textbox = this.showEditor(textbox, rows, textbox.getAttribute('value'));
|
||||
textbox.value = value;
|
||||
makeMultiline(editable, value) {
|
||||
editable.multiline = true;
|
||||
editable.value = value;
|
||||
// Move cursor to end
|
||||
textbox.selectionStart = value.length;
|
||||
editable.ref.selectionStart = value.length;
|
||||
}
|
||||
|
||||
hideEditor = async (event) => {
|
||||
var textbox = event.target;
|
||||
saveTag = async (event) => {
|
||||
var textbox = event.currentTarget;
|
||||
|
||||
Zotero.debug('Hiding editor');
|
||||
Zotero.debug('Saving tag');
|
||||
|
||||
var oldValue = textbox.getAttribute('value');
|
||||
var oldValue = textbox.initialValue;
|
||||
var value = textbox.value = textbox.value.trim();
|
||||
|
||||
var tagsbox = textbox.closest('.editable');
|
||||
if (!tagsbox) {
|
||||
Zotero.debug('Tagsbox not found', 1);
|
||||
return;
|
||||
}
|
||||
|
||||
var row = textbox.parentNode;
|
||||
|
||||
var isNew = row.getAttribute('isNew');
|
||||
|
@ -613,9 +462,8 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// If row hasn't changed, change back to label
|
||||
// If row hasn't changed, we're done
|
||||
if (oldValue == value) {
|
||||
this.textboxToLabel(textbox);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -628,9 +476,6 @@
|
|||
// The existing textbox will be removed in notify()
|
||||
this.removeRow(row);
|
||||
this.add(value);
|
||||
if (event.type != 'blur') {
|
||||
this._focusField();
|
||||
}
|
||||
try {
|
||||
this.item.replaceTag(oldValue, value);
|
||||
await this.item.saveTx();
|
||||
|
@ -644,9 +489,10 @@
|
|||
// Existing tag cleared
|
||||
else {
|
||||
try {
|
||||
let nextRowElem = row.nextElementSibling?.querySelector('editable-text');
|
||||
this.removeRow(row);
|
||||
if (event.type != 'blur') {
|
||||
this._focusField();
|
||||
if (event.type != 'change') {
|
||||
nextRowElem?.focus();
|
||||
}
|
||||
this.item.removeTag(oldValue);
|
||||
await this.item.saveTx();
|
||||
|
@ -659,8 +505,6 @@
|
|||
}
|
||||
// Multiple tags
|
||||
else if (tags.length > 1) {
|
||||
var lastTag = row == row.parentNode.lastChild;
|
||||
|
||||
if (!isNew) {
|
||||
// If old tag isn't in array, remove it
|
||||
if (tags.indexOf(oldValue) == -1) {
|
||||
|
@ -670,27 +514,24 @@
|
|||
// immediately. This isn't strictly necessary, but it
|
||||
// makes the transition nicer.
|
||||
else {
|
||||
textbox.value = textbox.getAttribute('value');
|
||||
this.textboxToLabel(textbox);
|
||||
textbox.value = textbox.initialValue;
|
||||
textbox.blur();
|
||||
}
|
||||
}
|
||||
|
||||
tags.forEach(tag => this.item.addTag(tag));
|
||||
await this.item.saveTx();
|
||||
|
||||
if (lastTag) {
|
||||
this._lastTabIndex = this.item.getTags().length;
|
||||
}
|
||||
|
||||
this.reload();
|
||||
}
|
||||
// Single tag at end
|
||||
else {
|
||||
if (event.type == 'blur') {
|
||||
if (event.type == 'change') {
|
||||
this.removeRow(row);
|
||||
}
|
||||
else {
|
||||
textbox.value = '';
|
||||
// We need a setTimeout here for some reason - why?
|
||||
setTimeout(() => textbox.focus());
|
||||
}
|
||||
this.add(value);
|
||||
this.item.addTag(value);
|
||||
|
@ -705,31 +546,13 @@
|
|||
};
|
||||
|
||||
newTag() {
|
||||
var rowsElement = this._id('rows');
|
||||
var rows = rowsElement.childNodes;
|
||||
|
||||
// Don't add new row if there already is one
|
||||
if (rows.length && rows[rows.length - 1].querySelector('.editable')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._section.empty = false;
|
||||
this._section.open = true;
|
||||
var row = this.addDynamicRow();
|
||||
// It needs relatively high delay to make focus-on-click work
|
||||
setTimeout(() => {
|
||||
row.firstChild.nextSibling.click();
|
||||
}, 50);
|
||||
|
||||
row.querySelector('editable-text').focus();
|
||||
return row;
|
||||
}
|
||||
|
||||
textboxToLabel(textbox) {
|
||||
var elem = this.createValueElement(
|
||||
textbox.value, textbox.getAttribute('ztabindex')
|
||||
);
|
||||
var row = textbox.parentNode;
|
||||
row.replaceChild(elem, textbox);
|
||||
}
|
||||
|
||||
add(tagName, tagType) {
|
||||
var rowsElement = this._id('rows');
|
||||
var rows = rowsElement.childNodes;
|
||||
|
@ -738,7 +561,7 @@
|
|||
var row = false;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].getAttribute('tagName') === tagName) {
|
||||
return rows[i].getAttribute('ztabindex');
|
||||
return rows[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -747,89 +570,54 @@
|
|||
type: tagType
|
||||
};
|
||||
|
||||
if (row) {
|
||||
// Update row and label
|
||||
this.updateRow(row, tagData);
|
||||
var elem = this.createValueElement(tagName);
|
||||
var color = this._tagColors.has(tagName);
|
||||
|
||||
// Remove the old row, which we'll reinsert at the correct place
|
||||
rowsElement.removeChild(row);
|
||||
|
||||
// Find the current label or textbox within the row
|
||||
// and replace it with the new element -- this is used
|
||||
// both when creating new rows and when hiding the
|
||||
// entry textbox
|
||||
var oldElem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
||||
row.replaceChild(elem, oldElem);
|
||||
}
|
||||
else {
|
||||
// Create new row, but don't insert it
|
||||
row = this.addDynamicRow(tagData, false, true);
|
||||
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
||||
}
|
||||
// Create new row, but don't insert it
|
||||
row = this.addDynamicRow(tagData, false, true);
|
||||
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
||||
|
||||
// Move row to appropriate place, alphabetically
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
var labels = rowsElement.getElementsByAttribute('fieldname', 'tag');
|
||||
var tagEditables = rowsElement.getElementsByAttribute('fieldname', 'tag');
|
||||
|
||||
var inserted = false;
|
||||
var newTabIndex = false;
|
||||
for (var i = 0; i < labels.length; i++) {
|
||||
let index = i + 1;
|
||||
if (inserted) {
|
||||
labels[i].setAttribute('ztabindex', index);
|
||||
for (let editable of tagEditables) {
|
||||
// Sort tags without colors below tags with colors
|
||||
if (!color && this._tagColors.has(editable.value)
|
||||
|| collation.compareString(1, tagName, editable.value) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collation.compareString(1, tagName, labels[i].textContent) > 0
|
||||
// Ignore textbox at end
|
||||
&& labels[i].tagName != 'input') {
|
||||
labels[i].setAttribute('ztabindex', index);
|
||||
continue;
|
||||
}
|
||||
|
||||
elem.setAttribute('ztabindex', index);
|
||||
rowsElement.insertBefore(row, labels[i].parentNode);
|
||||
newTabIndex = index;
|
||||
rowsElement.insertBefore(row, editable.parentNode);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
if (!inserted) {
|
||||
newTabIndex = i + 1;
|
||||
elem.setAttribute('ztabindex', newTabIndex);
|
||||
rowsElement.appendChild(row);
|
||||
}
|
||||
|
||||
this.updateCount(this.count + 1);
|
||||
|
||||
return newTabIndex;
|
||||
return elem;
|
||||
}
|
||||
|
||||
remove(tagName) {
|
||||
var rowsElement = this._id('rows');
|
||||
var rows = rowsElement.childNodes;
|
||||
var oldTabIndex = -1;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
let value = rows[i].getAttribute('tagName');
|
||||
if (value === tagName) {
|
||||
oldTabIndex = this.removeRow(rows[i]);
|
||||
this.removeRow(rows[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return oldTabIndex;
|
||||
}
|
||||
|
||||
// Remove the row and update tab indexes
|
||||
removeRow(row) {
|
||||
var origTabIndex = row.getElementsByAttribute('fieldname', 'tag')[0].getAttribute('ztabindex');
|
||||
var origRow = row;
|
||||
var i = origTabIndex;
|
||||
while (row = row.nextSibling) {
|
||||
let elem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
||||
elem.setAttribute('ztabindex', i++);
|
||||
}
|
||||
origRow.parentNode.removeChild(origRow);
|
||||
this.updateCount(this.count - 1);
|
||||
return origTabIndex;
|
||||
}
|
||||
|
||||
removeAll = () => {
|
||||
|
@ -854,7 +642,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
this._id('count').replaceChildren(Zotero.getString('pane.item.tags.count', count, count));
|
||||
this._section.setCount(count);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
|
@ -864,73 +652,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Open the textbox for a particular label
|
||||
//
|
||||
// Note: We're basically replicating the built-in tabindex functionality,
|
||||
// which doesn't work well with the weird label/textbox stuff we're doing.
|
||||
// (The textbox being tabbed away from is deleted before the blur()
|
||||
// completes, so it doesn't know where it's supposed to go next.)
|
||||
_focusField() {
|
||||
if (this._reloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._lastTabIndex === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var maxIndex = this._id('rows').childNodes.length + 1;
|
||||
|
||||
var tabindex = parseInt(this._lastTabIndex);
|
||||
var dir = this._tabDirection;
|
||||
|
||||
if (dir == 1) {
|
||||
var nextIndex = tabindex + 1;
|
||||
}
|
||||
else if (dir == -1) {
|
||||
if (tabindex == 1) {
|
||||
// Focus Add button
|
||||
this._id("tags-box-add-button").focus();
|
||||
return false;
|
||||
}
|
||||
var nextIndex = tabindex - 1;
|
||||
}
|
||||
else {
|
||||
var nextIndex = tabindex;
|
||||
}
|
||||
|
||||
nextIndex = Math.min(nextIndex, maxIndex);
|
||||
|
||||
Zotero.debug('Looking for tabindex ' + nextIndex, 4);
|
||||
|
||||
var next = this.querySelector(`[ztabindex="${nextIndex}"]`);
|
||||
if (next.length) {
|
||||
next = next[0];
|
||||
next.click();
|
||||
}
|
||||
else {
|
||||
next = this.newTag();
|
||||
next = next.firstChild.nextSibling;
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
Components.utils.reportError('Next row not found');
|
||||
return;
|
||||
}
|
||||
|
||||
next.scrollIntoView();
|
||||
}
|
||||
|
||||
_handleAddButtonKeyDown = (event) => {
|
||||
if (event.keyCode != event.DOM_VK_TAB || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
this._lastTabIndex = 0;
|
||||
this._tabDirection = 1;
|
||||
this._focusField();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
_handleAddButtonClick = async (event) => {
|
||||
await this.blurOpenField();
|
||||
this.newTag();
|
||||
|
@ -941,15 +662,13 @@
|
|||
}
|
||||
|
||||
async blurOpenField(stayOpen) {
|
||||
this._lastTabIndex = false;
|
||||
var textboxe = this.querySelector('.editable');
|
||||
if (textboxe) {
|
||||
await this.blurHandler({
|
||||
target: textboxe,
|
||||
// If coming from the Add button, pretend user pressed return
|
||||
type: stayOpen ? 'keypress' : 'blur',
|
||||
// DOM_VK_RETURN
|
||||
keyCode: stayOpen ? 13 : undefined
|
||||
var editable = this.querySelector('editable-text:focus-within');
|
||||
if (editable) {
|
||||
await this.saveTag({
|
||||
currentTarget: editable,
|
||||
// If coming from the Add button, pretend user pressed Enter
|
||||
type: stayOpen ? 'keypress' : 'change',
|
||||
key: stayOpen ? 'Enter' : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,8 +239,8 @@
|
|||
<menupopup/>
|
||||
</menulist>
|
||||
<zoterosearchagefield id="value-date-age" class="value-date-age" hidden="true"/>
|
||||
<label id="remove" class="zotero-clicky zotero-clicky-minus" value="-" onclick="this.closest('zoterosearchcondition').onRemoveClicked(event)"/>
|
||||
<label id="add" class="zotero-clicky zotero-clicky-plus" value="+" onclick="this.closest('zoterosearchcondition').onAddClicked(event)"/>
|
||||
<toolbarbutton id="remove" class="zotero-clicky zotero-clicky-minus" value="-" onclick="this.closest('zoterosearchcondition').onRemoveClicked(event)"/>
|
||||
<toolbarbutton id="add" class="zotero-clicky zotero-clicky-plus" value="+" onclick="this.closest('zoterosearchcondition').onAddClicked(event)"/>
|
||||
</html:div>
|
||||
`, ['chrome://zotero/locale/zotero.dtd', 'chrome://zotero/locale/searchbox.dtd']);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
var ZoteroItemPane = new function() {
|
||||
var _container;
|
||||
var _header, _sidenav, _scrollParent, _itemBox, _abstractBox, _tagsBox, _notesBox, _relatedBox, _boxes;
|
||||
var _header, _sidenav, _scrollParent, _itemBox, _abstractBox, _attachmentsBox, _tagsBox, _notesBox, _relatedBox, _boxes;
|
||||
var _deck;
|
||||
var _lastItem;
|
||||
var _selectedNoteID;
|
||||
|
@ -43,9 +43,10 @@ var ZoteroItemPane = new function() {
|
|||
_itemBox = document.getElementById('zotero-editpane-item-box');
|
||||
_abstractBox = document.getElementById('zotero-editpane-abstract');
|
||||
_notesBox = document.getElementById('zotero-editpane-notes');
|
||||
_attachmentsBox = document.getElementById('zotero-editpane-attachments');
|
||||
_tagsBox = document.getElementById('zotero-editpane-tags');
|
||||
_relatedBox = document.getElementById('zotero-editpane-related');
|
||||
_boxes = [_itemBox, _abstractBox, _notesBox, _tagsBox, _relatedBox];
|
||||
_boxes = [_itemBox, _abstractBox, _notesBox, _attachmentsBox, _tagsBox, _relatedBox];
|
||||
|
||||
_deck = document.getElementById('zotero-item-pane-content');
|
||||
|
||||
|
@ -90,6 +91,7 @@ var ZoteroItemPane = new function() {
|
|||
this.setTranslateButton();
|
||||
}
|
||||
|
||||
let inTrash = ZoteroPane.collectionsView.selectedTreeRow && ZoteroPane.collectionsView.selectedTreeRow.isTrash();
|
||||
for (let box of [_header, ..._boxes]) {
|
||||
if (mode) {
|
||||
box.mode = mode;
|
||||
|
@ -103,6 +105,7 @@ var ZoteroItemPane = new function() {
|
|||
}
|
||||
|
||||
box.item = item;
|
||||
box.inTrash = inTrash;
|
||||
}
|
||||
|
||||
_sidenav.selectedPane = pane;
|
||||
|
@ -112,7 +115,7 @@ var ZoteroItemPane = new function() {
|
|||
|
||||
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) {
|
||||
if (action == 'refresh' && _lastItem) {
|
||||
yield this.viewItem(_lastItem, null, 0);
|
||||
yield this.viewItem(_lastItem, null, _sidenav.selectedPane);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -724,38 +724,11 @@ var Zotero_Tabs = new function () {
|
|||
document.getElementById("attachment-note-editor").focus();
|
||||
return;
|
||||
}
|
||||
// Focusing on the last field in whichever tab is opened for
|
||||
// regular items
|
||||
const tabBox = document.getElementById("zotero-view-tabbox");
|
||||
if (tabBox.selectedIndex === 0) {
|
||||
const itembox = document.getElementById("zotero-editpane-item-box");
|
||||
itembox.focusLastField();
|
||||
}
|
||||
else if (tabBox.selectedIndex === 1) {
|
||||
const notes = document.getElementById("zotero-editpane-notes");
|
||||
const nodes = notes.querySelectorAll("button");
|
||||
const node = nodes[nodes.length - 1];
|
||||
node.focus();
|
||||
// TODO: the notes are currently inaccessible to the keyboard
|
||||
}
|
||||
else if (tabBox.selectedIndex === 2) {
|
||||
const tagContainer = document.getElementById("tags-box-container");
|
||||
const tags = tagContainer.querySelectorAll("#tags-box-add-button,.zotero-clicky");
|
||||
const last = tags[tags.length - 1];
|
||||
if (last.id === "tags-box-add-button") {
|
||||
last.focus();
|
||||
}
|
||||
else {
|
||||
last.click();
|
||||
}
|
||||
}
|
||||
else if (tabBox.selectedIndex === 3) {
|
||||
const related = tabBox.querySelector("relatedbox");
|
||||
related.receiveKeyboardFocus("end");
|
||||
}
|
||||
else {
|
||||
throw new Error("The selectedIndex should always be between 1 and 4");
|
||||
}
|
||||
// For regular items, focus the last field
|
||||
// We do that by moving focus backwards from the element following the pane, because Services.focus doesn't
|
||||
// support MOVEFOCUS_LAST on subtrees
|
||||
Services.focus.moveFocus(window, document.getElementById('zotero-context-splitter'),
|
||||
Services.focus.MOVEFOCUS_BACKWARD, 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1115,29 +1115,17 @@
|
|||
<pane-header id="zotero-item-pane-header" />
|
||||
|
||||
<html:div id="zotero-view-item" class="zotero-view-item">
|
||||
<collapsible-section data-l10n-id="section-info" data-pane="info">
|
||||
<item-box id="zotero-editpane-item-box"/>
|
||||
</collapsible-section>
|
||||
<item-box id="zotero-editpane-item-box" data-pane="info"/>
|
||||
|
||||
<collapsible-section data-l10n-id="section-abstract" data-pane="abstract">
|
||||
<abstract-box id="zotero-editpane-abstract" class="zotero-editpane-abstract"/>
|
||||
</collapsible-section>
|
||||
<abstract-box id="zotero-editpane-abstract" class="zotero-editpane-abstract" data-pane="abstract"/>
|
||||
|
||||
<collapsible-section data-l10n-id="section-attachments" data-pane="attachments" show-add="true">
|
||||
<html:span>[placeholder for attachments section]</html:span>
|
||||
</collapsible-section>
|
||||
<attachments-box id="zotero-editpane-attachments" data-pane="attachments"/>
|
||||
|
||||
<collapsible-section data-l10n-id="section-notes" data-pane="notes" show-add="true">
|
||||
<notes-box id="zotero-editpane-notes" class="zotero-editpane-notes"/>
|
||||
</collapsible-section>
|
||||
<notes-box id="zotero-editpane-notes" class="zotero-editpane-notes" data-pane="notes"/>
|
||||
|
||||
<collapsible-section data-l10n-id="section-tags" data-pane="tags" show-add="true">
|
||||
<tags-box id="zotero-editpane-tags" class="zotero-editpane-tags"/>
|
||||
</collapsible-section>
|
||||
<tags-box id="zotero-editpane-tags" class="zotero-editpane-tags" data-pane="tags"/>
|
||||
|
||||
<collapsible-section data-l10n-id="section-related" data-pane="related" show-add="true">
|
||||
<related-box id="zotero-editpane-related" class="zotero-editpane-related"/>
|
||||
</collapsible-section>
|
||||
<related-box id="zotero-editpane-related" class="zotero-editpane-related" data-pane="related"/>
|
||||
</html:div>
|
||||
</html:div>
|
||||
|
||||
|
|
|
@ -231,13 +231,22 @@ section-info =
|
|||
section-abstract =
|
||||
.label = { pane-abstract }
|
||||
section-attachments =
|
||||
.label = { pane-attachments }
|
||||
.label = { $count ->
|
||||
[one] { $count } Attachment
|
||||
*[other] { $count } Attachments
|
||||
}
|
||||
section-notes =
|
||||
.label = { pane-notes }
|
||||
.label = { $count ->
|
||||
[one] { $count } Note
|
||||
*[other] { $count } Notes
|
||||
}
|
||||
section-tags =
|
||||
.label = { pane-tags }
|
||||
.label = { $count ->
|
||||
[one] { $count } Tag
|
||||
*[other] { $count } Tags
|
||||
}
|
||||
section-related =
|
||||
.label = { pane-related }
|
||||
.label = { $count } Related
|
||||
|
||||
sidenav-info =
|
||||
.tooltiptext = { pane-info }
|
||||
|
|
|
@ -418,7 +418,6 @@ pane.item.creator.moveDown = Move Down
|
|||
pane.item.notes.allNotes = All Notes
|
||||
pane.item.notes.untitled = Untitled Note
|
||||
pane.item.notes.delete.confirm = Are you sure you want to delete this note?
|
||||
pane.item.notes.count = %1$S note;%1$S notes
|
||||
pane.item.notes.ignoreMissingImage = Some note images are missing and cannot be copied.
|
||||
pane.item.attachments.rename.title = New title:
|
||||
pane.item.attachments.rename.renameAssociatedFile = Rename associated file
|
||||
|
@ -440,9 +439,6 @@ pane.item.attachments.autoRelinkOthers.title = Additional Files Located
|
|||
pane.item.attachments.autoRelinkOthers.text = One other unlinked attachment in this library was found within the same directory. Relink this attachment as well?;%S other unlinked attachments in this library were found within the same directory. Relink all located attachments?
|
||||
pane.item.attachments.autoRelink.locateManually = Locate Manually…
|
||||
pane.item.attachments.autoRelink.relinkAll = Relink All
|
||||
pane.item.attachments.count.zero = %S attachments:
|
||||
pane.item.attachments.count.singular = %S attachment:
|
||||
pane.item.attachments.count.plural = %S attachments:
|
||||
pane.item.attachments.has = Has attachment
|
||||
pane.item.attachments.hasPDF = Has PDF attachment
|
||||
pane.item.attachments.hasSnapshot = Has snapshot
|
||||
|
@ -453,13 +449,9 @@ pane.item.attachments.PDF.installTools.title = PDF Tools Not Installed
|
|||
pane.item.attachments.PDF.installTools.text = To use this feature, you must first install the PDF tools in the Search pane of the Zotero preferences.
|
||||
pane.item.attachments.filename = Filename
|
||||
pane.item.noteEditor.clickHere = click here
|
||||
pane.item.tags.count = %1$S tag;%1$S tags
|
||||
pane.item.tags.icon.user = User-added tag
|
||||
pane.item.tags.icon.automatic = Automatically added tag
|
||||
pane.item.tags.icon.user = User-added tag
|
||||
pane.item.tags.icon.automatic = Automatically added tag
|
||||
pane.item.tags.removeAll = Remove all tags from this item?
|
||||
pane.item.related.count.zero = %S related:
|
||||
pane.item.related.count.singular = %S related:
|
||||
pane.item.related.count.plural = %S related:
|
||||
pane.item.parentItem = Parent Item:
|
||||
pane.item.viewOnline.tooltip = Go to this item online
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 14.5H14.5V1.5H1.5V8.5M7.5 14.5L1.5 8.5M7.5 14.5V8.5H1.5" stroke="black"/>
|
||||
<path d="M7.5 14.5H14.5V1.5H1.5V8.5M7.5 14.5L1.5 8.5M7.5 14.5V8.5H1.5" stroke="context-fill"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 239 B |
3
chrome/skin/default/zotero/8/universal/chevron-8.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="none">
|
||||
<path d="M0 2.70711L4 6.70711L8 2.70711L7.29289 2L4 5.29289L0.707107 2L0 2.70711Z" fill="context-fill"/>
|
||||
</svg>
|
After Width: | Height: | Size: 205 B |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon">
|
||||
<path id="Vector" d="M2 5.70711L8 11.7071L14 5.70711L13.2929 5L8 10.2929L2.70711 5L2 5.70711Z" fill="context-fill"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 239 B |
|
@ -27,9 +27,10 @@
|
|||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 8px;
|
||||
overflow-anchor: none; /* Work around tags box causing scroll to jump - figure this out */
|
||||
}
|
||||
|
||||
.zotero-view-item-container.feed-item .zotero-view-item > :is(notes-box, tags-box, related-box),
|
||||
.zotero-view-item-container.feed-item .zotero-view-item > :is(attachments-box, notes-box, tags-box, related-box),
|
||||
.zotero-view-item-container.feed-item .zotero-view-item-sidenav {
|
||||
display: none;
|
||||
}
|
||||
|
@ -37,7 +38,6 @@
|
|||
.zotero-view-item > * {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding-block: 4px;
|
||||
}
|
||||
|
||||
.zotero-view-item > :last-child {
|
||||
|
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.9 KiB |
|
@ -502,10 +502,6 @@
|
|||
height: 16px;
|
||||
}
|
||||
|
||||
.zotero-box-label {
|
||||
margin-inline-start: 3px !important;
|
||||
}
|
||||
|
||||
#retracted-items-banner, #sync-reminder-banner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="plus">
|
||||
<path id="Vector" d="M14 8H9V3H8V8H3V9H8V14H9V9H14V8Z" fill="context-fill"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 832 B |
10
chrome/skin/default/zotero/tag-fill.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="tag-fill" clip-path="url(#clip0_3570_12428)">
|
||||
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M0 0.75C0 0.335786 0.335787 0 0.75 0H5.20711L11.6768 6.46967C11.9697 6.76256 11.9697 7.23744 11.6768 7.53033L7.53033 11.6768C7.23744 11.9697 6.76256 11.9697 6.46967 11.6768L0 5.20711V0.75ZM3 4C3.55228 4 4 3.55228 4 3C4 2.44772 3.55228 2 3 2C2.44772 2 2 2.44772 2 3C2 3.55228 2.44772 4 3 4Z" fill="context-fill"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3570_12428">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 586 B |
10
chrome/skin/default/zotero/tag.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="tag" clip-path="url(#clip0_3570_22211)">
|
||||
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M1 4.79289V1H4.79289L10.7929 7L7 10.7929L1 4.79289ZM0.75 0C0.335787 0 0 0.335786 0 0.75V5V5.20711L0.146447 5.35355L6.46967 11.6768C6.76256 11.9697 7.23744 11.9697 7.53033 11.6768L11.6768 7.53033C11.9697 7.23744 11.9697 6.76256 11.6768 6.46967L5.35355 0.146447L5.20711 0H5H0.75ZM3 4C3.55228 4 4 3.55228 4 3C4 2.44772 3.55228 2 3 2C2.44772 2 2 2.44772 2 3C2 3.55228 2.44772 4 3 4Z" fill="context-fill"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3570_22211">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 723 B |
Before Width: | Height: | Size: 821 B |
|
@ -69,3 +69,6 @@
|
|||
@import "elements/itemPaneSidenav";
|
||||
@import "elements/abstractBox";
|
||||
@import "elements/collapsibleSection";
|
||||
@import "elements/attachmentsBox";
|
||||
@import "elements/attachmentRow";
|
||||
@import "elements/annotationRow";
|
||||
|
|
|
@ -79,3 +79,32 @@
|
|||
@content("dark");
|
||||
}
|
||||
}
|
||||
|
||||
@mixin clicky-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
padding-inline-start: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
border-radius: 5px;
|
||||
background-color: var(--fill-quinary);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 10;
|
||||
width: 0; // Needed to allow the label to shrink for some reason
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@include comfortable {
|
||||
.icon, .label {
|
||||
padding-block: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,41 @@
|
|||
.zotero-clicky {
|
||||
border-radius: 6px;
|
||||
border: var(--material-border-transparent);
|
||||
padding: 2px;
|
||||
|
||||
&:not([disabled=true]) {
|
||||
&:hover {
|
||||
background-color: var(--fill-quinary) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--fill-quarternary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Minus and plus buttons with clicky glow effect */
|
||||
.zotero-clicky-minus, .zotero-clicky-plus {
|
||||
color: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
margin-inline-end: 5px !important;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
color: var(--fill-secondary);
|
||||
|
||||
.toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.zotero-clicky-minus {
|
||||
background: url(chrome://zotero/skin/minus.png) center/auto 18px no-repeat !important;
|
||||
@include svgicon-menu("minus-circle", "universal", "16");
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
.zotero-clicky-plus {
|
||||
background: url(chrome://zotero/skin/plus.png) center/auto 18px no-repeat !important;
|
||||
@include svgicon-menu("plus-circle", "universal", "16");
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
.zotero-clicky-minus[disabled=true], .zotero-clicky-plus[disabled=true] {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.zotero-clicky-minus:not([disabled=true]):active {
|
||||
background-image: url('chrome://zotero/skin/minus-active.png') !important;
|
||||
}
|
||||
|
||||
.zotero-clicky-plus:not([disabled=true]):active {
|
||||
background-image: url('chrome://zotero/skin/plus-active.png') !important;
|
||||
}
|
||||
|
||||
.zotero-clicky:not([disabled=true]):not(.disabled):hover {
|
||||
background: rgb(187, 206, 241);
|
||||
border: 1px solid rgb(109, 149, 224);
|
||||
}
|
||||
|
||||
.zotero-clicky:not([disabled=true]):not(.disabled):not(menulist):active,
|
||||
.zotero-clicky[selected="true"],
|
||||
.zotero-clicky.selected {
|
||||
color: white;
|
||||
background: rgb(89, 139, 236);
|
||||
}
|
||||
|
||||
|
||||
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.zotero-clicky-minus { background: url(chrome://zotero/skin/minus@2x.png) center/auto 18px no-repeat !important; }
|
||||
.zotero-clicky-plus { background: url(chrome://zotero/skin/plus@2x.png) center/auto 18px no-repeat !important; }
|
||||
.zotero-clicky-minus:not([disabled=true]):active { background-image: url('chrome://zotero/skin/minus-active@2x.png') !important; }
|
||||
.zotero-clicky-plus:not([disabled=true]):active { background-image: url('chrome://zotero/skin/plus-active@2x.png') !important; }
|
||||
}
|
||||
|
|
|
@ -30,9 +30,7 @@
|
|||
.toolbarbutton-menu-dropmarker {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
background-image: url("chrome://zotero/skin/chevron-6.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@include svgicon("chevron-8", "universal", "8");
|
||||
fill: var(--fill-secondary);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
abstract-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
abstract-box .body {
|
||||
display: flex;
|
||||
|
||||
editable-text {
|
||||
flex: 1;
|
||||
|
|
64
scss/elements/_annotationRow.scss
Normal file
|
@ -0,0 +1,64 @@
|
|||
annotation-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--fill-quinary);
|
||||
background: var(--material-background);
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--annotation-color, var(--fill-secondary));
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 590;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.quote, .comment {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 9;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quote {
|
||||
margin: 3px 8px;
|
||||
padding-inline-start: 6px;
|
||||
border-inline-start: 2px solid var(--annotation-color, var(--fill-secondary));
|
||||
color: var(--fill-secondary);
|
||||
|
||||
@include comfortable {
|
||||
margin-block: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
padding: 3px 8px;
|
||||
|
||||
@include comfortable {
|
||||
padding-block: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
border-top: 1px solid var(--fill-quinary);
|
||||
padding: 3px 8px;
|
||||
}
|
||||
}
|
66
scss/elements/_attachmentRow.scss
Normal file
|
@ -0,0 +1,66 @@
|
|||
attachment-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
& > .head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.twisty {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: 4px;
|
||||
align-self: flex-start;
|
||||
|
||||
@include comfortable {
|
||||
padding-block: 2px;
|
||||
}
|
||||
|
||||
@include svgicon("chevron-8", "universal", "8");
|
||||
fill: var(--fill-secondary);
|
||||
|
||||
transform: rotate(0deg);
|
||||
transform-origin: center;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.clicky-item {
|
||||
@include clicky-item;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&[open]:not([empty]) > .head .twisty {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
&[empty] > .head .twisty {
|
||||
fill: var(--fill-tertiary);
|
||||
}
|
||||
|
||||
&.context > .head .title {
|
||||
// TODO This color is used in virtualized-table - probably want to change to something theme-defined
|
||||
color: gray;
|
||||
}
|
||||
|
||||
& > .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
max-height: var(--open-height, auto);
|
||||
opacity: 1;
|
||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:not([open]) {
|
||||
& > .body {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
overflow-y: hidden;
|
||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out, visibility 0s 0.2s, overflow-y 0s 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
10
scss/elements/_attachmentsBox.scss
Normal file
|
@ -0,0 +1,10 @@
|
|||
attachments-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > collapsible-section > .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,11 @@ collapsible-section {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
padding-block: 4px;
|
||||
|
||||
:not(:last-child) > & {
|
||||
border-bottom: 1px solid var(--fill-quinary);
|
||||
}
|
||||
|
||||
& > .head {
|
||||
@include comfortable {
|
||||
|
@ -33,29 +37,40 @@ collapsible-section {
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 2px;
|
||||
-moz-context-properties: fill, fill-opacity, stroke, stroke-opacity;
|
||||
fill: var(--fill-secondary);
|
||||
stroke: var(--fill-secondary);
|
||||
color: var(--fill-secondary);
|
||||
}
|
||||
|
||||
toolbarbutton.add {
|
||||
list-style-image: icon-url("plus.svg");
|
||||
@include svgicon-menu("plus", "universal", "16");
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: var(--fill-quinary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--fill-quarternary);
|
||||
}
|
||||
}
|
||||
|
||||
toolbarbutton.twisty .toolbarbutton-icon {
|
||||
list-style-image: icon-url("chevron-12.svg");
|
||||
@include svgicon-menu("chevron-12", "universal", "16");
|
||||
transform: rotate(0deg);
|
||||
transform-origin: center;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] > .head {
|
||||
&[open]:not([empty]) > .head {
|
||||
toolbarbutton.twisty .toolbarbutton-icon {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&[empty] > .head > toolbarbutton.twisty {
|
||||
fill: var(--fill-tertiary);
|
||||
}
|
||||
|
||||
@each $pane, $color in $item-pane-sections {
|
||||
&[data-pane="#{$pane}"] {
|
||||
& > .head > .title::before {
|
||||
|
@ -68,7 +83,6 @@ collapsible-section {
|
|||
}
|
||||
|
||||
& > :not(.head) {
|
||||
overflow: hidden;
|
||||
max-height: var(--open-height, auto);
|
||||
opacity: 1;
|
||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
|
@ -79,7 +93,8 @@ collapsible-section {
|
|||
max-height: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out, visibility 0s 0.2s;
|
||||
overflow-y: hidden;
|
||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out, visibility 0s 0.2s, overflow-y 0s 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ editable-text {
|
|||
display: grid;
|
||||
|
||||
&::after {
|
||||
content: attr(data-value) ' ';
|
||||
content: attr(value) ' ';
|
||||
visibility: hidden;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&::after, textarea {
|
||||
&::after, .input {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
|
||||
font: inherit;
|
||||
|
@ -30,7 +30,10 @@ editable-text {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
textarea {
|
||||
.input {
|
||||
// Necessary for consistent padding, even if it's actually an <input>
|
||||
-moz-default-appearance: textarea;
|
||||
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
|
||||
|
@ -55,11 +58,11 @@ editable-text {
|
|||
}
|
||||
|
||||
&[multiline] {
|
||||
&::after, textarea {
|
||||
&::after, .input {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
.input {
|
||||
min-height: 5em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
item-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
item-box .body {
|
||||
--row-height: 1.5em;
|
||||
|
||||
display: flex;
|
||||
|
|
|
@ -1,49 +1,35 @@
|
|||
notes-box, related-box {
|
||||
.header {
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
notes-box .body, related-box .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-inline-start: 16px - 2px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
padding-left: 10px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
|
||||
@include comfortable {
|
||||
padding-block: 2px;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 79px;
|
||||
margin: 5px 6px 3px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
color: ButtonText;
|
||||
text-shadow: none;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
.box {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
@include clicky-item;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
toolbarbutton {
|
||||
margin-inline-start: auto;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-left: 3px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&:is(:hover, :focus-within) toolbarbutton {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,75 @@
|
|||
tags-box {
|
||||
.tags-box-header {
|
||||
display: flex;
|
||||
padding-left: 10px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 79px;
|
||||
margin: 5px 6px 3px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
color: ButtonText;
|
||||
text-shadow: none;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
tags-box .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding-inline-start: 16px;
|
||||
|
||||
.tags-box-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 2px 0 0; // Leave space for textbox border on top tag
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
margin: 3px 0;
|
||||
margin-inline-start: 6px;
|
||||
align-items: center;
|
||||
height: 1.5em;
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 12px 1fr 20px;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
padding-block: 1px;
|
||||
|
||||
// Shift-Enter
|
||||
&.multiline {
|
||||
align-items: start;
|
||||
height: 9em;
|
||||
// Shift-Enter
|
||||
&.multiline {
|
||||
align-items: start;
|
||||
min-height: 9em;
|
||||
|
||||
textarea.editable {
|
||||
resize: none;
|
||||
}
|
||||
textarea.editable {
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.multiline) .editable {
|
||||
padding: 0 1px;
|
||||
}
|
||||
.zotero-box-icon {
|
||||
grid-column: 1;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
-moz-context-properties: fill;
|
||||
background: icon-url('tag.svg') center no-repeat;
|
||||
}
|
||||
|
||||
&[tagType="0"] .zotero-box-icon {
|
||||
// User tag: use tag color if we have one, blue accent if we don't
|
||||
fill: var(--tag-color, var(--accent-blue));
|
||||
}
|
||||
|
||||
&[tagType="1"] .zotero-box-icon {
|
||||
// Automatic tag: use tag color if we have one, gray if we don't
|
||||
fill: var(--tag-color, var(--fill-secondary));
|
||||
}
|
||||
|
||||
.zotero-box-label {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
&.has-color {
|
||||
.zotero-box-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: icon-url('tag-fill.svg');
|
||||
}
|
||||
|
||||
.zotero-box-label {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 0;
|
||||
font-weight: 590;
|
||||
}
|
||||
}
|
||||
|
||||
.editable {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
flex-grow: 1;
|
||||
margin: 0 2px;
|
||||
width: 0;
|
||||
}
|
||||
toolbarbutton {
|
||||
margin-inline-start: auto;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
}
|
||||
&:is(:hover, :focus-within) toolbarbutton {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.tags-box-list li img {
|
||||
margin-right: 1px;
|
||||
}
|