Notes/Tags/Related redesign
|
@ -938,45 +938,30 @@ var ZoteroContextPane = new function () {
|
||||||
div.className = 'zotero-view-item';
|
div.className = 'zotero-view-item';
|
||||||
main.append(div);
|
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
|
// Info
|
||||||
var itemBoxContainer = createSection('info');
|
|
||||||
var itemBox = new (customElements.get('item-box'));
|
var itemBox = new (customElements.get('item-box'));
|
||||||
itemBoxContainer.append(itemBox);
|
itemBox.setAttribute('data-pane', 'info');
|
||||||
|
div.append(itemBox);
|
||||||
|
|
||||||
// Abstract
|
// Abstract
|
||||||
var abstractBoxContainer = createSection('abstract');
|
|
||||||
var abstractBox = new (customElements.get('abstract-box'));
|
var abstractBox = new (customElements.get('abstract-box'));
|
||||||
abstractBox.className = 'zotero-editpane-abstract';
|
abstractBox.className = 'zotero-editpane-abstract';
|
||||||
abstractBoxContainer.append(abstractBox);
|
abstractBox.setAttribute('data-pane', 'abstract');
|
||||||
|
div.append(abstractBox);
|
||||||
|
|
||||||
// TODO: Attachments
|
// TODO: Attachments
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
var tagsBoxContainer = createSection('tags', true);
|
|
||||||
var tagsBox = new (customElements.get('tags-box'));
|
var tagsBox = new (customElements.get('tags-box'));
|
||||||
tagsBox.className = 'zotero-editpane-tags';
|
tagsBox.className = 'zotero-editpane-tags';
|
||||||
tagsBoxContainer.append(tagsBox);
|
tagsBox.setAttribute('data-pane', 'tags');
|
||||||
|
div.append(tagsBox);
|
||||||
|
|
||||||
// Related
|
// Related
|
||||||
var relatedBoxContainer = createSection('related', true);
|
|
||||||
var relatedBox = new (customElements.get('related-box'));
|
var relatedBox = new (customElements.get('related-box'));
|
||||||
relatedBox.className = 'zotero-editpane-related';
|
relatedBox.className = 'zotero-editpane-related';
|
||||||
relatedBox.addEventListener('click', (event) => {
|
relatedBox.setAttribute('data-pane', 'related');
|
||||||
if (event.originalTarget.closest('.zotero-clicky')) {
|
div.append(relatedBox);
|
||||||
Zotero_Tabs.select('zotero-pane');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
relatedBoxContainer.append(relatedBox);
|
|
||||||
|
|
||||||
div.append(itemBoxContainer, abstractBoxContainer, tagsBoxContainer, relatedBoxContainer);
|
|
||||||
|
|
||||||
// item-pane-sidenav
|
// item-pane-sidenav
|
||||||
var sidenav = document.createXULElement('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/itemPaneSidenav.js', this);
|
||||||
Services.scriptloader.loadSubScript('chrome://zotero/content/elements/abstractBox.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/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
|
// Fix missing property bug that breaks arrow key navigation between <tab>s
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,7 +28,11 @@
|
||||||
{
|
{
|
||||||
class AbstractBox extends XULElementBase {
|
class AbstractBox extends XULElementBase {
|
||||||
content = MozXULElement.parseXULToFragment(`
|
content = MozXULElement.parseXULToFragment(`
|
||||||
|
<collapsible-section data-l10n-id="section-abstract" data-pane="abstract">
|
||||||
|
<html:div class="body">
|
||||||
<editable-text multiline="true" data-l10n-id="abstract-field" />
|
<editable-text multiline="true" data-l10n-id="abstract-field" />
|
||||||
|
</html:div>
|
||||||
|
</collapsible-section>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
_item = null;
|
_item = null;
|
||||||
|
@ -83,18 +87,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async blurOpenField() {
|
async blurOpenField() {
|
||||||
|
if (this.abstractField?.matches(':focus-within')) {
|
||||||
this.abstractField.blur();
|
this.abstractField.blur();
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.item) {
|
if (!this.item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = this.item.getField('abstractNote');
|
let abstract = this.item.getField('abstractNote');
|
||||||
if (this.abstractField.initialValue !== title) {
|
if (!this.abstractField.initialValue || this.abstractField.initialValue !== abstract) {
|
||||||
this.abstractField.value = title;
|
this.abstractField.value = abstract;
|
||||||
|
this.abstractField.initialValue = '';
|
||||||
}
|
}
|
||||||
this.abstractField.readOnly = this._mode == 'view';
|
this.abstractField.readOnly = this._mode == 'view';
|
||||||
this.abstractField.setAttribute('aria-label', Zotero.ItemFields.getLocalizedString('abstractNote'));
|
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);
|
document.l10n.connectRoot(this.shadowRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.init();
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
|
|
|
@ -36,13 +36,16 @@
|
||||||
_listenerAdded = false;
|
_listenerAdded = false;
|
||||||
|
|
||||||
get open() {
|
get open() {
|
||||||
|
if (this.empty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return this.hasAttribute('open');
|
return this.hasAttribute('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
set open(val) {
|
set open(val) {
|
||||||
val = !!val;
|
val = !!val;
|
||||||
let open = this.open;
|
let open = this.open;
|
||||||
if (open === val) return;
|
if (open === val || this.empty) return;
|
||||||
this.render();
|
this.render();
|
||||||
let openHeight = this._head?.nextSibling?.scrollHeight;
|
let openHeight = this._head?.nextSibling?.scrollHeight;
|
||||||
if (openHeight) {
|
if (openHeight) {
|
||||||
|
@ -58,11 +61,28 @@
|
||||||
if (!this.dispatchEvent(new CustomEvent('toggle', { bubbles: false, cancelable: true }))) {
|
if (!this.dispatchEvent(new CustomEvent('toggle', { bubbles: false, cancelable: true }))) {
|
||||||
// Revert
|
// Revert
|
||||||
this.toggleAttribute('open', open);
|
this.toggleAttribute('open', open);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!val && this.ownerDocument?.activeElement && this.contains(this.ownerDocument?.activeElement)) {
|
||||||
|
this.ownerDocument.activeElement.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._saveOpenState();
|
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() {
|
get label() {
|
||||||
return this.getAttribute('label');
|
return this.getAttribute('label');
|
||||||
}
|
}
|
||||||
|
@ -80,7 +100,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['open', 'label', 'show-add'];
|
return ['open', 'empty', 'label', 'show-add'];
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
|
@ -92,7 +112,6 @@
|
||||||
throw new Error('data-pane is required');
|
throw new Error('data-pane is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._restoreOpenState();
|
|
||||||
this.tabIndex = 0;
|
this.tabIndex = 0;
|
||||||
|
|
||||||
this._head = document.createElement('div');
|
this._head = document.createElement('div');
|
||||||
|
@ -108,8 +127,7 @@
|
||||||
this._addButton = document.createXULElement('toolbarbutton');
|
this._addButton = document.createXULElement('toolbarbutton');
|
||||||
this._addButton.className = 'add';
|
this._addButton.className = 'add';
|
||||||
this._addButton.addEventListener('command', (event) => {
|
this._addButton.addEventListener('command', (event) => {
|
||||||
// TODO: Is this the best approach?
|
this.dispatchEvent(new CustomEvent('add', { ...event, bubbles: false }));
|
||||||
this._head.nextSibling?.dispatchEvent(new CustomEvent('add', { ...event, bubbles: true }));
|
|
||||||
});
|
});
|
||||||
this._head.append(this._addButton);
|
this._head.append(this._addButton);
|
||||||
|
|
||||||
|
@ -118,9 +136,14 @@
|
||||||
this._head.append(twisty);
|
this._head.append(twisty);
|
||||||
|
|
||||||
this.prepend(this._head);
|
this.prepend(this._head);
|
||||||
|
this._restoreOpenState();
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
this._notifierID = Zotero.Prefs.registerObserver(`panes.${this.dataset.pane}.open`, this._restoreOpenState.bind(this));
|
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() {
|
destroy() {
|
||||||
|
|
|
@ -27,13 +27,9 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
class EditableText extends XULElementBase {
|
class EditableText extends XULElementBase {
|
||||||
content = MozXULElement.parseXULToFragment(`
|
_input;
|
||||||
<html:textarea rows="1" />
|
|
||||||
`);
|
|
||||||
|
|
||||||
_textarea;
|
static observedAttributes = ['multiline', 'readonly', 'placeholder', 'label', 'aria-label', 'value'];
|
||||||
|
|
||||||
static observedAttributes = ['multiline', 'readonly', 'label'];
|
|
||||||
|
|
||||||
get multiline() {
|
get multiline() {
|
||||||
return this.hasAttribute('multiline');
|
return this.hasAttribute('multiline');
|
||||||
|
@ -51,44 +47,73 @@
|
||||||
this.toggleAttribute('readonly', readOnly);
|
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() {
|
get placeholder() {
|
||||||
return this._textarea.placeholder;
|
return this.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
set placeholder(placeholder) {
|
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() {
|
get label() {
|
||||||
return this.getAttribute('label');
|
return this.getAttribute('label') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set label(label) {
|
set label(label) {
|
||||||
this.setAttribute('label', label);
|
this.setAttribute('label', label || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
get ariaLabel() {
|
get ariaLabel() {
|
||||||
return this._textarea.getAttribute('aria-label');
|
return this.getAttribute('aria-label') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set ariaLabel(ariaLabel) {
|
set ariaLabel(ariaLabel) {
|
||||||
this._textarea.setAttribute('aria-label', ariaLabel);
|
this.setAttribute('aria-label', ariaLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this._textarea.value;
|
return this.getAttribute('value') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(value) {
|
set value(value) {
|
||||||
this._textarea.value = value;
|
this.setAttribute('value', value || '');
|
||||||
this.dataset.value = value;
|
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get initialValue() {
|
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() {
|
attributeChangedCallback() {
|
||||||
|
@ -96,46 +121,99 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
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();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this._textarea) return;
|
let autocompleteParams = this.autocomplete;
|
||||||
this._textarea.readOnly = this.readOnly;
|
let autocompleteEnabled = !this.multiline && !!autocompleteParams;
|
||||||
this._textarea.placeholder = this.label;
|
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() {
|
focus(options) {
|
||||||
this._textarea.focus();
|
this._input?.focus(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
blur() {
|
blur() {
|
||||||
this._textarea.blur();
|
this._input?.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("editable-text", EditableText);
|
customElements.define("editable-text", EditableText);
|
||||||
|
|
|
@ -57,6 +57,8 @@
|
||||||
this._initialVisibleCreators = 5;
|
this._initialVisibleCreators = 5;
|
||||||
|
|
||||||
this.content = MozXULElement.parseXULToFragment(`
|
this.content = MozXULElement.parseXULToFragment(`
|
||||||
|
<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">
|
<div id="item-box" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<popupset xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<menupopup id="creator-type-menu" position="after_start"/>
|
<menupopup id="creator-type-menu" position="after_start"/>
|
||||||
|
@ -93,6 +95,8 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</html:div>
|
||||||
|
</collapsible-section>
|
||||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,8 +964,7 @@
|
||||||
td.appendChild(toggleButton);
|
td.appendChild(toggleButton);
|
||||||
|
|
||||||
// Minus (-) button
|
// Minus (-) button
|
||||||
var removeButton = document.createElement('button');
|
var removeButton = document.createXULElement('toolbarbutton');
|
||||||
removeButton.textContent = "-";
|
|
||||||
removeButton.setAttribute("class", "zotero-clicky zotero-clicky-minus zotero-focusable");
|
removeButton.setAttribute("class", "zotero-clicky zotero-clicky-minus zotero-focusable");
|
||||||
removeButton.setAttribute('ztabindex', tabindex + 4);
|
removeButton.setAttribute('ztabindex', tabindex + 4);
|
||||||
removeButton.setAttribute('aria-label', Zotero.getString('general.delete'));
|
removeButton.setAttribute('aria-label', Zotero.getString('general.delete'));
|
||||||
|
@ -977,8 +980,7 @@
|
||||||
td.appendChild(removeButton);
|
td.appendChild(removeButton);
|
||||||
|
|
||||||
// Plus (+) button
|
// Plus (+) button
|
||||||
var addButton = document.createElement('button');
|
var addButton = document.createXULElement('toolbarbutton');
|
||||||
addButton.textContent = "+";
|
|
||||||
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus zotero-focusable");
|
addButton.setAttribute("class", "zotero-clicky zotero-clicky-plus zotero-focusable");
|
||||||
addButton.setAttribute('ztabindex', tabindex + 5);
|
addButton.setAttribute('ztabindex', tabindex + 5);
|
||||||
// If row isn't saved, don't let user add more
|
// If row isn't saved, don't let user add more
|
||||||
|
|
|
@ -104,6 +104,11 @@
|
||||||
_updateSelectedPane() {
|
_updateSelectedPane() {
|
||||||
let topPane = null;
|
let topPane = null;
|
||||||
let containerBoundingRect = this.container.getBoundingClientRect();
|
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()) {
|
for (let box of this._getPanes()) {
|
||||||
// Allow a little padding to deal with floating-point imprecision
|
// Allow a little padding to deal with floating-point imprecision
|
||||||
if (box.getBoundingClientRect().top > containerBoundingRect.top + 5) {
|
if (box.getBoundingClientRect().top > containerBoundingRect.top + 5) {
|
||||||
|
|
|
@ -28,56 +28,32 @@
|
||||||
import { getCSSItemTypeIcon } from 'components/icons';
|
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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._mode = 'view';
|
this._mode = 'view';
|
||||||
this._item = null;
|
this._item = null;
|
||||||
this._destroyed = false;
|
|
||||||
this._noteIDs = [];
|
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() {
|
init() {
|
||||||
this._destroyed = false;
|
this._section = this.querySelector('collapsible-section');
|
||||||
window.addEventListener("unload", this.destroy);
|
this._section.addEventListener('add', this._handleAdd);
|
||||||
|
|
||||||
let content = document.importNode(this.content, true);
|
|
||||||
this.append(content);
|
|
||||||
|
|
||||||
this._id('notes-add').addEventListener('click', this._handleAdd);
|
|
||||||
this.addEventListener('add', this._handleAdd);
|
|
||||||
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'notesBox');
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'notesBox');
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this._destroyed) {
|
this._section = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.removeEventListener("unload", this.destroy);
|
|
||||||
this._destroyed = true;
|
|
||||||
|
|
||||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
this.replaceChildren();
|
|
||||||
this.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
get mode() {
|
get mode() {
|
||||||
return this._mode;
|
return this._mode;
|
||||||
}
|
}
|
||||||
|
@ -118,38 +94,45 @@ import { getCSSItemTypeIcon } from 'components/icons';
|
||||||
}
|
}
|
||||||
|
|
||||||
this._noteIDs = this._item.getNotes();
|
this._noteIDs = this._item.getNotes();
|
||||||
this._id('notes-add').hidden = this._mode != 'edit';
|
|
||||||
|
|
||||||
let grid = this._id('notes-grid');
|
let body = this.querySelector('.body');
|
||||||
grid.replaceChildren();
|
body.replaceChildren();
|
||||||
|
|
||||||
let notes = Zotero.Items.get(this._item.getNotes());
|
let notes = Zotero.Items.get(this._item.getNotes());
|
||||||
for (let item of notes) {
|
for (let item of notes) {
|
||||||
let id = item.id;
|
let id = item.id;
|
||||||
let icon = getCSSItemTypeIcon(item.getItemTypeIconName());
|
|
||||||
|
|
||||||
let label = document.createElement("label");
|
let row = document.createElement('div');
|
||||||
label.append(item.getDisplayTitle());
|
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');
|
let box = document.createElement('div');
|
||||||
box.addEventListener('click', () => this._handleShowItem(id));
|
box.addEventListener('click', () => this._handleShowItem(id));
|
||||||
box.className = 'box zotero-clicky';
|
box.className = 'box';
|
||||||
box.appendChild(icon);
|
box.append(icon, label);
|
||||||
box.appendChild(label);
|
|
||||||
|
|
||||||
grid.append(box);
|
row.append(box);
|
||||||
|
|
||||||
if (this._mode == 'edit') {
|
if (this._mode == 'edit') {
|
||||||
let remove = document.createElement("label");
|
let remove = document.createXULElement("toolbarbutton");
|
||||||
remove.addEventListener('click', () => this._handleRemove(id));
|
remove.addEventListener('command', () => this._handleRemove(id));
|
||||||
remove.className = 'zotero-clicky zotero-clicky-minus';
|
remove.className = 'zotero-clicky zotero-clicky-minus';
|
||||||
remove.append('-');
|
row.append(remove);
|
||||||
grid.append(remove);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let num = this._noteIDs.length;
|
body.append(row);
|
||||||
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) => {
|
_handleAdd = (event) => {
|
||||||
|
@ -170,6 +153,56 @@ import { getCSSItemTypeIcon } from 'components/icons';
|
||||||
_id(id) {
|
_id(id) {
|
||||||
return this.querySelector(`#${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);
|
customElements.define("notes-box", NotesBox);
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,9 +128,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async blurOpenField() {
|
async blurOpenField() {
|
||||||
|
if (this.titleField?.matches(':focus-within')) {
|
||||||
this.titleField.blur();
|
this.titleField.blur();
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.item) {
|
if (!this.item) {
|
||||||
|
@ -140,8 +142,9 @@
|
||||||
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
this._titleFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(this.item.itemTypeID, 'title');
|
||||||
|
|
||||||
let title = this.item.getField(this._titleFieldID);
|
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.value = title;
|
||||||
|
this.titleField.initialValue = '';
|
||||||
}
|
}
|
||||||
this.titleField.readOnly = this._mode == 'view';
|
this.titleField.readOnly = this._mode == 'view';
|
||||||
this.titleField.placeholder = Zotero.ItemFields.getLocalizedString(this._titleFieldID);
|
this.titleField.placeholder = Zotero.ItemFields.getLocalizedString(this._titleFieldID);
|
||||||
|
|
|
@ -25,54 +25,32 @@
|
||||||
|
|
||||||
"use strict";
|
"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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._mode = 'view';
|
this._mode = 'view';
|
||||||
this._item = null;
|
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() {
|
init() {
|
||||||
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);
|
|
||||||
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'relatedbox');
|
||||||
|
this._section = this.querySelector('collapsible-section');
|
||||||
|
this._section.addEventListener('add', this.add);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this._destroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.removeEventListener("unload", this.destroy);
|
|
||||||
this._destroyed = true;
|
|
||||||
|
|
||||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
}
|
this._section = null;
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
this.replaceChildren();
|
|
||||||
this.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get mode() {
|
get mode() {
|
||||||
|
@ -126,10 +104,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this._id('related-add').hidden = this._mode != 'edit';
|
let body = this.querySelector('.body');
|
||||||
|
body.replaceChildren();
|
||||||
let grid = this._id('related-grid');
|
|
||||||
grid.replaceChildren();
|
|
||||||
|
|
||||||
if (this._item) {
|
if (this._item) {
|
||||||
let relatedKeys = this._item.relatedItems;
|
let relatedKeys = this._item.relatedItems;
|
||||||
|
@ -144,33 +120,42 @@
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let id = relatedItem.id;
|
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());
|
label.append(relatedItem.getDisplayTitle());
|
||||||
|
|
||||||
let box = document.createElement('div');
|
let box = document.createElement('div');
|
||||||
box.addEventListener('click', () => this._handleShowItem(id));
|
box.addEventListener('click', () => this._handleShowItem(id));
|
||||||
box.className = 'box zotero-clicky';
|
box.className = 'box';
|
||||||
box.appendChild(icon);
|
box.appendChild(icon);
|
||||||
box.appendChild(label);
|
box.appendChild(label);
|
||||||
|
row.append(box);
|
||||||
grid.append(box);
|
|
||||||
|
|
||||||
if (this._mode == 'edit') {
|
if (this._mode == 'edit') {
|
||||||
let remove = document.createElement("label");
|
let remove = document.createXULElement("toolbarbutton");
|
||||||
remove.addEventListener('click', () => this._handleRemove(id));
|
remove.addEventListener('command', () => this._handleRemove(id));
|
||||||
remove.className = 'zotero-clicky zotero-clicky-minus';
|
remove.className = 'zotero-clicky zotero-clicky-minus';
|
||||||
remove.append('-');
|
row.append(remove);
|
||||||
grid.append(remove);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.append(row);
|
||||||
}
|
}
|
||||||
this._updateCount();
|
this._updateCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._section.showAdd = this._mode == 'edit';
|
||||||
}
|
}
|
||||||
|
|
||||||
add = async () => {
|
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' };
|
let io = { dataIn: null, dataOut: null, deferred: Zotero.Promise.defer(), itemTreeID: 'related-box-select-item-dialog' };
|
||||||
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '',
|
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '',
|
||||||
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
||||||
|
@ -233,19 +218,7 @@
|
||||||
|
|
||||||
_updateCount() {
|
_updateCount() {
|
||||||
let count = this._item.relatedItems.length;
|
let count = this._item.relatedItems.length;
|
||||||
let str = 'pane.item.related.count.';
|
this._section.setCount(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]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_id(id) {
|
_id(id) {
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
this.clickHandler = null;
|
this.clickHandler = null;
|
||||||
|
|
||||||
this._lastTabIndex = false;
|
|
||||||
this._tabDirection = null;
|
this._tabDirection = null;
|
||||||
this._tagColors = [];
|
this._tagColors = [];
|
||||||
this._notifierID = null;
|
this._notifierID = null;
|
||||||
|
@ -41,16 +40,10 @@
|
||||||
this._item = null;
|
this._item = null;
|
||||||
|
|
||||||
this.content = MozXULElement.parseXULToFragment(`
|
this.content = MozXULElement.parseXULToFragment(`
|
||||||
<box flex="1" tooltip="html-tooltip" style="display: flex" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
<collapsible-section data-l10n-id="section-tags" data-pane="tags">
|
||||||
<div id="tags-box" style="flex-grow: 1" xmlns="http://www.w3.org/1999/xhtml">
|
<html:div class="body">
|
||||||
<div class="tags-box-header">
|
<html:div id="rows" class="tags-box-list"/>
|
||||||
<label id="count"/>
|
<popupset>
|
||||||
<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"/>
|
<tooltip id="html-tooltip" page="true"/>
|
||||||
<menupopup id="tags-context-menu">
|
<menupopup id="tags-context-menu">
|
||||||
<menuitem id="remove-all-item-tags" label="&zotero.item.tags.removeAll;"/>
|
<menuitem id="remove-all-item-tags" label="&zotero.item.tags.removeAll;"/>
|
||||||
|
@ -69,6 +62,8 @@
|
||||||
nomaxresults="true"
|
nomaxresults="true"
|
||||||
/>
|
/>
|
||||||
</popupset>
|
</popupset>
|
||||||
|
</html:div>
|
||||||
|
</collapsible-section>
|
||||||
`, ['chrome://zotero/locale/zotero.dtd']);
|
`, ['chrome://zotero/locale/zotero.dtd']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,23 +74,23 @@
|
||||||
let content = document.importNode(this.content, true);
|
let content = document.importNode(this.content, true);
|
||||||
this.append(content);
|
this.append(content);
|
||||||
|
|
||||||
this._id("tags-box-add-button").addEventListener('click', this._handleAddButtonClick);
|
this._section = this.querySelector('collapsible-section');
|
||||||
this.addEventListener('add', this._handleAddButtonClick);
|
this._section.addEventListener('add', this._handleAddButtonClick);
|
||||||
this._id("tags-box-add-button").addEventListener('keydown', this._handleAddButtonKeyDown);
|
this.addEventListener('click', (event) => {
|
||||||
this._id('tags-box').addEventListener('click', (event) => {
|
if (event.target === this) {
|
||||||
if (event.target.id == 'tags-box') {
|
|
||||||
this.blurOpenField();
|
this.blurOpenField();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let removeAllItemTags = this._id('remove-all-item-tags');
|
let removeAllItemTags = this._id('remove-all-item-tags');
|
||||||
this._id('remove-all-item-tags').addEventListener('command', this.removeAll);
|
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;
|
removeAllItemTags.disabled = !this.count;
|
||||||
this._id('tags-context-menu').openPopupAtScreen(event.screenX, event.screenY, true);
|
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() {
|
destroy() {
|
||||||
|
@ -105,6 +100,7 @@
|
||||||
window.removeEventListener("unload", this.destroy);
|
window.removeEventListener("unload", this.destroy);
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
|
||||||
|
this._section = null;
|
||||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,8 +126,6 @@
|
||||||
case 'edit':
|
case 'edit':
|
||||||
this.clickable = true;
|
this.clickable = true;
|
||||||
this.editable = true;
|
this.editable = true;
|
||||||
this.clickHandler = this.showEditor;
|
|
||||||
this.blurHandler = this.hideEditor;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -151,7 +145,6 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._item = val;
|
this._item = val;
|
||||||
this._lastTabIndex = false;
|
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,20 +168,7 @@
|
||||||
let tagType = data.type;
|
let tagType = data.type;
|
||||||
|
|
||||||
if (event == 'add') {
|
if (event == 'add') {
|
||||||
var newTabIndex = this.add(tagName, tagType);
|
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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (event == 'modify') {
|
else if (event == 'modify') {
|
||||||
let oldTagName = data.old.tag;
|
let oldTagName = data.old.tag;
|
||||||
|
@ -196,20 +176,7 @@
|
||||||
this.add(tagName, tagType);
|
this.add(tagName, tagType);
|
||||||
}
|
}
|
||||||
else if (event == 'remove') {
|
else if (event == 'remove') {
|
||||||
var oldTabIndex = this.remove(tagName);
|
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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,10 +196,10 @@
|
||||||
// Cancel field focusing while we're updating
|
// Cancel field focusing while we're updating
|
||||||
this._reloading = true;
|
this._reloading = true;
|
||||||
|
|
||||||
this._id("tags-box-add-button").hidden = !this.editable;
|
|
||||||
|
|
||||||
this._tagColors = Zotero.Tags.getColors(this.item.libraryID);
|
this._tagColors = Zotero.Tags.getColors(this.item.libraryID);
|
||||||
|
|
||||||
|
let focusedTag = this._id('rows').querySelector('editable-text:focus')?.value;
|
||||||
|
|
||||||
let tagRows = this._id('rows');
|
let tagRows = this._id('rows');
|
||||||
tagRows.replaceChildren();
|
tagRows.replaceChildren();
|
||||||
|
|
||||||
|
@ -240,15 +207,32 @@
|
||||||
|
|
||||||
// Sort tags alphabetically
|
// Sort tags alphabetically
|
||||||
var collation = Zotero.getLocaleCollation();
|
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++) {
|
for (let i = 0; i < tags.length; i++) {
|
||||||
this.addDynamicRow(tags[i], i + 1);
|
this.addDynamicRow(tags[i], i + 1);
|
||||||
}
|
}
|
||||||
this.updateCount(tags.length);
|
this.updateCount(tags.length);
|
||||||
|
this._section.showAdd = this.editable;
|
||||||
|
|
||||||
this._reloading = false;
|
this._reloading = false;
|
||||||
this._focusField();
|
|
||||||
|
if (focusedTag) {
|
||||||
|
this._id('rows').querySelector(`[value="${CSS.escape(focusedTag)}"]`)?.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addDynamicRow(tagData, tabindex, skipAppend) {
|
addDynamicRow(tagData, tabindex, skipAppend) {
|
||||||
|
@ -259,7 +243,7 @@
|
||||||
tabindex = this._id('rows').childNodes.length + 1;
|
tabindex = this._id('rows').childNodes.length + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = document.createElement("img");
|
var icon = document.createElement("div");
|
||||||
icon.className = "zotero-box-icon";
|
icon.className = "zotero-box-icon";
|
||||||
|
|
||||||
// DEBUG: Why won't just this.nextSibling.blur() work?
|
// DEBUG: Why won't just this.nextSibling.blur() work?
|
||||||
|
@ -270,13 +254,18 @@
|
||||||
var label = this.createValueElement(name, tabindex);
|
var label = this.createValueElement(name, tabindex);
|
||||||
|
|
||||||
if (this.editable) {
|
if (this.editable) {
|
||||||
var remove = document.createXULElement("label");
|
var remove = document.createXULElement("toolbarbutton");
|
||||||
remove.setAttribute('value', '-');
|
remove.setAttribute('value', '-');
|
||||||
remove.setAttribute('class', 'zotero-clicky zotero-clicky-minus');
|
remove.setAttribute('class', 'zotero-clicky zotero-clicky-minus');
|
||||||
remove.setAttribute('tabindex', -1);
|
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) {
|
if (isNew) {
|
||||||
row.setAttribute('isNew', true);
|
row.setAttribute('isNew', true);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +291,6 @@
|
||||||
var tagName = tagData ? tagData.tag : "";
|
var tagName = tagData ? tagData.tag : "";
|
||||||
var tagType = (tagData && tagData.type) ? tagData.type : 0;
|
var tagType = (tagData && tagData.type) ? tagData.type : 0;
|
||||||
|
|
||||||
var icon = row.firstChild;
|
|
||||||
if (this.editable) {
|
if (this.editable) {
|
||||||
var remove = row.lastChild;
|
var remove = row.lastChild;
|
||||||
}
|
}
|
||||||
|
@ -312,21 +300,15 @@
|
||||||
row.setAttribute('tagType', tagType);
|
row.setAttribute('tagType', tagType);
|
||||||
|
|
||||||
// Icon
|
// Icon
|
||||||
var iconFile = 'tag';
|
let icon = row.firstChild;
|
||||||
if (!tagData || tagType == 0) {
|
icon.title = tagType == 0
|
||||||
icon.setAttribute('title', Zotero.getString('pane.item.tags.icon.user'));
|
? Zotero.getString('pane.item.tags.icon.user')
|
||||||
}
|
: Zotero.getString('pane.item.tags.icon.automatic');
|
||||||
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`);
|
|
||||||
|
|
||||||
// "-" button
|
// "-" button
|
||||||
if (this.editable) {
|
if (this.editable) {
|
||||||
remove.setAttribute('disabled', false);
|
remove.setAttribute('disabled', false);
|
||||||
remove.addEventListener('click', async (event) => {
|
remove.addEventListener('click', async (event) => {
|
||||||
this._lastTabIndex = false;
|
|
||||||
if (tagData) {
|
if (tagData) {
|
||||||
let item = this.item;
|
let item = this.item;
|
||||||
this.remove(tagName);
|
this.remove(tagName);
|
||||||
|
@ -354,164 +336,81 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createValueElement(valueText, tabindex) {
|
createValueElement(valueText) {
|
||||||
var valueElement = document.createXULElement("label");
|
var valueElement = document.createXULElement("editable-text");
|
||||||
valueElement.setAttribute('fieldname', 'tag');
|
valueElement.setAttribute('fieldname', 'tag');
|
||||||
valueElement.setAttribute('flex', 1);
|
valueElement.setAttribute('flex', 1);
|
||||||
valueElement.className = 'zotero-box-label';
|
valueElement.className = 'zotero-box-label';
|
||||||
|
valueElement.readOnly = !this.editable;
|
||||||
if (this.clickable) {
|
valueElement.value = valueText;
|
||||||
if (tabindex) {
|
let params = {
|
||||||
valueElement.setAttribute('ztabindex', tabindex);
|
fieldName: 'tag',
|
||||||
}
|
libraryID: this._item.libraryID,
|
||||||
valueElement.addEventListener('click', (event) => {
|
itemID: this._item.id || ''
|
||||||
/* Skip right-click on Windows */
|
};
|
||||||
if (event.button) {
|
valueElement.autocomplete = {
|
||||||
return;
|
ignoreBlurWhileSearching: true,
|
||||||
}
|
popup: 'PopupAutoComplete',
|
||||||
this.clickHandler(event.target, 1, valueText);
|
search: 'zotero',
|
||||||
}, false);
|
searchParam: JSON.stringify(params),
|
||||||
valueElement.className += ' zotero-clicky';
|
completeSelectedIndex: true
|
||||||
}
|
};
|
||||||
|
valueElement.addEventListener('blur', this.saveTag);
|
||||||
var firstSpace;
|
valueElement.addEventListener('keydown', this.handleKeyDown);
|
||||||
if (typeof valueText == 'string') {
|
valueElement.addEventListener('paste', this.handlePaste);
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
return valueElement;
|
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) => {
|
handleKeyDown = async (event) => {
|
||||||
var target = event.target;
|
var target = event.currentTarget;
|
||||||
var focused = document.activeElement;
|
|
||||||
|
|
||||||
switch (event.keyCode) {
|
if (event.key === 'Enter') {
|
||||||
case event.DOM_VK_RETURN:
|
var multiline = target.multiline;
|
||||||
var multiline = target.parentNode.classList.contains('multiline');
|
|
||||||
var empty = target.value == "";
|
var empty = target.value == "";
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
if (!multiline) {
|
if (!multiline) {
|
||||||
var self = this;
|
setTimeout(() => {
|
||||||
setTimeout(function () {
|
|
||||||
var val = target.value;
|
var val = target.value;
|
||||||
if (val !== "") {
|
if (val !== "") {
|
||||||
val += "\n";
|
val += "\n";
|
||||||
}
|
}
|
||||||
self.makeMultiline(target, val, 6);
|
this.makeMultiline(target, val);
|
||||||
}, 0);
|
});
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
// Submit
|
// Submit
|
||||||
}
|
}
|
||||||
else if (multiline) {
|
else if (multiline) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var row = target.closest('li');
|
event.preventDefault();
|
||||||
|
|
||||||
|
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 blurOnly = false;
|
||||||
|
let focusField = false;
|
||||||
|
|
||||||
// If non-empty last row, only blur, because the open textbox will
|
// If non-empty last row, only blur, because the open textbox will
|
||||||
// be cleared in hideEditor() and remain in place
|
// be cleared in saveTag() and remain in place
|
||||||
if (row == row.parentNode.lastChild && !empty) {
|
if (row == row.parentNode.lastChild && !empty) {
|
||||||
blurOnly = true;
|
blurOnly = true;
|
||||||
}
|
}
|
||||||
// If empty non-last row, refocus current row
|
// If empty non-last row, refocus current row
|
||||||
else if (row != row.parentNode.lastChild && empty) {
|
else if (row != row.parentNode.lastChild && empty) {
|
||||||
var focusField = true;
|
focusField = row.nextElementSibling;
|
||||||
}
|
|
||||||
// If non-empty non-last row, return focus to items pane
|
|
||||||
else {
|
|
||||||
var focusField = false;
|
|
||||||
this._lastTabIndex = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.blurHandler(event);
|
await this.blurOpenField();
|
||||||
|
|
||||||
if (blurOnly) {
|
if (blurOnly) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
if (focusField) {
|
if (focusField) {
|
||||||
this._focusField();
|
focusField.focus();
|
||||||
}
|
}
|
||||||
// Return focus to items pane
|
// Return focus to items pane
|
||||||
else {
|
else {
|
||||||
|
@ -520,52 +419,13 @@
|
||||||
tree.focus();
|
tree.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
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
|
// Intercept paste, check for newlines, and convert textbox
|
||||||
// to multiline if necessary
|
// to multiline if necessary
|
||||||
handlePaste = (event) => {
|
handlePaste = (event) => {
|
||||||
var textbox = event.target;
|
var textbox = event.currentTarget;
|
||||||
var str = event.clipboardData.getData('text');
|
var str = event.clipboardData.getData('text');
|
||||||
|
|
||||||
var multiline = !!str.trim().match(/\n/);
|
var multiline = !!str.trim().match(/\n/);
|
||||||
|
@ -577,32 +437,21 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
makeMultiline(textbox, value, rows) {
|
makeMultiline(editable, value) {
|
||||||
textbox.parentNode.classList.add('multiline');
|
editable.multiline = true;
|
||||||
// If rows not specified, use one more than lines in input
|
editable.value = value;
|
||||||
if (!rows) {
|
|
||||||
rows = value.match(/\n/g).length + 1;
|
|
||||||
}
|
|
||||||
textbox = this.showEditor(textbox, rows, textbox.getAttribute('value'));
|
|
||||||
textbox.value = value;
|
|
||||||
// Move cursor to end
|
// Move cursor to end
|
||||||
textbox.selectionStart = value.length;
|
editable.ref.selectionStart = value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
hideEditor = async (event) => {
|
saveTag = async (event) => {
|
||||||
var textbox = event.target;
|
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 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 row = textbox.parentNode;
|
||||||
|
|
||||||
var isNew = row.getAttribute('isNew');
|
var isNew = row.getAttribute('isNew');
|
||||||
|
@ -613,9 +462,8 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If row hasn't changed, change back to label
|
// If row hasn't changed, we're done
|
||||||
if (oldValue == value) {
|
if (oldValue == value) {
|
||||||
this.textboxToLabel(textbox);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,9 +476,6 @@
|
||||||
// The existing textbox will be removed in notify()
|
// The existing textbox will be removed in notify()
|
||||||
this.removeRow(row);
|
this.removeRow(row);
|
||||||
this.add(value);
|
this.add(value);
|
||||||
if (event.type != 'blur') {
|
|
||||||
this._focusField();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
this.item.replaceTag(oldValue, value);
|
this.item.replaceTag(oldValue, value);
|
||||||
await this.item.saveTx();
|
await this.item.saveTx();
|
||||||
|
@ -644,9 +489,10 @@
|
||||||
// Existing tag cleared
|
// Existing tag cleared
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
|
let nextRowElem = row.nextElementSibling?.querySelector('editable-text');
|
||||||
this.removeRow(row);
|
this.removeRow(row);
|
||||||
if (event.type != 'blur') {
|
if (event.type != 'change') {
|
||||||
this._focusField();
|
nextRowElem?.focus();
|
||||||
}
|
}
|
||||||
this.item.removeTag(oldValue);
|
this.item.removeTag(oldValue);
|
||||||
await this.item.saveTx();
|
await this.item.saveTx();
|
||||||
|
@ -659,8 +505,6 @@
|
||||||
}
|
}
|
||||||
// Multiple tags
|
// Multiple tags
|
||||||
else if (tags.length > 1) {
|
else if (tags.length > 1) {
|
||||||
var lastTag = row == row.parentNode.lastChild;
|
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
// If old tag isn't in array, remove it
|
// If old tag isn't in array, remove it
|
||||||
if (tags.indexOf(oldValue) == -1) {
|
if (tags.indexOf(oldValue) == -1) {
|
||||||
|
@ -670,27 +514,24 @@
|
||||||
// immediately. This isn't strictly necessary, but it
|
// immediately. This isn't strictly necessary, but it
|
||||||
// makes the transition nicer.
|
// makes the transition nicer.
|
||||||
else {
|
else {
|
||||||
textbox.value = textbox.getAttribute('value');
|
textbox.value = textbox.initialValue;
|
||||||
this.textboxToLabel(textbox);
|
textbox.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.forEach(tag => this.item.addTag(tag));
|
tags.forEach(tag => this.item.addTag(tag));
|
||||||
await this.item.saveTx();
|
await this.item.saveTx();
|
||||||
|
|
||||||
if (lastTag) {
|
|
||||||
this._lastTabIndex = this.item.getTags().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
}
|
||||||
// Single tag at end
|
// Single tag at end
|
||||||
else {
|
else {
|
||||||
if (event.type == 'blur') {
|
if (event.type == 'change') {
|
||||||
this.removeRow(row);
|
this.removeRow(row);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
textbox.value = '';
|
textbox.value = '';
|
||||||
|
// We need a setTimeout here for some reason - why?
|
||||||
|
setTimeout(() => textbox.focus());
|
||||||
}
|
}
|
||||||
this.add(value);
|
this.add(value);
|
||||||
this.item.addTag(value);
|
this.item.addTag(value);
|
||||||
|
@ -705,31 +546,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
newTag() {
|
newTag() {
|
||||||
var rowsElement = this._id('rows');
|
this._section.empty = false;
|
||||||
var rows = rowsElement.childNodes;
|
this._section.open = true;
|
||||||
|
|
||||||
// Don't add new row if there already is one
|
|
||||||
if (rows.length && rows[rows.length - 1].querySelector('.editable')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var row = this.addDynamicRow();
|
var row = this.addDynamicRow();
|
||||||
// It needs relatively high delay to make focus-on-click work
|
row.querySelector('editable-text').focus();
|
||||||
setTimeout(() => {
|
|
||||||
row.firstChild.nextSibling.click();
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
textboxToLabel(textbox) {
|
|
||||||
var elem = this.createValueElement(
|
|
||||||
textbox.value, textbox.getAttribute('ztabindex')
|
|
||||||
);
|
|
||||||
var row = textbox.parentNode;
|
|
||||||
row.replaceChild(elem, textbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(tagName, tagType) {
|
add(tagName, tagType) {
|
||||||
var rowsElement = this._id('rows');
|
var rowsElement = this._id('rows');
|
||||||
var rows = rowsElement.childNodes;
|
var rows = rowsElement.childNodes;
|
||||||
|
@ -738,7 +561,7 @@
|
||||||
var row = false;
|
var row = false;
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
if (rows[i].getAttribute('tagName') === tagName) {
|
if (rows[i].getAttribute('tagName') === tagName) {
|
||||||
return rows[i].getAttribute('ztabindex');
|
return rows[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,89 +570,54 @@
|
||||||
type: tagType
|
type: tagType
|
||||||
};
|
};
|
||||||
|
|
||||||
if (row) {
|
var color = this._tagColors.has(tagName);
|
||||||
// Update row and label
|
|
||||||
this.updateRow(row, tagData);
|
|
||||||
var elem = this.createValueElement(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
|
// Create new row, but don't insert it
|
||||||
row = this.addDynamicRow(tagData, false, true);
|
row = this.addDynamicRow(tagData, false, true);
|
||||||
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
|
||||||
}
|
|
||||||
|
|
||||||
// Move row to appropriate place, alphabetically
|
// Move row to appropriate place, alphabetically
|
||||||
var collation = Zotero.getLocaleCollation();
|
var collation = Zotero.getLocaleCollation();
|
||||||
var labels = rowsElement.getElementsByAttribute('fieldname', 'tag');
|
var tagEditables = rowsElement.getElementsByAttribute('fieldname', 'tag');
|
||||||
|
|
||||||
var inserted = false;
|
var inserted = false;
|
||||||
var newTabIndex = false;
|
for (let editable of tagEditables) {
|
||||||
for (var i = 0; i < labels.length; i++) {
|
// Sort tags without colors below tags with colors
|
||||||
let index = i + 1;
|
if (!color && this._tagColors.has(editable.value)
|
||||||
if (inserted) {
|
|| collation.compareString(1, tagName, editable.value) > 0) {
|
||||||
labels[i].setAttribute('ztabindex', index);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collation.compareString(1, tagName, labels[i].textContent) > 0
|
rowsElement.insertBefore(row, editable.parentNode);
|
||||||
// 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;
|
|
||||||
inserted = true;
|
inserted = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
newTabIndex = i + 1;
|
|
||||||
elem.setAttribute('ztabindex', newTabIndex);
|
|
||||||
rowsElement.appendChild(row);
|
rowsElement.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateCount(this.count + 1);
|
this.updateCount(this.count + 1);
|
||||||
|
|
||||||
return newTabIndex;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(tagName) {
|
remove(tagName) {
|
||||||
var rowsElement = this._id('rows');
|
var rowsElement = this._id('rows');
|
||||||
var rows = rowsElement.childNodes;
|
var rows = rowsElement.childNodes;
|
||||||
var oldTabIndex = -1;
|
|
||||||
for (var i = 0; i < rows.length; i++) {
|
for (var i = 0; i < rows.length; i++) {
|
||||||
let value = rows[i].getAttribute('tagName');
|
let value = rows[i].getAttribute('tagName');
|
||||||
if (value === tagName) {
|
if (value === tagName) {
|
||||||
oldTabIndex = this.removeRow(rows[i]);
|
this.removeRow(rows[i]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return oldTabIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the row and update tab indexes
|
// Remove the row and update tab indexes
|
||||||
removeRow(row) {
|
removeRow(row) {
|
||||||
var origTabIndex = row.getElementsByAttribute('fieldname', 'tag')[0].getAttribute('ztabindex');
|
|
||||||
var origRow = row;
|
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);
|
origRow.parentNode.removeChild(origRow);
|
||||||
this.updateCount(this.count - 1);
|
this.updateCount(this.count - 1);
|
||||||
return origTabIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAll = () => {
|
removeAll = () => {
|
||||||
|
@ -854,7 +642,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._id('count').replaceChildren(Zotero.getString('pane.item.tags.count', count, count));
|
this._section.setCount(count);
|
||||||
this.count = 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) => {
|
_handleAddButtonClick = async (event) => {
|
||||||
await this.blurOpenField();
|
await this.blurOpenField();
|
||||||
this.newTag();
|
this.newTag();
|
||||||
|
@ -941,15 +662,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async blurOpenField(stayOpen) {
|
async blurOpenField(stayOpen) {
|
||||||
this._lastTabIndex = false;
|
var editable = this.querySelector('editable-text:focus-within');
|
||||||
var textboxe = this.querySelector('.editable');
|
if (editable) {
|
||||||
if (textboxe) {
|
await this.saveTag({
|
||||||
await this.blurHandler({
|
currentTarget: editable,
|
||||||
target: textboxe,
|
// If coming from the Add button, pretend user pressed Enter
|
||||||
// If coming from the Add button, pretend user pressed return
|
type: stayOpen ? 'keypress' : 'change',
|
||||||
type: stayOpen ? 'keypress' : 'blur',
|
key: stayOpen ? 'Enter' : undefined
|
||||||
// DOM_VK_RETURN
|
|
||||||
keyCode: stayOpen ? 13 : undefined
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,8 +239,8 @@
|
||||||
<menupopup/>
|
<menupopup/>
|
||||||
</menulist>
|
</menulist>
|
||||||
<zoterosearchagefield id="value-date-age" class="value-date-age" hidden="true"/>
|
<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)"/>
|
<toolbarbutton 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="add" class="zotero-clicky zotero-clicky-plus" value="+" onclick="this.closest('zoterosearchcondition').onAddClicked(event)"/>
|
||||||
</html:div>
|
</html:div>
|
||||||
`, ['chrome://zotero/locale/zotero.dtd', 'chrome://zotero/locale/searchbox.dtd']);
|
`, ['chrome://zotero/locale/zotero.dtd', 'chrome://zotero/locale/searchbox.dtd']);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
var ZoteroItemPane = new function() {
|
var ZoteroItemPane = new function() {
|
||||||
var _container;
|
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 _deck;
|
||||||
var _lastItem;
|
var _lastItem;
|
||||||
var _selectedNoteID;
|
var _selectedNoteID;
|
||||||
|
@ -43,9 +43,10 @@ var ZoteroItemPane = new function() {
|
||||||
_itemBox = document.getElementById('zotero-editpane-item-box');
|
_itemBox = document.getElementById('zotero-editpane-item-box');
|
||||||
_abstractBox = document.getElementById('zotero-editpane-abstract');
|
_abstractBox = document.getElementById('zotero-editpane-abstract');
|
||||||
_notesBox = document.getElementById('zotero-editpane-notes');
|
_notesBox = document.getElementById('zotero-editpane-notes');
|
||||||
|
_attachmentsBox = document.getElementById('zotero-editpane-attachments');
|
||||||
_tagsBox = document.getElementById('zotero-editpane-tags');
|
_tagsBox = document.getElementById('zotero-editpane-tags');
|
||||||
_relatedBox = document.getElementById('zotero-editpane-related');
|
_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');
|
_deck = document.getElementById('zotero-item-pane-content');
|
||||||
|
|
||||||
|
@ -90,6 +91,7 @@ var ZoteroItemPane = new function() {
|
||||||
this.setTranslateButton();
|
this.setTranslateButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inTrash = ZoteroPane.collectionsView.selectedTreeRow && ZoteroPane.collectionsView.selectedTreeRow.isTrash();
|
||||||
for (let box of [_header, ..._boxes]) {
|
for (let box of [_header, ..._boxes]) {
|
||||||
if (mode) {
|
if (mode) {
|
||||||
box.mode = mode;
|
box.mode = mode;
|
||||||
|
@ -103,6 +105,7 @@ var ZoteroItemPane = new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
box.item = item;
|
box.item = item;
|
||||||
|
box.inTrash = inTrash;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sidenav.selectedPane = pane;
|
_sidenav.selectedPane = pane;
|
||||||
|
@ -112,7 +115,7 @@ var ZoteroItemPane = new function() {
|
||||||
|
|
||||||
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) {
|
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) {
|
||||||
if (action == 'refresh' && _lastItem) {
|
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();
|
document.getElementById("attachment-note-editor").focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Focusing on the last field in whichever tab is opened for
|
// For regular items, focus the last field
|
||||||
// regular items
|
// We do that by moving focus backwards from the element following the pane, because Services.focus doesn't
|
||||||
const tabBox = document.getElementById("zotero-view-tabbox");
|
// support MOVEFOCUS_LAST on subtrees
|
||||||
if (tabBox.selectedIndex === 0) {
|
Services.focus.moveFocus(window, document.getElementById('zotero-context-splitter'),
|
||||||
const itembox = document.getElementById("zotero-editpane-item-box");
|
Services.focus.MOVEFOCUS_BACKWARD, 0);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1115,29 +1115,17 @@
|
||||||
<pane-header id="zotero-item-pane-header" />
|
<pane-header id="zotero-item-pane-header" />
|
||||||
|
|
||||||
<html:div id="zotero-view-item" class="zotero-view-item">
|
<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" data-pane="info"/>
|
||||||
<item-box id="zotero-editpane-item-box"/>
|
|
||||||
</collapsible-section>
|
|
||||||
|
|
||||||
<collapsible-section data-l10n-id="section-abstract" data-pane="abstract">
|
<abstract-box id="zotero-editpane-abstract" class="zotero-editpane-abstract" data-pane="abstract"/>
|
||||||
<abstract-box id="zotero-editpane-abstract" class="zotero-editpane-abstract"/>
|
|
||||||
</collapsible-section>
|
|
||||||
|
|
||||||
<collapsible-section data-l10n-id="section-attachments" data-pane="attachments" show-add="true">
|
<attachments-box id="zotero-editpane-attachments" data-pane="attachments"/>
|
||||||
<html:span>[placeholder for attachments section]</html:span>
|
|
||||||
</collapsible-section>
|
|
||||||
|
|
||||||
<collapsible-section data-l10n-id="section-notes" data-pane="notes" show-add="true">
|
<notes-box id="zotero-editpane-notes" class="zotero-editpane-notes" data-pane="notes"/>
|
||||||
<notes-box id="zotero-editpane-notes" class="zotero-editpane-notes"/>
|
|
||||||
</collapsible-section>
|
|
||||||
|
|
||||||
<collapsible-section data-l10n-id="section-tags" data-pane="tags" show-add="true">
|
<tags-box id="zotero-editpane-tags" class="zotero-editpane-tags" data-pane="tags"/>
|
||||||
<tags-box id="zotero-editpane-tags" class="zotero-editpane-tags"/>
|
|
||||||
</collapsible-section>
|
|
||||||
|
|
||||||
<collapsible-section data-l10n-id="section-related" data-pane="related" show-add="true">
|
<related-box id="zotero-editpane-related" class="zotero-editpane-related" data-pane="related"/>
|
||||||
<related-box id="zotero-editpane-related" class="zotero-editpane-related"/>
|
|
||||||
</collapsible-section>
|
|
||||||
</html:div>
|
</html:div>
|
||||||
</html:div>
|
</html:div>
|
||||||
|
|
||||||
|
|
|
@ -231,13 +231,22 @@ section-info =
|
||||||
section-abstract =
|
section-abstract =
|
||||||
.label = { pane-abstract }
|
.label = { pane-abstract }
|
||||||
section-attachments =
|
section-attachments =
|
||||||
.label = { pane-attachments }
|
.label = { $count ->
|
||||||
|
[one] { $count } Attachment
|
||||||
|
*[other] { $count } Attachments
|
||||||
|
}
|
||||||
section-notes =
|
section-notes =
|
||||||
.label = { pane-notes }
|
.label = { $count ->
|
||||||
|
[one] { $count } Note
|
||||||
|
*[other] { $count } Notes
|
||||||
|
}
|
||||||
section-tags =
|
section-tags =
|
||||||
.label = { pane-tags }
|
.label = { $count ->
|
||||||
|
[one] { $count } Tag
|
||||||
|
*[other] { $count } Tags
|
||||||
|
}
|
||||||
section-related =
|
section-related =
|
||||||
.label = { pane-related }
|
.label = { $count } Related
|
||||||
|
|
||||||
sidenav-info =
|
sidenav-info =
|
||||||
.tooltiptext = { pane-info }
|
.tooltiptext = { pane-info }
|
||||||
|
|
|
@ -418,7 +418,6 @@ pane.item.creator.moveDown = Move Down
|
||||||
pane.item.notes.allNotes = All Notes
|
pane.item.notes.allNotes = All Notes
|
||||||
pane.item.notes.untitled = Untitled Note
|
pane.item.notes.untitled = Untitled Note
|
||||||
pane.item.notes.delete.confirm = Are you sure you want to delete this 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.notes.ignoreMissingImage = Some note images are missing and cannot be copied.
|
||||||
pane.item.attachments.rename.title = New title:
|
pane.item.attachments.rename.title = New title:
|
||||||
pane.item.attachments.rename.renameAssociatedFile = Rename associated file
|
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.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.locateManually = Locate Manually…
|
||||||
pane.item.attachments.autoRelink.relinkAll = Relink All
|
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.has = Has attachment
|
||||||
pane.item.attachments.hasPDF = Has PDF attachment
|
pane.item.attachments.hasPDF = Has PDF attachment
|
||||||
pane.item.attachments.hasSnapshot = Has snapshot
|
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.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.attachments.filename = Filename
|
||||||
pane.item.noteEditor.clickHere = click here
|
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.user = User-added tag
|
||||||
pane.item.tags.icon.automatic = Automatically added tag
|
pane.item.tags.icon.automatic = Automatically added tag
|
||||||
pane.item.tags.removeAll = Remove all tags from this item?
|
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.parentItem = Parent Item:
|
||||||
pane.item.viewOnline.tooltip = Go to this item online
|
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">
|
<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>
|
</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;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 8px;
|
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 {
|
.zotero-view-item-container.feed-item .zotero-view-item-sidenav {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +38,6 @@
|
||||||
.zotero-view-item > * {
|
.zotero-view-item > * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-block: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-view-item > :last-child {
|
.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;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-box-label {
|
|
||||||
margin-inline-start: 3px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#retracted-items-banner, #sync-reminder-banner {
|
#retracted-items-banner, #sync-reminder-banner {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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/itemPaneSidenav";
|
||||||
@import "elements/abstractBox";
|
@import "elements/abstractBox";
|
||||||
@import "elements/collapsibleSection";
|
@import "elements/collapsibleSection";
|
||||||
|
@import "elements/attachmentsBox";
|
||||||
|
@import "elements/attachmentRow";
|
||||||
|
@import "elements/annotationRow";
|
||||||
|
|
|
@ -79,3 +79,32 @@
|
||||||
@content("dark");
|
@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 {
|
.zotero-clicky {
|
||||||
border-radius: 6px;
|
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 */
|
/* Minus and plus buttons with clicky glow effect */
|
||||||
.zotero-clicky-minus, .zotero-clicky-plus {
|
.zotero-clicky-minus, .zotero-clicky-plus {
|
||||||
color: transparent !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
margin-inline-end: 5px !important;
|
width: 20px;
|
||||||
width: 18px;
|
height: 20px;
|
||||||
height: 18px;
|
border-radius: 2px;
|
||||||
|
color: var(--fill-secondary);
|
||||||
|
|
||||||
|
.toolbarbutton-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-clicky-minus {
|
.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;
|
border: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-clicky-plus {
|
.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;
|
border: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-clicky-minus[disabled=true], .zotero-clicky-plus[disabled=true] {
|
.zotero-clicky-minus[disabled=true], .zotero-clicky-plus[disabled=true] {
|
||||||
opacity: .5;
|
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 {
|
.toolbarbutton-menu-dropmarker {
|
||||||
display: block;
|
display: block;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
background-image: url("chrome://zotero/skin/chevron-6.svg");
|
@include svgicon("chevron-8", "universal", "8");
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
fill: var(--fill-secondary);
|
fill: var(--fill-secondary);
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
abstract-box {
|
abstract-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract-box .body {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
editable-text {
|
editable-text {
|
||||||
flex: 1;
|
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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
padding-block: 4px;
|
||||||
|
|
||||||
|
:not(:last-child) > & {
|
||||||
border-bottom: 1px solid var(--fill-quinary);
|
border-bottom: 1px solid var(--fill-quinary);
|
||||||
|
}
|
||||||
|
|
||||||
& > .head {
|
& > .head {
|
||||||
@include comfortable {
|
@include comfortable {
|
||||||
|
@ -33,29 +37,40 @@ collapsible-section {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
-moz-context-properties: fill, fill-opacity, stroke, stroke-opacity;
|
color: var(--fill-secondary);
|
||||||
fill: var(--fill-secondary);
|
|
||||||
stroke: var(--fill-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toolbarbutton.add {
|
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 {
|
toolbarbutton.twisty .toolbarbutton-icon {
|
||||||
list-style-image: icon-url("chevron-12.svg");
|
@include svgicon-menu("chevron-12", "universal", "16");
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[open] > .head {
|
&[open]:not([empty]) > .head {
|
||||||
toolbarbutton.twisty .toolbarbutton-icon {
|
toolbarbutton.twisty .toolbarbutton-icon {
|
||||||
transform: rotate(-180deg);
|
transform: rotate(-180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[empty] > .head > toolbarbutton.twisty {
|
||||||
|
fill: var(--fill-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
@each $pane, $color in $item-pane-sections {
|
@each $pane, $color in $item-pane-sections {
|
||||||
&[data-pane="#{$pane}"] {
|
&[data-pane="#{$pane}"] {
|
||||||
& > .head > .title::before {
|
& > .head > .title::before {
|
||||||
|
@ -68,7 +83,6 @@ collapsible-section {
|
||||||
}
|
}
|
||||||
|
|
||||||
& > :not(.head) {
|
& > :not(.head) {
|
||||||
overflow: hidden;
|
|
||||||
max-height: var(--open-height, auto);
|
max-height: var(--open-height, auto);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||||
|
@ -79,7 +93,8 @@ collapsible-section {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
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;
|
display: grid;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: attr(data-value) ' ';
|
content: attr(value) ' ';
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after, textarea {
|
&::after, .input {
|
||||||
grid-area: 1 / 1 / 2 / 2;
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
|
padding: var(--editable-text-padding-block) var(--editable-text-padding-inline);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
@ -30,7 +30,10 @@ editable-text {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
.input {
|
||||||
|
// Necessary for consistent padding, even if it's actually an <input>
|
||||||
|
-moz-default-appearance: textarea;
|
||||||
|
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
@ -55,11 +58,11 @@ editable-text {
|
||||||
}
|
}
|
||||||
|
|
||||||
&[multiline] {
|
&[multiline] {
|
||||||
&::after, textarea {
|
&::after, .input {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
.input {
|
||||||
min-height: 5em;
|
min-height: 5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
item-box {
|
item-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
item-box .body {
|
||||||
--row-height: 1.5em;
|
--row-height: 1.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,49 +1,35 @@
|
||||||
notes-box, related-box {
|
notes-box, related-box {
|
||||||
.header {
|
|
||||||
font-weight: normal;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 10px;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
notes-box .body, related-box .body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-inline-start: 16px - 2px;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
@include comfortable {
|
@include comfortable {
|
||||||
padding-block: 2px;
|
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 {
|
.box {
|
||||||
overflow: hidden;
|
@include clicky-item;
|
||||||
display: flex;
|
flex: 1;
|
||||||
margin-left: 5px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
toolbarbutton {
|
||||||
margin-left: 3px;
|
margin-inline-start: auto;
|
||||||
overflow: hidden;
|
visibility: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:is(:hover, :focus-within) toolbarbutton {
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,75 @@
|
||||||
tags-box {
|
tags-box {
|
||||||
.tags-box-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 10px;
|
flex-direction: column;
|
||||||
align-items: center;
|
}
|
||||||
|
|
||||||
button {
|
tags-box .body {
|
||||||
min-width: 79px;
|
display: flex;
|
||||||
margin: 5px 6px 3px;
|
flex-direction: column;
|
||||||
padding-top: 1px;
|
margin: 0;
|
||||||
padding-bottom: 1px;
|
padding-inline-start: 16px;
|
||||||
color: ButtonText;
|
|
||||||
text-shadow: none;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-box-list {
|
.tags-box-list {
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 0 0; // Leave space for textbox border on top tag
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 3px 0;
|
flex-direction: column;
|
||||||
margin-inline-start: 6px;
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 12px 1fr 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 1.5em;
|
column-gap: 4px;
|
||||||
|
padding-block: 1px;
|
||||||
|
|
||||||
// Shift-Enter
|
// Shift-Enter
|
||||||
&.multiline {
|
&.multiline {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
height: 9em;
|
min-height: 9em;
|
||||||
|
|
||||||
textarea.editable {
|
textarea.editable {
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.multiline) .editable {
|
.zotero-box-icon {
|
||||||
padding: 0 1px;
|
grid-column: 1;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
-moz-context-properties: fill;
|
||||||
|
background: icon-url('tag.svg') center no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zotero-box-icon {
|
&[tagType="0"] .zotero-box-icon {
|
||||||
width: 16px;
|
// User tag: use tag color if we have one, blue accent if we don't
|
||||||
height: 16px;
|
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 {
|
.zotero-box-label {
|
||||||
flex-grow: 1;
|
grid-column: 2;
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.editable {
|
&.has-color {
|
||||||
font-family: inherit;
|
.zotero-box-icon {
|
||||||
font-size: inherit;
|
background-image: icon-url('tag-fill.svg');
|
||||||
flex-grow: 1;
|
|
||||||
margin: 0 2px;
|
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.zotero-box-label {
|
||||||
border: 0;
|
font-weight: 590;
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
width: 20px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolbarbutton {
|
||||||
|
margin-inline-start: auto;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:is(:hover, :focus-within) toolbarbutton {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
.tags-box-list li img {
|
|
||||||
margin-right: 1px;
|
|
||||||
}
|
|