Merge PDF reader branch

This commit is contained in:
Dan Stillman 2021-03-03 04:50:58 -05:00
commit f0992f959c
254 changed files with 13206 additions and 4178 deletions

12
.gitmodules vendored
View file

@ -29,3 +29,15 @@
[submodule "resource/SingleFile"]
path = resource/SingleFile
url = https://github.com/gildas-lormeau/SingleFile.git
[submodule "pdf-reader"]
path = pdf-reader
url = ssh://git@github.com/zotero/pdf-reader.git
branch = master
[submodule "pdf-worker"]
path = pdf-worker
url = ssh://git@github.com/zotero/pdf-worker.git
branch = master
[submodule "note-editor"]
path = note-editor
url = ssh://git@github.com/zotero/note-editor.git
branch = master

View file

@ -1,10 +1,10 @@
/* Use standard tab appearance for item pane tabs */
#zotero-pane #zotero-view-tabbox > tabs > tab {
.zotero-view-tabbox > tabs > tab {
-moz-appearance: tab;
}
/* Active tab label color in item pane and elsewhere */
#zotero-pane tabs#zotero-editpane-tabs > tab[visuallyselected="true"][selected="true"] hbox > .tab-text,
tabs.zotero-editpane-tabs > tab[visuallyselected="true"][selected="true"] hbox > .tab-text,
#zotero-prefs tab[visuallyselected="true"]:not(:-moz-window-inactive),
tabs > tab[visuallyselected="true"] hbox > .tab-text {
color: black !important;
@ -12,10 +12,6 @@ tabs > tab[visuallyselected="true"] hbox > .tab-text {
}
/* Quick Format dialog, which is based on window corners, which are different on Big Sur */
#quick-format-dialog #zotero-icon {
margin-top: 2px;
}
#quick-format-iframe {
margin-top: 2px;
}

View file

@ -4,7 +4,7 @@ html > body {
}
body {
line-height: 1.45em;
line-height: 1.6em;
font-size: 15px;
}
@ -12,45 +12,42 @@ body[multiline="true"] {
line-height: 26px;
}
#quick-format-dialog {
window.citation-dialog {
background: transparent;
-moz-appearance: none;
padding: 0;
width: 800px;
}
#quick-format-dialog.progress-bar #quick-format-deck {
.citation-dialog.progress-bar .citation-dialog.deck {
height: 37px;
}
#quick-format-search {
.citation-dialog.search {
background: white;
-moz-appearance: searchfield;
}
#quick-format-search[multiline="true"] {
padding: 2px 2px 0 19.5px;
padding: 2px 2px 0 2px;
margin: 2.5px 3.5px;
border: 1px solid rgba(0, 0, 0, 0.5);
-moz-appearance: none;
border-radius: 10px;
}
#quick-format-search:not([multiline="true"]) {
padding-top: 3.5px;
height: 37px !important;
.citation-dialog.search:not([multiline="true"]) {
height: 32px !important;
}
#quick-format-entry {
.citation-dialog.entry {
background: -moz-linear-gradient(-90deg, rgb(243,123,119) 0, rgb(180,47,38) 50%, rgb(156,36,27) 50%);
padding: 12px;
}
#zotero-icon {
margin: -2px 0 3px -6px;
.note-dialog .citation-dialog.entry {
background: -moz-linear-gradient(-90deg, rgb(249, 231, 179) 0, rgb(228, 193, 94) 50%, rgb(221, 184, 81) 50%);
}
#quick-format-search[multiline="true"] #zotero-icon {
margin: 0 0 1px -13px;
#zotero-icon {
margin: -1px 0 0 4px;
-moz-appearance: none;
}
#citation-properties menulist {
@ -106,7 +103,6 @@ panel button:-moz-focusring {
box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
}
.quick-format-bubble {
margin-top: 0;
.citation-dialog.bubble {
padding: 1px 6px 1px 6px;
}

View file

@ -107,12 +107,12 @@ input {
margin: 0;
}
#zotero-view-tabbox {
.zotero-view-tabbox {
background-color: #fff;
padding: 0;
}
#zotero-item-pane-content .groupbox-body {
.zotero-item-pane-content .groupbox-body {
-moz-appearance: none;
background-color: #ffffff;
}
@ -121,13 +121,13 @@ input {
color: #7f7f7f;
}
#zotero-view-tabbox > tabpanels {
.zotero-view-tabbox > tabpanels {
margin: 12px 0 0 0;
padding: 0;
-moz-appearance: none;
}
#zotero-view-tabbox > tabs {
.zotero-editpane-tabs {
-moz-appearance: none;
background: -moz-linear-gradient(top, #ededed, #cccccc);
border-style: solid;
@ -136,15 +136,15 @@ input {
padding: 2px 0 2px 0;
}
#zotero-view-tabbox > tabs > tab > hbox {
.zotero-editpane-tabs > tab > hbox {
padding: 0;
}
#zotero-view-tabbox > tabs > tab > hbox > .tab-icon {
.zotero-editpane-tabs > tab > hbox > .tab-icon {
display: none;
}
#zotero-pane #zotero-view-tabbox > tabs > tab {
.zotero-editpane-tabs > tab {
-moz-box-orient: vertical;
-moz-box-align: center;
-moz-appearance: toolbarbutton;
@ -153,7 +153,7 @@ input {
padding: 3px 1px 3px 1px;
}
#zotero-pane #zotero-view-tabbox > tabs > tab > hbox .tab-text {
.zotero-editpane-tabs > tab > hbox .tab-text {
font-size: 11px;
font-weight: bold;
margin: 2px 7px 2px 9px !important;
@ -161,11 +161,11 @@ input {
}
/* This seems to be necessary to center the tabs. Not sure why. */
#zotero-pane #zotero-view-tabbox > tabs > tab:last-of-type > hbox .tab-text {
.zotero-editpane-tabs > tab:last-of-type > hbox .tab-text {
margin: 2px 9px 2px 9px !important;
}
#zotero-pane #zotero-view-tabbox > tabs > tab[selected=true] > hbox .tab-text {
.zotero-editpane-tabs > tab[selected=true] > hbox .tab-text {
color: #FFF !important;
text-shadow: rgba(0, 0, 0, 0.4) 0 1px;
}
@ -217,18 +217,21 @@ input {
}
#zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed])[orient=horizontal]
#zotero-items-splitter:not([state=collapsed])[orient=horizontal],
#zotero-context-splitter:not([state=collapsed])[orient=horizontal]
{
-moz-appearance: none;
border-inline-start: 1px solid #bdbdbd;
margin-inline-end: -4px;
width: 5px !important;
min-width: 5px;
position: relative;
/* Create a separate stacking context to be on top */
opacity: 0.99;
background-image: none;
}
#zotero-items-splitter[orient=vertical]
#zotero-items-splitter[orient=vertical],
#zotero-context-splitter-stacked
{
-moz-border-start: none !important;
-moz-border-end: none !important;
@ -239,17 +242,25 @@ input {
}
#zotero-collections-splitter:not([state=collapsed]) > grippy,
#zotero-items-splitter:not([state=collapsed]) > grippy
#zotero-items-splitter:not([state=collapsed]) > grippy,
#zotero-context-splitter:not([state=collapsed]) > grippy,
#zotero-context-splitter-stacked:not([state=collapsed]) > grippy
{
display: none;
}
#zotero-collections-splitter[state=collapsed], #zotero-items-splitter[state=collapsed] {
#zotero-collections-splitter[state=collapsed],
#zotero-items-splitter[state=collapsed],
#zotero-context-splitter[state=collapsed],
#zotero-context-splitter-stacked[state=collapsed]
{
border: 0 solid #d6d6d6 !important;
padding: 0;
}
#zotero-collections-splitter[state=collapsed], #zotero-items-splitter[state=collapsed][orient=horizontal]
#zotero-collections-splitter[state=collapsed],
#zotero-items-splitter[state=collapsed][orient=horizontal],
#zotero-context-splitter[state=collapsed][orient=horizontal]
{
background-image: url("chrome://zotero/skin/mac/vsplitter.png");
background-repeat: repeat-y;
@ -258,7 +269,8 @@ input {
width: 8px !important;
}
#zotero-items-splitter[state=collapsed][orient=vertical]
#zotero-items-splitter[state=collapsed][orient=vertical],
#zotero-context-splitter-stacked[state=collapsed][orient=vertical]
{
background-image: url("chrome://zotero/skin/mac/hsplitter.png");
background-repeat: repeat-x;
@ -271,11 +283,17 @@ input {
border-right-width: 1px !important;
}
#zotero-items-splitter[state=collapsed] {
#zotero-items-splitter[state=collapsed],
#zotero-context-splitter[state=collapsed],
#zotero-context-splitter-stacked[state=collapsed]
{
border-left-width: 1px !important;
}
#zotero-collections-splitter[state=collapsed] > grippy, #zotero-items-splitter[state=collapsed] > grippy
#zotero-collections-splitter[state=collapsed] > grippy,
#zotero-items-splitter[state=collapsed] > grippy,
#zotero-context-splitter[state=collapsed] > grippy,
#zotero-context-splitter-stacked[state=collapsed] > grippy
{
-moz-appearance: none;
background: url(chrome://zotero/skin/mac/vgrippy.png) center/auto 8px no-repeat;
@ -301,11 +319,20 @@ input {
height: 8px;
}
#zotero-tags-splitter > grippy:hover, #zotero-collections-splitter > grippy:hover, #zotero-items-splitter > grippy:hover
#zotero-tags-splitter > grippy:hover,
#zotero-collections-splitter > grippy:hover,
#zotero-items-splitter > grippy:hover,
#zotero-context-splitter > grippy:hover,
#zotero-context-splitter-stacked > grippy:hover
{
background-color:transparent;
}
#zotero-context-toolbar-extension {
/* To cover #zotero-context-splitter 1px border */
margin-inline-start: -1px;
}
#zotero-items-tree
{
-moz-appearance: none;

View file

@ -2,18 +2,18 @@ body {
line-height: 1.5em;
}
#quick-format-search:not([multiline="true"]) {
.citation-dialog.search:not([multiline="true"]) {
height: 29px !important;
}
#quick-format-search {
.citation-dialog.search {
background: white;
padding: 0 2px 0 0;
border: 1px solid rgba(0, 0, 0, 0.5);
-moz-appearance: textfield;
}
#quick-format-dialog {
window.citation-dialog {
-moz-appearance: none;
padding: 5px;
}

View file

@ -11,20 +11,20 @@
visibility: visible;
}
#zotero-item-pane-content {
.zotero-item-pane-content {
margin-right: 6px;
}
/* Make the item pane appear white (same colour as treeview), making the UI more consistent */
#zotero-item-pane-content tab, #zotero-item-pane-content tabpanels {
.zotero-item-pane-content tab, .zotero-item-pane-content tabpanels {
background-color: -moz-Field; /* Same as background colour for treeview */
}
/* Possibly irrelevant if mozilla fixes https://bugzilla.mozilla.org/show_bug.cgi?id=1306425 */
#zotero-view-tabbox tabs tab[visuallyselected=true] {
.zotero-view-tabbox tabs tab[visuallyselected=true] {
margin-top: 0px !important; /* Importanter than ./itemPane.css:20 */
margin-bottom: -2px !important; /* Importanter than skin/itemPane.css:12 */
}
#zotero-view-tabbox tabs tab {
.zotero-view-tabbox tabs tab {
margin-top: 2px !important; /* Importanter than skin/itemPane.css:11 */
}

View file

@ -76,3 +76,9 @@ tab {
background-color: transparent;
background-image: none;
}
#zotero-context-splitter-stacked {
-moz-appearance: none;
background-color: #ececec;
border-top: 1px solid hsla(0, 0%, 0%, 0.2);
}

View file

@ -1,9 +1,10 @@
#quick-format-dialog {
window.citation-dialog {
background: transparent;
padding: 0;
-moz-appearance: none;
}
#quick-format-search {
.citation-dialog.search {
background: white;
padding: 2px 2px 2px 0;
border: 1px solid rgba(0, 0, 0, 0.5);
@ -11,22 +12,20 @@
-moz-appearance: none;
}
#quick-format-dialog {
background: transparent;
-moz-appearance: none;
padding: 0;
}
#quick-format-search:not([multiline="true"]) {
.citation-dialog.search:not([multiline="true"]) {
height: 28px !important;
}
#quick-format-entry {
.citation-dialog.entry {
background: -moz-linear-gradient(-90deg, rgb(243,123,119) 0, rgb(180,47,38) 50%, rgb(156,36,27) 50%);
padding: 10px;
}
#quick-format-entry:not([square="true"]) {
.note-dialog .citation-dialog.entry {
background: -moz-linear-gradient(-90deg, rgb(249, 231, 179) 0, rgb(228, 193, 94) 50%, rgb(221, 184, 81) 50%);
}
.citation-dialog.entry:not([square="true"]) {
-moz-border-radius: 15px;
border-radius: 15px;
}

View file

@ -49,7 +49,7 @@
padding-top: 4px;
}
#zotero-view-tabbox tab {
.zotero-view-tabbox tab {
padding-left: .7em;
padding-right: .7em;
}
@ -61,7 +61,9 @@
#zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed]),
#zotero-tags-splitter:not([state=collapsed]) {
#zotero-tags-splitter:not([state=collapsed]),
#zotero-context-splitter:not([state=collapsed]),
#zotero-context-splitter-stacked:not([state=collapsed]) {
border: 0;
background-color: transparent;
position: relative;
@ -70,9 +72,15 @@
z-index: 1;
}
#zotero-context-splitter:not([state=collapsed]),
#zotero-context-splitter-stacked:not([state=collapsed]) {
z-index: 0;
}
#zotero-collections-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed]):not([orient=vertical]),
#zotero-tags-splitter:not([state=collapsed]) {
#zotero-tags-splitter:not([state=collapsed]),
#zotero-context-splitter:not([state=collapsed]) {
border-inline-end: 1px solid var(--theme-border-color);
min-width: 0;
width: 3px;
@ -80,7 +88,8 @@
}
#zotero-tags-splitter:not([state=collapsed]),
#zotero-items-splitter:not([state=collapsed])[orient=vertical] {
#zotero-items-splitter:not([state=collapsed])[orient=vertical],
#zotero-context-splitter-stacked:not([state=collapsed]) {
border-block-end: 1px solid var(--theme-border-color);
min-height: 0;
height: 3px;
@ -89,17 +98,20 @@
#zotero-collections-splitter > grippy,
#zotero-items-splitter > grippy,
#zotero-tags-splitter > grippy {
#zotero-tags-splitter > grippy,
#zotero-context-splitter > grippy {
border: 0;
}
#zotero-collections-splitter:not([state=collapsed]) > grippy,
#zotero-items-splitter:not([state=collapsed]) > grippy,
#zotero-tags-splitter:not([state=collapsed]) > grippy {
#zotero-tags-splitter:not([state=collapsed]) > grippy,
#zotero-context-splitter:not([state=collapsed]) > grippy,
#zotero-context-splitter-stacked:not([state=collapsed]) > grippy {
display: none;
}
#zotero-collections-tree, #zotero-items-tree, #zotero-view-item {
#zotero-collections-tree, #zotero-items-tree, .zotero-view-item {
-moz-appearance: none;
border-style: solid;
border-color: #818790;
@ -142,11 +154,11 @@ tree {
margin: .04em 0 0 .15em !important;
}
#zotero-editpane-tabs spacer {
.zotero-editpane-tabs spacer {
border: 0;
}
#zotero-view-item {
.zotero-view-item {
padding: 0 !important;
-moz-appearance: none;
background-color: -moz-field;
@ -154,7 +166,7 @@ tree {
border-color: var(--theme-border-color);
}
#zotero-view-tabbox > tabs {
.zotero-editpane-tabs {
margin-top: 2px;
}
@ -163,8 +175,8 @@ tree {
border-width: 0;
}
#zotero-editpane-item-box > scrollbox, #zotero-view-item > tabpanel > vbox,
#zotero-editpane-tags > scrollbox, #zotero-editpane-related {
.zotero-editpane-item-box > scrollbox, .zotero-view-item > tabpanel > vbox,
#zotero-editpane-tags > scrollbox, .zotero-editpane-related {
padding-top: 5px;
}
@ -172,6 +184,6 @@ tree {
padding-left: 5px;
}
#zotero-view-item > tabpanel > vbox {
.zotero-view-item > tabpanel > vbox {
padding-left: 5px;
}

View file

@ -360,9 +360,19 @@
indexStatusRow.hidden = true;
}
var type = Zotero.Libraries.get(this.item.libraryID).libraryType;
var switherDeck = this._id('attachment-note-editor-switcher');
// Note editor
var noteEditor = this._id('attachment-note-editor');
if (this.displayNote && (this.displayNoteIfEmpty || this.item.getNote() != '')) {
if (type == 'group' || !Zotero.isPDFBuild) {
var noteEditor = this._id('attachment-note-editor-old');
switherDeck.selectedIndex = 0;
}
else {
var noteEditor = this._id('attachment-note-editor');
switherDeck.selectedIndex = 1;
}
if (this.displayNote && (this.displayNoteIfEmpty || this.item.note != '')) {
noteEditor.linksOnTop = true;
noteEditor.hidden = false;
@ -624,7 +634,10 @@
</grid>
</vbox>
<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
<deck id="attachment-note-editor-switcher" flex="1">
<oldzoteronoteeditor id="attachment-note-editor-old" notitle="1" flex="1"/>
<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
</deck>
<button id="select-button" hidden="true"/>
</vbox>

View file

@ -652,7 +652,7 @@
for (var i=0; i<itemTypes.length; i++) {
var name = itemTypes[i].name;
if (name != 'attachment' && name != 'note') {
if (name != 'attachment' && name != 'note' && name != 'annotation') {
this.itemTypeMenu.appendItem(itemTypes[i].localized, itemTypes[i].id);
}
}

View file

@ -335,7 +335,9 @@
break;
case 'note':
elementName = 'zoteronoteeditor';
var type = Zotero.Libraries.get(this.libraryID).libraryType;
var useOld = type == 'group' || !Zotero.isPDFBuild;
elementName = useOld ? 'oldzoteronoteeditor' : 'zoteronoteeditor';
break;
default:

View file

@ -1,99 +1,205 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
-->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="note-editor">
<resources>
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
</resources>
<implementation>
<!--
Public properties
-->
<field name="editable">false</field>
<field name="saveOnEdit">false</field>
<field name="displayTags">false</field>
<field name="displayRelated">false</field>
<field name="displayButton">false</field>
<field name="hideLinksContainer"/>
<field name="buttonCaption"/>
<field name="parentClickHandler"/>
<field name="keyDownHandler"/>
<field name="commandHandler"/>
<field name="clickHandler"/>
<field name="navigateHandler"/>
<field name="returnHandler"/>
<constructor><![CDATA[
this._destroyed = false;
this._noteEditorID = Zotero.Utilities.randomString();
this._iframe = document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view');
this._iframe.addEventListener('DOMContentLoaded', (e) => {
// For iframes without chrome priviledges, for unknown reasons,
// dataTransfer.getData() returns empty value for `drop` event
// when dragging something from the outside of Zotero
this._iframe.contentWindow.addEventListener('drop', (event) => {
this._iframe.contentWindow.wrappedJSObject.droppedData = Components.utils.cloneInto({
'text/plain': event.dataTransfer.getData('text/plain'),
'text/html': event.dataTransfer.getData('text/html'),
'zotero/annotation': event.dataTransfer.getData('zotero/annotation'),
'zotero/item': event.dataTransfer.getData('zotero/item')
}, this._iframe.contentWindow);
}, true);
this._initialized = true;
});
window.fillTooltip = (tooltip) => {
let node = window.document.tooltipNode.closest('*[title]');
if (!node || !node.getAttribute('title')) {
return false;
}
tooltip.setAttribute('label', node.getAttribute('title'));
return true;
}
this.saveSync = () => {
if (this._editorInstance) {
this._editorInstance.saveSync();
}
}
this.getCurrentInstance = () => {
return this._editorInstance;
}
this.initEditor = async (state) => {
if (this._editorInstance) {
this._editorInstance.uninit();
}
this._editorInstance = new Zotero.EditorInstance();
await this._editorInstance.init({
state,
item: this._item,
iframeWindow: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view').contentWindow,
popup: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-menu'),
onNavigate: this._navigateHandler,
readOnly: !this.editable,
disableUI: this.mode == 'merge',
onReturn: this._returnHandler,
placeholder: this.placeholder
});
if (this._onInitCallback) {
this._onInitCallback();
}
}
this.onInit = (callback) => {
if (this._editorInstance) {
return callback();
}
this._onInitCallback = callback;
}
this.notify = async (event, type, ids, extraData) => {
if (this._editorInstance) {
await this._editorInstance.notify(ids);
}
if (!this.item) return;
// Try to use the state from the item save event
let id = this.item.id;
if (ids.includes(id)) {
let state = extraData && extraData[id] && extraData[id].state;
if (state) {
if (extraData[id].noteEditorID !== this._editorInstance.instanceID) {
this.initEditor(state);
}
}
else {
let curValue = this.item.note;
if (curValue !== this._lastHtmlValue) {
this.initEditor();
}
}
this._lastHtmlValue = this.item.note;
}
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
this._id('links-box').refresh();
}
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
]]></constructor>
<property name="editorInstance" onget="return this._editorInstance"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
<property name="mode" onget="return this._mode;">
<setter>
<![CDATA[
// Duplicate default property settings here
this.editable = false;
this.saveOnEdit = false;
this.displayTags = false;
this.displayRelated = false;
this.displayButton = false;
switch (val) {
case 'view':
case 'merge':
if (this.noteField) {
this.noteField.onInit(ed => ed.setMode('readonly'));
}
break;
case 'edit':
if (this.noteField) {
this.noteField.onInit(ed => ed.setMode('design'));
}
this.editable = true;
this.saveOnEdit = true;
this.parentClickHandler = this.selectParent;
this.keyDownHandler = this.handleKeyDown;
this.commandHandler = this.save;
this.displayTags = true;
this.displayRelated = true;
break;
default:
throw ("Invalid mode '" + val + "' in noteeditor.xml");
}
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
this._id('links-box').mode = val;
]]>
// Duplicate default property settings here
this.editable = false;
this.displayTags = false;
this.displayRelated = false;
this.displayButton = false;
switch (val) {
case 'view':
case 'merge':
this.editable = false;
break;
case 'edit':
this.editable = true;
this.parentClickHandler = this.selectParent;
this.keyDownHandler = this.handleKeyDown;
this.commandHandler = this.save;
this.displayTags = true;
this.displayRelated = true;
break;
default:
throw ("Invalid mode '" + val + "' in noteeditor.xml");
}
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
this._id('links-box').mode = val;
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
this._id('links-box').refresh();
]]>
</setter>
</property>
<field name="returnHandler"/>
<property name="returnHandler" onget="return this._returnHandler;">
<setter>
<![CDATA[
this._returnHandler = val;
]]>
</setter>
</property>
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
@ -102,301 +208,156 @@
]]>
</setter>
</property>
<field name="_mtime"/>
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter><![CDATA[
this._item = val;
// TODO: use clientDateModified instead
this._mtime = val.getField('dateModified');
var parentKey = this.item.parentKey;
if (parentKey) {
this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
}
this._id('links-box').item = this.item;
this.refresh();
]]></setter>
return (async () => {
// `item` field can be set before the constructor is called
// or noteditor is attached to dom (which happens in the
// merge dialog i.e.), therefore we wait for the initialization
let n = 0;
while (!this._initialized && !this._destroyed) {
if (n >= 1000) {
throw new Error('Waiting for noteeditor initialization failed');
}
await Zotero.Promise.delay(10);
n++;
}
// The binding can also be immediately destructed
// (which also happens in the merge dialog)
if (this._destroyed) {
return;
}
if (!val) this._item = null;
if (this._item && this._item.id === val.id) return;
this._lastHtmlValue = val.note;
this._item = val;
this.initEditor();
// var parentKey = this._item.parentKey;
// if (parentKey) {
// this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
// }
this._id('links-box').item = this._item;
})();
]]></setter>
</property>
<property name="linksOnTop">
<setter>
<![CDATA[
if(val) {
var container = this._id('links-container');
var parent = container.parentNode;
var sib = container.nextSibling;
while (parent.firstChild !== container) {
parent.insertBefore(parent.removeChild(parent.firstChild), sib);
}
// if (val) {
// var container = this._id('links-container');
// var parent = container.parentNode;
// var sib = container.nextSibling;
// while (parent.firstChild !== container) {
// parent.insertBefore(parent.removeChild(parent.firstChild), sib);
// }
// }
]]>
</setter>
</property>
<property name="navigateHandler">
<setter>
<![CDATA[
if (this._editorInstance) {
this._editorInstance.onNavigate = val;
}
this._navigateHandler = val;
]]>
</setter>
</property>
<property name="note"
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
<property name="ref" onget="return this._item" onset="this.item = val"/>
<property name="hideLinksContainer">
<setter>
<![CDATA[
this._hideLinksContainer = val;
this._id('links-container').hidden = val;
]]>
</setter>
</property>
<field name="collection"/>
<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
<constructor>
<![CDATA[
this.instanceID = Zotero.Utilities.randomString();
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
]]>
</constructor>
<destructor>
<![CDATA[
<![CDATA[
Zotero.Notifier.unregisterObserver(this._notifierID);
if (this._editorInstance) {
this._editorInstance.uninit();
}
this._destroyed = true;
this._initialized = false;
this._editorInstance = null;
]]>
</destructor>
<method name="notify">
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
<parameter name="extraData"/>
<body><![CDATA[
if (event != 'modify' || !this.item || !this.item.id) return;
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
if (id != this.item.id) {
continue;
}
if (extraData && extraData[id] && extraData[id].noteEditorID == this.instanceID) {
//Zotero.debug("Skipping notification from current note field");
continue;
}
if (this.noteField.changed) {
//Zotero.debug("Note has changed since last save -- skipping refresh");
return;
}
this.refresh();
break;
}
]]></body>
</method>
<method name="refresh">
<body><![CDATA[
Zotero.debug('Refreshing note editor');
var textbox = this.noteField;
var textboxReadOnly = this._id('noteFieldReadOnly');
var button = this._id('goButton');
if (this.editable) {
textbox.hidden = false;
textboxReadOnly.hidden = true;
}
else {
textbox.hidden = true;
textboxReadOnly.hidden = false;
textbox = textboxReadOnly;
}
//var scrollPos = textbox.inputField.scrollTop;
if (this.item) {
// For sanity check in save()
textbox.setAttribute('itemID', this.item.id);
textbox.value = this.item.getNote();
}
else {
textbox.value = '';
textbox.removeAttribute('itemID');
}
//textbox.inputField.scrollTop = scrollPos;
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
this._id('links-box').refresh();
if (this.keyDownHandler) {
textbox.setAttribute('onkeydown',
'document.getBindingParent(this).handleKeyDown(event)');
}
else {
textbox.removeAttribute('onkeydown');
}
if (this.commandHandler) {
textbox.setAttribute('oncommand',
'document.getBindingParent(this).commandHandler()');
}
else {
textbox.removeAttribute('oncommand');
}
if (this.displayButton) {
button.label = this.buttonCaption;
button.hidden = false;
button.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
button.hidden = true;
}
]]></body>
</method>
<method name="save">
<body><![CDATA[
return Zotero.spawn(function* () {
try {
if (this._mode == 'view') {
Zotero.debug("Not saving read-only note");
return;
}
var noteField = this._id('noteField');
var value = noteField.value;
if (value === null) {
Zotero.debug("Note value not available -- not saving", 2);
return;
}
// Update note
if (this.item) {
// If note field doesn't match item, abort save and run error handler
if (noteField.getAttribute('itemID') != this.item.id) {
throw new Error("Note field doesn't match current item");
}
let changed = this.item.setNote(value);
if (changed && this.saveOnEdit) {
this.noteField.changed = false;
yield this.item.saveTx({
notifierData: {
noteEditorID: this.instanceID
}
});
}
return;
}
// Create new note
var item = new Zotero.Item('note');
if (this.parentItem) {
item.libraryID = this.parentItem.libraryID;
}
item.setNote(value);
if (this.parentItem) {
item.parentKey = this.parentItem.key;
}
if (this.saveOnEdit) {
var id = yield item.saveTx();
if (!this.parentItem && this.collection) {
this.collection.addItem(id);
}
}
this.item = item;
}
catch (e) {
Zotero.logError(e);
if (this.hasAttribute('onerror')) {
let fn = new Function("", this.getAttribute('onerror'));
fn.call(this)
}
if (this.onError) {
this.onError(e);
}
}
}.bind(this));
]]></body>
return (async () => {
})();
]]></body>
</method>
<!-- Used to insert a tab manually -->
<method name="handleKeyDown">
<parameter name="event"/>
<body>
<![CDATA[
var noteField = this._id('noteField');
switch (event.keyCode) {
case 9:
// On Shift-Tab, if focus was moved out of the note, focus the element
// specified in the 'previousfocus' attribute. We check for focus
// because Shift-Tab doesn't and shouldn't move focus out of the note if
// the cursor is in a list.
if (event.shiftKey) {
let id = this.getAttribute('previousfocus');
if (id) {
setTimeout(() => {
if (!noteField.hasFocus()) {
document.getElementById(id).focus();
}
}, 0);
}
return;
}
break;
}
]]>
</body>
</method>
<method name="focus">
<body>
<![CDATA[
this._id('noteField').focus();
setTimeout(() => {
if (this._iframe && this._iframe.contentWindow) {
this._iframe.focus();
this._editorInstance.focus();
}
}, 500);
]]>
</body>
</method>
<method name="clearUndo">
<body>
<![CDATA[
this._id('noteField').clearUndo();
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]>
</body>
</method>
</implementation>
<content>
<xul:vbox xbl:inherits="flex">
<xul:textbox id="noteField" type="styled" mode="note"
timeout="1000" flex="1" hidden="true"/>
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
readonly="true" flex="1" hidden="true"/>
<xul:vbox xbl:inherits="flex" style="display: flex;flex-direction: column;flex-grow: 1;">
<xul:iframe tooltip="editor-tooltip" anonid="editor-view" flex="1" overflow="auto" style="width: 100%;margin-right: 5px;border: 0;width: 100%;margin-right: 5px;border: 0;flex-grow: 1;"
frameBorder="0" src="resource://zotero/note-editor/editor.html" type="content"/>
<xul:hbox id="links-container" hidden="true">
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
</xul:hbox>
<xul:button id="goButton" hidden="true"/>
<xul:popupset>
<xul:tooltip id="editor-tooltip" onpopupshowing="return fillTooltip(this);"/>
<xul:menupopup anonid="editor-menu" id="editor-menu" flex="1">
</xul:menupopup>
</xul:popupset>
</xul:vbox>
</content>
</binding>
<binding id="links-box">
<implementation>
<implementation>
<field name="itemRef"/>
<property name="item" onget="return this.itemRef;">
<setter>
<![CDATA[
this.itemRef = val;
this.id('tags').item = this.item;
this.id('related').item = this.item;
this.refresh();
@ -406,127 +367,131 @@
<property name="mode">
<setter>
<![CDATA[
this.id('related').mode = val;
this.id('tags').mode = val;
]]>
this.id('related').mode = val;
this.id('tags').mode = val;
]]>
</setter>
</property>
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
this._parentItem = val;
var parentText = this.id('parentText');
if (parentText.firstChild) {
parentText.removeChild(parentText.firstChild);
}
if (this._parentItem && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
}
]]>
this._parentItem = val;
var parentText = this.id('parentText');
if (parentText.firstChild) {
parentText.removeChild(parentText.firstChild);
}
if (this._parentItem && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
}
]]>
</setter>
</property>
<method name="tagsClick">
<body><![CDATA[
this.id('tags').reload();
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false);
// If editable and no existing tags, open new empty row
var tagsBox = this.id('tags');
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
this.id('tags').newTag();
}
]]></body>
this.id('tags').reload();
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false);
// If editable and no existing tags, open new empty row
var tagsBox = this.id('tags');
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
this.id('tags').newTag();
}
]]></body>
</method>
<method name="refresh">
<body><![CDATA[
this.updateTagsSummary();
this.updateRelatedSummary();
]]></body>
this.updateTagsSummary();
this.updateRelatedSummary();
]]></body>
</method>
<method name="updateTagsSummary">
<body><![CDATA[
var v = this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
]]></body>
var v = this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
]]></body>
</method>
<method name="relatedClick">
<body><![CDATA[
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false);
}
else {
this.id('related').add();
}
]]></body>
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false);
}
else {
this.id('related').add();
}
]]></body>
</method>
<method name="updateRelatedSummary">
<body><![CDATA[
var v = this.id('related').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v;
]]></body>
var v = this.id('related').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v;
]]></body>
</method>
<method name="parentClick">
<body>
<![CDATA[
if (!this.item || !this.item.id) {
return;
}
if (document.getElementById('zotero-pane')) {
var zp = ZoteroPane;
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
if (!lastWin) {
var lastWin = window.open();
if (!this.item || !this.item.id) {
return;
}
var zp = lastWin.ZoteroPane;
}
Zotero.spawn(function* () {
var parentID = this.item.parentID;
yield zp.clearQuicksearch();
zp.selectItem(parentID);
}, this);
]]>
if (document.getElementById('zotero-pane')) {
var zp = ZoteroPane;
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
if (!lastWin) {
var lastWin = window.open();
}
if (lastWin.ZoteroOverlay && !lastWin.ZoteroPane.isShowing()) {
lastWin.ZoteroOverlay.toggleDisplay(true);
}
var zp = lastWin.ZoteroPane;
}
Zotero.spawn(function* () {
var parentID = this.item.parentID;
yield zp.clearQuicksearch();
zp.selectItem(parentID);
}, this);
]]>
</body>
</method>
<method name="id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]>
</body>
</method>
@ -541,15 +506,18 @@
<xul:rows>
<xul:row id="parent-row" hidden="true">
<xul:label id="parentLabel"/>
<xul:label id="parentText" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).parentClick();"/>
<xul:label id="parentText" class="zotero-clicky" crop="end"
onclick="document.getBindingParent(this).parentClick();"/>
</xul:row>
<xul:row>
<xul:label id="relatedLabel"/>
<xul:label id="relatedClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).relatedClick();"/>
<xul:label id="relatedClick" class="zotero-clicky" crop="end"
onclick="document.getBindingParent(this).relatedClick();"/>
</xul:row>
<xul:row>
<xul:label id="tagsLabel"/>
<xul:label id="tagsClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
<xul:label id="tagsClick" class="zotero-clicky" crop="end"
onclick="document.getBindingParent(this).tagsClick();"/>
</xul:row>
</xul:rows>
</xul:grid>
@ -562,17 +530,17 @@
seems to get triggered by these events for reasons that are less than
clear) so that we can manually refresh the popup if it's open after
autocomplete is used to prevent it from becoming unresponsive
Note: Code in tagsbox.xml is dependent on the DOM path between the
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
-->
<xul:menupopup id="tagsPopup" ignorekeys="true"
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
<xul:tagsbox id="tags" flex="1" mode="edit"/>
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
<xul:tagsbox id="tags" flex="1" mode="edit" style="display: flex"/>
</xul:menupopup>
</xul:popupset>
</xul:vbox>
</content>
</binding>
</bindings>
</bindings>

View file

@ -0,0 +1,578 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
-->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="note-editor">
<resources>
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
</resources>
<implementation>
<!--
Public properties
-->
<field name="editable">false</field>
<field name="saveOnEdit">false</field>
<field name="displayTags">false</field>
<field name="displayRelated">false</field>
<field name="displayButton">false</field>
<field name="buttonCaption"/>
<field name="parentClickHandler"/>
<field name="keyDownHandler"/>
<field name="commandHandler"/>
<field name="clickHandler"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
<property name="mode" onget="return this._mode;">
<setter>
<![CDATA[
// Duplicate default property settings here
this.editable = false;
this.saveOnEdit = false;
this.displayTags = false;
this.displayRelated = false;
this.displayButton = false;
switch (val) {
case 'view':
case 'merge':
if (this.noteField) {
this.noteField.onInit(ed => ed.setMode('readonly'));
}
break;
case 'edit':
if (this.noteField) {
this.noteField.onInit(ed => ed.setMode('design'));
}
this.editable = true;
this.saveOnEdit = true;
this.parentClickHandler = this.selectParent;
this.keyDownHandler = this.handleKeyDown;
this.commandHandler = this.save;
this.displayTags = true;
this.displayRelated = true;
break;
default:
throw ("Invalid mode '" + val + "' in noteeditor.xml");
}
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
this._id('links-box').mode = val;
]]>
</setter>
</property>
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
this._parentItem = this._id('links-box').parentItem = val;
]]>
</setter>
</property>
<field name="_mtime"/>
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter><![CDATA[
this._item = val;
// TODO: use clientDateModified instead
this._mtime = val.getField('dateModified');
var parentKey = this.item.parentKey;
if (parentKey) {
this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
}
this._id('links-box').item = this.item;
this.refresh();
]]></setter>
</property>
<property name="linksOnTop">
<setter>
<![CDATA[
if(val) {
var container = this._id('links-container');
var parent = container.parentNode;
var sib = container.nextSibling;
while (parent.firstChild !== container) {
parent.insertBefore(parent.removeChild(parent.firstChild), sib);
}
}
]]>
</setter>
</property>
<property name="note"
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
<property name="ref" onget="return this._item" onset="this.item = val"/>
<field name="collection"/>
<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
<constructor>
<![CDATA[
this.instanceID = Zotero.Utilities.randomString();
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
]]>
</constructor>
<destructor>
<![CDATA[
Zotero.Notifier.unregisterObserver(this._notifierID);
]]>
</destructor>
<method name="notify">
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
<parameter name="extraData"/>
<body><![CDATA[
if (event != 'modify' || !this.item || !this.item.id) return;
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
if (id != this.item.id) {
continue;
}
if (extraData && extraData[id] && extraData[id].noteEditorID == this.instanceID) {
//Zotero.debug("Skipping notification from current note field");
continue;
}
if (this.noteField.changed) {
//Zotero.debug("Note has changed since last save -- skipping refresh");
return;
}
this.refresh();
break;
}
]]></body>
</method>
<method name="refresh">
<body><![CDATA[
Zotero.debug('Refreshing note editor');
var textbox = this.noteField;
var textboxReadOnly = this._id('noteFieldReadOnly');
var button = this._id('goButton');
if (this.editable) {
textbox.hidden = false;
textboxReadOnly.hidden = true;
}
else {
textbox.hidden = true;
textboxReadOnly.hidden = false;
textbox = textboxReadOnly;
}
//var scrollPos = textbox.inputField.scrollTop;
if (this.item) {
// For sanity check in save()
textbox.setAttribute('itemID', this.item.id);
textbox.value = this.item.getNote();
}
else {
textbox.value = '';
textbox.removeAttribute('itemID');
}
//textbox.inputField.scrollTop = scrollPos;
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
this._id('links-box').refresh();
if (this.keyDownHandler) {
textbox.setAttribute('onkeydown',
'document.getBindingParent(this).handleKeyDown(event)');
}
else {
textbox.removeAttribute('onkeydown');
}
if (this.commandHandler) {
textbox.setAttribute('oncommand',
'document.getBindingParent(this).commandHandler()');
}
else {
textbox.removeAttribute('oncommand');
}
if (this.displayButton) {
button.label = this.buttonCaption;
button.hidden = false;
button.setAttribute('oncommand',
'document.getBindingParent(this).clickHandler(this)');
}
else {
button.hidden = true;
}
]]></body>
</method>
<method name="save">
<body><![CDATA[
return Zotero.spawn(function* () {
try {
if (this._mode == 'view') {
Zotero.debug("Not saving read-only note");
return;
}
var noteField = this._id('noteField');
var value = noteField.value;
if (value === null) {
Zotero.debug("Note value not available -- not saving", 2);
return;
}
// Update note
if (this.item) {
// If note field doesn't match item, abort save and run error handler
if (noteField.getAttribute('itemID') != this.item.id) {
throw new Error("Note field doesn't match current item");
}
let changed = this.item.setNote(value);
if (changed && this.saveOnEdit) {
this.noteField.changed = false;
yield this.item.saveTx({
notifierData: {
noteEditorID: this.instanceID
}
});
}
return;
}
// Create new note
var item = new Zotero.Item('note');
if (this.parentItem) {
item.libraryID = this.parentItem.libraryID;
}
item.setNote(value);
if (this.parentItem) {
item.parentKey = this.parentItem.key;
}
if (this.saveOnEdit) {
var id = yield item.saveTx();
if (!this.parentItem && this.collection) {
this.collection.addItem(id);
}
}
this.item = item;
}
catch (e) {
Zotero.logError(e);
if (this.hasAttribute('onerror')) {
let fn = new Function("", this.getAttribute('onerror'));
fn.call(this)
}
if (this.onError) {
this.onError(e);
}
}
}.bind(this));
]]></body>
</method>
<!-- Used to insert a tab manually -->
<method name="handleKeyDown">
<parameter name="event"/>
<body>
<![CDATA[
var noteField = this._id('noteField');
switch (event.keyCode) {
case 9:
// On Shift-Tab, if focus was moved out of the note, focus the element
// specified in the 'previousfocus' attribute. We check for focus
// because Shift-Tab doesn't and shouldn't move focus out of the note if
// the cursor is in a list.
if (event.shiftKey) {
let id = this.getAttribute('previousfocus');
if (id) {
setTimeout(() => {
if (!noteField.hasFocus()) {
document.getElementById(id).focus();
}
}, 0);
}
return;
}
break;
}
]]>
</body>
</method>
<method name="focus">
<body>
<![CDATA[
this._id('noteField').focus();
]]>
</body>
</method>
<method name="clearUndo">
<body>
<![CDATA[
this._id('noteField').clearUndo();
]]>
</body>
</method>
<method name="_id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
]]>
</body>
</method>
</implementation>
<content>
<xul:vbox xbl:inherits="flex">
<xul:textbox id="noteField" type="styled" mode="note"
timeout="1000" flex="1" hidden="true"/>
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
readonly="true" flex="1" hidden="true"/>
<xul:hbox id="links-container" hidden="true">
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
</xul:hbox>
<xul:button id="goButton" hidden="true"/>
</xul:vbox>
</content>
</binding>
<binding id="links-box">
<implementation>
<field name="itemRef"/>
<property name="item" onget="return this.itemRef;">
<setter>
<![CDATA[
this.itemRef = val;
this.id('tags').item = this.item;
this.id('related').item = this.item;
this.refresh();
]]>
</setter>
</property>
<property name="mode">
<setter>
<![CDATA[
this.id('related').mode = val;
this.id('tags').mode = val;
]]>
</setter>
</property>
<field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
this._parentItem = val;
var parentText = this.id('parentText');
if (parentText.firstChild) {
parentText.removeChild(parentText.firstChild);
}
if (this._parentItem && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
}
]]>
</setter>
</property>
<method name="tagsClick">
<body><![CDATA[
this.id('tags').reload();
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false);
// If editable and no existing tags, open new empty row
var tagsBox = this.id('tags');
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
this.id('tags').newTag();
}
]]></body>
</method>
<method name="refresh">
<body><![CDATA[
this.updateTagsSummary();
this.updateRelatedSummary();
]]></body>
</method>
<method name="updateTagsSummary">
<body><![CDATA[
var v = this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
]]></body>
</method>
<method name="relatedClick">
<body><![CDATA[
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false);
}
else {
this.id('related').add();
}
]]></body>
</method>
<method name="updateRelatedSummary">
<body><![CDATA[
var v = this.id('related').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v;
]]></body>
</method>
<method name="parentClick">
<body>
<![CDATA[
if (!this.item || !this.item.id) {
return;
}
if (document.getElementById('zotero-pane')) {
var zp = ZoteroPane;
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var lastWin = wm.getMostRecentWindow("navigator:browser");
if (!lastWin) {
var lastWin = window.open();
}
var zp = lastWin.ZoteroPane;
}
Zotero.spawn(function* () {
var parentID = this.item.parentID;
yield zp.clearQuicksearch();
zp.selectItem(parentID);
}, this);
]]>
</body>
</method>
<method name="id">
<parameter name="id"/>
<body>
<![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
]]>
</body>
</method>
</implementation>
<content>
<xul:vbox xbl:inherits="flex">
<xul:grid>
<xul:columns>
<xul:column/>
<xul:column flex="1"/>
</xul:columns>
<xul:rows>
<xul:row id="parent-row" hidden="true">
<xul:label id="parentLabel"/>
<xul:label id="parentText" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).parentClick();"/>
</xul:row>
<xul:row>
<xul:label id="relatedLabel"/>
<xul:label id="relatedClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).relatedClick();"/>
</xul:row>
<xul:row>
<xul:label id="tagsLabel"/>
<xul:label id="tagsClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
</xul:row>
</xul:rows>
</xul:grid>
<xul:popupset>
<xul:menupopup id="relatedPopup" width="300" onpopupshowing="this.firstChild.refresh();">
<xul:relatedbox id="related" flex="1"/>
</xul:menupopup>
<!-- The onpopup* stuff is an ugly hack to keep track of when the
popup is open (and not the descendent autocomplete popup, which also
seems to get triggered by these events for reasons that are less than
clear) so that we can manually refresh the popup if it's open after
autocomplete is used to prevent it from becoming unresponsive
Note: Code in tagsbox.xml is dependent on the DOM path between the
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
-->
<xul:menupopup id="tagsPopup" ignorekeys="true"
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
<xul:tagsbox id="tags" flex="1" mode="edit"/>
</xul:menupopup>
</xul:popupset>
</xul:vbox>
</content>
</binding>
</bindings>

View file

@ -55,4 +55,5 @@ function i(name, svgOrSrc, hasDPI=true) {
i('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png")
i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.png")
i('Xmark', "chrome://zotero/skin/xmark.png")

View file

@ -0,0 +1,93 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 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 *****
*/
import React, { forwardRef, useImperativeHandle, useState } from 'react';
import cx from 'classnames';
const MAX_ALL_NOTES = 7;
const NoteRow = ({ title, body, date, onClick, parentItemType, parentTitle }) => {
return (
<div className={cx('note-row', { 'standalone-note-row': !parentItemType })} onClick={onClick}>
<div className="inner">
{ parentItemType
? <div className="parent-line">
<img className="parent-item-type" src={Zotero.ItemTypes.getImageSrc(parentItemType)} />
<span className="parent-title">{parentTitle}</span>
</div>
: null
}
<div className="title-line">
<div className="title">{title}</div>
</div>
<div className="body-line">
<div className="date">{date}</div>
<div className="body">{body}</div>
</div>
</div>
</div>
);
};
const NotesList = forwardRef(({ onClick, onAddChildButtonDown, onAddStandaloneButtonDown }, ref) => {
const [notes, setNotes] = useState([]);
const [expanded, setExpanded] = useState(false);
const [hasParent, setHasParent] = useState(true);
useImperativeHandle(ref, () => ({ setNotes, setExpanded, setHasParent }));
function handleClickMore() {
setExpanded(true);
}
let childNotes = notes.filter(x => x.isCurrentChild);
let allNotes = notes.filter(x => !x.isCurrentChild);
return (
<div className="notes-list">
{hasParent && <section>
<div className="header-row">
<h2>{Zotero.getString('pane.context.itemNotes')}</h2>
<button onMouseDown={onAddChildButtonDown}>+</button>
</div>
{!childNotes.length && <div className="empty-row">{Zotero.getString('pane.context.noNotes')}</div>}
{childNotes.map(note => <NoteRow key={note.id} {...note} onClick={() => onClick(note.id)}/>)}
</section>}
<section>
<div className="header-row">
<h2>{Zotero.getString('pane.context.allNotes')}</h2>
<button onMouseDown={onAddStandaloneButtonDown}>+</button>
</div>
{!allNotes.length && <div className="empty-row">{Zotero.getString('pane.context.noNotes')}</div>}
{(expanded ? allNotes : allNotes.slice(0, MAX_ALL_NOTES)).map(note => <NoteRow key={note.id} {...note} onClick={() => onClick(note.id)}/>)}
{!expanded && allNotes.length > MAX_ALL_NOTES
&& <div className="more-row" onClick={handleClickMore}>{
Zotero.getString('general.numMore', Zotero.Utilities.numberFormat([allNotes.length - MAX_ALL_NOTES], 0))
}</div>
}
</section>
</div>
);
});
export default NotesList;

View file

@ -0,0 +1,129 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 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 React, { forwardRef, useState, useRef, useImperativeHandle, useEffect } from 'react';
import cx from 'classnames';
const { IconXmark } = require('./icons');
const TabBar = forwardRef(function (props, ref) {
const [tabs, setTabs] = useState([]);
const draggingID = useRef(null);
const tabsRef = useRef();
const mouseMoveWaitUntil = useRef(0);
useEffect(() => {
window.addEventListener('mouseup', handleMouseUp);
return () => {
window.removeEventListener('mouseup', handleMouseUp);
};
}, []);
useImperativeHandle(ref, () => ({ setTabs }));
function handleMouseDown(event, id, index) {
if (event.target.closest('.tab-close')) {
return;
}
if (index != 0) {
draggingID.current = id;
}
props.onTabSelect(id);
event.stopPropagation();
}
function handleMouseMove(event) {
if (!draggingID.current || mouseMoveWaitUntil.current > Date.now()) {
return;
}
let points = Array.from(tabsRef.current.children).map((child) => {
let rect = child.getBoundingClientRect();
return rect.left + rect.width / 2;
});
let index = null;
for (let i = 0; i < points.length - 1; i++) {
let point1 = points[i];
let point2 = points[i + 1];
if (event.clientX > Math.min(point1, point2)
&& event.clientX < Math.max(point1, point2)) {
index = i + 1;
break;
}
}
if (index === null) {
let point1 = points[0];
let point2 = points[points.length - 1];
if ((point1 < point2 && event.clientX < point1
|| point1 > point2 && event.clientX > point1)) {
index = 0;
}
else {
index = points.length;
}
}
if (index == 0) {
index = 1;
}
props.onTabMove(draggingID.current, index);
mouseMoveWaitUntil.current = Date.now() + 100;
}
function handleMouseUp(event) {
draggingID.current = null;
event.stopPropagation();
}
function handleTabClose(event, id) {
props.onTabClose(id);
event.stopPropagation();
}
function renderTab({ id, title, selected }, index) {
return (
<div
key={id}
className={cx('tab', { selected })}
onMouseDown={(event) => handleMouseDown(event, id, index)}
>
<div className="tab-name">{title}</div>
<div
className="tab-close"
onClick={(event) => handleTabClose(event, id)}
>
<IconXmark/>
</div>
</div>
);
}
return (
<div className="tabs" ref={tabsRef} onMouseMove={handleMouseMove}>
{tabs.map((tab, index) => renderTab(tab, index))}
</div>
);
});
export default TabBar;

View file

@ -0,0 +1,718 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, Virginia, USA
https://digitalscholar.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 *****
*/
// TODO: Fix import/require related isues that might be
// related with `require` not reusing the context
var React = require('react');
var ReactDOM = require('react-dom');
var TagsBoxContainer = require('containers/tagsBoxContainer').default;
var NotesList = require('components/itemPane/notesList').default;
var ZoteroContextPane = new function () {
const HTML_NS = 'http://www.w3.org/1999/xhtml';
var _tabCover;
var _contextPane;
var _contextPaneInner;
var _contextPaneSplitter;
var _contextPaneSplitterStacked;
var _itemToggle;
var _notesToggle;
var _panesDeck;
var _itemPaneDeck;
var _notesPaneDeck;
var _itemToolbar;
var _splitButton;
var _itemPaneToggle;
var _notesPaneToggle;
var _toolbar;
var _tabToolbarContainer;
var _itemContexts = [];
var _notesContexts = [];
// Using attribute instead of propery to set 'selectedIndex'
// is more reliable
this.update = _update;
this.getActiveEditor = _getActiveEditor;
this.onLoad = function () {
if (!Zotero) {
return;
}
_tabCover = document.getElementById('zotero-tab-cover');
_itemToggle = document.getElementById('zotero-tb-toggle-item-pane');
_notesToggle = document.getElementById('zotero-tb-toggle-notes-pane');
_contextPane = document.getElementById('zotero-context-pane');
_contextPaneInner = document.getElementById('zotero-context-pane-inner');
_contextPaneSplitter = document.getElementById('zotero-context-splitter');
_contextPaneSplitterStacked = document.getElementById('zotero-context-splitter-stacked');
_itemToolbar = document.getElementById('zotero-item-toolbar');
_splitButton = document.getElementById('zotero-tb-split');
_itemPaneToggle = document.getElementById('zotero-tb-toggle-item-pane');
_notesPaneToggle = document.getElementById('zotero-tb-toggle-notes-pane');
_toolbar = document.getElementById('zotero-toolbar');
_tabToolbarContainer = document.getElementById('zotero-tab-toolbar-container');
_init();
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane');
window.addEventListener('resize', _update);
_itemToggle.addEventListener('click', _toggleItemButton);
_notesToggle.addEventListener('click', _toggleNotesButton);
Zotero.Reader.onChangeSidebarWidth = _updatePaneWidth;
Zotero.Reader.onChangeSidebarOpen = _updatePaneWidth;
this._mutationObserver = new MutationObserver(() => {
_updateToolbarWidth();
// Sometimes XUL is late to reflow
setTimeout(_updateToolbarWidth, 100);
});
this._mutationObserver.observe(_tabToolbarContainer, { attributes: true, childList: true, subtree: true });
};
this.onUnload = function () {
_itemToggle.removeEventListener('click', _toggleItemButton);
_notesToggle.removeEventListener('click', _toggleNotesButton);
window.removeEventListener('resize', _update);
Zotero.Notifier.unregisterObserver(this._notifierID);
this._mutationObserver.disconnect();
Zotero.Reader.onChangeSidebarWidth = () => {};
Zotero.Reader.onChangeSidebarOpen = () => {};
_contextPaneInner.innerHTML = '';
_itemContexts = [];
_notesContexts = [];
};
this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) {
if (type == 'item') {
// Update, remove or re-create item panes
for (let context of _itemContexts.slice()) {
let item = Zotero.Items.get(context.itemID);
if (!item) {
_removeItemContext(context.tabID);
}
else if (item.parentID != context.parentID) {
_removeItemContext(context.tabID);
_addItemContext(context.tabID, context.itemID);
}
else {
context.update();
}
}
// Update notes lists for affected libraries
let libraryIDs = [];
for (let id of ids) {
let item = Zotero.Items.get(id);
if (item && item.isNote()) {
libraryIDs.push(item.libraryID);
}
else if (action == 'delete') {
libraryIDs.push(extraData[id].libraryID);
}
}
for (let context of _notesContexts) {
if (libraryIDs.includes(context.libraryID)) {
context.update();
}
}
}
else if (type == 'tab') {
if (action == 'add') {
_addItemContext(ids[0], extraData[ids[0]].itemID);
}
else if (action == 'close') {
_removeItemContext(ids[0]);
if (Zotero_Tabs.deck.children.length == 1) {
_notesContexts.forEach(x => x.notesListRef.current.setExpanded(false));
}
}
else if (action == 'select') {
if (Zotero_Tabs.selectedIndex == 0) {
_contextPaneSplitter.setAttribute('hidden', true);
_contextPane.setAttribute('collapsed', true);
_toolbar.append(_itemToolbar);
_itemToolbar.classList.remove('tab-mode');
_splitButton.classList.add('hidden');
_tabCover.hidden = true;
}
else {
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (reader) {
_tabCover.hidden = false;
(async () => {
await reader._initPromise;
_tabCover.hidden = true;
})();
var attachment = Zotero.Items.get(reader.itemID);
_selectNotesContext(attachment.libraryID);
var notesContext = _getNotesContext(attachment.libraryID);
notesContext.updateFromCache();
}
_contextPaneSplitter.setAttribute('hidden', false);
_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
_tabToolbarContainer.append(_itemToolbar);
_itemToolbar.classList.add('tab-mode');
_splitButton.classList.remove('hidden');
}
_selectItemContext(ids[0]);
_update();
}
}
});
function _toggleItemButton() {
_togglePane(0);
}
function _toggleNotesButton() {
_togglePane(1);
}
function _removeNote(id) {
var ps = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
.getService(Components.interfaces.nsIPromptService);
if (ps.confirm(null, '', Zotero.getString('pane.item.notes.delete.confirm'))) {
Zotero.Items.trashTx(id);
}
}
function _getActiveEditor() {
var splitter;
if (Zotero.Prefs.get('layout') == 'stacked') {
splitter = _contextPaneSplitterStacked;
}
else {
splitter = _contextPaneSplitter;
}
if (splitter.getAttribute('state') != 'collapsed') {
if (_panesDeck.selectedIndex == 0) {
let child = _itemPaneDeck.selectedPanel;
if (child) {
var tabPanels = child.querySelector('tabpanels');
if (tabPanels && tabPanels.selectedIndex == 1) {
var notesDeck = child.querySelector('.notes-deck');
if (notesDeck.selectedIndex == 1) {
return child.querySelector('zoteronoteeditor');
}
}
}
}
else {
var node = _notesPaneDeck.selectedPanel;
if (node.selectedIndex == 1) {
return node.querySelector('zoteronoteeditor');
}
}
}
}
function _updateAddToNote() {
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (reader) {
var editor = _getActiveEditor();
reader.enableAddToNote(!!editor);
}
}
function _updatePaneWidth() {
var stacked = Zotero.Prefs.get('layout') == 'stacked';
var width = Zotero.Reader.getSidebarWidth() + 'px';
if (!Zotero.Reader.getSidebarOpen()) {
width = 0;
}
_contextPane.style.left = stacked ? width : 'unset';
}
function _updateToolbarWidth() {
var stacked = Zotero.Prefs.get('layout') == 'stacked';
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (reader) {
if ((stacked || _contextPaneSplitter.getAttribute('state') == 'collapsed')) {
reader.setToolbarPlaceholderWidth(_tabToolbarContainer.boxObject.width);
}
else {
reader.setToolbarPlaceholderWidth(0);
}
}
}
function _update() {
if (Zotero_Tabs.selectedIndex == 0) {
return;
}
var splitter;
var stacked = Zotero.Prefs.get('layout') == 'stacked';
if (stacked) {
_contextPaneSplitterStacked.setAttribute('hidden', false);
_contextPaneSplitter.setAttribute('state', 'open');
_contextPaneSplitter.setAttribute('hidden', true);
_contextPane.classList.add('stacked');
_contextPane.classList.remove('standard');
splitter = _contextPaneSplitterStacked;
}
else {
_contextPaneSplitter.setAttribute('hidden', false);
_contextPaneSplitterStacked.setAttribute('hidden', true);
_contextPaneSplitterStacked.setAttribute('state', 'open');
_contextPane.classList.add('standard');
_contextPane.classList.remove('stacked');
splitter = _contextPaneSplitter;
}
var collapsed = splitter.getAttribute('state') == 'collapsed';
var selectedIndex = _panesDeck.selectedIndex;
if (!collapsed && selectedIndex == 0) {
_itemPaneToggle.classList.add('toggled');
}
else {
_itemPaneToggle.classList.remove('toggled');
}
if (!collapsed && selectedIndex == 1) {
_notesPaneToggle.classList.add('toggled');
}
else {
_notesPaneToggle.classList.remove('toggled');
}
if (Zotero_Tabs.selectedIndex > 0) {
var height = 0;
if (Zotero.Prefs.get('layout') == 'stacked'
&& _contextPane.getAttribute('collapsed') != 'true') {
height = _contextPaneInner.boxObject.height;
}
Zotero.Reader.setBottomPlaceholderHeight(height);
}
_updatePaneWidth();
_updateToolbarWidth();
_updateAddToNote();
}
function _togglePane(paneIndex) {
var splitter = Zotero.Prefs.get('layout') == 'stacked'
? _contextPaneSplitterStacked : _contextPaneSplitter;
var isOpen = splitter.getAttribute('state') != 'collapsed';
var hide = false;
var currentPane = _panesDeck.selectedIndex;
if (isOpen && currentPane == paneIndex) {
hide = true;
}
else {
_panesDeck.setAttribute('selectedIndex', paneIndex);
}
splitter.setAttribute('state', hide ? 'collapsed' : 'open');
_update();
}
function _init() {
// vbox
var vbox = document.createElement('vbox');
vbox.setAttribute('flex', '1');
_contextPaneInner.append(vbox);
// Toolbar extension
var toolbarExtension = document.createElement('box');
toolbarExtension.style.height = '32px';
toolbarExtension.id = 'zotero-context-toolbar-extension';
_panesDeck = document.createElement('deck');
_panesDeck.setAttribute('flex', 1);
_panesDeck.setAttribute('selectedIndex', 0);
vbox.append(toolbarExtension, _panesDeck);
// Item pane deck
_itemPaneDeck = document.createElement('deck');
// Notes pane deck
_notesPaneDeck = document.createElement('deck');
_notesPaneDeck.style.backgroundColor = 'white';
_notesPaneDeck.setAttribute('flex', 1);
_notesPaneDeck.className = 'notes-pane-deck';
_panesDeck.append(_itemPaneDeck, _notesPaneDeck);
}
function _getCurrentAttachment() {
var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (reader) {
return Zotero.Items.get(reader.itemID);
}
}
function _addNotesContext(libraryID) {
var list = document.createElement('vbox');
list.setAttribute('flex', 1);
list.className = 'zotero-context-notes-list';
var noteContainer = document.createElement('vbox');
var title = document.createElement('vbox');
title.className = 'zotero-context-pane-editor-parent-line';
var editor = document.createElement('zoteronoteeditor');
editor.className = 'zotero-context-pane-pinned-note';
editor.setAttribute('flex', 1);
noteContainer.append(title, editor);
let contextNode = document.createElement('deck');
contextNode.append(list, noteContainer);
_notesPaneDeck.append(contextNode);
contextNode.className = 'context-node';
contextNode.setAttribute('selectedIndex', 0);
editor.returnHandler = () => {
contextNode.setAttribute('selectedIndex', 0);
_updateAddToNote();
};
var head = document.createElement('hbox');
head.style.display = 'flex';
async function _createNoteFromAnnotations(child) {
var attachment = _getCurrentAttachment();
if (!attachment) {
return;
}
var note = await Zotero.EditorInstance.createNoteFromAnnotations(
attachment.getAnnotations(), child && attachment.parentID
);
_updateAddToNote();
input.value = '';
_updateNotesList();
_setPinnedNote(note.id);
}
function _createNote(child) {
contextNode.setAttribute('selectedIndex', 1);
var item = new Zotero.Item('note');
item.libraryID = libraryID;
if (child) {
var attachment = _getCurrentAttachment();
if (!attachment) {
return;
}
item.parentID = attachment.parentID;
}
editor.mode = 'edit';
editor.item = item;
editor.parentItem = null;
editor.focus();
_updateAddToNote();
input.value = '';
_updateNotesList();
}
var vbox = document.createElement('vbox');
vbox.style.flex = '1';
var input = document.createElement('textbox');
input.style.width = 'calc(100% - 42px)';
input.style.marginLeft = '12px';
input.setAttribute('type', 'search');
input.setAttribute('timeout', '250');
input.addEventListener('command', () => {
notesListRef.current.setExpanded(false);
_updateNotesList();
});
vbox.append(input);
head.append(vbox);
var listBox = document.createElement('vbox');
listBox.style.display = 'flex';
listBox.setAttribute('flex', '1');
var listInner = document.createElementNS(HTML_NS, 'div');
listInner.className = 'notes-list-container';
listBox.append(listInner);
list.append(head, listBox);
var notesListRef = React.createRef();
async function _updateNotesList(useCached) {
var query = input.value;
var notes;
if (useCached && context.cachedNotes.length) {
notes = context.cachedNotes;
}
else {
await Zotero.Schema.schemaUpdatePromise;
var s = new Zotero.Search();
s.addCondition('libraryID', 'is', libraryID);
s.addCondition('itemType', 'is', 'note');
if (query) {
let parts = Zotero.SearchConditions.parseSearchString(query);
for (let part of parts) {
s.addCondition('note', 'contains', part.text);
}
}
notes = await s.search();
notes = Zotero.Items.get(notes);
notes.sort((a, b) => {
a = a.getField('dateModified');
b = b.getField('dateModified');
return b.localeCompare(a);
});
notes = notes.map(note => {
var parentItem = note.parentItem;
var text = note.note;
text = Zotero.Utilities.unescapeHTML(text);
text = text.trim();
text = text.slice(0, 500);
var parts = text.split('\n').map(x => x.trim()).filter(x => x.length);
var title = parts[0] && parts[0].slice(0, Zotero.Notes.MAX_TITLE_LENGTH);
var date = Zotero.Date.sqlToDate(note.dateModified);
date = Zotero.Date.toFriendlyDate(date);
return {
id: note.id,
title: title || Zotero.getString('pane.item.notes.untitled'),
body: parts[1] || '',
date,
parentID: note.parentID,
parentItemType: parentItem && parentItem.itemType,
parentTitle: parentItem && parentItem.getDisplayTitle()
};
});
context.cachedNotes = notes;
}
var attachment = _getCurrentAttachment();
var parentID = attachment && attachment.parentID;
notesListRef.current.setHasParent(!!parentID);
notesListRef.current.setNotes(notes.map(note => ({
...note,
isCurrentChild: parentID && note.parentID == parentID
})));
}
var context = {
libraryID,
node: contextNode,
editor,
notesListRef,
cachedNotes: [],
update: Zotero.Utilities.throttle(_updateNotesList, 1000, { leading: false }),
updateFromCache: () => _updateNotesList(true)
};
function _handleAddChildNotePopupClick(event) {
switch (event.originalTarget.id) {
case 'context-pane-add-child-note':
_createNote(true);
break;
case 'context-pane-add-child-note-from-annotations':
_createNoteFromAnnotations(true);
break;
default:
}
}
function _handleAddStandaloneNotePopupClick(event) {
switch (event.originalTarget.id) {
case 'context-pane-add-standalone-note':
_createNote();
break;
case 'context-pane-add-standalone-note-from-annotations':
_createNoteFromAnnotations();
break;
default:
}
}
ReactDOM.render(
<NotesList
ref={notesListRef}
onClick={(id) => {
_setPinnedNote(id);
}}
onAddChildButtonDown={(event) => {
var popup = document.getElementById('context-pane-add-child-note-button-popup');
popup.onclick = _handleAddChildNotePopupClick;
popup.openPopup(event.target, 'after_end');
}}
onAddStandaloneButtonDown={(event) => {
var popup = document.getElementById('context-pane-add-standalone-note-button-popup');
popup.onclick = _handleAddStandaloneNotePopupClick;
popup.openPopup(event.target, 'after_end');
}}
/>,
listInner,
() => {
_updateNotesList();
}
);
_notesContexts.push(context);
return context;
}
function _getNotesContext(libraryID) {
var context = _notesContexts.find(x => x.libraryID == libraryID);
if (!context) {
context = _addNotesContext(libraryID);
}
return context;
}
function _selectNotesContext(libraryID) {
let context = _getNotesContext(libraryID);
_notesPaneDeck.setAttribute('selectedIndex', Array.from(_notesPaneDeck.children).findIndex(x => x == context.node));
}
function _removeNotesContext(libraryID) {
var context = _notesContexts.find(x => x.libraryID == libraryID);
context.node.remove();
_notesContexts = _notesContexts.filter(x => x.libraryID != libraryID);
}
function _isLibraryEditable(libraryID) {
var type = Zotero.Libraries.get(libraryID).libraryType;
if (type == 'group') {
var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
var group = Zotero.Groups.get(groupID);
return group.editable;
}
return true;
}
function _setPinnedNote(itemID) {
var item = Zotero.Items.get(itemID);
if (!item) {
return;
}
var editable = _isLibraryEditable(item.libraryID);
var context = _getNotesContext(item.libraryID);
if (context) {
var { editor, node } = context;
node.setAttribute('selectedIndex', 1);
editor.mode = editable ? 'edit' : 'view';
editor.item = item;
editor.parentItem = null;
editor.hideLinksContainer = true;
node.querySelector('.zotero-context-pane-editor-parent-line').innerHTML = '';
var parentItem = item.parentItem;
if (parentItem) {
var container = document.createElementNS(HTML_NS, 'div');
var img = document.createElementNS(HTML_NS, 'img');
img.src = Zotero.ItemTypes.getImageSrc(parentItem.itemType);
img.className = 'parent-item-type';
var title = document.createElementNS(HTML_NS, 'div');
title.append(parentItem.getDisplayTitle());
title.className = 'parent-title';
container.append(img, title);
node.querySelector('.zotero-context-pane-editor-parent-line').append(container);
}
_updateAddToNote();
}
}
function _removeItemContext(tabID) {
document.getElementById(tabID + '-context').remove();
_itemContexts = _itemContexts.filter(x => x.tabID != tabID);
}
function _selectItemContext(tabID) {
let selectedIndex = Array.from(_itemPaneDeck.children).findIndex(x => x.id == tabID + '-context');
if (selectedIndex != -1) {
_itemPaneDeck.setAttribute('selectedIndex', selectedIndex);
}
}
function _addItemContext(tabID, itemID) {
var item = Zotero.Items.get(itemID);
if (!item) {
return;
}
var libraryID = item.libraryID;
var editable = _isLibraryEditable(libraryID);
var parentID = item.parentID;
var container = document.createElement('vbox');
container.id = tabID + '-context';
container.className = 'zotero-item-pane-content';
_itemPaneDeck.appendChild(container);
var context = {
tabID,
itemID,
parentID,
libraryID,
update: () => {}
};
_itemContexts.push(context);
if (!parentID) {
var vbox = document.createElement('vbox');
vbox.setAttribute('flex', '1');
vbox.setAttribute('align', 'center');
vbox.setAttribute('pack', 'center');
var description = document.createElement('description');
vbox.append(description);
description.append(Zotero.getString('pane.context.noParent'));
container.append(vbox);
return;
}
var parentItem = Zotero.Items.get(item.parentID);
// Info pane
var panelInfo = document.createElement('vbox');
panelInfo.setAttribute('flex', '1');
panelInfo.className = 'zotero-editpane-item-box';
var itemBox = document.createElement('zoteroitembox');
itemBox.setAttribute('flex', '1');
panelInfo.append(itemBox);
container.append(panelInfo);
itemBox.mode = editable ? 'edit' : 'view';
itemBox.item = parentItem;
}
};
addEventListener('load', function (e) { ZoteroContextPane.onLoad(e); }, false);
addEventListener('unload', function (e) { ZoteroContextPane.onUnload(e); }, false);

View file

@ -34,8 +34,6 @@ const OPTION_PREFIX = "export-option-";
// Class to provide options for export
var Zotero_File_Interface_Export = new function() {
this.init = init;
this.updateOptions = updateOptions;
this.accept = accept;
this.cancel = cancel;
@ -44,7 +42,7 @@ var Zotero_File_Interface_Export = new function() {
/*
* add options to export
*/
function init() {
this.init = function () {
// Set font size from pref
var sbc = document.getElementById('zotero-export-options-container');
Zotero.setFontSize(sbc);
@ -82,10 +80,25 @@ var Zotero_File_Interface_Export = new function() {
// right now, option interface supports only boolean values, which
// it interprets as checkboxes
if(typeof(translators[i].displayOptions[option]) == "boolean") {
var checkbox = document.createElement("checkbox");
let checkbox = document.createElement("checkbox");
checkbox.setAttribute("id", OPTION_PREFIX+option);
checkbox.setAttribute("label", optionLabel);
optionsBox.insertBefore(checkbox, charsetBox);
// Add "Include Annotations" after "Export Files"
if (option == 'exportFileData') {
checkbox.onclick = () => {
setTimeout(() => this.updateAnnotationsCheckbox());
};
checkbox = document.createElement("checkbox");
checkbox.setAttribute("id", OPTION_PREFIX + 'includeAnnotations');
checkbox.setAttribute(
"label",
Zotero.getString('exportOptions.includeAnnotations')
);
optionsBox.insertBefore(checkbox, charsetBox);
}
}
addedOptions[option] = true;
@ -108,13 +121,13 @@ var Zotero_File_Interface_Export = new function() {
_charsets = Zotero_Charset_Menu.populate(document.getElementById(OPTION_PREFIX+"exportCharset"), true);
}
updateOptions(Zotero.Prefs.get("export.translatorSettings"));
this.updateOptions(Zotero.Prefs.get("export.translatorSettings"));
}
/*
* update translator-specific options
*/
function updateOptions(optionString) {
this.updateOptions = function (optionString) {
// get selected translator
var index = document.getElementById("format-menu").selectedIndex;
var translatorOptions = window.arguments[0].translators[index].displayOptions;
@ -133,7 +146,9 @@ var Zotero_File_Interface_Export = new function() {
var node = optionsBox.childNodes[i];
// skip non-options
if(node.id.length <= OPTION_PREFIX.length
|| node.id.substr(0, OPTION_PREFIX.length) != OPTION_PREFIX) {
|| node.id.substr(0, OPTION_PREFIX.length) != OPTION_PREFIX
// Handled separately by updateAnnotationsCheckbox()
|| node.id == 'export-option-includeAnnotations') {
continue;
}
@ -161,6 +176,10 @@ var Zotero_File_Interface_Export = new function() {
}
}
this.updateAnnotationsCheckbox(
(options && options.includeAnnotations) ? options.includeAnnotations : false
);
// handle charset popup
if(_charsets && translatorOptions && translatorOptions.exportCharset) {
optionsBox.hidden = undefined;
@ -181,6 +200,21 @@ var Zotero_File_Interface_Export = new function() {
window.sizeToContent();
}
this.updateAnnotationsCheckbox = function (defaultValue) {
var filesCheckbox = document.getElementById(OPTION_PREFIX + 'exportFileData');
var annotationsCheckbox = document.getElementById(OPTION_PREFIX + 'includeAnnotations');
if (filesCheckbox.hidden) {
annotationsCheckbox.hidden = true;
annotationsCheckbox.checked = false;
return;
}
annotationsCheckbox.hidden = false;
annotationsCheckbox.disabled = !filesCheckbox.checked;
if (defaultValue !== undefined) {
annotationsCheckbox.checked = defaultValue;
}
};
/*
* make option array reflect status
*/
@ -210,6 +244,13 @@ var Zotero_File_Interface_Export = new function() {
}
}
// If "Export Files" is shown, add "Include Annotations" checkbox value
if (optionsAvailable && optionsAvailable.exportFileData !== undefined) {
let elem1 = document.getElementById(OPTION_PREFIX + 'exportFileData');
let elem2 = document.getElementById(OPTION_PREFIX + 'includeAnnotations');
displayOptions.includeAnnotations = elem1.checked && elem2.checked;
}
// save options
var optionString = JSON.stringify(displayOptions);
Zotero.Prefs.set("export.translatorSettings", optionString);

View file

@ -1,5 +1,7 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window [
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd" >
%zoteroDTD;

View file

@ -0,0 +1,31 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2021 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
var defaultBubbleizeSelected = Zotero_QuickFormat._bubbleizeSelected;
Zotero_QuickFormat.citingNotes = true;
Zotero_QuickFormat._bubbleizeSelected = async function () {
await defaultBubbleizeSelected();
await Zotero_QuickFormat._accept();
}

View file

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2021 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
-->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/integration.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window
id="insert-note-dialog"
class="citation-dialog note-dialog"
orient="vertical"
title="&zotero.integration.quickFormatDialog.title;"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
persist="screenX screenY"
onkeypress="Zotero_QuickFormat.onKeyPress(event)"
onunload="Zotero_QuickFormat.onUnload()">
<script src="../include.js"/>
<script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="quickFormat.js" type="text/javascript"/>
<script src="insertNoteDialog.js" type="text/javascript"/>
<box orient="horizontal" class="citation-dialog entry">
<deck class="citation-dialog deck" selectedIndex="0" flex="1">
<hbox class="citation-dialog search" flex="1" align="start">
<hbox flex="1">
<iframe class="citation-dialog iframe" ondragstart="event.stopPropagation()" src="data:application/xhtml+xml,%3C!DOCTYPE%20html%20PUBLIC%20%22-//W3C//DTD%20XHTML%201.0%20Strict//EN%22%20%22http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd%22%3E%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Chead%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero/skin/integration.css%22/%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero-platform/content/integration.css%22/%3E%3C/head%3E%3Cbody%20contenteditable=%22true%22%20spellcheck=%22false%22%20class=%22citation-dialog%20editor%22/%3E%3C/html%3E"
tabindex="1" flex="1"/>
<vbox class="citation-dialog spinner" style="visibility: hidden">
<image class="zotero-spinner-16"/>
</vbox>
</hbox>
</hbox>
<progressmeter class="citation-dialog progress-meter" mode="undetermined" value="0" flex="1"/>
</deck>
</box>
<panel class="citation-dialog reference-panel" noautofocus="true" norestorefocus="true"
height="0" width="0">
<richlistbox class="citation-dialog reference-list" flex="1"/>
</panel>
</window>

View file

@ -38,6 +38,10 @@ var Zotero_ProgressBar = new function () {
io.onLoad(_onProgress);
}
if (io.isNote) {
document.documentElement.classList.add('note-dialog');
}
// Only hide chrome on Windows or Mac
if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true);
@ -45,12 +49,12 @@ var Zotero_ProgressBar = new function () {
document.documentElement.setAttribute("hidechrome", true);
}
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
new WindowDraggingElement(document.querySelector(".citation-dialog"), window);
// With fx60 and drawintitlebar=true Firefox calculates the minHeight
// as titlebar+maincontent, so we have hack around that here.
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
document.getElementById("quick-format-entry").style.marginBottom = "-22px";
if (Zotero.isMac) {
document.querySelector(".citation-dialog.entry").style.marginBottom = "-28px";
}
}
@ -75,7 +79,7 @@ var Zotero_ProgressBar = new function () {
* Called when progress changes
*/
function _onProgress(percent) {
var meter = document.getElementById("quick-format-progress-meter");
var meter = document.querySelector(".citation-dialog.progress-meter");
if(percent === null) {
meter.mode = "undetermined";
} else {

View file

@ -31,8 +31,8 @@
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window
id="quick-format-dialog"
class="progress-bar"
id="progress-bar"
class="citation-dialog progress-bar"
orient="vertical"
title="&zotero.progress.title;"
xmlns:html="http://www.w3.org/1999/xhtml"
@ -43,9 +43,9 @@
<script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="progressBar.js" type="text/javascript"/>
<box orient="horizontal" id="quick-format-entry">
<deck id="quick-format-deck" selectedIndex="0" flex="1">
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
<box orient="horizontal" class="citation-dialog entry">
<deck class="citation-dialog deck" selectedIndex="0" flex="1">
<progressmeter class="citation-dialog progress-meter" mode="undetermined" value="0" flex="1"/>
</deck>
</box>
</window>

View file

@ -34,7 +34,7 @@ var Zotero_QuickFormat = new function () {
const numRe = /^[0-9\-]+$/;
var initialized, io, qfs, qfi, qfiWindow, qfiDocument, qfe, qfb, qfbHeight, qfGuidance,
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging,
panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator,
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false;
@ -56,6 +56,10 @@ var Zotero_QuickFormat = new function () {
Zotero.debug(`Quick Format received citation:`);
Zotero.debug(JSON.stringify(io.citation.toJSON()));
if (io.disableClassicDialog) {
document.getElementById('classic-view').hidden = true;
}
// Only hide chrome on Windows or Mac
if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true);
@ -65,18 +69,18 @@ var Zotero_QuickFormat = new function () {
// Include a different key combo in message on Mac
if(Zotero.isMac) {
var qf = document.getElementById('quick-format-guidance');
qf.setAttribute('about', qf.getAttribute('about') + "Mac");
var qf = document.querySelector('.citation-dialog.guidance');
qf && qf.setAttribute('about', qf.getAttribute('about') + "Mac");
}
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
new WindowDraggingElement(document.querySelector("window.citation-dialog"), window);
qfs = document.getElementById("quick-format-search");
qfi = document.getElementById("quick-format-iframe");
qfb = document.getElementById("quick-format-entry");
qfs = document.querySelector(".citation-dialog.search");
qfi = document.querySelector(".citation-dialog.iframe");
qfb = document.querySelector(".citation-dialog.entry");
qfbHeight = qfb.scrollHeight;
referencePanel = document.getElementById("quick-format-reference-panel");
referenceBox = document.getElementById("quick-format-reference-list");
referencePanel = document.querySelector(".citation-dialog.reference-panel");
referenceBox = document.querySelector(".citation-dialog.reference-list");
if (Zotero.isWin) {
referencePanel.style.marginTop = "-29px";
@ -84,31 +88,15 @@ var Zotero_QuickFormat = new function () {
qfb.setAttribute("square", "true");
}
}
// With fx60 and drawintitlebar=true Firefox calculates the minHeight
// as titlebar+maincontent, so we have hack around that here.
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
else if (Zotero.isMac) {
qfb.style.marginBottom = "-28px";
}
// add labels to popup
var locators = Zotero.Cite.labels;
var menu = document.getElementById("locator-label");
var labelList = document.getElementById("locator-label-popup");
for(var locator of locators) {
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
// add to list of labels
var child = document.createElement("menuitem");
child.setAttribute("value", locator);
child.setAttribute("label", locatorLabel);
labelList.appendChild(child);
}
menu.selectedIndex = 0;
keepSorted = document.getElementById("keep-sorted");
showEditor = document.getElementById("show-editor");
if(io.sortable) {
if(keepSorted && io.sortable) {
keepSorted.hidden = false;
if(!io.citation.properties.unsorted) {
keepSorted.setAttribute("checked", "true");
@ -117,13 +105,31 @@ var Zotero_QuickFormat = new function () {
// Nodes for citation properties panel
panel = document.getElementById("citation-properties");
panelPrefix = document.getElementById("prefix");
panelSuffix = document.getElementById("suffix");
panelSuppressAuthor = document.getElementById("suppress-author");
panelLocatorLabel = document.getElementById("locator-label");
panelLocator = document.getElementById("locator");
panelInfo = document.getElementById("citation-properties-info");
panelLibraryLink = document.getElementById("citation-properties-library-link");
if (panel) {
panelPrefix = document.getElementById("prefix");
panelSuffix = document.getElementById("suffix");
panelSuppressAuthor = document.getElementById("suppress-author");
panelLocatorLabel = document.getElementById("locator-label");
panelLocator = document.getElementById("locator");
panelInfo = document.getElementById("citation-properties-info");
panelLibraryLink = document.getElementById("citation-properties-library-link");
// add labels to popup
var locators = Zotero.Cite.labels;
var menu = document.getElementById("locator-label");
var labelList = document.getElementById("locator-label-popup");
for(var locator of locators) {
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
// add to list of labels
var child = document.createElement("menuitem");
child.setAttribute("value", locator);
child.setAttribute("label", locatorLabel);
labelList.appendChild(child);
}
menu.selectedIndex = 0;
}
// Don't need to set noautohide dynamically on these platforms, so do it now
if(Zotero.isMac || Zotero.isWin) {
@ -134,9 +140,12 @@ var Zotero_QuickFormat = new function () {
qfiDocument = qfi.contentDocument;
qfb.addEventListener("click", _onQuickSearchClick, false);
qfb.addEventListener("keypress", _onQuickSearchKeyPress, false);
qfe = qfiDocument.getElementById("quick-format-editor");
qfe = qfiDocument.querySelector(".citation-dialog.editor");
qfe.addEventListener("drop", _onBubbleDrop, false);
qfe.addEventListener("paste", _onPaste, false);
if (Zotero_QuickFormat.citingNotes) {
_quickFormat();
}
}
}
@ -160,8 +169,8 @@ var Zotero_QuickFormat = new function () {
Zotero.debug(`Moving window to ${targetX}, ${targetY}`);
window.moveTo(targetX, targetY);
}
qfGuidance = document.getElementById('quick-format-guidance');
qfGuidance.show();
qfGuidance = document.querySelector('.citation-dialog.guidance');
qfGuidance && qfGuidance.show();
_refocusQfe();
})();
@ -224,7 +233,21 @@ var Zotero_QuickFormat = new function () {
var node = _getCurrentEditorTextNode();
return node ? node.wholeText : false;
}
/**
* Updates currentLocator based on a string
* @param {String} str String to search for locator
* @return {String} str without locator
*/
function _updateLocator(str) {
m = locatorRe.exec(str);
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
currentLocator = m[3];
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
}
return str;
}
/**
* Does the dirty work of figuring out what the user meant to type
*/
@ -307,63 +330,86 @@ var Zotero_QuickFormat = new function () {
// Exclude feeds
Zotero.Feeds.getAll()
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
s.addCondition("itemType", "isNot", "attachment");
if (Zotero_QuickFormat.citingNotes) {
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
}
else {
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
s.addCondition("itemType", "isNot", "attachment");
if (io.filterLibraryIDs) {
io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
}
}
haveConditions = true;
}
}
if(haveConditions) {
if (!haveConditions && Zotero_QuickFormat.citingNotes) {
s = new Zotero.Search();
str = "";
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
haveConditions = true;
}
if (haveConditions) {
var searchResultIDs = (haveConditions ? (yield s.search()) : []);
// Show items list without cited items to start
yield _updateItemList(false, false, str, searchResultIDs);
yield _updateItemList({ searchString: str, searchResultIDs });
// Check to see which search results match items already in the document
var citedItems, completed = false, isAsync = false;
var citedItems, completed = !!Zotero_QuickFormat.citingNotes, isAsync = false;
// Save current search time so that when we get items, we know whether it's too late to
// process them or not
var lastSearchTime = currentSearchTime = Date.now();
// This may or may not be synchronous
io.getItems().then(function(citedItems) {
// Don't do anything if panel is already closed
if(isAsync &&
((referencePanel.state !== "open" && referencePanel.state !== "showing")
|| lastSearchTime !== currentSearchTime)) return;
completed = true;
if(str.toLowerCase() === Zotero.getString("integration.ibid").toLowerCase()) {
// If "ibid" is entered, show all cited items
citedItemsMatchingSearch = citedItems;
} else {
Zotero.debug("Searching cited items");
// Search against items. We do this here because it's possible that some of these
// items are only in the doc, and not in the DB.
var splits = Zotero.Fulltext.semanticSplitter(str),
citedItemsMatchingSearch = [];
for(var i=0, iCount=citedItems.length; i<iCount; i++) {
// Generate a string to search for each item
let item = citedItems[i];
let itemStr = item.getCreators()
.map(creator => creator.firstName + " " + creator.lastName)
.concat([item.getField("title"), item.getField("date", true, true).substr(0, 4)])
.join(" ");
// See if words match
for(var j=0, jCount=splits.length; j<jCount; j++) {
var split = splits[j];
if(itemStr.toLowerCase().indexOf(split) === -1) break;
if (!Zotero_QuickFormat.citingNotes) {
io.getItems().then(function(citedItems) {
// Don't do anything if panel is already closed
if(isAsync &&
((referencePanel.state !== "open" && referencePanel.state !== "showing")
|| lastSearchTime !== currentSearchTime)) return;
completed = true;
if(str.toLowerCase() === Zotero.getString("integration.ibid").toLowerCase()) {
// If "ibid" is entered, show all cited items
citedItemsMatchingSearch = citedItems;
} else {
Zotero.debug("Searching cited items");
// Search against items. We do this here because it's possible that some of these
// items are only in the doc, and not in the DB.
var splits = Zotero.Fulltext.semanticSplitter(str),
citedItemsMatchingSearch = [];
for(var i=0, iCount=citedItems.length; i<iCount; i++) {
// Generate a string to search for each item
let item = citedItems[i];
let itemStr = item.getCreators()
.map(creator => creator.firstName + " " + creator.lastName)
.concat([item.getField("title"), item.getField("date", true, true).substr(0, 4)])
.join(" ");
// See if words match
for(var j=0, jCount=splits.length; j<jCount; j++) {
var split = splits[j];
if(itemStr.toLowerCase().indexOf(split) === -1) break;
}
// If matched, add to citedItemsMatchingSearch
if(j === jCount) citedItemsMatchingSearch.push(item);
}
// If matched, add to citedItemsMatchingSearch
if(j === jCount) citedItemsMatchingSearch.push(item);
Zotero.debug("Searched cited items");
}
Zotero.debug("Searched cited items");
}
_updateItemList(citedItems, citedItemsMatchingSearch, str, searchResultIDs, isAsync);
});
_updateItemList({
citedItems,
citedItemsMatchingSearch,
searchString: str,
searchResultIDs,
preserveSelection: isAsync
});
});
}
if(!completed) {
// We are going to have to wait until items have been retrieved from the document.
@ -374,30 +420,26 @@ var Zotero_QuickFormat = new function () {
}
} else {
// No search conditions, so just clear the box
_updateItemList([], [], "", []);
_updateItemList({ citedItems: [] });
}
});
/**
* Updates currentLocator based on a string
* @param {String} str String to search for locator
* @return {String} str without locator
*/
function _updateLocator(str) {
m = locatorRe.exec(str);
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
currentLocator = m[3];
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
}
return str;
}
/**
* Updates the item list
*/
var _updateItemList = Zotero.Promise.coroutine(function* (citedItems, citedItemsMatchingSearch,
searchString, searchResultIDs, preserveSelection) {
var _updateItemList = async function (options = {}) {
options = Object.assign({
citedItems: false,
citedItemsMatchingSearch: false,
searchString: "",
searchResultIDs: [],
preserveSelection: false
}, options);
let { citedItems, citedItemsMatchingSearch, searchString,
searchResultIDs, preserveSelection } = options
var selectedIndex = 1, previousItemID;
if (Zotero_QuickFormat.citingNotes) citedItems = [];
// Do this so we can preserve the selected item after cited items have been loaded
if(preserveSelection && referenceBox.selectedIndex !== -1 && referenceBox.selectedIndex !== 2) {
@ -454,13 +496,13 @@ var Zotero_QuickFormat = new function () {
if(searchResultIDs.length && (!citedItemsMatchingSearch || citedItemsMatchingSearch.length < 50)) {
// Search results might be in an unloaded library, so get items asynchronously and load
// necessary data
var items = yield Zotero.Items.getAsync(searchResultIDs);
yield Zotero.Items.loadDataTypes(items);
var items = await Zotero.Items.getAsync(searchResultIDs);
await Zotero.Items.loadDataTypes(items);
searchString = searchString.toLowerCase();
var collation = Zotero.getLocaleCollation();
items.sort(function _itemSort(a, b) {
function _itemSort(a, b) {
var firstCreatorA = a.firstCreator, firstCreatorB = b.firstCreator;
// Favor left-bound name matches (e.g., "Baum" < "Appelbaum"),
@ -506,7 +548,15 @@ var Zotero_QuickFormat = new function () {
var yearA = a.getField("date", true, true).substr(0, 4),
yearB = b.getField("date", true, true).substr(0, 4);
return yearA - yearB;
});
}
function _noteSort(a, b) {
return collation.compareString(
1, b.getField('dateModified'), a.getField('dateModified')
);
}
items.sort(Zotero_QuickFormat.citingNotes ? _noteSort : _itemSort);
var previousLibrary = -1;
for(var i=0, n=Math.min(items.length, citedItemsMatchingSearch ? 50-citedItemsMatchingSearch.length : 50); i<n; i++) {
@ -532,70 +582,85 @@ var Zotero_QuickFormat = new function () {
referenceBox.selectedIndex = selectedIndex;
referenceBox.ensureIndexIsVisible(selectedIndex);
}
});
};
/**
* Builds a string describing an item. We avoid CSL here for speed.
*/
function _buildItemDescription(item, infoHbox) {
var nodes = [];
var author, authorDate = "";
if(item.firstCreator) author = authorDate = item.firstCreator;
var date = item.getField("date", true, true);
if(date && (date = date.substr(0, 4)) !== "0000") {
authorDate += " (" + parseInt(date) + ")";
}
authorDate = authorDate.trim();
if(authorDate) nodes.push(authorDate);
var publicationTitle = item.getField("publicationTitle", false, true);
if(publicationTitle) {
var label = document.createElement("label");
label.setAttribute("value", publicationTitle);
label.setAttribute("crop", "end");
label.style.fontStyle = "italic";
nodes.push(label);
}
var volumeIssue = item.getField("volume");
var issue = item.getField("issue");
if(issue) volumeIssue += "("+issue+")";
if(volumeIssue) nodes.push(volumeIssue);
var publisherPlace = [], field;
if((field = item.getField("publisher"))) publisherPlace.push(field);
if((field = item.getField("place"))) publisherPlace.push(field);
if(publisherPlace.length) nodes.push(publisherPlace.join(": "));
var pages = item.getField("pages");
if(pages) nodes.push(pages);
if(!nodes.length) {
var url = item.getField("url");
if(url) nodes.push(url);
}
// compile everything together
var str = "";
for(var i=0, n=nodes.length; i<n; i++) {
var node = nodes[i];
if (item.isNote()) {
var date = Zotero.Date.sqlToDate(item.dateModified);
date = Zotero.Date.toFriendlyDate(date);
str += date;
if(i != 0) str += ", ";
if(typeof node === "object") {
var label = document.createElement("label");
label.setAttribute("value", str);
label.setAttribute("crop", "end");
infoHbox.appendChild(label);
infoHbox.appendChild(node);
str = "";
} else {
str += node;
var text = item.note;
text = Zotero.Utilities.unescapeHTML(text);
text = text.trim();
text = text.slice(0, 500);
var parts = text.split('\n').map(x => x.trim()).filter(x => x.length);
if (parts[1]) str += " " + parts[1];
}
else {
var author, authorDate = "";
if(item.firstCreator) author = authorDate = item.firstCreator;
var date = item.getField("date", true, true);
if(date && (date = date.substr(0, 4)) !== "0000") {
authorDate += " (" + parseInt(date) + ")";
}
authorDate = authorDate.trim();
if(authorDate) nodes.push(authorDate);
var publicationTitle = item.getField("publicationTitle", false, true);
if(publicationTitle) {
var label = document.createElement("label");
label.setAttribute("value", publicationTitle);
label.setAttribute("crop", "end");
label.style.fontStyle = "italic";
nodes.push(label);
}
var volumeIssue = item.getField("volume");
var issue = item.getField("issue");
if(issue) volumeIssue += "("+issue+")";
if(volumeIssue) nodes.push(volumeIssue);
var publisherPlace = [], field;
if((field = item.getField("publisher"))) publisherPlace.push(field);
if((field = item.getField("place"))) publisherPlace.push(field);
if(publisherPlace.length) nodes.push(publisherPlace.join(": "));
var pages = item.getField("pages");
if(pages) nodes.push(pages);
if(!nodes.length) {
var url = item.getField("url");
if(url) nodes.push(url);
}
// compile everything together
for(var i=0, n=nodes.length; i<n; i++) {
var node = nodes[i];
if(i != 0) str += ", ";
if(typeof node === "object") {
var label = document.createElement("label");
label.setAttribute("value", str);
label.setAttribute("crop", "end");
infoHbox.appendChild(label);
infoHbox.appendChild(node);
str = "";
} else {
str += node;
}
}
if(nodes.length && (!str.length || str[str.length-1] !== ".")) str += ".";
}
if(nodes.length && (!str.length || str[str.length-1] !== ".")) str += ".";
var label = document.createElement("label");
label.setAttribute("value", str);
label.setAttribute("crop", "end");
@ -608,23 +673,23 @@ var Zotero_QuickFormat = new function () {
*/
function _buildListItem(item) {
var titleNode = document.createElement("label");
titleNode.setAttribute("class", "quick-format-title");
titleNode.setAttribute("class", "citation-dialog title");
titleNode.setAttribute("flex", "1");
titleNode.setAttribute("crop", "end");
titleNode.setAttribute("value", item.getDisplayTitle());
var infoNode = document.createElement("hbox");
infoNode.setAttribute("class", "quick-format-info");
infoNode.setAttribute("class", "citation-dialog info");
_buildItemDescription(item, infoNode);
// add to rich list item
var rll = document.createElement("richlistitem");
rll.setAttribute("orient", "vertical");
rll.setAttribute("class", "quick-format-item");
rll.setAttribute("class", "citation-dialog item");
rll.setAttribute("zotero-item", item.cslItemID ? item.cslItemID : item.id);
rll.appendChild(titleNode);
rll.appendChild(infoNode);
rll.addEventListener("click", _bubbleizeSelected, false);
rll.addEventListener("click", Zotero_QuickFormat._bubbleizeSelected, false);
return rll;
}
@ -634,7 +699,7 @@ var Zotero_QuickFormat = new function () {
*/
function _buildListSeparator(labelText, loading) {
var titleNode = document.createElement("label");
titleNode.setAttribute("class", "quick-format-separator-title");
titleNode.setAttribute("class", "citation-dialog separator-title");
titleNode.setAttribute("flex", "1");
titleNode.setAttribute("crop", "end");
titleNode.setAttribute("value", labelText);
@ -643,7 +708,7 @@ var Zotero_QuickFormat = new function () {
var rll = document.createElement("richlistitem");
rll.setAttribute("orient", "vertical");
rll.setAttribute("disabled", true);
rll.setAttribute("class", loading ? "quick-format-loading" : "quick-format-separator");
rll.setAttribute("class", loading ? "citation-dialog loading" : "citation-dialog separator");
rll.appendChild(titleNode);
rll.addEventListener("mousedown", _ignoreClick, true);
rll.addEventListener("click", _ignoreClick, true);
@ -663,8 +728,12 @@ var Zotero_QuickFormat = new function () {
var str = item.getField("firstCreator");
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
if(!str) {
str = Zotero.getString("punctuation.openingQMark") + item.getDisplayTitle() + Zotero.getString("punctuation.closingQMark");
title = item.getDisplayTitle();
if (item.isNote()) {
title = title.substr(0, 24) + '…';
}
if (!str) {
str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
}
// Date
@ -713,7 +782,7 @@ var Zotero_QuickFormat = new function () {
// a XUL label for these things works best. A regular span causes issues with moving the
// cursor.
var bubble = qfiDocument.createElement("span");
bubble.setAttribute("class", "quick-format-bubble");
bubble.setAttribute("class", "citation-dialog bubble");
bubble.setAttribute("draggable", "true");
bubble.textContent = str;
bubble.addEventListener("click", _onBubbleClick, false);
@ -744,12 +813,11 @@ var Zotero_QuickFormat = new function () {
/**
* Converts the selected item to a bubble
*/
var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
this._bubbleizeSelected = Zotero.Promise.coroutine(function* () {
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
if (typeof citationItem.id === "string" && citationItem.id.indexOf("/") !== -1) {
var item = Zotero.Cite.getItem(citationItem.id);
citationItem.uris = item.cslURIs;
citationItem.itemData = item.cslItemData;
}
@ -824,13 +892,13 @@ var Zotero_QuickFormat = new function () {
var childNodes = referenceBox.childNodes, numReferences = 0, numSeparators = 0,
firstReference, firstSeparator, height;
for(var i=0, n=childNodes.length; i<n && numReferences < SHOWN_REFERENCES; i++) {
if(childNodes[i].className === "quick-format-item") {
if(childNodes[i].className === "citation-dialog item") {
numReferences++;
if(!firstReference) {
firstReference = childNodes[i];
if(referenceBox.selectedIndex === -1) referenceBox.selectedIndex = i;
}
} else if(childNodes[i].className === "quick-format-separator") {
} else if(childNodes[i].className === "citation-dialog separator") {
numSeparators++;
if(!firstSeparator) firstSeparator = childNodes[i];
}
@ -885,7 +953,6 @@ var Zotero_QuickFormat = new function () {
}
}
}
referencePanel.sizeTo(window.outerWidth-30,
numReferences*referenceHeight+numSeparators*separatorHeight+panelFrameHeight);
if(!panelShowing) _openReferencePanel();
@ -900,29 +967,29 @@ var Zotero_QuickFormat = new function () {
* Opens the reference panel and potentially refocuses the main text box
*/
function _openReferencePanel() {
if(!Zotero.isMac && !Zotero.isWin) {
var panelShowing = referencePanel.state === "open" || referencePanel.state === "showing";
if (!panelShowing && !Zotero.isMac && !Zotero.isWin) {
// noautohide and noautofocus are incompatible on Linux
// https://bugzilla.mozilla.org/show_bug.cgi?id=545265
referencePanel.setAttribute("noautohide", "false");
}
referencePanel.openPopup(document.documentElement, "after_start", 15,
qfb.clientHeight-window.clientHeight, false, false, null);
if(!Zotero.isMac && !Zotero.isWin) {
// reinstate noautohide after the window is shown
referencePanel.addEventListener("popupshowing", function() {
referencePanel.removeEventListener("popupshowing", arguments.callee, false);
referencePanel.setAttribute("noautohide", "true");
}, false);
}
referencePanel.openPopup(document.documentElement, "after_start", 15,
qfb.clientHeight-window.clientHeight, false, false, null);
}
/**
* Clears all citations
*/
function _clearCitation() {
var citations = qfe.getElementsByClassName("quick-format-bubble");
var citations = qfe.getElementsByClassName("citation-dialog bubble");
while(citations.length) {
citations[0].parentNode.removeChild(citations[0]);
}
@ -933,7 +1000,7 @@ var Zotero_QuickFormat = new function () {
*/
function _showCitation(insertBefore) {
if(!io.citation.properties.unsorted
&& keepSorted.hasAttribute("checked")
&& keepSorted && keepSorted.hasAttribute("checked")
&& io.citation.sortedItems
&& io.citation.sortedItems.length) {
for(var i=0, n=io.citation.sortedItems.length; i<n; i++) {
@ -965,7 +1032,7 @@ var Zotero_QuickFormat = new function () {
}
if(io.sortable) {
if(keepSorted.hasAttribute("checked")) {
if(keepSorted && keepSorted.hasAttribute("checked")) {
delete io.citation.properties.unsorted;
} else {
io.citation.properties.unsorted = true;
@ -990,8 +1057,8 @@ var Zotero_QuickFormat = new function () {
* Generates the preview and sorts citations
*/
var _previewAndSort = Zotero.Promise.coroutine(function* () {
var shouldKeepSorted = keepSorted.hasAttribute("checked"),
editorShowing = showEditor.hasAttribute("checked");
var shouldKeepSorted = keepSorted && keepSorted.hasAttribute("checked"),
editorShowing = showEditor && showEditor.hasAttribute("checked");
if(!shouldKeepSorted && !editorShowing) return;
_updateCitationObject();
@ -1002,7 +1069,7 @@ var Zotero_QuickFormat = new function () {
_showCitation();
// select past last citation
var lastBubble = qfe.getElementsByClassName("quick-format-bubble");
var lastBubble = qfe.getElementsByClassName("citation-dialog bubble");
lastBubble = lastBubble[lastBubble.length-1];
_moveCursorToEnd();
@ -1052,7 +1119,7 @@ var Zotero_QuickFormat = new function () {
* Called when progress changes
*/
function _onProgress(percent) {
var meter = document.getElementById("quick-format-progress-meter");
var meter = document.querySelector(".citation-dialog .progress-meter");
if(percent === null) {
meter.mode = "undetermined";
} else {
@ -1064,12 +1131,12 @@ var Zotero_QuickFormat = new function () {
/**
* Accepts current selection and adds citation
*/
function _accept() {
this._accept = function() {
if(accepted) return;
accepted = true;
try {
_updateCitationObject();
document.getElementById("quick-format-deck").selectedIndex = 1;
document.querySelector(".citation-dialog.deck").selectedIndex = 1;
io.accept(_onProgress);
} catch(e) {
Zotero.debug(e);
@ -1089,14 +1156,14 @@ var Zotero_QuickFormat = new function () {
/**
* Handle escape for entire window
*/
this.onKeyPress = function(event) {
this.onKeyPress = function (event) {
var keyCode = event.keyCode;
if(keyCode === event.DOM_VK_ESCAPE && !accepted) {
if (keyCode === event.DOM_VK_ESCAPE && !accepted) {
accepted = true;
io.citation.citationItems = [];
io.accept();
}
}
};
/**
* Get bubbles within the current selection
@ -1141,7 +1208,7 @@ var Zotero_QuickFormat = new function () {
*/
function _resetSearchTimer() {
// Show spinner
var spinner = document.getElementById('quick-format-spinner');
var spinner = document.querySelector('.citation-dialog.spinner');
spinner.style.visibility = '';
// Cancel current search if active
if (_searchPromise && _searchPromise.isPending()) {
@ -1187,8 +1254,8 @@ var Zotero_QuickFormat = new function () {
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_RETURN) {
event.preventDefault();
if(!(yield _bubbleizeSelected()) && !_getEditorContent()) {
_accept();
if(!(yield Zotero_QuickFormat._bubbleizeSelected()) && !_getEditorContent()) {
Zotero_QuickFormat._accept();
}
} else if (keyCode === event.DOM_VK_ESCAPE) {
// Handled in the event handler up, but we have to cancel it here
@ -1196,7 +1263,7 @@ var Zotero_QuickFormat = new function () {
return;
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
event.preventDefault();
_bubbleizeSelected();
Zotero_QuickFormat._bubbleizeSelected();
} else if(keyCode === event.DOM_VK_BACK_SPACE || keyCode === event.DOM_VK_DELETE) {
var bubble = _getSelectedBubble(keyCode === event.DOM_VK_DELETE);
@ -1331,7 +1398,7 @@ var Zotero_QuickFormat = new function () {
var bubble = _insertBubble(JSON.parse(dragging.dataset.citationItem), range);
// If moved out of order, turn off "Keep Sources Sorted"
if(io.sortable && keepSorted.hasAttribute("checked") && oldPosition !== -1 &&
if(io.sortable && keepSorted && keepSorted.hasAttribute("checked") && oldPosition !== -1 &&
oldPosition != _getBubbleIndex(bubble)) {
keepSorted.removeAttribute("checked");
}

View file

@ -33,6 +33,7 @@
<window
id="quick-format-dialog"
class="citation-dialog"
orient="vertical"
title="&zotero.integration.quickFormatDialog.title;"
xmlns:html="http://www.w3.org/1999/xhtml"
@ -44,40 +45,41 @@
<script src="../include.js"/>
<script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="quickFormat.js" type="text/javascript"/>
<box orient="horizontal" id="quick-format-entry">
<deck id="quick-format-deck" selectedIndex="0" flex="1">
<hbox id="quick-format-search" flex="1" align="start">
<box orient="horizontal" class="citation-dialog entry">
<deck class="citation-dialog deck" selectedIndex="0" flex="1">
<hbox class="citation-dialog search" flex="1" align="start">
<hbox flex="1">
<toolbarbutton id="zotero-icon" type="menu">
<menupopup>
<menuitem id="keep-sorted" label="&zotero.citation.keepSorted.label;"
oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox"
hidden="true"/>
oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox"
hidden="true"/>
<menuitem id="show-editor" label="&zotero.integration.showEditor.label;"
oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox"
hidden="true"/>
oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox"
hidden="true"/>
<menuitem id="classic-view" label="&zotero.integration.classicView.label;"
oncommand="Zotero_QuickFormat.onClassicViewCommand()"/>
oncommand="Zotero_QuickFormat.onClassicViewCommand()"/>
</menupopup>
</toolbarbutton>
<iframe id="quick-format-iframe" ondragstart="event.stopPropagation()" src="data:application/xhtml+xml,%3C!DOCTYPE%20html%20PUBLIC%20%22-//W3C//DTD%20XHTML%201.0%20Strict//EN%22%20%22http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd%22%3E%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Chead%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero/skin/integration.css%22/%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero-platform/content/integration.css%22/%3E%3C/head%3E%3Cbody%20contenteditable=%22true%22%20spellcheck=%22false%22%20id=%22quick-format-editor%22/%3E%3C/html%3E"
tabindex="1" flex="1"/>
<vbox id="quick-format-spinner" style="visibility: hidden">
<iframe class="citation-dialog iframe" ondragstart="event.stopPropagation()" src="data:application/xhtml+xml,%3C!DOCTYPE%20html%20PUBLIC%20%22-//W3C//DTD%20XHTML%201.0%20Strict//EN%22%20%22http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd%22%3E%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Chead%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero/skin/integration.css%22/%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero-platform/content/integration.css%22/%3E%3C/head%3E%3Cbody%20contenteditable=%22true%22%20spellcheck=%22false%22%20class=%22citation-dialog%20editor%22/%3E%3C/html%3E"
tabindex="1" flex="1"/>
<vbox class="citation-dialog spinner" style="visibility: hidden">
<image class="zotero-spinner-16"/>
</vbox>
</hbox>
</hbox>
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
<progressmeter class="citation-dialog progress-meter" mode="undetermined" value="0" flex="1"/>
</deck>
</box>
<panel id="quick-format-reference-panel" noautofocus="true" norestorefocus="true"
height="0" width="0">
<richlistbox id="quick-format-reference-list" flex="1"/>
<panel class="citation-dialog reference-panel" noautofocus="true" norestorefocus="true"
height="0" width="0">
<richlistbox class="citation-dialog reference-list" flex="1"/>
</panel>
<panel id="citation-properties" type="arrow" orient="vertical"
onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)"
onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)">
onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)"
onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)">
<vbox flex="1">
<description id="citation-properties-title"/>
<hbox id="citation-properties-info"/>
@ -90,25 +92,25 @@
<rows>
<row align="center">
<menulist id="locator-label" sizetopopup="none"
oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)">
oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)">
<menupopup id="locator-label-popup"/>
</menulist>
<textbox id="locator" flex="1"
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
</row>
<row align="center">
<label value="&zotero.citation.prefix.label;"/>
<textbox class="citation-textbox" id="prefix" flex="1"
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
</row>
<row align="center">
<label value="&zotero.citation.suffix.label;"/>
<textbox class="citation-textbox" id="suffix" flex="1"
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
</row>
<html:div>
<html:input type="checkbox" id="suppress-author"
onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/>
onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/>
<html:label for="suppress-author">
&zotero.citation.suppressAuthor.label;
</html:label>
@ -119,6 +121,6 @@
<button id="citation-properties-library-link" onclick="Zotero_QuickFormat.showInLibrary()"/>
</vbox>
</panel>
<zoteroguidancepanel id="quick-format-guidance" about="quickFormat"
for="zotero-icon" x="26"/>
<zoteroguidancepanel class="citation-dialog guidance" about="quickFormat"
for="zotero-icon" x="26"/>
</window>

View file

@ -253,30 +253,45 @@ var ZoteroItemPane = new function() {
tree.focus();
}
}
this.switchEditorEngine = function (useOld) {
var switherDeck = document.getElementById('zotero-note-editor-switcher');
switherDeck.selectedIndex = useOld ? 0 : 1;
};
this.onNoteSelected = function (item, editable) {
_selectedNoteID = item.id;
// If an external note window is open for this item, don't show the editor
if (ZoteroPane.findNoteWindow(item.id)) {
this.showNoteWindowMessage();
return;
var type = Zotero.Libraries.get(item.libraryID).libraryType;
if (type == 'group' || !Zotero.isPDFBuild) {
// If an external note window is open for this item, don't show the editor
if (ZoteroPane.findNoteWindow(item.id)) {
this.showNoteWindowMessage();
return;
}
var noteEditor = document.getElementById('zotero-note-editor-old');
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
noteEditor.mode = editable ? 'edit' : 'view';
noteEditor.parent = null;
noteEditor.item = item;
if (clearUndo) {
noteEditor.clearUndo();
}
}
var noteEditor = document.getElementById('zotero-note-editor');
// If loading new or different note, disable undo while we repopulate the text field
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
// undo content from another note into the current one.
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
noteEditor.mode = editable ? 'edit' : 'view';
noteEditor.parent = null;
noteEditor.item = item;
if (clearUndo) {
noteEditor.clearUndo();
else {
var noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = editable ? 'edit' : 'view';
noteEditor.parent = null;
noteEditor.item = item;
}
document.getElementById('zotero-view-note-button').hidden = !editable;
@ -295,16 +310,19 @@ var ZoteroItemPane = new function() {
this.openNoteWindow = async function () {
var selectedNote = Zotero.Items.get(_selectedNoteID);
// We don't want to show the note in two places, since it causes unnecessary UI updates
// and can result in weird bugs where note content gets lost.
//
// If this is a child note, select the parent
if (selectedNote.parentID) {
await ZoteroPane.selectItem(selectedNote.parentID);
}
// Otherwise, hide note and replace with a message that we're editing externally
else {
this.showNoteWindowMessage();
var type = Zotero.Libraries.get(selectedNote.libraryID).libraryType;
if (type == 'group' || !Zotero.isPDFBuild) {
// We don't want to show the note in two places, since it causes unnecessary UI updates
// and can result in weird bugs where note content gets lost.
//
// If this is a child note, select the parent
if (selectedNote.parentID) {
await ZoteroPane.selectItem(selectedNote.parentID);
}
// Otherwise, hide note and replace with a message that we're editing externally
else {
this.showNoteWindowMessage();
}
}
ZoteroPane.openNoteWindow(selectedNote.id);
};
@ -438,21 +456,7 @@ var ZoteroItemPane = new function() {
function _updateNoteCount() {
var c = _notesList.childNodes.length;
var str = 'pane.item.notes.count.';
switch (c){
case 0:
str += 'zero';
break;
case 1:
str += 'singular';
break;
default:
str += 'plural';
break;
}
_notesLabel.value = Zotero.getString(str, [c]);
_notesLabel.value = Zotero.getString('pane.item.notes.count', c, c);
}
}

View file

@ -62,23 +62,23 @@
<button id="zotero-item-show-original" label="Show Original"
oncommand="ZoteroPane_Local.showOriginalItem()" hidden="true"/>
<deck id="zotero-item-pane-content" selectedIndex="0" flex="1">
<deck id="zotero-item-pane-content" class="zotero-item-pane-content" selectedIndex="0" flex="1">
<!-- Center label (for zero or multiple item selection) -->
<groupbox id="zotero-item-pane-groupbox" pack="center" align="center">
<vbox id="zotero-item-pane-message-box"/>
</groupbox>
<!-- Regular item -->
<tabbox id="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
<tabs id="zotero-editpane-tabs">
<tabbox id="zotero-view-tabbox" class="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
<tabs id="zotero-editpane-tabs" class="zotero-editpane-tabs">
<tab id="zotero-editpane-info-tab" label="&zotero.tabs.info.label;"/>
<tab id="zotero-editpane-notes-tab" label="&zotero.tabs.notes.label;"/>
<tab id="zotero-editpane-tags-tab" label="&zotero.tabs.tags.label;"/>
<tab id="zotero-editpane-related-tab" label="&zotero.tabs.related.label;"/>
</tabs>
<tabpanels id="zotero-view-item" flex="1">
<tabpanel>
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
<tabpanels id="zotero-view-item" class="zotero-view-item" flex="1">
<tabpanel flex="1">
<zoteroitembox id="zotero-editpane-item-box" class="zotero-editpane-item-box" flex="1"/>
</tabpanel>
<tabpanel flex="1" orient="vertical">
@ -97,12 +97,12 @@
</vbox>
</tabpanel>
<tabpanel id="tags-pane" orient="vertical" context="tags-context-menu">
<html:div id="tags-box-container"></html:div>
<tabpanel id="tags-pane" class="tags-pane" orient="vertical" context="tags-context-menu">
<html:div id="tags-box-container" class="tags-box-container"></html:div>
</tabpanel>
<tabpanel>
<relatedbox id="zotero-editpane-related" flex="1"/>
<relatedbox id="zotero-editpane-related" class="zotero-editpane-related" flex="1"/>
</tabpanel>
</tabpanels>
</tabbox>
@ -113,9 +113,13 @@
'onerror' handler crashes the app on a save error to prevent typing in notes
while they're not being saved
-->
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
previousfocus="zotero-items-tree"
onerror="ZoteroPane.displayErrorMessage(); this.mode = 'view'"/>
<deck id="zotero-note-editor-switcher" flex="1">
<oldzoteronoteeditor id="zotero-note-editor-old" flex="1" notitle="1"
previousfocus="zotero-items-tree"
onerror="ZoteroPane.displayErrorMessage(); this.mode = 'view'"/>
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
previousfocus="zotero-items-tree"/>
</deck>
<button id="zotero-view-note-button"
label="&zotero.notes.separate;"
oncommand="ZoteroItemPane.openNoteWindow()"/>

View file

@ -25,14 +25,14 @@
var noteEditor;
var notifierUnregisterID;
var type;
function switchEditorEngine(useOld) {
var switherDeck = document.getElementById('zotero-note-editor-switcher');
switherDeck.selectedIndex = useOld ? 0 : 1;
}
async function onLoad() {
noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'edit';
// Set font size from pref
Zotero.setFontSize(noteEditor);
if (window.arguments) {
var io = window.arguments[0];
}
@ -41,6 +41,35 @@ async function onLoad() {
var collectionID = parseInt(io.collectionID);
var parentItemKey = io.parentItemKey;
if (itemID) {
var ref = await Zotero.Items.getAsync(itemID);
var libraryID = ref.libraryID;
}
else {
if (parentItemKey) {
var ref = Zotero.Items.getByLibraryAndKey(parentItemKey);
var libraryID = ref.libraryID;
}
else {
if (collectionID && collectionID != '' && collectionID != 'undefined') {
var collection = Zotero.Collections.get(collectionID);
var libraryID = collection.libraryID;
}
}
}
type = Zotero.Libraries.get(libraryID).libraryType;
switchEditorEngine(type == 'group' || !Zotero.isPDFBuild);
if (type == 'group' || !Zotero.isPDFBuild) {
noteEditor = document.getElementById('zotero-note-editor-old');
}
else {
noteEditor = document.getElementById('zotero-note-editor');
}
noteEditor.mode = 'edit';
// Set font size from pref
Zotero.setFontSize(noteEditor);
if (itemID) {
var ref = await Zotero.Items.getAsync(itemID);
noteEditor.item = ref;
@ -77,9 +106,13 @@ function onError() {
function onUnload() {
Zotero.Notifier.unregisterObserver(notifierUnregisterID);
if (noteEditor.item) {
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteEditor.value);
if (type == 'group' || !Zotero.isPDFBuild) {
if (noteEditor.item) {
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteEditor.value);
}
}
else {
noteEditor.saveSync();
}
}
@ -87,9 +120,7 @@ var NotifyCallback = {
notify: function(action, type, ids){
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
var noteTitle = noteEditor.item.getNoteTitle();
if (!document.title && noteTitle != '') {
document.title = noteTitle;
}
document.title = noteTitle;
// Update the window name (used for focusing) in case this is a new note
window.name = 'zotero-note-' + noteEditor.item.id;

View file

@ -21,6 +21,9 @@
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
</keyset>
<command id="cmd_close" oncommand="window.close();"/>
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="onError()"/>
</window>
<deck id="zotero-note-editor-switcher" flex="1">
<oldzoteronoteeditor id="zotero-note-editor-old" flex="1" onerror="onError()"/>
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
</deck>
</window>

View file

@ -0,0 +1,57 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://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 *****
*/
var noteEditor;
async function onLoad() {
noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'view';
// Set font size from pref
Zotero.setFontSize(noteEditor);
if (window.arguments) {
var io = window.arguments[0];
}
var itemID = parseInt(io.itemID);
var item = await Zotero.Items.getAsync(itemID);
let note = await Zotero.NoteBackups.getNote(item.id);
if (!note) {
note = item.getNote();
}
var tmpItem = new Zotero.Item('note');
tmpItem.libraryID = item.libraryID;
tmpItem.setNote(note);
noteEditor.item = tmpItem;
document.title = 'BACKUP OF: ' + item.getNoteTitle();
noteEditor.focus();
}
addEventListener("load", function(e) { onLoad(e); }, false);

View file

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window
id="zotero-note-window"
orient="vertical"
width="400"
height="350"
title="&zotero.items.menu.attach.note;"
persist="screenX screenY width height"
windowtype="zotero:note"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="include.js"/>
<script src="noteBackup.js"/>
<keyset>
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
</keyset>
<command id="cmd_close" oncommand="window.close();"/>
<oldzoteronoteeditor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
</window>

View file

@ -41,6 +41,15 @@ var ZoteroOverlay = new function () {
throw new Error("Skipping loading");
}
// Keep in sync with standalone.xul
if (Zotero.test) {
let ns = 'http://www.w3.org/1999/xhtml';
let div = document.createElementNS(ns, 'div');
let beforeNode = document.getElementById('browser');
div.id = 'tab-bar-container';
beforeNode.parentNode.insertBefore(div, beforeNode);
}
ZoteroPane.init();
}
catch (e) {

View file

@ -38,9 +38,12 @@ Zotero_Preferences.General = {
'zotero.preferences.launchNonNativeFiles', Zotero.appName
);
}
var menuitem = document.getElementById('fileHandler-internal');
menuitem.setAttribute('label', Zotero.appName);
this.updateAutoRenameFilesUI();
this._updateFileHandlerUI();
this._updateZotero6BetaCheckbox();
},
updateAutoRenameFilesUI: function () {
@ -88,6 +91,16 @@ Zotero_Preferences.General = {
var handler = Zotero.Prefs.get('fileHandler.pdf');
var menulist = document.getElementById('fileHandler-pdf');
var customMenuItem = document.getElementById('fileHandler-custom');
// TEMP: Use separate checkbox for now
/*if (handler == 'zotero') {
let menuitem = document.getElementById('fileHandler-internal');
menulist.selectedIndex = 0;
customMenuItem.hidden = true;
return;
}*/
// Custom handler
if (handler) {
let icon;
try {
@ -113,11 +126,12 @@ Zotero_Preferences.General = {
customMenuItem.className = '';
}
customMenuItem.hidden = false;
menulist.selectedIndex = 0;
menulist.selectedIndex = 1;
}
// System default
else {
customMenuItem.hidden = true;
menulist.selectedIndex = 1;
menulist.selectedIndex = 2;
}
},
@ -126,5 +140,38 @@ Zotero_Preferences.General = {
throw new Error(`Unknown file type ${type}`);
}
return 'fileHandler.pdf';
},
handleZotero6BetaChange: function (event) {
var ps = Services.prompt;
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
var index = ps.confirmEx(
window,
Zotero.getString('general.restartRequired'),
Zotero.getString('general.restartRequiredForChange', Zotero.appName),
buttonFlags,
Zotero.getString('general.restartApp', Zotero.appName),
null, null, null, {}
);
if (index == 0) {
Zotero.Prefs.set('beta.zotero6', !event.target.checked);
Zotero.Utilities.Internal.quitZotero(true);
return;
}
// Set to opposite so the click changes it back to what it was before
event.target.checked = !event.target.checked;
},
_updateZotero6BetaCheckbox: function () {
var checkbox = document.getElementById('zotero6-checkbox');
if (Zotero.Prefs.get('beta.zotero6')) {
checkbox.setAttribute('checked', true);
}
else {
checkbox.removeAttribute('checked');
}
}
}

View file

@ -44,7 +44,6 @@
<preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
<preference id="pref-groups-copyChildLinks" name="extensions.zotero.groups.copyChildLinks" type="bool"/>
<preference id="pref-groups-copyTags" name="extensions.zotero.groups.copyTags" type="bool"/>
</preferences>
<groupbox id="zotero-prefpane-file-handling-groupbox">
@ -67,6 +66,9 @@
<label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/>
<menulist id="fileHandler-pdf" class="fileHandler-menu">
<menupopup>
<menuitem id="fileHandler-internal"
oncommand="Zotero_Preferences.General.setFileHandler('pdf', 'zotero')"
hidden="true"/>
<menuitem id="fileHandler-custom"/>
<menuitem label="&zotero.preferences.fileHandler.systemDefault;"
oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/>
@ -75,6 +77,13 @@
</menupopup>
</menulist>
</hbox>
<checkbox
id="zotero6-checkbox"
label="Enable the Zotero PDF reader and new note editor (preview, My Library only)"
style="margin-top: 5px"
onclick="Zotero_Preferences.General.handleZotero6BetaChange(event)"/>
<label class="zotero-text-link" value="Learn more about the preview"
style="margin-left: 23px; margin-top: 1px" href="https://www.zotero.org/support/pdf_reader_preview"/>
</groupbox>
<groupbox id="zotero-prefpane-miscellaneous-groupbox">

View file

@ -0,0 +1,120 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xul-overlay href="chrome://zotero/content/standalone/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://zotero-platform/content/standalone/menuOverlay.xul"?>
<!DOCTYPE window [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
<!ENTITY % standaloneDTD SYSTEM "chrome://zotero/locale/standalone.dtd" > %standaloneDTD;
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd"> %zoteroDTD;
]>
<window
id="pdf-reader"
windowtype="zotero:reader"
orient="vertical"
width="1300"
height="800"
persist="screenX screenY width height"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
<script type="application/javascript">
Components.utils.import('resource://gre/modules/Services.jsm');
</script>
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
<script src="include.js"/>
<commandset id="mainCommandSet">
<!--FILE-->
<command id="cmd_quitApplication" oncommand="goQuitApplication();"/>
<command id="cmd_close" oncommand="window.close();"/>
<!--EDIT-->
<commandset id="editMenuCommands"/>
<command id="cmd_find"
oncommand="document.getElementById('zotero-tb-search').select()"/>
</commandset>
<!-- TODO: Localize -->
<tooltip id="iframeTooltip" onpopupshowing="if (tooltipTitleNode = document.tooltipNode.closest('*[title]')) {this.setAttribute('label', tooltipTitleNode.getAttribute('title')); return true; } return false"/>
<menubar>
<menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
<menupopup id="menu_FilePopup">
<menuitem label="Export" oncommand="menuCmd('export')"/>
<menuitem label="Print" oncommand="menuCmd('print')"/>
<menuseparator/>
<menuitem id="menu_close" label="&closeCmd.label;" key="key_close"
accesskey="&closeCmd.accesskey;" command="cmd_close"/>
</menupopup>
</menu>
<menu id="menu_edit">
<menupopup id="menu_EditPopup">
<menuitem id="menu_undo"/>
<menuitem id="menu_redo"/>
<menuseparator/>
<menuitem id="menu_cut"/>
<menuitem id="menu_copy"/>
<menuitem id="menu_paste"/>
<menuitem id="menu_delete"/>
</menupopup>
</menu>
<menu label="View">
<menupopup>
<!--
To make presentation mode work it's necessary to set
full-screen-api.enabled=true
full-screen-api.allow-trusted-requests-only=false
and then hide all other visible window elements like toolbar, note sidebar, tabs, etc.
-->
<!-- <menuitem label="Switch to Presentation Mode" oncommand="menuCmd('presentationmode')"/>-->
<!-- <menuseparator/>-->
<menuitem label="Go to First Page" oncommand="menuCmd('firstpage')"/>
<menuitem label="Go to Last Page" oncommand="menuCmd('lastpage')"/>
<menuseparator/>
<menuitem label="Rotate Clockwise" oncommand="menuCmd('rotatecw')"/>
<menuitem label="Rotate Counterclockwise" oncommand="menuCmd('rotateccw')"/>
<menuseparator/>
<menuitem label="Text Selection Tool" oncommand="menuCmd('switchcursortool_select')"/>
<menuitem label="Hand Tool" oncommand="menuCmd('switchcursortool_hand')"/>
<menuseparator/>
<menuitem label="Vertical Scrolling" oncommand="menuCmd('switchscrollmode_vertical')"/>
<menuitem label="Horizontal Scrolling" oncommand="menuCmd('switchscrollmode_horizontal')"/>
<menuitem label="Wrapped Scrolling" oncommand="menuCmd('switchscrollmode_wrapped')"/>
<menuseparator/>
<menuitem label="No Spreads" oncommand="menuCmd('switchspreadmode_none')"/>
<menuitem label="Odd Spreads" oncommand="menuCmd('switchspreadmode_odd')"/>
<menuitem label="Even Spreads" oncommand="menuCmd('switchspreadmode_even')"/>
</menupopup>
</menu>
<!-- <menu label="Tools">-->
<!-- <menupopup>-->
<!-- <menuitem label="Remove password (not implemented)" oncommand="menuCmd('remove_password')"/>-->
<!-- </menupopup>-->
<!-- </menu>-->
<menu id="windowMenu"
label="&windowMenu.label;"
onpopupshowing="macWindowMenuDidShow();"
onpopuphidden="macWindowMenuDidHide();"
>
</menu>
</menubar>
<hbox flex="1">
<vbox id="zotero-reader" flex="3">
<browser id="reader"
tooltip="iframeTooltip"
type="content"
primary="true"
transparent="transparent"
src="resource://zotero/pdf-reader/viewer.html"
flex="1"/>
<popupset id="zotero-reader-popupset">
</popupset>
</vbox>
</hbox>
</window>

View file

@ -42,6 +42,28 @@ const ZoteroStandalone = new function() {
window.document.documentElement.setAttribute('sizemode', 'normal');
}
// Create tab bar by default
if (Zotero.isMac) {
document.documentElement.setAttribute('drawintitlebar', true);
document.documentElement.setAttribute('tabsintitlebar', true);
document.documentElement.setAttribute('chromemargin', '0,-1,-1,-1');
}
this.switchMenuType('library');
Zotero.Notifier.registerObserver(
{
notify: async (action, type, ids, extraData) => {
if (action == 'select') {
// "library" or "reader"
this.switchMenuType(extraData[ids[0]].type);
setTimeout(() => ZoteroPane.updateToolbarPosition(), 0);
}
}
},
['tab'],
'tab'
);
Zotero.Promise.try(function () {
if(!Zotero) {
throw true;
@ -59,6 +81,13 @@ const ZoteroStandalone = new function() {
ZoteroStandalone.DebugOutput.init();
// TEMP: Remove tab bar if not PDF build
if (Zotero.isMac && !Zotero.isPDFBuild) {
document.documentElement.removeAttribute('drawintitlebar');
document.documentElement.removeAttribute('tabsintitlebar');
document.documentElement.removeAttribute('chromemargin');
}
Zotero.hideZoteroPaneOverlays();
ZoteroPane.init();
ZoteroPane.makeVisible();
@ -90,7 +119,16 @@ const ZoteroStandalone = new function() {
return;
});
}
this.switchMenuType = function (type) {
document.querySelectorAll('.menu-type-library, .menu-type-reader').forEach(el => el.collapsed = true);
document.querySelectorAll('.menu-type-' + type).forEach(el => el.collapsed = false);
};
this.onReaderCmd = function (cmd) {
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
reader.menuCmd(cmd);
};
this.onFileMenuOpen = function () {
var active = false;
@ -422,7 +460,6 @@ const ZoteroStandalone = new function() {
this.updateNoteFontSize = function (event) {
var size = event.originalTarget.getAttribute('label');
Zotero.Prefs.set('note.fontSize', size);
this.promptForRestart();
};

View file

@ -43,6 +43,7 @@
<window id="main-window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
windowtype="navigator:browser"
title="&brandShortName;"
width="1000" height="600"
@ -111,7 +112,15 @@
accesskey="&selectAllCmd.accesskey;"
command="cmd_selectAll"/>
</menupopup>
<vbox id="titlebar">
<hbox id="titlebar-buttonbox-container" skipintoolbarset="true">
<hbox id="titlebar-buttonbox">
<toolbarbutton class="titlebar-button titlebar-min" oncommand="window.minimize();"/>
<toolbarbutton class="titlebar-button titlebar-max" oncommand="onTitlebarMaxClick();"/>
<toolbarbutton class="titlebar-button titlebar-close" command="cmd_closeWindow"/>
</hbox>
</hbox>
</vbox>
<toolbox id="navigator-toolbox" class="toolbox-top" mode="icons" defaultmode="icons">
<!-- Menu -->
<toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
@ -119,28 +128,38 @@
mode="icons" iconsize="small" defaulticonsize="small"
context="toolbar-context-menu">
<toolbaritem id="menubar-items" align="center">
<!-- TODO: Localize labels -->
<!--
On macOS the document's top-most menubar node is used and
there is no way to change it after the initialization, only
the children can be modified. All non-menu nodes are ignored
-->
<menubar id="main-menubar"
style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
<menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;"
onpopupshowing="ZoteroStandalone.onFileMenuOpen()">
<menupopup id="menu_FilePopup">
<menu id="menu_newItem" label="&zotero.toolbar.newItem.label;">
<menu id="menu_newItem" class="menu-type-library" label="&zotero.toolbar.newItem.label;">
<menupopup id="menu_NewItemPopup"
onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/>
</menu>
<menuitem id="menu_newNote" label="&zotero.toolbar.newNote;"
<menuitem id="menu_newNote" class="menu-type-library" label="&zotero.toolbar.newNote;"
command="cmd_zotero_newStandaloneNote"/>
<menuitem id="menu_newCollection" label="&zotero.toolbar.newCollection.label;"
<menuitem id="menu_newCollection" class="menu-type-library" label="&zotero.toolbar.newCollection.label;"
command="cmd_zotero_newCollection"/>
<menuitem label="Save As" class="menu-type-reader" oncommand="ZoteroStandalone.onReaderCmd('export')"/>
<menuitem label="Print" class="menu-type-reader" oncommand="ZoteroStandalone.onReaderCmd('print')"/>
<menuseparator/>
<menuitem id="menu_close" label="&closeCmd.label;" key="key_close"
<menuitem id="menu_close" class="menu-type-library" label="&closeCmd.label;" key="key_close"
accesskey="&closeCmd.accesskey;" command="cmd_close"/>
<menuseparator/>
<menuitem id="menu_import" label="&importCmd.label;"
<menuitem id="menu_close_tab" class="menu-type-reader" label="&closeCmd.label;" key="key_close"
accesskey="&closeCmd.accesskey;" oncommand="Zotero_Tabs.close()"/>
<menuseparator class="menu-type-library"/>
<menuitem id="menu_import" class="menu-type-library" label="&importCmd.label;"
command="cmd_zotero_import" key="key_import"/>
<menuitem id="menu_importFromClipboard" label="&importFromClipboardCmd.label;"
<menuitem id="menu_importFromClipboard" class="menu-type-library" label="&importFromClipboardCmd.label;"
command="cmd_zotero_importFromClipboard" key="key_importFromClipboard"/>
<menuitem id="menu_exportLibrary" label="&zotero.toolbar.export.label;"
<menuitem id="menu_exportLibrary" class="menu-type-library" label="&zotero.toolbar.export.label;"
command="cmd_zotero_exportLibrary"/>
</menupopup>
</menu>
@ -154,29 +173,33 @@
<menuitem id="menu_cut"/>
<menuitem id="menu_copy"/>
<menuitem id="menu_copyCitation"
class="menu-type-library"
label="&copyCitationCmd.label;"
command="cmd_zotero_copyCitation"
key="key_copyCitation"
hidden="true"/>
<menuitem id="menu_copyBibliography"
class="menu-type-library"
label="&copyBibliographyCmd.label;"
command="cmd_zotero_copyBibliography"
key="key_copyBibliography"
hidden="true"/>
<menuitem id="menu_copyExport"
class="menu-type-library"
key="key_copyBibliography"
command="cmd_zotero_copyBibliography"
hidden="true"/>
<menuitem id="menu_paste"/>
<menuitem id="menu_delete"/>
<menuseparator/>
<menuitem id="menu_selectAll"/>
<menuseparator/>
<menuitem id="menu_find"/>
<menuseparator class="menu-type-library"/>
<menuitem id="menu_selectAll" class="menu-type-library"/>
<menuseparator class="menu-type-library"/>
<menuitem id="menu_find" class="menu-type-library"/>
<menuitem id="menu_advancedSearch"
class="menu-type-library"
label="&zotero.toolbar.advancedSearch;"
command="cmd_zotero_advancedSearch"/>
<menuseparator hidden="true" id="textfieldDirection-separator"/>
<menuseparator class="menu-type-library" hidden="true" id="textfieldDirection-separator"/>
<menuitem id="textfieldDirection-swap"
command="cmd_switchTextDirection"
key="key_switchTextDirection"
@ -190,8 +213,27 @@
label="&viewMenu.label;"
onpopupshowing="ZoteroStandalone.onViewMenuOpen()">
<menupopup id="menu_viewPopup">
<menu id="layout-menu"
label="&layout.label;">
<!-- <menuitem class="menu-type-reader" label="Switch to Presentation Mode" oncommand="ZoteroStandalone.onReaderCmd('presentationmode')"/>-->
<!-- <menuseparator class="menu-type-reader"/>-->
<menuitem class="menu-type-reader" label="Go to First Page" oncommand="ZoteroStandalone.onReaderCmd('firstpage')"/>
<menuitem class="menu-type-reader" label="Go to Last Page" oncommand="ZoteroStandalone.onReaderCmd('lastpage')"/>
<menuseparator class="menu-type-reader"/>
<menuitem class="menu-type-reader" label="Rotate Clockwise" oncommand="ZoteroStandalone.onReaderCmd('rotatecw')"/>
<menuitem class="menu-type-reader" label="Rotate Counterclockwise" oncommand="ZoteroStandalone.onReaderCmd('rotateccw')"/>
<menuseparator class="menu-type-reader"/>
<menuitem class="menu-type-reader" label="Text Selection Tool" oncommand="ZoteroStandalone.onReaderCmd('switchcursortool_select')"/>
<menuitem class="menu-type-reader" label="Hand Tool" oncommand="ZoteroStandalone.onReaderCmd('switchcursortool_hand')"/>
<menuseparator class="menu-type-reader"/>
<menuitem class="menu-type-reader" label="Vertical Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_vertical')"/>
<menuitem class="menu-type-reader" label="Horizontal Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_horizontal')"/>
<menuitem class="menu-type-reader" label="Wrapped Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_wrapped')"/>
<menuseparator class="menu-type-reader"/>
<menuitem class="menu-type-reader" label="No Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_none')"/>
<menuitem class="menu-type-reader" label="Odd Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_odd')"/>
<menuitem class="menu-type-reader" label="Even Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_even')"/>
<menuseparator class="menu-type-reader"/>
<menu id="layout-menu" label="&layout.label;">
<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
<menuitem
id="view-menuitem-standard"
@ -259,6 +301,7 @@
</menu>
<menuseparator/>
<menuitem id="view-menuitem-recursive-collections"
class="menu-type-library"
label="&recursiveCollections.label;"
oncommand="ZoteroStandalone.onViewMenuItemClick(event)"
type="checkbox"
@ -268,16 +311,17 @@
<menu id="toolsMenu" label="&toolsMenu.label;" accesskey="&toolsMenu.accesskey;">
<menupopup id="menu_ToolsPopup">
<menuitem id="menu_createTimeline" label="&zotero.toolbar.timeline.label;"
<menuitem id="menu_createTimeline" class="menu-type-library" label="&zotero.toolbar.timeline.label;"
command="cmd_zotero_createTimeline"/>
<menuitem id="menu_rtfScan" label="&zotero.toolbar.rtfScan.label;"
<menuitem id="menu_rtfScan" class="menu-type-library" label="&zotero.toolbar.rtfScan.label;"
command="cmd_zotero_rtfScan"/>
<menuseparator/>
<menuitem id="installConnector"
class="menu-type-library"
accessKey="&installConnector.accesskey;"
label="&installConnector.label;"
oncommand="ZoteroStandalone.openHelp('connectors');"/>
<menuitem id="menu_addons" label="&addons.label;"
<menuitem id="menu_addons" class="menu-type-library" label="&addons.label;"
oncommand="Zotero.openInViewer('chrome://mozapps/content/extensions/extensions.xul', ZoteroStandalone.updateAddonsPane)"/>
<menu id="developer-menu"
label="&developer.label;">
@ -289,7 +333,7 @@
</menupopup>
</menu>
<menuseparator/>
<menu id="manage-attachments-menu" label="&manageAttachments.label;"
<menu id="manage-attachments-menu" class="menu-type-library" label="&manageAttachments.label;"
onpopupshowing="ZoteroStandalone.onManageAttachmentsMenuOpen()"
oncommand="ZoteroStandalone.onManageAttachmentsMenuItemClick(event)">
<menupopup id="manage-attachments-menupopup">
@ -372,6 +416,8 @@
</toolbaritem>
</toolbar>
</toolbox>
<!-- Keep in sync with Zotero.test conditional block in overlay.js -->
<div xmlns="http://www.w3.org/1999/xhtml" id="tab-bar-container"/>
<!--<toolbarpalette/>
<toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"

View file

@ -0,0 +1,247 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 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';
// Using 'import' breaks hooks
var React = require('react');
var ReactDOM = require('react-dom');
import TabBar from 'components/tabBar';
var Zotero_Tabs = new function () {
Object.defineProperty(this, 'selectedID', {
get: () => this._selectedID
});
Object.defineProperty(this, 'selectedIndex', {
get: () => this._getTab(this._selectedID).tabIndex
});
Object.defineProperty(this, 'deck', {
get: () => document.getElementById('tabs-deck')
});
this._tabBarRef = React.createRef();
this._tabs = [{
id: 'zotero-pane',
type: 'library',
title: ''
}];
this._selectedID = 'zotero-pane';
this._getTab = function (id) {
var tabIndex = this._tabs.findIndex(tab => tab.id == id);
return { tab: this._tabs[tabIndex], tabIndex };
};
this._update = function () {
this._tabBarRef.current.setTabs(this._tabs.map(tab => ({
id: tab.id,
type: tab.type,
title: tab.title,
selected: tab.id == this._selectedID
})));
var { tab } = this._getTab(this._selectedID);
document.title = (tab.title.length ? tab.title + ' - ' : '') + 'Zotero';
};
this.init = function () {
ReactDOM.render(
<TabBar
ref={this._tabBarRef}
onTabSelect={this.select.bind(this)}
onTabMove={this.move.bind(this)}
onTabClose={this.close.bind(this)}
/>,
document.getElementById('tab-bar-container'),
() => {
this._update();
}
);
};
/**
* Add a new tab
*
* @param {String} type
* @param {String} title
* @param {Integer} index
* @param {Boolean} select
* @param {Function} onClose
* @return {{ id: string, container: XULElement}} id - tab id, container - a new tab container created in the deck
*/
this.add = function ({ type, title, index, select, onClose, notifierData }) {
//this.showTabBar();
if (typeof type != 'string') {
throw new Error(`'type' should be a string (was ${typeof type})`);
}
if (typeof title != 'string') {
throw new Error(`'title' should be a string (was ${typeof title})`);
}
if (index !== undefined && (!Number.isInteger(index) || index < 1)) {
throw new Error(`'index' should be an integer > 0 (was ${index} (${typeof index})`);
}
if (onClose !== undefined && typeof onClose != 'function') {
throw new Error(`'onClose' should be a function (was ${typeof onClose})`);
}
var id = 'tab-' + Zotero.Utilities.randomString();
var container = document.createElement('vbox');
container.id = id;
this.deck.appendChild(container);
var tab = { id, type, title, onClose };
index = index || this._tabs.length;
this._tabs.splice(index, 0, tab);
this._update();
Zotero.Notifier.trigger('add', 'tab', [id], { [id]: notifierData }, true);
if (select) {
this.select(id);
}
return { id, container };
};
/**
* Set a new tab title
*
* @param {String} id
* @param {String} title
*/
this.rename = function (id, title) {
if (typeof title != 'string') {
throw new Error(`'title' should be a string (was ${typeof title})`);
}
var { tab } = this._getTab(id);
if (!tab) {
return;
}
tab.title = title;
this._update();
};
/**
* Close a tab
*
* @param {String} id
*/
this.close = function (id) {
var { tab, tabIndex } = this._getTab(id || this._selectedID);
if (tabIndex == 0) {
throw new Error('Library tab cannot be closed');
}
if (!tab) {
return;
}
this.select((this._tabs[tabIndex + 1] || this._tabs[tabIndex - 1]).id);
this._tabs.splice(tabIndex, 1);
document.getElementById(tab.id).remove();
if (tab.onClose) {
tab.onClose();
}
Zotero.Notifier.trigger('close', 'tab', [tab.id], true);
this._update();
/*if (this._tabs.length == 1) {
this.hideTabBar();
}*/
};
/**
* Move a tab to the specified index
*
* @param {String} id
* @param {Integer} newIndex
*/
this.move = function (id, newIndex) {
if (!Number.isInteger(newIndex) || newIndex < 1) {
throw new Error(`'newIndex' should be an interger > 0 (was ${newIndex} (${typeof newIndex})`);
}
var { tab, tabIndex } = this._getTab(id);
if (tabIndex == 0) {
throw new Error('Library tab cannot be moved');
}
if (!tab || tabIndex == newIndex) {
return;
}
if (newIndex > tabIndex) {
newIndex--;
}
this._tabs.splice(tabIndex, 1);
this._tabs.splice(newIndex, 0, tab);
this._update();
};
/**
* Select a tab
*
* @param {String} id
*/
this.select = function (id) {
var { tab } = this._getTab(id);
if (!tab) {
return;
}
this._selectedID = id;
this.deck.selectedIndex = Array.from(this.deck.children).findIndex(x => x.id == id);
this._update();
Zotero.Notifier.trigger('select', 'tab', [tab.id], { [tab.id]: { type: tab.type } }, true);
};
/**
* Select the previous tab (closer to the library tab)
*/
this.selectPrev = function () {
var { tabIndex } = this._getTab(this._selectedID);
this.select((this._tabs[tabIndex - 1] || this._tabs[this._tabs.length - 1]).id);
};
/**
* Select the next tab (farther to the library tab)
*/
this.selectNext = function () {
var { tabIndex } = this._getTab(this._selectedID);
this.select((this._tabs[tabIndex + 1] || this._tabs[0]).id);
};
// Unused
this.showTabBar = function () {
document.documentElement.setAttribute('drawintitlebar', true);
document.documentElement.setAttribute('tabsintitlebar', true);
document.documentElement.setAttribute('chromemargin', '0,-1,-1,-1');
document.getElementById('titlebar').hidden = false;
document.getElementById('tab-bar-container').hidden = false;
document.getElementById('main-window').removeAttribute('legacytoolbar')
};
// Unused
this.hideTabBar = function () {
document.documentElement.removeAttribute('drawintitlebar');
document.documentElement.removeAttribute('tabsintitlebar');
document.documentElement.removeAttribute('chromemargin');
document.getElementById('titlebar').hidden = true
document.getElementById('tab-bar-container').hidden = true;
document.getElementById('main-window').setAttribute('legacytoolbar', 'true')
};
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,210 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 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";
Zotero.Annotations = new function () {
// Keep in sync with items.js::loadAnnotations()
Zotero.defineProperty(this, 'ANNOTATION_TYPE_HIGHLIGHT', { value: 1 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_NOTE', { value: 2 });
Zotero.defineProperty(this, 'ANNOTATION_TYPE_IMAGE', { value: 3 });
this.getCacheImagePath = function ({ libraryID, key }) {
var file = this._getLibraryCacheDirectory(libraryID);
return OS.Path.join(file, key + '.png');
};
this.hasCacheImage = async function (item) {
return OS.File.exists(this.getCacheImagePath(item));
};
this.saveCacheImage = async function ({ libraryID, key }, blob) {
var item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, key);
if (!item) {
throw new Error(`Item not found`);
}
if (item.itemType != 'annotation' || item.annotationType != 'image') {
throw new Error("Item must be an image annotation item");
}
var cacheDir = Zotero.DataDirectory.getSubdirectory('cache', true);
var file = this._getLibraryCacheDirectory(item.libraryID);
await Zotero.File.createDirectoryIfMissingAsync(file, { from: cacheDir });
file = OS.Path.join(file, item.key + '.png');
Zotero.debug("Creating annotation cache file " + file);
await Zotero.File.putContentsAsync(file, blob);
await Zotero.File.setNormalFilePermissions(file);
return file;
};
this.removeCacheImage = async function ({ libraryID, key }) {
var path = this.getCacheImagePath({ libraryID, key });
Zotero.debug("Deleting annotation cache file " + path);
await OS.File.remove(path, { ignoreAbsent: true });
};
/**
* Remove cache files that are no longer in use
*/
this.removeOrphanedCacheFiles = async function () {
// TODO
};
/**
* Remove all cache files for a given library
*/
this.removeLibraryCacheFiles = async function (libraryID) {
var path = this._getLibraryCacheDirectory(libraryID);
await OS.File.removeDir(path, { ignoreAbsent: true, ignorePermissions: true });
};
this._getLibraryCacheDirectory = function (libraryID) {
var parts = [Zotero.DataDirectory.getSubdirectory('cache')];
var library = Zotero.Libraries.get(libraryID);
if (library.libraryType == 'user') {
parts.push('library');
}
else if (library.libraryType == 'group') {
parts.push('groups', library.groupID);
}
else {
throw new Error(`Unexpected library type '${library.libraryType}'`);
}
return OS.Path.join(...parts);
};
this.toJSON = async function (item) {
var o = {};
o.libraryID = item.libraryID;
o.key = item.key;
o.type = item.annotationType;
o.isExternal = item.annotationIsExternal;
o.isAuthor = !item.createdByUserID || item.createdByUserID == Zotero.Users.getCurrentUserID();
if (!o.isAuthor) {
o.authorName = Zotero.Users.getName(item.createdByUserID);
}
if (o.type == 'highlight') {
o.text = item.annotationText;
}
else if (o.type == 'image') {
let file = this.getCacheImagePath(item);
if (await OS.File.exists(file)) {
o.image = await Zotero.File.generateDataURI(file, 'image/png');
}
}
o.comment = item.annotationComment;
o.pageLabel = item.annotationPageLabel;
o.color = item.annotationColor;
o.sortIndex = item.annotationSortIndex;
// annotationPosition is a JSON string, but we want to pass the raw object to the reader
o.position = JSON.parse(item.annotationPosition);
// Add tags and tag colors
var tagColors = Zotero.Tags.getColors(item.libraryID);
var tags = item.getTags().map((t) => {
let obj = {
name: t.tag
};
if (tagColors.has(t.tag)) {
obj.color = tagColors.get(t.tag).color;
// Add 'position' for sorting
obj.position = tagColors.get(t.tag).position;
}
return obj;
});
// Sort colored tags by position and other tags by name
tags.sort((a, b) => {
if (!a.color && !b.color) return Zotero.localeCompare(a.name, b.name);
if (!a.color && !b.color) return -1;
if (!a.color && b.color) return 1;
return a.position - b.position;
});
// Remove temporary 'position' value
tags.forEach(t => delete t.position);
if (tags.length) {
o.tags = tags;
}
o.dateModified = item.dateModified;
return o;
};
/**
* @param {Zotero.Item} attachment - Saved parent attachment item
* @param {Object} json
* @return {Promise<Zotero.Item>} - Promise for an annotation item
*/
this.saveFromJSON = async function (attachment, json, saveOptions = {}) {
if (!attachment) {
throw new Error("'attachment' not provided");
}
if (!attachment.libraryID) {
throw new Error("'attachment' is not saved");
}
if (!json.key) {
throw new Error("'key' not provided in JSON");
}
var item = Zotero.Items.getByLibraryAndKey(attachment.libraryID, json.key);
if (!item) {
item = new Zotero.Item('annotation');
item.libraryID = attachment.libraryID;
item.key = json.key;
await item.loadPrimaryData();
}
item.parentID = attachment.id;
item._requireData('annotation');
item._requireData('annotationDeferred');
item.annotationType = json.type;
if (json.type == 'highlight') {
item.annotationText = json.text;
}
item.annotationIsExternal = !!json.isExternal;
item.annotationComment = json.comment;
item.annotationColor = json.color;
item.annotationPageLabel = json.pageLabel;
item.annotationSortIndex = json.sortIndex;
item.annotationPosition = JSON.stringify(Object.assign({}, json.position));
// TODO: Can colors be set?
item.setTags((json.tags || []).map(t => ({ tag: t.name })));
await item.saveTx(saveOptions);
return item;
};
};

View file

@ -24,11 +24,13 @@
*/
Zotero.Attachments = new function(){
// Keep in sync with Zotero.Schema.integrityCheck()
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
this.LINK_MODE_IMPORTED_FILE = 0;
this.LINK_MODE_IMPORTED_URL = 1;
this.LINK_MODE_LINKED_FILE = 2;
this.LINK_MODE_LINKED_URL = 3;
this.LINK_MODE_EMBEDDED_IMAGE = 4;
this.BASE_PATH_PLACEHOLDER = 'attachments:';
var _findPDFQueue = [];
@ -351,6 +353,98 @@ Zotero.Attachments = new function(){
});
/**
* Saves an image for a parent note or image annotation
*
* Emerging formats like WebP and AVIF are supported here,
* but should be filtered on the calling logic for now
*
* @param {Object} params
* @param {Blob} params.blob - Image to save
* @param {Integer} params.parentItemID - Note or annotation item to add item to
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
* @return {Promise<Zotero.Item>}
*/
this.importEmbeddedImage = async function ({ blob, parentItemID, saveOptions }) {
Zotero.debug('Importing embedded image');
if (!parentItemID) {
throw new Error("parentItemID must be provided");
}
var contentType = blob.type;
var fileExt;
switch (contentType) {
case 'image/apng':
fileExt = 'apng';
break;
case 'image/avif': // Supported from FF 86
fileExt = 'avif';
break;
case 'image/gif':
fileExt = 'gif';
break;
case 'image/jpeg':
fileExt = 'jpg';
break;
case 'image/png':
fileExt = 'png';
break;
case 'image/svg+xml':
fileExt = 'svg';
break;
case 'image/webp': // Supported from FF 65
fileExt = 'webp';
break;
case 'image/bmp':
fileExt = 'bmp';
break;
default:
throw new Error(`Unsupported embedded image content type '${contentType}'`);
}
var filename = 'image.' + fileExt;
var attachmentItem;
var destDir;
try {
await Zotero.DB.executeTransaction(async function () {
// Create a new attachment
attachmentItem = new Zotero.Item('attachment');
let { libraryID: parentLibraryID } = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
attachmentItem.libraryID = parentLibraryID;
attachmentItem.parentID = parentItemID;
attachmentItem.attachmentLinkMode = this.LINK_MODE_EMBEDDED_IMAGE;
attachmentItem.attachmentPath = 'storage:' + filename;
attachmentItem.attachmentContentType = contentType;
await attachmentItem.save(saveOptions);
// Write blob to file in attachment directory
destDir = await this.createDirectoryForItem(attachmentItem);
let file = OS.Path.join(destDir, filename);
await Zotero.File.putContentsAsync(file, blob);
await Zotero.File.setNormalFilePermissions(file);
}.bind(this));
}
catch (e) {
Zotero.logError("Failed importing image:\n\n" + e);
// Clean up
try {
if (destDir) {
await OS.File.removeDir(destDir, { ignoreAbsent: true });
}
}
catch (e) {
Zotero.logError(e);
}
throw e;
}
return attachmentItem;
};
/**
* @param {Object} options
* @param {Integer} options.libraryID
@ -2102,6 +2196,9 @@ Zotero.Attachments = new function(){
if (!(item instanceof Zotero.Item)) {
throw new Error("'item' must be a Zotero.Item");
}
if (!item.key) {
throw new Error("Item key must be set");
}
return this.getStorageDirectoryByLibraryAndKey(item.libraryID, item.key);
}
@ -2211,6 +2308,9 @@ Zotero.Attachments = new function(){
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
break;
case Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE:
return false;
default:
throw new Error("Invalid attachment link mode");
}
@ -2367,7 +2467,7 @@ Zotero.Attachments = new function(){
Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
if (attachment.isStoredFileAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;
}
@ -2379,7 +2479,7 @@ Zotero.Attachments = new function(){
// Move files over if they exist
var oldDir;
var newDir;
if (newAttachment.isImportedAttachment()) {
if (newAttachment.isStoredFileAttachment()) {
oldDir = this.getStorageDirectory(attachment).path;
if (await OS.File.exists(oldDir)) {
newDir = this.getStorageDirectory(newAttachment).path;
@ -2405,7 +2505,7 @@ Zotero.Attachments = new function(){
}
catch (e) {
// Move files back if old item can't be deleted
if (newAttachment.isImportedAttachment()) {
if (newAttachment.isStoredFileAttachment()) {
try {
await OS.File.move(newDir, oldDir);
}
@ -2431,7 +2531,7 @@ Zotero.Attachments = new function(){
Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
if (attachment.isStoredFileAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;
}
@ -2441,7 +2541,7 @@ Zotero.Attachments = new function(){
yield newAttachment.save();
// Copy over files if they exist
if (newAttachment.isImportedAttachment() && (yield attachment.fileExists())) {
if (newAttachment.isStoredFileAttachment() && (yield attachment.fileExists())) {
let dir = Zotero.Attachments.getStorageDirectory(attachment);
let newDir = yield Zotero.Attachments.createDirectoryForItem(newAttachment);
yield Zotero.File.copyDirectory(dir, newDir);
@ -2787,6 +2887,8 @@ Zotero.Attachments = new function(){
return 'linked_file';
case this.LINK_MODE_LINKED_URL:
return 'linked_url';
case this.LINK_MODE_EMBEDDED_IMAGE:
return 'embedded_image';
default:
throw new Error(`Invalid link mode ${linkMode}`);
}

View file

@ -60,6 +60,7 @@ Zotero.HTTPIntegrationClient.Application = function() {
this.outputFormat = 'html';
this.supportedNotes = ['footnotes'];
this.supportsImportExport = false;
this.supportsTextInsertion = false;
this.processorName = "HTTP Integration";
};
Zotero.HTTPIntegrationClient.Application.prototype = {
@ -68,6 +69,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = {
this.outputFormat = result.outputFormat || this.outputFormat;
this.supportedNotes = result.supportedNotes || this.supportedNotes;
this.supportsImportExport = result.supportsImportExport || this.supportsImportExport;
this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion;
this.processorName = result.processorName || this.processorName;
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
}
@ -80,7 +82,8 @@ Zotero.HTTPIntegrationClient.Document = function(documentID) {
this._documentID = documentID;
};
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
"setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument"]) {
"setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument",
"insertText"]) {
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
[this._documentID].concat(Array.prototype.slice.call(arguments)));
@ -146,6 +149,10 @@ Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields,
fields = fields.map((f) => f._id);
await Zotero.HTTPIntegrationClient.sendCommand("Document.convert", [this._documentID, fields, fieldType, noteTypes]);
};
Zotero.HTTPIntegrationClient.Document.prototype.convertPlaceholdersToFields = async function(codes, placeholderIDs, noteType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.convertPlaceholdersToFields", [this._documentID, codes, placeholderIDs, noteType]);
return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
}
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
Zotero.HTTPIntegrationClient.inProgress = false;
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);

View file

@ -1701,7 +1701,8 @@ Zotero.Server.Connector.Ping.prototype = {
let response = {
prefs: {
automaticSnapshots: Zotero.Prefs.get('automaticSnapshots')
automaticSnapshots: Zotero.Prefs.get('automaticSnapshots'),
googleDocsAddNoteEnabled: Zotero.isPDFBuild
}
};
if (Zotero.QuickCopy.hasSiteSettings()) {

View file

@ -354,6 +354,8 @@ Zotero.ItemTypes = new function() {
var _primaryTypeNames = ['book', 'bookSection', 'journalArticle', 'newspaperArticle', 'document'];
var _primaryTypes;
var _secondaryTypes;
// Item types hidden from New Item menu
var _hiddenTypeNames = ['webpage', 'attachment', 'note', 'annotation'];
var _hiddenTypes;
var _numPrimary = 5;
@ -371,12 +373,13 @@ Zotero.ItemTypes = new function() {
// Secondary types
_secondaryTypes = yield this._getTypesFromDB(
`WHERE display != 0 AND display NOT IN ('${_primaryTypeNames.join("', '")}')`
+ " AND name != 'webpage'"
`WHERE typeName NOT IN ('${_primaryTypeNames.concat(_hiddenTypeNames).join("', '")}')`
);
// Hidden types
_hiddenTypes = yield this._getTypesFromDB('WHERE display=0')
_hiddenTypes = yield this._getTypesFromDB(
`WHERE typeName IN ('${_hiddenTypeNames.join("', '")}')`
);
// Custom labels and icons
var sql = "SELECT customItemTypeID AS id, label, icon FROM customItemTypes";
@ -402,8 +405,8 @@ Zotero.ItemTypes = new function() {
mru.split(',')
.slice(0, _numPrimary)
.map(name => this.getName(name))
// Ignore 'webpage' item type
.filter(name => name && name != 'webpage')
// Ignore hidden item types and 'webpage'
.filter(name => name && !_hiddenTypeNames.concat('webpage').includes(name))
);
// Add types from defaults until we reach our limit

View file

@ -31,8 +31,7 @@ Zotero.Collection = function(params = {}) {
this._childCollections = new Set();
this._childItems = new Set();
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID',
'parentKey', 'lastSync']);
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID', 'parentKey']);
}
Zotero.extendClass(Zotero.DataObject, Zotero.Collection);

View file

@ -156,81 +156,6 @@ Zotero.Collections = function() {
}
/**
* Sort an array of collectionIDs from top-level to deepest
*
* Order within each level is undefined.
*
* This is used to sort higher-level collections first in upload JSON, since otherwise the API
* would reject lower-level collections for having missing parents.
*/
this.sortByLevel = function (ids) {
let levels = {};
// Get objects from ids
let objs = {};
ids.forEach(id => objs[id] = Zotero.Collections.get(id));
// Get top-level collections
let top = ids.filter(id => !objs[id].parentID);
levels["0"] = top.slice();
ids = Zotero.Utilities.arrayDiff(ids, top);
// For each collection in list, walk up its parent tree. If a parent is present in the
// list of ids, add it to the appropriate level bucket and remove it.
while (ids.length) {
let tree = [ids[0]];
let keep = [ids[0]];
let id = ids.shift();
let seen = new Set([id]);
while (true) {
let c = Zotero.Collections.get(id);
let parentID = c.parentID;
if (!parentID) {
break;
}
// Avoid an infinite loop if collections are incorrectly nested within each other
if (seen.has(parentID)) {
throw new Zotero.Error(
"Incorrectly nested collections",
Zotero.Error.ERROR_INVALID_COLLECTION_NESTING,
{
collectionID: id
}
);
}
seen.add(parentID);
tree.push(parentID);
// If parent is in list, remove it
let pos = ids.indexOf(parentID);
if (pos != -1) {
keep.push(parentID);
ids.splice(pos, 1);
}
id = parentID;
}
let level = tree.length - 1;
for (let i = 0; i < tree.length; i++) {
let currentLevel = level - i;
for (let j = 0; j < keep.length; j++) {
if (tree[i] != keep[j]) continue;
if (!levels[currentLevel]) {
levels[currentLevel] = [];
}
levels[currentLevel].push(keep[j]);
}
}
}
var orderedIDs = [];
for (let level in levels) {
orderedIDs = orderedIDs.concat(levels[level]);
}
return orderedIDs;
};
this._loadChildCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID "
+ "FROM collections C1 LEFT JOIN collections C2 ON (C1.collectionID=C2.parentCollectionID) "

View file

@ -639,7 +639,6 @@ Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function*
throw new Error(this._ObjectType + " " + (id ? id : libraryID + "/" + key)
+ " not found in Zotero." + this._ObjectType + ".loadPrimaryData()");
}
this._clearChanged('primaryData');
// If object doesn't exist, mark all data types as loaded
this._markAllDataTypeLoadStates(true);
@ -735,11 +734,26 @@ Zotero.DataObject.prototype._markAllDataTypeLoadStates = function (loaded) {
}
}
Zotero.DataObject.prototype._hasFieldChanged = function (field) {
return field in this._changedData;
};
Zotero.DataObject.prototype._getChangedField = function (field) {
return this._changedData[field];
};
/**
* Get either the unsaved value of a field or the saved value if unchanged since the last save
*/
Zotero.DataObject.prototype._getLatestField = function (field) {
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
};
/**
* Get either the unsaved value of a field or the saved value if unchanged since the last save
*/
Zotero.DataObject.prototype._getLatestField = function (field) {
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
};
/**
@ -749,10 +763,13 @@ Zotero.DataObject.prototype._getLatestField = function (field) {
*/
Zotero.DataObject.prototype._markFieldChange = function (field, value) {
// New method (changedData)
if (['deleted', 'tags'].includes(field)) {
if (['deleted', 'tags'].includes(field) || field.startsWith('annotation')) {
if (Array.isArray(value)) {
this._changedData[field] = [...value];
}
else if (typeof value === 'object' && value !== null) {
this._changedData[field] = Object.assign({}, value);
}
else {
this._changedData[field] = value;
}
@ -1258,10 +1275,6 @@ Zotero.DataObject.prototype._initErase = Zotero.Promise.method(function (env) {
if (!env.options.skipEditCheck) this.editCheck();
if (env.options.skipDeleteLog) {
env.notifierData[this.id].skipDeleteLog = true;
}
return true;
});
@ -1277,6 +1290,10 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
this.ObjectsClass.unload(env.deletedObjectIDs || this.id);
}.bind(this));
if (env.options.skipDeleteLog) {
env.notifierData[this.id].skipDeleteLog = true;
}
if (!env.options.skipNotifier) {
Zotero.Notifier.queue(
'delete',

View file

@ -486,9 +486,14 @@ Zotero.DataObjects.prototype.loadDataTypes = Zotero.Promise.coroutine(function*
* @param {Integer[]} [ids]
*/
Zotero.DataObjects.prototype._loadDataTypeInLibrary = Zotero.Promise.coroutine(function* (dataType, libraryID, ids) {
var funcName = "_load" + dataType[0].toUpperCase() + dataType.substr(1)
// note → loadNotes
// itemData → loadItemData
// annotationDeferred → loadAnnotationsDeferred
var baseDataType = dataType.replace('Deferred', '');
var funcName = "_load" + dataType[0].toUpperCase() + baseDataType.substr(1)
// Single data types need an 's' (e.g., 'note' -> 'loadNotes()')
+ ((dataType.endsWith('s') || dataType.endsWith('Data') ? '' : 's'));
+ ((baseDataType.endsWith('s') || baseDataType.endsWith('Data') ? '' : 's'))
+ (dataType.endsWith('Deferred') ? 'Deferred' : '');
if (!this[funcName]) {
throw new Error(`Zotero.${this._ZDO_Objects}.${funcName} is not a function`);
}
@ -706,6 +711,150 @@ Zotero.DataObjects.prototype._loadRelations = Zotero.Promise.coroutine(function*
});
/**
* Sort an array of collections or items from top-level to deepest, grouped by level
*
* All top-level objects are returned, followed by all second-level objects, followed by
* third-level, etc. The order within each level is undefined.
*
* This is used to sort higher-level objects first in upload JSON, since otherwise the API would
* reject lower-level objects for having missing parents.
*
* @param {Zotero.DataObject[]} objects - An array of objects
* @return {Zotero.DataObject[]} - A sorted array of objects
*/
Zotero.DataObjects.prototype.sortByLevel = function (objects) {
// Convert to ids
var ids = objects.map(o => o.id);
var levels = {};
// Get top-level objects
var top = objects.filter(o => !o.parentID).map(o => o.id);
levels["0"] = top.slice();
ids = Zotero.Utilities.arrayDiff(ids, top);
// For each object in list, walk up its parent tree. If a parent is present in the
// list of ids, add it to the appropriate level bucket and remove it.
while (ids.length) {
let tree = [ids[0]];
let keep = [ids[0]];
let id = ids.shift();
let seen = new Set([id]);
while (true) {
let o = Zotero[this._ZDO_Objects].get(id);
let parentID = o.parentID;
if (!parentID) {
break;
}
// Avoid an infinite loop if objects are incorrectly nested within each other
if (seen.has(parentID)) {
throw new Zotero.Error(
`Incorrectly nested ${this._ZDO_objects}`,
Zotero.Error.ERROR_INVALID_OBJECT_NESTING,
{
[this._ZDO_id]: id
}
);
}
seen.add(parentID);
tree.push(parentID);
// If parent is in list, remove it
let pos = ids.indexOf(parentID);
if (pos != -1) {
keep.push(parentID);
ids.splice(pos, 1);
}
id = parentID;
}
let level = tree.length - 1;
for (let i = 0; i < tree.length; i++) {
let currentLevel = level - i;
for (let j = 0; j < keep.length; j++) {
if (tree[i] != keep[j]) continue;
if (!levels[currentLevel]) {
levels[currentLevel] = [];
}
levels[currentLevel].push(keep[j]);
}
}
}
var ordered = [];
for (let level in levels) {
ordered = ordered.concat(levels[level]);
}
// Convert back to objects
return ordered.map(id => Zotero[this._ZDO_Objects].get(id));
};
/**
* Sort an array of collections or items from top-level to deepest, grouped by parent
*
* Child objects are included before any sibling objects. The order within each level is undefined.
*
* This is used to sort higher-level objects first in upload JSON, since otherwise the API would
* reject lower-level objects for having missing parents.
*
* @param {Zotero.DataObject[]} ids - An array of data objects
* @return {Zotero.DataObject[]} - A sorted array of data objects
*/
Zotero.DataObjects.prototype.sortByParent = function (objects) {
// Convert to ids
var ids = objects.map(o => o.id);
var ordered = [];
// For each object in list, walk up its parent tree. If a parent is present in the list of
// objects, keep track of it and remove it from the list. When we get to a top-level object, add
// all the objects we've kept to the ordered list.
while (ids.length) {
let id = ids.shift();
let keep = [id];
let seen = new Set([id]);
while (true) {
let o = Zotero[this._ZDO_Objects].get(id);
let parentID = o.parentID;
if (!parentID) {
// We've reached a top-level object, so add any kept ids to the list
ordered.push(...keep);
break;
}
// Avoid an infinite loop if objects are incorrectly nested within each other
if (seen.has(parentID)) {
throw new Zotero.Error(
`Incorrectly nested ${this._ZDO_objects}`,
Zotero.Error.ERROR_INVALID_OBJECT_NESTING,
{
[this._ZDO_id]: id
}
);
}
seen.add(parentID);
// If parent is in list of ids, keep it and remove it from list
let pos = ids.indexOf(parentID);
if (pos != -1) {
keep.unshift(parentID);
ids.splice(pos, 1);
}
// Otherwise, check if parent has already been added to the ordered list, in which case
// we can slot in all kept ids after it
else {
pos = ordered.indexOf(parentID);
if (pos != -1) {
ordered.splice(pos + 1, 0, ...keep);
break;
}
}
id = parentID;
}
}
// Convert back to objects
return ordered.map(id => Zotero[this._ZDO_Objects].get(id));
}
/**
* Flatten API JSON relations object into an array of unique predicate-object pairs
*

File diff suppressed because it is too large Load diff

View file

@ -240,7 +240,7 @@ Zotero.ItemFields = new function() {
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw new Error("Invalid field '" + baseField + '" for base field');
throw new Error("Invalid field '" + baseField + "' for base field");
}
if (fieldID == baseFieldID) {
@ -277,7 +277,7 @@ Zotero.ItemFields = new function() {
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw new Error("Invalid field '" + baseField + '" for base field');
throw new Error("Invalid field '" + baseField + "' for base field");
}
// If field isn't a base field, return it if it's valid for the type
@ -419,7 +419,7 @@ Zotero.ItemFields = new function() {
var fieldID = Zotero.ItemFields.getID(field);
if (!fieldID) {
Zotero.debug((new Error).stack, 1);
throw new Error(`Invalid field '${field}`);
throw new Error(`Invalid field '${field}'`);
}
return fieldID;
}
@ -518,7 +518,9 @@ Zotero.ItemFields = new function() {
var rows = yield Zotero.DB.queryAsync(sql);
_itemTypeFields = {
[Zotero.ItemTypes.getID('note')]: [] // Notes have no fields
// Notes and annotations have no fields
[Zotero.ItemTypes.getID('note')]: [],
[Zotero.ItemTypes.getID('annotation')]: []
};
for (let i=0; i<rows.length; i++) {

View file

@ -38,6 +38,7 @@ Zotero.Items = function() {
get: function () {
var itemTypeAttachment = Zotero.ItemTypes.getID('attachment');
var itemTypeNote = Zotero.ItemTypes.getID('note');
var itemTypeAnnotation = Zotero.ItemTypes.getID('annotation');
return {
itemID: "O.itemID",
@ -49,16 +50,25 @@ Zotero.Items = function() {
version: "O.version",
synced: "O.synced",
createdByUserID: "createdByUserID",
lastModifiedByUserID: "lastModifiedByUserID",
firstCreator: _getFirstCreatorSQL(),
sortCreator: _getSortCreatorSQL(),
deleted: "DI.itemID IS NOT NULL AS deleted",
inPublications: "PI.itemID IS NOT NULL AS inPublications",
parentID: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.itemID `
+ `WHEN ${itemTypeNote} THEN INoP.itemID END) AS parentID`,
parentKey: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.key `
+ `WHEN ${itemTypeNote} THEN INoP.key END) AS parentKey`,
parentID: `(CASE O.itemTypeID `
+ `WHEN ${itemTypeAttachment} THEN IAP.itemID `
+ `WHEN ${itemTypeNote} THEN INoP.itemID `
+ `WHEN ${itemTypeAnnotation} THEN IAnP.itemID `
+ `END) AS parentID`,
parentKey: `(CASE O.itemTypeID `
+ `WHEN ${itemTypeAttachment} THEN IAP.key `
+ `WHEN ${itemTypeNote} THEN INoP.key `
+ `WHEN ${itemTypeAnnotation} THEN IAnP.key `
+ `END) AS parentKey`,
attachmentCharset: "CS.charset AS attachmentCharset",
attachmentLinkMode: "IA.linkMode AS attachmentLinkMode",
@ -66,7 +76,8 @@ Zotero.Items = function() {
attachmentPath: "IA.path AS attachmentPath",
attachmentSyncState: "IA.syncState AS attachmentSyncState",
attachmentSyncedModificationTime: "IA.storageModTime AS attachmentSyncedModificationTime",
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash"
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash",
attachmentLastProcessedModificationTime: "IA.lastProcessedModificationTime AS attachmentLastProcessedModificationTime",
};
}
}, {lazy: true});
@ -77,9 +88,12 @@ Zotero.Items = function() {
+ "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
+ "LEFT JOIN itemAnnotations IAn ON (O.itemID=IAn.itemID) "
+ "LEFT JOIN items IAnP ON (IAn.parentItemID=IAnP.itemID) "
+ "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
+ "LEFT JOIN publicationsItems PI ON (O.itemID=PI.itemID) "
+ "LEFT JOIN charsets CS ON (IA.charsetID=CS.charsetID)";
+ "LEFT JOIN charsets CS ON (IA.charsetID=CS.charsetID)"
+ "LEFT JOIN groupItems GI ON (O.itemID=GI.itemID)";
this._relationsTable = "itemRelations";
@ -479,6 +493,88 @@ Zotero.Items = function() {
});
this._loadAnnotations = async function (libraryID, ids, idSQL) {
var sql = "SELECT itemID, IA.parentItemID, IA.type, IA.text, IA.comment, IA.color, "
+ "IA.sortIndex, IA.isExternal "
+ "FROM items JOIN itemAnnotations IA USING (itemID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
await Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not found");
}
item._parentItemID = row.getResultByIndex(1);
var typeID = row.getResultByIndex(2);
var type;
switch (typeID) {
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
type = 'highlight';
break;
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
type = 'note';
break;
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
type = 'image';
break;
default:
throw new Error(`Unknown annotation type id ${typeID}`);
}
item._annotationType = type;
item._annotationText = row.getResultByIndex(3);
item._annotationComment = row.getResultByIndex(4);
item._annotationColor = row.getResultByIndex(5);
item._annotationSortIndex = row.getResultByIndex(6);
item._annotationIsExternal = !!row.getResultByIndex(7);
item._loaded.annotation = true;
item._clearChanged('annotation');
}.bind(this)
}
);
};
this._loadAnnotationsDeferred = async function (libraryID, ids, idSQL) {
var sql = "SELECT itemID, IA.position, IA.pageLabel FROM items "
+ "JOIN itemAnnotations IA USING (itemID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
await Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not found");
}
item._annotationPosition = row.getResultByIndex(1);
item._annotationPageLabel = row.getResultByIndex(2);
item._loaded.annotationDeferred = true;
item._clearChanged('annotationDeferred');
}.bind(this)
}
);
};
this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var params = [libraryID];
var rows = [];
@ -499,6 +595,9 @@ Zotero.Items = function() {
});
};
//
// Attachments
//
var sql = "SELECT parentItemID, A.itemID, value AS title, "
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemAttachments A "
@ -602,9 +701,56 @@ Zotero.Items = function() {
ids.forEach(id => setNoteItem(id, []));
}
// Mark all top-level items as having child items loaded
sql = "SELECT itemID FROM items I WHERE libraryID=?" + idSQL + " AND itemID NOT IN "
+ "(SELECT itemID FROM itemAttachments UNION SELECT itemID FROM itemNotes)";
//
// Annotations
//
sql = "SELECT parentItemID, IAn.itemID, "
+ "text || ' - ' || comment AS title, " // TODO: Make better
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemAnnotations IAn "
+ "JOIN items I ON (IAn.parentItemID=I.itemID) "
+ "LEFT JOIN deletedItems DI USING (itemID) "
+ "WHERE libraryID=?"
+ (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : "")
+ " ORDER BY parentItemID, sortIndex";
var setAnnotationItem = function (itemID, rows) {
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not loaded");
}
rows.sort((a, b) => a.sortIndex - b.sortIndex);
item._annotations = {
rows,
withTrashed: null,
withoutTrashed: null
};
}.bind(this);
lastItemID = null;
rows = [];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
onRow(row, setAnnotationItem);
}
}
);
// Process unprocessed rows
if (lastItemID) {
setAnnotationItem(lastItemID, rows);
}
// Otherwise clear existing entries for passed items
else if (ids.length) {
ids.forEach(id => setAnnotationItem(id, []));
}
// Mark either all passed items or all items as having child items loaded
sql = "SELECT itemID FROM items I WHERE libraryID=?";
if (idSQL) {
sql += idSQL;
}
yield Zotero.DB.queryAsync(
sql,
params,

View file

@ -25,7 +25,7 @@
Zotero.Notes = new function() {
this.noteToTitle = noteToTitle;
this._editorInstances = [];
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; });
this.__defineGetter__("defaultNote", function () { return '<div class="zotero-note znv1"></div>'; });
@ -35,7 +35,7 @@ Zotero.Notes = new function() {
/**
* Return first line (or first MAX_LENGTH characters) of note content
**/
function noteToTitle(text) {
this.noteToTitle = function(text) {
var origText = text;
text = text.trim();
text = Zotero.Utilities.unescapeHTML(text);
@ -59,9 +59,101 @@ Zotero.Notes = new function() {
t = t.substring(0, ln);
}
return t;
}
}
};
this.registerEditorInstance = function(instance) {
this._editorInstances.push(instance);
};
this.unregisterEditorInstance = async function(instance) {
// Make sure the editor instance is not unregistered while
// Zotero.Notes.updateUser is in progress, otherwise the
// instance might not get the`disableSaving` flag set
await Zotero.DB.executeTransaction(async () => {
let index = this._editorInstances.indexOf(instance);
if (index >= 0) {
this._editorInstances.splice(index, 1);
}
});
};
if (typeof process === 'object' && process + '' === '[object process]'){
module.exports = Zotero.Notes;
/**
* Replace local URIs for citations and highlights
* in all notes. Cut-off note saving for the opened
* notes and then trigger notification to refresh
*
* @param {Number} fromUserID
* @param {Number} toUserID
* @returns {Promise<void>}
*/
this.updateUser = async function (fromUserID, toUserID) {
if (!fromUserID) {
fromUserID = 'local%2F' + Zotero.Users.getLocalUserKey();
}
if (!toUserID) {
throw new Error('Invalid target userID ' + toUserID);
}
Zotero.DB.requireTransaction();
// `"http://zotero.org/users/${fromUserID}/items/`
let from = `%22http%3A%2F%2Fzotero.org%2Fusers%2F${fromUserID}%2Fitems%2F`;
// `"http://zotero.org/users/${toUserId}/items/`
let to = `%22http%3A%2F%2Fzotero.org%2Fusers%2F${toUserID}%2Fitems%2F`;
let sql = `UPDATE itemNotes SET note=REPLACE(note, '${from}', '${to}')`;
await Zotero.DB.queryAsync(sql);
// Disable saving for each editor instance to make sure none
// of the instances can overwrite our changes
this._editorInstances.forEach(x => x.disableSaving = true);
let idsToRefresh = [];
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
let loadedObjects = objectsClass.getLoaded();
for (let object of loadedObjects) {
if (object.isNote()) {
idsToRefresh.push(object.id);
await object.reload(['note'], true);
}
}
Zotero.DB.addCurrentCallback('commit', async () => {
await Zotero.Notifier.trigger('refresh', 'item', idsToRefresh);
});
};
this.getExportableNote = async function(item) {
if (!item.isNote()) {
throw new Error('Item is not a note');
}
var note = item.getNote();
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
var doc = parser.parseFromString(note, 'text/html');
var nodes = doc.querySelectorAll('img[data-attachment-key]');
for (var node of nodes) {
var attachmentKey = node.getAttribute('data-attachment-key');
if (attachmentKey) {
var attachment = Zotero.Items.getByLibraryAndKey(item.libraryID, attachmentKey);
if (attachment && attachment.parentID == item.id) {
var dataURI = await attachment.attachmentDataURI;
node.setAttribute('src', dataURI);
}
}
node.removeAttribute('data-attachment-key');
}
return doc.body.innerHTML;
};
this.hasSchemaVersion = function (note) {
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.createInstance(Components.interfaces.nsIDOMParser);
let doc = parser.parseFromString(note, 'text/html');
return !!doc.querySelector('body > div[data-schema-version]');
};
};
if (typeof process === 'object' && process + '' === '[object process]') {
module.exports = Zotero.Notes;
}

View file

@ -306,14 +306,19 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
var parts = Zotero.SearchConditions.parseSearchString(value);
for (let part of parts) {
this.addCondition('blockStart');
if (condition == 'quicksearch-titleCreatorYearNote') {
this.addCondition('note', operator, part.text, false);
continue;
}
this.addCondition('blockStart');
// Allow searching for exact object key
if (operator == 'contains' && Zotero.Utilities.isValidObjectKey(part.text)) {
this.addCondition('key', 'is', part.text, false);
}
if (condition == 'quicksearch-titleCreatorYear') {
if (condition.startsWith('quicksearch-titleCreatorYear')) {
this.addCondition('title', operator, part.text, false);
this.addCondition('publicationTitle', operator, part.text, false);
this.addCondition('shortTitle', operator, part.text, false);
@ -328,7 +333,8 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
this.addCondition('creator', operator, part.text, false);
if (condition == 'quicksearch-everything') {
this.addCondition('annotation', operator, part.text, false);
this.addCondition('annotationText', operator, part.text, false);
this.addCondition('annotationComment', operator, part.text, false);
if (part.inQuotes) {
this.addCondition('fulltextContent', operator, part.text, false);
@ -347,6 +353,9 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
if (condition == 'quicksearch-titleCreatorYear') {
this.addCondition('noChildren', 'true');
}
else if (condition == 'quicksearch-titleCreatorYearNote') {
this.addCondition('itemType', 'is', 'note');
}
return false;
}

View file

@ -168,6 +168,17 @@ Zotero.SearchConditions = new function(){
noLoad: true
},
{
name: 'quicksearch-titleCreatorYearNote',
operators: {
is: true,
isNot: true,
contains: true,
doesNotContain: true
},
noLoad: true
},
{
name: 'quicksearch-fields',
operators: {
@ -490,13 +501,25 @@ Zotero.SearchConditions = new function(){
},
{
name: 'annotation',
name: 'annotationText',
operators: {
contains: true,
doesNotContain: true
},
table: 'annotations',
field: 'text'
table: 'itemAnnotations',
field: 'text',
special: true,
},
{
name: 'annotationComment',
operators: {
contains: true,
doesNotContain: true
},
table: 'itemAnnotations',
field: 'comment',
special: true,
},
{

View file

@ -853,6 +853,40 @@ Zotero.Date = new function(){
}
this.toFriendlyDate = function (date) {
// 6:14:36 PM
if (isToday(date)) {
return date.toLocaleString(false, { hour: 'numeric', minute: 'numeric' })
}
// 'Thursday'
if (isThisWeek(date)) {
return date.toLocaleString(false, { weekday: 'long' });
}
return date.toLocaleDateString(false, { year: '2-digit', month: 'numeric', day: 'numeric' });
};
function isToday(date) {
var d = new Date();
return d.getDate() == date.getDate()
&& d.getMonth() == d.getMonth()
&& d.getFullYear() == d.getFullYear();
}
function isThisWeek(date) {
var d = new Date();
return d.getFullYear() == date.getFullYear() && getWeekNumber(d) == getWeekNumber(date);
}
// https://stackoverflow.com/a/27125580
function getWeekNumber(date) {
let onejan = new Date(date.getFullYear(), 0, 1);
return Math.ceil((((date.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
}
function getFileDateString(file){
var date = new Date();
date.setTime(file.lastModifiedTime);

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,7 @@ Zotero.Error.ERROR_ZFS_UPLOAD_QUEUE_LIMIT = 6;
Zotero.Error.ERROR_ZFS_FILE_EDITING_DENIED = 7;
Zotero.Error.ERROR_INVALID_ITEM_TYPE = 8;
Zotero.Error.ERROR_USER_NOT_AVAILABLE = 9;
Zotero.Error.ERROR_INVALID_COLLECTION_NESTING = 10;
Zotero.Error.ERROR_INVALID_OBJECT_NESTING = 10;
//Zotero.Error.ERROR_SYNC_EMPTY_RESPONSE_FROM_SERVER = 6;
//Zotero.Error.ERROR_SYNC_INVALID_RESPONSE_FROM_SERVER = 7;

View file

@ -778,23 +778,25 @@ Zotero.File = new function(){
/**
* Generate a data: URI from an nsIFile
* Generate a data: URI from a file path
*
* From https://developer.mozilla.org/en-US/docs/data_URIs
* @param {String} path
* @param {String} contentType
*/
this.generateDataURI = function (file) {
var contentType = Components.classes["@mozilla.org/mime;1"]
.getService(Components.interfaces.nsIMIMEService)
.getTypeFromFile(file);
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
inputStream.init(file, 0x01, 0o600, 0);
var stream = Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
stream.setInputStream(inputStream);
var encoded = btoa(stream.readBytes(stream.available()));
return "data:" + contentType + ";base64," + encoded;
}
this.generateDataURI = async function (file, contentType) {
if (!contentType) {
throw new Error("contentType not provided");
}
var buf = await OS.File.read(file, {});
var bytes = new Uint8Array(buf);
var binary = '';
var len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return 'data:' + contentType + ';base64,' + btoa(binary);
};
this.setNormalFilePermissions = function (file) {
@ -1008,14 +1010,17 @@ Zotero.File = new function(){
}
this.createDirectoryIfMissingAsync = async function (path) {
this.createDirectoryIfMissingAsync = async function (path, options = {}) {
try {
await OS.File.makeDir(
path,
{
ignoreExisting: false,
unixMode: 0o755
}
Object.assign(
{
ignoreExisting: false,
unixMode: 0o755
},
options
)
)
}
catch (e) {

File diff suppressed because it is too large Load diff

View file

@ -328,6 +328,10 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
Zotero.CollectionTreeCache.clear();
// Get the full set of items we want to show
let newSearchItems = yield this.collectionTreeRow.getItems();
// TEMP: Hide annotations
newSearchItems = newSearchItems.filter(item => !item.isAnnotation());
// A temporary workaround to make item tree crash less often
newSearchItems = newSearchItems.filter(item => !(item.isAttachment() && item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE));
// Remove notes and attachments if necessary
if (this.regularOnly) {
newSearchItems = newSearchItems.filter(item => item.isRegularItem());
@ -3505,7 +3509,7 @@ Zotero.ItemTreeRow.prototype.numNotes = function() {
return 0;
}
if (this.ref.isAttachment()) {
return this.ref.getNote() !== '' ? 1 : 0;
return this.ref.note !== '' ? 1 : 0;
}
return this.ref.numNotes(false, true) || 0;
}

View file

@ -0,0 +1,41 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, Virginia, USA
http://digitalscholar.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 *****
*/
Zotero.NoteBackups = {
init: async function () {
await Zotero.DB.queryAsync("CREATE TABLE IF NOT EXISTS noteBackups (\n itemID INTEGER PRIMARY KEY,\n note TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n);");
},
getNote: async function(itemID) {
return Zotero.DB.valueQueryAsync("SELECT note FROM noteBackups WHERE itemID=?", [itemID]);
},
ensureBackup: async function(item) {
let note = item.note;
if (note && !Zotero.Notes.hasSchemaVersion(note)) {
await Zotero.DB.queryAsync("INSERT OR IGNORE INTO noteBackups VALUES (?, ?)", [item.id, item.note]);
}
},
};

View file

@ -30,7 +30,7 @@ Zotero.Notifier = new function(){
var _types = [
'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key'
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key', 'tab'
];
var _transactionID = false;
var _queue = {};

View file

@ -26,7 +26,32 @@
/* eslint-disable array-element-newline */
Zotero.OpenPDF = {
openToPage: async function (path, page) {
openToPage: async function (pathOrItem, page) {
var path;
if (pathOrItem == 'string') {
Zotero.logError("Zotero.OpenPDF.openToPage() now takes a Zotero.Item rather than a path "
+ "-- please update your code");
path = pathOrItem;
}
else {
let item = pathOrItem;
let library = Zotero.Libraries.get(item.libraryID);
// TEMP
if (Zotero.isPDFBuild && library.libraryType == 'user') {
let location = {
pageIndex: page - 1
};
await Zotero.Reader.open(item.id, location);
return true;
}
path = await item.getFilePathAsync();
if (!path) {
Zotero.warn(`${path} not found`);
return false;
}
}
var handler = Zotero.Prefs.get("fileHandler.pdf");
var opened = false;

View file

@ -0,0 +1,484 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, Virginia, USA
http://digitalscholar.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 *****
*/
const WORKER_URL = 'chrome://zotero/content/xpcom/pdfWorker/worker.js';
const CMAPS_URL = 'resource://zotero/pdf-reader/cmaps/';
const RENDERER_URL = 'resource://zotero/pdf-renderer/renderer.html';
class PDFWorker {
constructor() {
this._worker = null;
this._lastPromiseID = 0;
this._waitingPromises = {};
this._queue = [];
this._processingQueue = false;
}
async _processQueue() {
this._init();
if (this._processingQueue) {
return;
}
this._processingQueue = true;
let item;
while ((item = this._queue.shift())) {
if (item) {
let [fn, resolve, reject] = item;
try {
resolve(await fn());
}
catch (e) {
reject(e);
}
}
}
this._processingQueue = false;
}
async _enqueue(fn, isPriority) {
return new Promise((resolve, reject) => {
if (isPriority) {
this._queue.unshift([fn, resolve, reject]);
}
else {
this._queue.push([fn, resolve, reject]);
}
this._processQueue();
});
}
async _query(action, data, transfer) {
return new Promise((resolve, reject) => {
this._lastPromiseID++;
this._waitingPromises[this._lastPromiseID] = { resolve, reject };
this._worker.postMessage({ id: this._lastPromiseID, action, data }, transfer);
});
}
_init() {
if (this._worker) return;
this._worker = new Worker(WORKER_URL);
this._worker.addEventListener('message', async (event) => {
let message = event.data;
if (message.responseID) {
let { resolve, reject } = this._waitingPromises[message.responseID];
delete this._waitingPromises[message.responseID];
if (message.data) {
resolve(message.data);
}
else {
reject(new Error(JSON.stringify(message.error)));
}
return;
}
if (message.id) {
let respData = null;
try {
if (message.action === 'FetchBuiltInCMap') {
let response = await Zotero.HTTP.request(
'GET',
CMAPS_URL + event.data.data.name + '.bcmap',
{ responseType: 'arraybuffer' }
);
respData = {
compressionType: 1,
cMapData: new Uint8Array(response.response)
};
}
}
catch (e) {
Zotero.debug('Failed to fetch CMap data:');
Zotero.debug(e);
}
this._worker.postMessage({ responseID: event.data.id, data: respData });
}
});
this._worker.addEventListener('error', (event) => {
Zotero.logError(`PDF Web Worker error (${event.filename}:${event.lineno}): ${event.message}`);
});
}
canImport(item) {
if (item.isPDFAttachment()) {
return true;
}
else if (item.isRegularItem()) {
let ids = item.getAttachments();
for (let id of ids) {
let attachment = Zotero.Items.get(id);
if (attachment.isPDFAttachment()) {
return true;
}
}
}
}
/**
* Export attachment with annotations to specified path
*
* @param {Integer} itemID
* @param {String} path
* @param {Boolean} [isPriority]
* @param {String} [password]
* @returns {Promise<Integer>} Number of written annotations
*/
async export(itemID, path, isPriority, password) {
return this._enqueue(async () => {
let attachment = await Zotero.Items.getAsync(itemID);
if (!attachment.isPDFAttachment()) {
throw new Error('Item must be a PDF attachment');
}
let items = attachment.getAnnotations();
items = items.filter(x => !x.annotationIsExternal);
let annotations = [];
for (let item of items) {
annotations.push({
id: item.key,
type: item.annotationType,
authorName: Zotero.Users.getName(item.createdByUserID) || Zotero.Users.getCurrentUsername() || '',
comment: item.annotationComment || '',
color: item.annotationColor,
position: JSON.parse(item.annotationPosition),
dateModified: item.dateModified
});
}
let attachmentPath = await attachment.getFilePathAsync();
let buf = await OS.File.read(attachmentPath, {});
buf = new Uint8Array(buf).buffer;
try {
var res = await this._query('export', {
buf, annotations, password
}, [buf]);
}
catch (e) {
let error = new Error(`Worker 'export' failed: ${JSON.stringify({
annotations,
error: e.message
})}`);
Zotero.logError(error);
throw error;
}
await OS.File.writeAtomic(path, new Uint8Array(res.buf));
return annotations.length;
}, isPriority);
}
/**
* Export children PDF attachments with annotations
*
* @param {Zotero.Item} item
* @param {String} directory
* @param {Boolean} [isPriority]
*/
async exportParent(item, directory, isPriority) {
if (!item.isRegularItem()) {
throw new Error('Item must be a regular item');
}
if (!directory) {
throw new Error('\'directory\' not provided');
}
let promises = [];
let ids = item.getAttachments();
for (let id of ids) {
let attachment = Zotero.Items.get(id);
if (attachment.isPDFAttachment()) {
let path = OS.Path.join(directory, attachment.attachmentFilename);
promises.push(this.export(id, path, isPriority));
}
}
await Promise.all(promises);
}
/**
* Import annotations from PDF attachment
*
* @param {Integer} itemID Attachment item id
* @param {Boolean} [isPriority]
* @param {String} [password]
* @returns {Promise<Integer>} Number of annotations
*/
async import(itemID, isPriority, password) {
return this._enqueue(async () => {
let attachment = await Zotero.Items.getAsync(itemID);
if (!attachment.isPDFAttachment()) {
throw new Error('Item must be a PDF attachment');
}
let mtime = Math.floor(await attachment.attachmentModificationTime / 1000);
if (attachment.attachmentLastProcessedModificationTime === mtime) {
return false;
}
let existingAnnotations = attachment
.getAnnotations()
.filter(x => x.annotationIsExternal)
.map(annotation => ({
id: annotation.key,
type: annotation.annotationType,
position: JSON.parse(annotation.annotationPosition),
comment: annotation.annotationComment || ''
}));
let path = await attachment.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
try {
var { imported, deleted } = await this._query('import', {
buf, existingAnnotations, password
}, [buf]);
}
catch (e) {
let error = new Error(`Worker 'import' failed: ${JSON.stringify({
existingAnnotations,
error: e.message
})}`);
Zotero.logError(error);
throw error;
}
for (let annotation of imported) {
annotation.key = Zotero.DataObjectUtilities.generateKey();
annotation.isExternal = true;
await Zotero.Annotations.saveFromJSON(attachment, annotation);
}
for (let key of deleted) {
let annotation = Zotero.Items.getByLibraryAndKey(attachment.libraryID, key);
await annotation.eraseTx();
}
attachment.attachmentLastProcessedModificationTime = mtime;
await attachment.saveTx({ skipDateModifiedUpdate: true });
return !!(imported.length || deleted.length);
}, isPriority);
}
/**
* Import annotations for each PDF attachment of parent item
*
* @param {Zotero.Item} item
* @param {Boolean} [isPriority]
*/
async importParent(item, isPriority) {
if (!item.isRegularItem()) {
throw new Error('Item must be a regular item');
}
let promises = [];
let ids = item.getAttachments();
for (let id of ids) {
let attachment = Zotero.Items.get(id);
if (attachment.isPDFAttachment()) {
promises.push(this.import(id, isPriority));
}
}
await Promise.all(promises);
}
}
Zotero.PDFWorker = new PDFWorker();
// PDF Renderer
class PDFRenderer {
constructor() {
this._browser = null;
this._lastPromiseID = 0;
this._waitingPromises = {};
this._queue = [];
this._processingQueue = false;
}
async _processQueue() {
await this._init();
if (this._processingQueue) {
return;
}
this._processingQueue = true;
let item;
while ((item = this._queue.shift())) {
if (item) {
let [fn, resolve, reject] = item;
try {
resolve(await fn());
}
catch (e) {
reject(e);
}
}
}
this._processingQueue = false;
}
async _enqueue(fn, isPriority) {
return new Promise((resolve, reject) => {
if (isPriority) {
this._queue.unshift([fn, resolve, reject]);
}
else {
this._queue.push([fn, resolve, reject]);
}
this._processQueue();
});
}
async _query(action, data, transfer) {
return new Promise((resolve, reject) => {
this._lastPromiseID++;
this._waitingPromises[this._lastPromiseID] = { resolve, reject };
this._browser.contentWindow.postMessage({
id: this._lastPromiseID,
action,
data
}, this._browser.contentWindow.origin, transfer);
});
}
async _init() {
if (this._browser) return;
return new Promise((resolve) => {
this._browser = Zotero.Browser.createHiddenBrowser();
let doc = this._browser.ownerDocument;
let container = doc.createElement('hbox');
container.style.position = 'fixed';
container.style.zIndex = '-1';
container.append(this._browser);
doc.documentElement.append(container);
this._browser.style.width = '1px';
this._browser.style.height = '1px';
this._browser.addEventListener('DOMContentLoaded', (event) => {
if (this._browser.contentWindow.location.href === 'about:blank') return;
this._browser.contentWindow.addEventListener('message', _handleMessage);
});
this._browser.loadURI(RENDERER_URL);
let _handleMessage = async (event) => {
if (event.source !== this._browser.contentWindow) {
return;
}
let message = event.data;
if (message.responseID) {
let { resolve, reject } = this._waitingPromises[message.responseID];
delete this._waitingPromises[message.responseID];
if (message.data) {
resolve(message.data);
}
else {
let err = new Error(message.error.message);
Object.assign(err, message.error);
reject(err);
}
return;
}
if (message.action === 'initialized') {
this._browser.contentWindow.postMessage(
{ responseID: message.id, data: {} },
this._browser.contentWindow.origin
);
resolve();
}
else if (message.action === 'renderedAnnotation') {
let { id, image } = message.data.annotation;
try {
let item = await Zotero.Items.getAsync(id);
let win = Zotero.getMainWindow();
let blob = new win.Blob([new Uint8Array(image)]);
await Zotero.Annotations.saveCacheImage(item, blob);
await Zotero.Notifier.trigger('modify', 'item', [item.id]);
} catch (e) {
Zotero.logError(e);
}
this._browser.contentWindow.postMessage(
{ responseID: message.id, data: {} },
this._browser.contentWindow.origin
);
}
};
});
}
/**
* Render missing image annotation images for attachment
*
* @param {Integer} itemID Attachment item id
* @param {Boolean} [isPriority]
* @returns {Promise<Integer>}
*/
async renderAttachmentAnnotations(itemID, isPriority) {
return this._enqueue(async () => {
let attachment = await Zotero.Items.getAsync(itemID);
let annotations = [];
for (let annotation of attachment.getAnnotations()) {
if (annotation.annotationType === 'image'
&& !await Zotero.Annotations.hasCacheImage(annotation)) {
annotations.push({
id: annotation.id,
position: JSON.parse(annotation.annotationPosition)
});
}
}
if (!annotations.length) {
return 0;
}
let path = await attachment.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
return this._query('renderAnnotations', { buf, annotations }, [buf]);
}, isPriority);
}
/**
* Render image annotation image
*
* @param {Integer} itemID Attachment item id
* @param {Boolean} [isPriority]
* @returns {Promise<Boolean>}
*/
async renderAnnotation(itemID, isPriority) {
return this._enqueue(async () => {
let annotation = await Zotero.Items.getAsync(itemID);
if (await Zotero.Annotations.hasCacheImage(annotation)) {
return false;
}
let attachment = await Zotero.Items.getAsync(annotation.parentID);
let path = await attachment.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
let annotations = [{
id: annotation.id,
position: JSON.parse(annotation.annotationPosition)
}];
return !!await this._query('renderAnnotations', { buf, annotations }, [buf]);
}, isPriority);
}
}
Zotero.PDFRenderer = new PDFRenderer();

View file

@ -73,6 +73,11 @@ Zotero.Prefs = new function(){
this.clear('firstRunGuidanceShown.saveIcon');
this.clear('firstRunGuidanceShown.saveButton');
break;
// TEMP: Uncomment and set toVersion above to 3 when adding to prefs drop-down
//case 3:
// this.clear('fileHandler.pdf');
// break;
}
}
this.set('prefVersion', toVersion);

View file

@ -292,7 +292,7 @@ Zotero.QuickCopy = new function() {
div.className = "zotero-note";
// AMO reviewer: This documented is never rendered (and the inserted markup
// is sanitized anyway)
div.insertAdjacentHTML('afterbegin', notes[i].getNote());
div.insertAdjacentHTML('afterbegin', notes[i].note);
container.appendChild(div);
textContainer.appendChild(textDoc.importNode(div, true));
}

View file

@ -0,0 +1,815 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2021 Corporation for Digital Scholarship
Vienna, Virginia, USA
http://digitalscholar.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 *****
*/
class ReaderInstance {
constructor() {
this.pdfStateFileName = '.zotero-pdf-state';
this.annotationItemIDs = [];
this.onChangeSidebarWidth = null;
this._instanceID = Zotero.Utilities.randomString();
this._window = null;
this._iframeWindow = null;
this._itemID = null;
this._isReaderInitialized = false;
this._showItemPaneToggle = false;
this._initPromise = new Promise((resolve, reject) => {
this._resolveInitPromise = resolve;
this._rejectInitPromise = reject;
});
}
async open({ itemID, state, location }) {
let item = await Zotero.Items.getAsync(itemID);
if (!item) {
return false;
}
this._itemID = item.id;
this.updateTitle();
let path = await item.getFilePathAsync();
let buf = await OS.File.read(path, {});
buf = new Uint8Array(buf).buffer;
let annotationItems = item.getAnnotations();
let annotations = (await Promise.all(annotationItems.map(x => this._getAnnotation(x)))).filter(x => x);
this.annotationItemIDs = annotationItems.map(x => x.id);
state = state || await this._getState();
this._postMessage({
action: 'open',
buf,
annotations,
state,
location,
promptImport: false,
showItemPaneToggle: this._showItemPaneToggle,
sidebarWidth: this._sidebarWidth,
sidebarOpen: this._sidebarOpen,
bottomPlaceholderHeight: this._bottomPlaceholderHeight
}, [buf]);
return true;
}
get itemID() {
return this._itemID;
}
updateTitle() {
let item = Zotero.Items.get(this._itemID);
let title = item.getField('title');
let parentItemID = item.parentItemID;
if (parentItemID) {
let parentItem = Zotero.Items.get(parentItemID);
if (parentItem) {
title = parentItem.getField('title');
}
}
this._setTitleValue(title);
}
async setAnnotations(items) {
let annotations = [];
for (let item of items) {
let annotation = await this._getAnnotation(item);
if (annotation) {
annotations.push(annotation);
}
}
if (annotations.length) {
let data = { action: 'setAnnotations', annotations };
this._postMessage(data);
}
}
unsetAnnotations(keys) {
let data = { action: 'unsetAnnotations', ids: keys };
this._postMessage(data);
}
async navigate(location) {
this._postMessage({ action: 'navigate', location });
}
enableAddToNote(enable) {
this._postMessage({ action: 'enableAddToNote', enable });
}
setSidebarWidth(width) {
this._postMessage({ action: 'setSidebarWidth', width });
}
setSidebarOpen(open) {
this._postMessage({ action: 'setSidebarOpen', open });
}
async setBottomPlaceholderHeight(height) {
await this._initPromise;
this._postMessage({ action: 'setBottomPlaceholderHeight', height });
}
async setToolbarPlaceholderWidth(width) {
await this._initPromise;
this._postMessage({ action: 'setToolbarPlaceholderWidth', width });
}
async _setState(state) {
let item = Zotero.Items.get(this._itemID);
item.setAttachmentLastPageIndex(state.pageIndex);
let file = Zotero.Attachments.getStorageDirectory(item);
file.append(this.pdfStateFileName);
await Zotero.File.putContentsAsync(file, JSON.stringify(state));
}
async _getState() {
let item = Zotero.Items.get(this._itemID);
let file = Zotero.Attachments.getStorageDirectory(item);
file.append(this.pdfStateFileName);
file = file.path;
let state;
try {
if (await OS.File.exists(file)) {
state = JSON.parse(await Zotero.File.getContentsAsync(file));
}
}
catch (e) {
Zotero.logError(e);
}
let pageIndex = item.getAttachmentLastPageIndex();
if (state) {
if (Number.isInteger(pageIndex) && state.pageIndex !== pageIndex) {
state.pageIndex = pageIndex;
delete state.top;
delete state.left;
}
return state;
}
else if (Number.isInteger(pageIndex)) {
return { pageIndex };
}
return null;
}
_dataURLtoBlob(dataurl) {
let parts = dataurl.split(',');
let mime = parts[0].match(/:(.*?);/)[1];
if (parts[0].indexOf('base64') !== -1) {
let bstr = atob(parts[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new this._iframeWindow.Blob([u8arr], { type: mime });
}
}
_getColorIcon(color, selected) {
let stroke = selected ? 'lightgray' : 'transparent';
let fill = '%23' + color.slice(1);
return `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect shape-rendering="geometricPrecision" fill="${fill}" stroke-width="2" x="2" y="2" stroke="${stroke}" width="12" height="12" rx="3"/></svg>`;
}
_openTagsPopup(x, y, item) {
let menupopup = this._window.document.createElement('menupopup');
menupopup.style.minWidth = '300px';
menupopup.setAttribute('ignorekeys', true);
let tagsbox = this._window.document.createElement('tagsbox');
menupopup.appendChild(tagsbox);
tagsbox.setAttribute('flex', '1');
this._popupset.appendChild(menupopup);
menupopup.openPopupAtScreen(x, y, false);
tagsbox.mode = 'edit';
tagsbox.item = item;
}
_openAnnotationPopup(x, y, annotationID, colors, selectedColor) {
let popup = this._window.document.createElement('menupopup');
this._popupset.appendChild(popup);
popup.addEventListener('popuphidden', function () {
popup.remove();
});
let menuitem;
// Add to note
if (this._window.ZoteroContextPane.getActiveEditor()) {
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', 'Add to Note');
menuitem.addEventListener('command', () => {
let data = {
action: 'popupCmd',
cmd: 'addToNote',
id: annotationID
};
this._postMessage(data);
});
popup.appendChild(menuitem);
// Separator
popup.appendChild(this._window.document.createElement('menuseparator'));
}
// Colors
for (let color of colors) {
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', color[0]);
menuitem.className = 'menuitem-iconic';
menuitem.setAttribute('image', this._getColorIcon(color[1], color[1] === selectedColor));
menuitem.addEventListener('command', () => {
let data = {
action: 'popupCmd',
cmd: 'setAnnotationColor',
id: annotationID,
color: color[1]
};
this._postMessage(data);
});
popup.appendChild(menuitem);
}
// Separator
popup.appendChild(this._window.document.createElement('menuseparator'));
// Delete
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', 'Delete');
menuitem.addEventListener('command', () => {
let data = {
action: 'popupCmd',
cmd: 'deleteAnnotation',
id: annotationID
};
this._postMessage(data);
});
popup.appendChild(menuitem);
popup.openPopupAtScreen(x, y, true);
}
_openColorPopup(x, y, colors, selectedColor) {
let popup = this._window.document.createElement('menupopup');
this._popupset.appendChild(popup);
popup.addEventListener('popuphidden', function () {
popup.remove();
});
let menuitem;
for (let color of colors) {
menuitem = this._window.document.createElement('menuitem');
menuitem.setAttribute('label', color[0]);
menuitem.className = 'menuitem-iconic';
menuitem.setAttribute('image', this._getColorIcon(color[1], color[1] === selectedColor));
menuitem.addEventListener('command', () => {
let data = {
action: 'popupCmd',
cmd: 'setColor',
color: color[1]
};
this._postMessage(data);
});
popup.appendChild(menuitem);
}
popup.openPopupAtScreen(x, y, true);
}
async _postMessage(message, transfer) {
await this._waitForReader();
this._iframeWindow.postMessage({ itemID: this._itemID, message }, this._iframeWindow.origin, transfer);
}
_handleMessage = async (event) => {
let message;
try {
if (event.source !== this._iframeWindow) {
return;
}
// Clone data to avoid the dead object error when the window is closed
let data = JSON.parse(JSON.stringify(event.data));
// Filter messages coming from previous reader instances,
// except for `setAnnotation` to still allow saving it
if (data.itemID !== this._itemID && data.message.action !== 'setAnnotation') {
return;
}
message = data.message;
switch (message.action) {
case 'initialized': {
this._resolveInitPromise();
return;
}
case 'setAnnotation': {
let attachment = Zotero.Items.get(data.itemID);
let { annotation } = message;
annotation.key = annotation.id;
let saveOptions = {
notifierData: {
instanceID: this._instanceID
}
};
let savedAnnotation = await Zotero.Annotations.saveFromJSON(attachment, annotation, saveOptions);
if (annotation.image && !await Zotero.Annotations.hasCacheImage(savedAnnotation)) {
let blob = this._dataURLtoBlob(annotation.image);
await Zotero.Annotations.saveCacheImage(savedAnnotation, blob);
}
return;
}
case 'deleteAnnotations': {
let { ids: keys } = message;
let attachment = Zotero.Items.get(this._itemID);
let libraryID = attachment.libraryID;
for (let key of keys) {
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
// A small check, as we are receiving a list of item keys from a less secure code
if (annotation && annotation.isAnnotation() && annotation.parentID === this._itemID) {
this.annotationItemIDs = this.annotationItemIDs.filter(id => id !== annotation.id);
await annotation.eraseTx();
}
}
return;
}
case 'setState': {
let { state } = message;
this._setState(state);
return;
}
case 'openTagsPopup': {
let { id: key, x, y } = message;
let attachment = Zotero.Items.get(this._itemID);
let libraryID = attachment.libraryID;
let annotation = Zotero.Items.getByLibraryAndKey(libraryID, key);
if (annotation) {
this._openTagsPopup(x, y, annotation);
}
return;
}
case 'openAnnotationPopup': {
let { x, y, id, colors, selectedColor } = message;
this._openAnnotationPopup(x, y, id, colors, selectedColor);
return;
}
case 'openColorPopup': {
let { x, y, colors, selectedColor } = message;
this._openColorPopup(x, y, colors, selectedColor);
return;
}
case 'openURL': {
let { url } = message;
let win = Services.wm.getMostRecentWindow('navigator:browser');
if (win) {
win.ZoteroPane.loadURI(url);
}
return;
}
case 'addToNote': {
let { annotations } = message;
this._addToNote(annotations);
return;
}
case 'save': {
let zp = Zotero.getActiveZoteroPane();
zp.exportPDF(this._itemID);
return;
}
case 'toggleNoteSidebar': {
let { isToggled } = message;
this._toggleNoteSidebar(isToggled);
return;
}
case 'changeSidebarWidth': {
let { width } = message;
if (this.onChangeSidebarWidth) {
this.onChangeSidebarWidth(width);
}
return;
}
case 'changeSidebarOpen': {
let { open } = message;
if (this.onChangeSidebarOpen) {
this.onChangeSidebarOpen(open);
}
return;
}
}
}
catch (e) {
this._postMessage({
action: 'error',
message: `An error occured during '${message ? message.action : ''}'`,
moreInfo: {
message: e.message,
stack: e.stack,
fileName: e.fileName,
lineNumber: e.lineNumber
}
});
throw e;
}
}
async _waitForReader() {
if (this._isReaderInitialized) {
return;
}
let n = 0;
while (!this._iframeWindow || !this._iframeWindow.eval('window.isReady')) {
if (n >= 500) {
throw new Error('Waiting for reader failed');
}
await Zotero.Promise.delay(10);
n++;
}
this._isReaderInitialized = true;
}
/**
* Return item JSON in the pdf-reader ready format
*
* @param {Zotero.Item} item
* @returns {Object|null}
*/
async _getAnnotation(item) {
try {
if (!item || !item.isAnnotation()) {
return null;
}
let json = await Zotero.Annotations.toJSON(item);
json.id = item.key;
json.readOnly = !json.isAuthor || json.isExternal;
delete json.key;
for (let key in json) {
json[key] = json[key] || '';
}
json.tags = json.tags || [];
return json;
}
catch (e) {
Zotero.logError(e);
return null;
}
}
}
class ReaderTab extends ReaderInstance {
constructor({ itemID, sidebarWidth, sidebarOpen, bottomPlaceholderHeight }) {
super();
this._itemID = itemID;
this._sidebarWidth = sidebarWidth;
this._sidebarOpen = sidebarOpen;
this._bottomPlaceholderHeight = bottomPlaceholderHeight;
this._showItemPaneToggle = true;
this._window = Services.wm.getMostRecentWindow('navigator:browser');
let { id, container } = this._window.Zotero_Tabs.add({
type: 'reader',
title: '',
select: true,
notifierData: {
itemID
}
});
this.tabID = id;
this._tabContainer = container;
this._iframe = this._window.document.createElement('iframe');
this._iframe.setAttribute('flex', '1');
this._iframe.setAttribute('type', 'content');
this._iframe.setAttribute('src', 'resource://zotero/pdf-reader/viewer.html');
this._tabContainer.appendChild(this._iframe);
this._popupset = this._window.document.createElement('popupset');
this._tabContainer.appendChild(this._popupset);
this._window.addEventListener('DOMContentLoaded', (event) => {
if (this._iframe && this._iframe.contentWindow && this._iframe.contentWindow.document === event.target) {
this._iframeWindow = this._iframe.contentWindow;
this._iframeWindow.addEventListener('message', this._handleMessage);
this._iframeWindow.addEventListener('error', (event) => {
Zotero.logError(event.error);
});
}
});
this._iframe.setAttribute('tooltip', 'html-tooltip');
}
close() {
if (this.tabID) {
this._window.Zotero_Tabs.close(this.tabID);
}
}
menuCmd(cmd) {
if (cmd === 'export') {
let zp = Zotero.getActiveZoteroPane();
zp.exportPDF(this._itemID);
return;
}
let data = {
action: 'menuCmd',
cmd
};
this._postMessage(data);
}
_toggleNoteSidebar(isToggled) {
let itemPane = this._window.document.getElementById('zotero-item-pane');
if (itemPane.hidden) {
itemPane.hidden = false;
}
else {
itemPane.hidden = true;
}
}
_setTitleValue(title) {
this._window.Zotero_Tabs.rename(this.tabID, title);
}
_addToNote(annotations) {
let noteEditor = this._window.ZoteroContextPane.getActiveEditor();
if (!noteEditor) {
return;
}
let editorInstance = noteEditor.getCurrentInstance();
if (editorInstance) {
editorInstance.focus();
editorInstance.insertAnnotations(annotations);
}
}
}
class ReaderWindow extends ReaderInstance {
constructor({ sidebarWidth, sidebarOpen, bottomPlaceholderHeight }) {
super();
this._sidebarWidth = sidebarWidth;
this._sidebarOpen = sidebarOpen;
this._bottomPlaceholderHeight = bottomPlaceholderHeight;
this.init();
}
init() {
let win = Services.wm.getMostRecentWindow('navigator:browser');
if (!win) return;
this._window = win.open(
'chrome://zotero/content/reader.xul', '', 'chrome,resizable'
);
this._window.addEventListener('DOMContentLoaded', (event) => {
if (event.target === this._window.document) {
this._window.addEventListener('keypress', this._handleKeyPress);
this._popupset = this._window.document.getElementById('zotero-reader-popupset');
this._window.menuCmd = (cmd) => {
if (cmd === 'export') {
let zp = Zotero.getActiveZoteroPane();
zp.exportPDF(this._itemID);
return;
}
let data = {
action: 'menuCmd',
cmd
};
this._postMessage(data);
};
this._iframe = this._window.document.getElementById('reader');
}
if (this._iframe.contentWindow && this._iframe.contentWindow.document === event.target) {
this._iframeWindow = this._window.document.getElementById('reader').contentWindow;
this._iframeWindow.addEventListener('message', this._handleMessage);
}
});
}
close() {
this._window.close();
}
_setTitleValue(title) {
this._window.document.title = title;
}
_handleKeyPress = (event) => {
if ((Zotero.isMac && event.metaKey || event.ctrlKey)
&& !event.shiftKey && !event.altKey && event.key === 'w') {
this._window.close();
}
}
}
class Reader {
constructor() {
this._sidebarWidth = 200;
this._sidebarOpen = false;
this._bottomPlaceholderHeight = 800;
this._readers = [];
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'reader');
this.onChangeSidebarWidth = null;
this.onChangeSidebarOpen = null;
this._debounceSidebarWidthUpdate = Zotero.Utilities.debounce(() => {
let readers = this._readers.filter(r => r instanceof ReaderTab);
for (let reader of readers) {
reader.setSidebarWidth(this._sidebarWidth);
}
}, 500);
}
getSidebarWidth() {
return this._sidebarWidth;
}
_loadSidebarOpenState() {
let win = Zotero.getMainWindow();
if (win) {
let pane = win.document.getElementById('zotero-reader-sidebar-pane');
this._sidebarOpen = pane.getAttribute('collapsed') == 'false';
}
}
_setSidebarOpenState() {
let win = Zotero.getMainWindow();
if (win) {
let pane = win.document.getElementById('zotero-reader-sidebar-pane');
pane.setAttribute('collapsed', this._sidebarOpen ? 'false' : 'true');
}
}
getSidebarOpen() {
return this._sidebarOpen;
}
setSidebarWidth(width) {
this._sidebarWidth = width;
let readers = this._readers.filter(r => r instanceof ReaderTab);
for (let reader of readers) {
reader.setSidebarWidth(width);
}
}
setSidebarOpen(open) {
this._sidebarOpen = open;
let readers = this._readers.filter(r => r instanceof ReaderTab);
for (let reader of readers) {
reader.setSidebarOpen(open);
}
this._setSidebarOpenState();
}
setBottomPlaceholderHeight(height) {
this._bottomPlaceholderHeight = height;
let readers = this._readers.filter(r => r instanceof ReaderTab);
for (let reader of readers) {
reader.setBottomPlaceholderHeight(height);
}
}
notify(event, type, ids, extraData) {
if (type === 'tab') {
let reader = Zotero.Reader.getByTabID(ids[0]);
if (reader) {
if (event === 'close') {
this._readers.splice(this._readers.indexOf(reader), 1);
}
else if (event === 'select') {
this.triggerAnnotationsImportCheck(reader._itemID);
}
}
}
else if (type === 'item') {
// Listen for the parent item, PDF attachment and its annotations updates
for (let reader of this._readers) {
if (event === 'delete') {
let disappearedIDs = reader.annotationItemIDs.filter(x => ids.includes(x));
if (disappearedIDs.length) {
let keys = disappearedIDs.map(id => extraData[id].key);
reader.unsetAnnotations(keys);
}
if (ids.includes(reader._itemID)) {
reader.close();
}
}
else {
let item = Zotero.Items.get(reader._itemID);
let annotationItems = item.getAnnotations();
reader.annotationItemIDs = annotationItems.map(x => x.id);
let affectedAnnotations = annotationItems.filter(({ id }) => (
ids.includes(id)
&& !(extraData && extraData[id] && extraData[id].instanceID === reader._instanceID)
));
if (affectedAnnotations.length) {
reader.setAnnotations(affectedAnnotations);
}
// Update title if the PDF attachment or the parent item changes
if (ids.includes(reader._itemID) || ids.includes(item.parentItemID)) {
reader.updateTitle();
}
}
}
}
}
getByTabID(tabID) {
return this._readers.find(r => (r instanceof ReaderTab) && r.tabID === tabID);
}
async openURI(itemURI, location, openWindow) {
let item = await Zotero.URI.getURIItem(itemURI);
if (!item) return;
await this.open(item.id, location, openWindow);
}
async open(itemID, location, openWindow) {
this._loadSidebarOpenState();
this.triggerAnnotationsImportCheck(itemID);
let reader;
if (openWindow) {
reader = this._readers.find(r => r._itemID === itemID && (r instanceof ReaderWindow));
}
else {
reader = this._readers.find(r => r._itemID === itemID);
}
if (reader) {
if (reader instanceof ReaderTab) {
reader._window.Zotero_Tabs.select(reader.tabID);
}
if (location) {
reader.navigate(location);
}
}
else if (openWindow) {
reader = new ReaderWindow({
sidebarWidth: this._sidebarWidth,
sidebarOpen: this._sidebarOpen,
bottomPlaceholderHeight: this._bottomPlaceholderHeight
});
this._readers.push(reader);
if (!(await reader.open({ itemID, location }))) {
return;
}
reader._window.addEventListener('unload', () => {
this._readers.splice(this._readers.indexOf(reader), 1);
});
}
else {
reader = new ReaderTab({
itemID,
sidebarWidth: this._sidebarWidth,
sidebarOpen: this._sidebarOpen,
bottomPlaceholderHeight: this._bottomPlaceholderHeight
});
this._readers.push(reader);
if (!(await reader.open({ itemID, location }))) {
return;
}
reader.onChangeSidebarWidth = (width) => {
this._sidebarWidth = width;
this._debounceSidebarWidthUpdate();
if (this.onChangeSidebarWidth) {
this.onChangeSidebarWidth(width);
}
};
reader.onChangeSidebarOpen = (open) => {
this._sidebarOpen = open;
this.setSidebarOpen(open);
if (this.onChangeSidebarOpen) {
this.onChangeSidebarOpen(open);
}
};
}
if (reader instanceof ReaderWindow) {
reader._window.focus();
}
}
async triggerAnnotationsImportCheck(itemID) {
let item = await Zotero.Items.getAsync(itemID);
let mtime = await item.attachmentModificationTime;
if (item.attachmentLastProcessedModificationTime < Math.floor(mtime / 1000)) {
await Zotero.PDFWorker.import(itemID, true);
}
}
}
Zotero.Reader = new Reader();

View file

@ -41,12 +41,12 @@ Zotero.Schema = new function(){
// If updating from this userdata version or later, don't show "Upgrading database…" and don't make
// DB backup first. This should be set to false when breaking compatibility or making major changes.
const minorUpdateFrom = 107;
const minorUpdateFrom = false;
var _dbVersions = [];
var _schemaVersions = [];
// Update when adding _updateCompatibility() line to schema update step
var _maxCompatibility = 6;
var _maxCompatibility = 7;
var _repositoryTimerID;
var _repositoryNotificationTimerID;
@ -1790,6 +1790,9 @@ Zotero.Schema = new function(){
var noteID = parseInt(yield Zotero.DB.valueQueryAsync(
"SELECT itemTypeID FROM itemTypes WHERE typeName='note'"
));
var annotationID = parseInt((yield Zotero.DB.valueQueryAsync(
"SELECT itemTypeID FROM itemTypes WHERE typeName='annotation'"
)) || -1);
// The first position is for testing and the second is for repairing. Can be either SQL
// statements or promise-returning functions. For statements, the repair entry can be either
@ -1915,16 +1918,29 @@ Zotero.Schema = new function(){
`SELECT COUNT(*) > 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`,
`INSERT INTO itemAttachments (itemID, linkMode) SELECT itemID, 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`,
],
// Note/child parents
// Attachments with note parents, unless they're embedded-image attachments
[
`SELECT COUNT(*) > 0 FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
`UPDATE itemAttachments SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
`SELECT COUNT(*) > 0 FROM itemAttachments `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID=${noteID}) `
+ `AND linkMode != ${Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE}`,
`UPDATE itemAttachments SET parentItemID=NULL `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID=${noteID}) `
+ `AND linkMode != ${Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE}`,
],
// Attachments with attachment or annotation parents
[
`SELECT COUNT(*) > 0 FROM itemNotes WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
`UPDATE itemNotes SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
`SELECT COUNT(*) > 0 FROM itemAttachments `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${attachmentID}, ${annotationID}))`,
`UPDATE itemAttachments SET parentItemID=NULL `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${attachmentID}, ${annotationID}))`,
],
// Notes with note/attachment/annotation parents
[
`SELECT COUNT(*) > 0 FROM itemNotes `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}, ${annotationID}))`,
`UPDATE itemNotes SET parentItemID=NULL `
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}, ${annotationID}))`,
],
// Delete empty creators
// This may cause itemCreator gaps, but that's better than empty creators
[
@ -1944,8 +1960,8 @@ Zotero.Schema = new function(){
],
// Invalid link mode -- set to imported url
[
"SELECT COUNT(*) > 0 FROM itemAttachments WHERE linkMode NOT IN (0,1,2,3)",
"UPDATE itemAttachments SET linkMode=1 WHERE linkMode NOT IN (0,1,2,3)"
"SELECT COUNT(*) > 0 FROM itemAttachments WHERE linkMode NOT IN (0,1,2,3,4)",
"UPDATE itemAttachments SET linkMode=1 WHERE linkMode NOT IN (0,1,2,3,4)"
],
// Creators with first name can't be fieldMode 1
[
@ -3218,6 +3234,22 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("CREATE INDEX deletedSearches_dateDeleted ON deletedSearches(dateDeleted)");
}
else if (i == 112) {
yield _updateCompatibility(7);
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS annotations");
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS highlights");
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS users");
yield Zotero.DB.queryAsync("CREATE TABLE users (\n userID INTEGER PRIMARY KEY,\n name TEXT NOT NULL\n)");
yield Zotero.DB.queryAsync("CREATE TABLE itemAnnotations (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT NOT NULL,\n type INTEGER NOT NULL,\n text TEXT,\n comment TEXT,\n color TEXT,\n pageLabel TEXT,\n sortIndex TEXT NOT NULL,\n position TEXT NOT NULL,\n isExternal INT NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("CREATE INDEX itemAnnotations_parentItemID ON itemAnnotations(parentItemID)");
yield Zotero.DB.queryAsync("ALTER TABLE itemAttachments ADD COLUMN lastProcessedModificationTime INT");
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_lastProcessedModificationTime ON itemAttachments(lastProcessedModificationTime)");
}
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
}

View file

@ -502,13 +502,14 @@ Zotero.Sync.Storage.Local = {
*/
getFilesToUpload: function (libraryID) {
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
+ "WHERE libraryID=? AND syncState IN (?,?) AND linkMode IN (?,?)";
+ "WHERE libraryID=? AND syncState IN (?,?) AND linkMode IN (?,?,?)";
var params = [
libraryID,
this.SYNC_STATE_TO_UPLOAD,
this.SYNC_STATE_FORCE_UPLOAD,
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE,
];
return Zotero.DB.columnQueryAsync(sql, params);
},
@ -566,12 +567,13 @@ Zotero.Sync.Storage.Local = {
return Zotero.DB.executeTransaction(async function () {
var sql = "SELECT itemID FROM items JOIN itemAttachments USING (itemID) "
+ "WHERE libraryID=? AND itemTypeID=? AND linkMode IN (?, ?)";
+ "WHERE libraryID=? AND itemTypeID=? AND linkMode IN (?, ?, ?)";
var params = [
libraryID,
Zotero.ItemTypes.getID('attachment'),
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE,
];
var itemIDs = await Zotero.DB.columnQueryAsync(sql, params);
for (let itemID of itemIDs) {

View file

@ -35,6 +35,7 @@ Zotero.Sync.APIClient = function (options) {
this.baseURL = options.baseURL;
this.apiVersion = options.apiVersion;
this.apiKey = options.apiKey;
this.schemaVersion = options.schemaVersion || Zotero.Schema.globalSchemaVersion;
this.caller = options.caller;
this.debugUploadPolicy = Zotero.Prefs.get('sync.debugUploadPolicy');
this.cancellerReceiver = options.cancellerReceiver;
@ -298,7 +299,6 @@ Zotero.Sync.APIClient.prototype = {
target: objectTypePlural,
libraryType: libraryType,
libraryTypeID: libraryTypeID,
format: 'json'
};
params[objectType + "Key"] = objectKeys.join(",");
if (objectType == 'item') {
@ -617,6 +617,7 @@ Zotero.Sync.APIClient.prototype = {
if (this.apiKey) {
newHeaders["Zotero-API-Key"] = this.apiKey;
}
newHeaders["Zotero-Schema-Version"] = this.schemaVersion;
return newHeaders;
},

View file

@ -149,6 +149,7 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
throw e;
}
Zotero.debug("Upload failed -- performing download", 2);
Zotero.debug(e, 1);
downloadResult = yield this._startDownload();
Zotero.debug("Download result is " + downloadResult, 4);
throw e;
@ -223,6 +224,15 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
skipNotifier: true
});
if (this.library.libraryType == 'group') {
try {
yield this._updateGroupItemUsers();
}
catch (e) {
Zotero.logError(e);
}
}
Zotero.debug("Done syncing " + this.library.name);
});
@ -1428,6 +1438,58 @@ Zotero.Sync.Data.Engine.prototype._uploadDeletions = Zotero.Promise.coroutine(fu
});
/**
* Update createdByUserID/lastModifiedByUserID for previously downloaded group items
*
* TEMP: Currently only processes one batch of items, but before we start displaying the names,
* we'll need to update it to fetch all
*/
Zotero.Sync.Data.Engine.prototype._updateGroupItemUsers = async function () {
// TODO: Do more at once when we actually start showing these names
var max = this.apiClient.MAX_OBJECTS_PER_REQUEST;
var sql = "SELECT key FROM items LEFT JOIN groupItems GI USING (itemID) "
+ `WHERE libraryID=? AND GI.itemID IS NULL ORDER BY itemID LIMIT ${max}`;
var keys = await Zotero.DB.columnQueryAsync(sql, this.libraryID);
if (!keys.length) {
return;
}
Zotero.debug(`Updating item users in ${this.library.name}`);
var jsonItems = await this.apiClient.downloadObjects(
this.library.libraryType, this.libraryTypeID, 'item', keys
)[0];
if (!Array.isArray(jsonItems)) {
Zotero.logError(e);
return;
}
for (let jsonItem of jsonItems) {
let item = Zotero.Items.getByLibraryAndKey(this.libraryID, jsonItem.key);
let params = [null, null];
// This should almost always exist, but maybe doesn't for some old items?
if (jsonItem.meta.createdByUser) {
let { id: userID, username, name } = jsonItem.meta.createdByUser;
await Zotero.Users.setName(userID, name !== '' ? name : username);
params[0] = userID;
}
if (jsonItem.meta.lastModifiedByUser) {
let { id: userID, username, name } = jsonItem.meta.lastModifiedByUser;
await Zotero.Users.setName(userID, name !== '' ? name : username);
params[1] = userID;
}
await item.updateCreatedByUser.apply(item, params);
}
return;
};
Zotero.Sync.Data.Engine.prototype._getJSONForObject = function (objectType, id, options = {}) {
return Zotero.DB.executeTransaction(function* () {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);

View file

@ -124,24 +124,39 @@ Zotero.Sync.EventListeners.AutoSyncListener = {
}
// Only trigger sync for certain types
//
// TODO: settings, full text
if (!Zotero.DataObjectUtilities.getTypes().includes(type)) {
// TODO: full text
if (![...Zotero.DataObjectUtilities.getTypes(), 'setting'].includes(type)) {
return;
}
// Determine affected libraries so only those can be synced
let libraries = [];
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
ids.forEach(id => {
let lk = objectsClass.getLibraryAndKeyFromID(id);
if (lk) {
let library = Zotero.Libraries.get(lk.libraryID);
if (type == 'setting') {
for (let id of ids) {
// E.g., '1/lastPageIndex_u_ABCD2345'
let libraryID = parseInt(id.split('/')[0]);
let library = Zotero.Libraries.get(libraryID);
if (library.syncable) {
libraries.push(library);
}
}
});
}
else if (Zotero.DataObjectUtilities.getTypes().includes(type)) {
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
ids.forEach(id => {
let lk = objectsClass.getLibraryAndKeyFromID(id);
if (lk) {
let library = Zotero.Libraries.get(lk.libraryID);
if (library.syncable) {
libraries.push(library);
}
}
});
}
else {
return;
}
libraries = Zotero.Sync.Data.Local.filterSkippedLibraries(libraries);
if (!libraries.length) {

View file

@ -175,6 +175,7 @@ Zotero.Sync.Data.Local = {
// Replace local user key with libraryID, in case duplicates were merged before the
// first sync
yield Zotero.Relations.updateUser(null, userID);
yield Zotero.Notes.updateUser(null, userID);
}
});
@ -542,12 +543,13 @@ Zotero.Sync.Data.Local = {
var sql = "SELECT O." + objectsClass.idColumn + " FROM " + objectsClass.table + " O";
if (objectType == 'item') {
sql += " LEFT JOIN itemAttachments IA USING (itemID) "
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) ";
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ "LEFT JOIN itemAnnotations IAn ON (O.itemID=IAn.itemID)";
}
sql += " WHERE libraryID=? AND synced=0";
// Sort child items last
// Don't sync external annotations
if (objectType == 'item') {
sql += " ORDER BY COALESCE(IA.parentItemID, INo.parentItemID)";
sql += " AND (IAn.isExternal IS NULL OR IAN.isExternal=0)";
}
var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
@ -555,12 +557,12 @@ Zotero.Sync.Data.Local = {
// Sort descendent collections last
if (objectType == 'collection') {
try {
ids = Zotero.Collections.sortByLevel(ids);
ids = Zotero.Collections.sortByLevel(ids.map(id => Zotero.Collections.get(id))).map(o => o.id);
}
catch (e) {
Zotero.logError(e);
// If collections were incorrectly nested, fix and try again
if (e instanceof Zotero.Error && e.error == Zotero.Error.ERROR_INVALID_COLLECTION_NESTING) {
if (e instanceof Zotero.Error && e.error == Zotero.Error.ERROR_INVALID_OBJECT_NESTING) {
let c = Zotero.Collections.get(e.collectionID);
Zotero.debug(`Removing parent collection ${c.parentKey} from collection ${c.key}`);
c.parentID = null;
@ -572,11 +574,22 @@ Zotero.Sync.Data.Local = {
}
}
}
else if (objectType == 'item') {
ids = Zotero.Items.sortByParent(ids.map(id => Zotero.Items.get(id))).map(o => o.id);
}
return ids;
}),
isSyncItem: function (item) {
if (item.itemType == 'annotation' && item.annotationIsExternal) {
return false;
}
return true;
},
//
// Cache management
//
@ -883,7 +896,7 @@ Zotero.Sync.Data.Local = {
// For items, check if mtime or file hash changed in metadata,
// which would indicate that a remote storage sync took place and
// a download is needed
if (objectType == 'item' && obj.isImportedAttachment()) {
if (objectType == 'item' && obj.isStoredFileAttachment()) {
if (jsonDataLocal.mtime != jsonData.mtime
|| jsonDataLocal.md5 != jsonData.md5) {
saveOptions.storageDetailsChanged = true;
@ -1459,8 +1472,23 @@ Zotero.Sync.Data.Local = {
if (!options.skipData) {
obj.fromJSON(json.data, { strict: true });
}
if (obj.objectType == 'item' && obj.isImportedAttachment()) {
yield this._checkAttachmentForDownload(obj, json.data.mtime, options.isNewObject);
if (obj.objectType == 'item') {
// Update createdByUserID and lastModifiedByUserID
for (let p of ['createdByUser', 'lastModifiedByUser']) {
if (json.meta && json.meta[p]) {
let { id: userID, username, name } = json.meta[p];
obj[p + 'ID'] = userID;
name = name !== '' ? name : username;
// Update stored name if it changed
if (Zotero.Users.getName(userID) != name) {
yield Zotero.Users.setName(userID, name);
}
}
}
if (obj.isStoredFileAttachment()) {
yield this._checkAttachmentForDownload(obj, json.data.mtime, options.isNewObject);
}
}
obj.version = json.data.version;
if (!options.saveAsUnsynced) {
@ -1493,7 +1521,7 @@ Zotero.Sync.Data.Local = {
yield this._removeObjectFromSyncQueue(obj.objectType, obj.libraryID, json.key);
// Mark updated attachments for download
if (obj.objectType == 'item' && obj.isImportedAttachment()) {
if (obj.objectType == 'item' && obj.isStoredFileAttachment()) {
// If storage changes were made (attachment mtime or hash), mark
// library as requiring download
if (options.isNewObject || options.storageDetailsChanged) {

View file

@ -95,6 +95,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
return new Zotero.Sync.APIClient({
baseURL: this.baseURL,
apiVersion: this.apiVersion,
schemaVersion: this.globalSchemaVersion,
apiKey: options.apiKey,
caller: this.caller,
cancellerReceiver: _cancellerReceiver,

View file

@ -2541,8 +2541,14 @@ Zotero.Translate.Export.prototype._prepareTranslation = Zotero.Promise.method(fu
function rest() {
// export file data, if requested
if(this._displayOptions["exportFileData"]) {
this.location = this._itemGetter.exportFiles(this.location, this.translator[0].target);
if (this._displayOptions.exportFileData) {
this.location = this._itemGetter.exportFiles(
this.location,
this.translator[0].target,
{
includeAnnotations: this._displayOptions.includeAnnotations
}
);
}
// initialize IO

View file

@ -1038,7 +1038,15 @@ Zotero.Translate.ItemGetter.prototype = {
},
"setAll": Zotero.Promise.coroutine(function* (libraryID, getChildCollections) {
this._itemsLeft = yield Zotero.Items.getAll(libraryID, true);
this._itemsLeft = (yield Zotero.Items.getAll(libraryID, true))
.filter((item) => {
// Don't export annotations
switch (item.itemType) {
case 'annotation':
return false;
}
return true;
});
if(getChildCollections) {
this._collectionsLeft = Zotero.Collections.getByLibrary(libraryID);
@ -1048,9 +1056,10 @@ Zotero.Translate.ItemGetter.prototype = {
this.numItems = this._itemsLeft.length;
}),
"exportFiles":function(dir, extension) {
exportFiles: function (dir, extension, { includeAnnotations }) {
// generate directory
this._exportFileDirectory = dir.parent.clone();
this._includeAnnotations = includeAnnotations;
// delete this file if it exists
if(dir.exists()) {
@ -1079,6 +1088,7 @@ Zotero.Translate.ItemGetter.prototype = {
var attachmentArray = Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy);
var linkMode = attachment.attachmentLinkMode;
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
let includeAnnotations = attachment.isPDFAttachment() && this._includeAnnotations;
attachmentArray.localPath = attachment.getFilePath();
if(this._exportFileDirectory) {
@ -1114,7 +1124,7 @@ Zotero.Translate.ItemGetter.prototype = {
* file to be overwritten. If true, the file will be silently overwritten.
* defaults to false if not provided.
*/
attachmentArray.saveFile = function(attachPath, overwriteExisting) {
attachmentArray.saveFile = async function (attachPath, overwriteExisting) {
// Ensure a valid path is specified
if(attachPath === undefined || attachPath == "") {
throw new Error("ERROR_EMPTY_PATH");
@ -1162,18 +1172,13 @@ Zotero.Translate.ItemGetter.prototype = {
var directory = targetFile.parent;
// The only attachments that can have multiple supporting files are imported
// attachments of mime type text/html
// For snapshots with supporting files, check if any of the supporting
// files would cause a name conflict, and build a list of transfers
// that should be performed
//
// TEMP: This used to check getNumFiles() here, but that's now async.
// It could be restored (using hasMultipleFiles()) when this is made
// async, but it's probably not necessary. (The below can also be changed
// to use OS.File.DirectoryIterator.)
if(attachment.attachmentContentType == "text/html"
&& linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
// Attachment is a snapshot with supporting files. Check if any of the
// supporting files would cause a name conflict, and build a list of transfers
// that should be performed
// TODO: Change the below to use OS.File.DirectoryIterator?
if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE
&& await Zotero.Attachments.hasMultipleFiles(attachment)) {
var copySrcs = [];
var files = attachment.getFile().parent.directoryEntries;
while (files.hasMoreElements()) {
@ -1204,10 +1209,22 @@ Zotero.Translate.ItemGetter.prototype = {
for(var i = 0; i < copySrcs.length; i++) {
copySrcs[i].copyTo(directory, copySrcs[i].leafName);
}
} else {
// Attachment is a single file
// Copy the file to the specified location
attachFile.copyTo(directory, targetFile.leafName);
}
// For single files, just copy to the specified location
else {
if (includeAnnotations) {
// TODO: Make export async
try {
await Zotero.PDFWorker.export(attachment.id, targetFile.path);
}
catch (e) {
Zotero.logError(e);
throw e;
}
}
else {
attachFile.copyTo(directory, targetFile.leafName);
}
}
attachmentArray.path = targetFile.path;

View file

@ -28,10 +28,11 @@ Zotero.Users = new function () {
var _libraryID;
var _username;
var _localUserKey;
var _users = {};
this.init = Zotero.Promise.coroutine(function* () {
this.init = async function () {
let sql = "SELECT key, value FROM settings WHERE setting='account'";
let rows = yield Zotero.DB.queryAsync(sql);
let rows = await Zotero.DB.queryAsync(sql);
let settings = {};
for (let i=0; i<rows.length; i++) {
@ -56,11 +57,16 @@ Zotero.Users = new function () {
let key = Zotero.randomString(8);
sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
yield Zotero.DB.queryAsync(sql, key);
await Zotero.DB.queryAsync(sql, key);
_localUserKey = key;
}
});
rows = await Zotero.DB.queryAsync("SELECT userID, name FROM users");
for (let row of rows) {
_users[row.userID] = row.name;
}
};
this.getCurrentUserID = function() { return _userID };
@ -87,4 +93,18 @@ Zotero.Users = new function () {
this.getLocalUserKey = function () {
return _localUserKey;
};
this.getName = function (userID) {
return _users[userID] || '';
};
this.setName = async function (userID, name) {
if (this.getName(userID) == name) {
return;
}
await Zotero.DB.queryAsync("REPLACE INTO users VALUES (?, ?)", [userID, name]);
_users[userID] = name;
}
};

View file

@ -49,6 +49,62 @@ Zotero.Utilities = {
};
},
/**
* Creates and returns a new, throttled version of the
* passed function, that, when invoked repeatedly,
* will only actually call the original function at most
* once per every wait milliseconds
*
* By default, throttle will execute the function as soon
* as you call it for the first time, and, if you call it
* again any number of times during the wait period, as soon
* as that period is over. If you'd like to disable the
* leading-edge call, pass {leading: false}, and if you'd
* like to disable the execution on the trailing-edge,
* pass {trailing: false}. See
* https://underscorejs.org/#throttle
* https://github.com/jashkenas/underscore/blob/master/underscore.js
* (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Underscore may be freely distributed under the MIT license.
*
* @param {Function} func Function to throttle
* @param {Integer} wait Wait period in milliseconds
* @param {Boolean} [options.leading] Call at the beginning of the wait period
* @param {Boolean} [options.trailing] Call at the end of the wait period
*/
throttle: function (func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
}
else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
},
/**
* Fixes author name capitalization.
* Currently for all uppercase names only

View file

@ -228,7 +228,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
var version = yield deferred.promise;
}
Zotero.version = version;
Zotero.isDevBuild = Zotero.version.includes('beta') || Zotero.version.includes('SOURCE');
Zotero.isDevBuild = Zotero.version.includes('beta')
|| Zotero.version.includes('dev')
|| Zotero.version.includes('SOURCE');
// OS platform
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
@ -257,6 +259,8 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
return;
}
Zotero.isPDFBuild = Zotero.Prefs.get('beta.zotero6');
try {
yield Zotero.DataDirectory.init();
if (this.restarting) {
@ -719,6 +723,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
yield Zotero.Groups.init();
yield Zotero.Relations.init();
yield Zotero.Retractions.init();
yield Zotero.NoteBackups.init();
// Migrate fields from Extra that can be moved to item fields after a schema update
yield Zotero.Schema.migrateExtraFields();

View file

@ -69,6 +69,13 @@ var ZoteroPane = new function()
this.init = function () {
Zotero.debug("Initializing Zotero pane");
if (!Zotero.isPDFBuild) {
let win = document.getElementById('main-window')
win.setAttribute('legacytoolbar', 'true');
document.getElementById('titlebar').hidden = true;
document.getElementById('tab-bar-container').hidden = true;
}
// Set key down handler
document.getElementById('appcontent').addEventListener('keydown', ZoteroPane_Local.handleKeyDown, true);
@ -144,6 +151,7 @@ var ZoteroPane = new function()
Zotero.hiDPI = window.devicePixelRatio > 1;
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
Zotero_Tabs.init();
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
// Add a default progress window
@ -525,6 +533,21 @@ var ZoteroPane = new function()
* Trigger actions based on keyboard shortcuts
*/
function handleKeyDown(event, from) {
// Close current tab
if (event.key == 'w') {
let close = Zotero.isMac
? (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey)
: (event.ctrlKey && !event.shiftKey && !event.altKey);
if (close) {
if (Zotero_Tabs.selectedIndex > 0) {
Zotero_Tabs.close();
event.preventDefault();
event.stopPropagation();
}
return;
}
}
try {
// Ignore keystrokes outside of Zotero pane
if (!(event.originalTarget.ownerDocument instanceof XULDocument)) {
@ -541,6 +564,20 @@ var ZoteroPane = new function()
}
if (from == 'zotero-pane') {
// Tab navigation
// TODO: Select across tabs without selecting with Ctrl-Shift, as in Firefox?
let ctrlOnly = event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
if (ctrlOnly) {
if (event.key == 'PageUp') {
Zotero_Tabs.selectPrev();
}
else if (event.key == 'PageDown') {
Zotero_Tabs.selectNext();
}
event.preventDefault();
return;
}
// Highlight collections containing selected items
//
// We use Control (17) on Windows because Alt triggers the menubar;
@ -1166,6 +1203,14 @@ var ZoteroPane = new function()
return;
}
// Rename tab
if (Zotero.isPDFBuild) {
Zotero_Tabs.rename('zotero-pane', collectionTreeRow.getName());
}
let type = Zotero.Libraries.get(collectionTreeRow.ref.libraryID).libraryType;
ZoteroItemPane.switchEditorEngine(type == 'group' || !Zotero.isPDFBuild);
// Clear quick search and tag selector when switching views
document.getElementById('zotero-tb-search').value = "";
if (ZoteroPane.tagSelector) {
@ -2690,6 +2735,8 @@ var ZoteroPane = new function()
'showInLibrary',
'sep1',
'addNote',
'createNoteFromAnnotations',
'createNoteFromAnnotationsMenu',
'addAttachments',
'sep2',
'findPDF',
@ -2858,6 +2905,7 @@ var ZoteroPane = new function()
}
}
}
}
// Single item selected
@ -2875,6 +2923,33 @@ var ZoteroPane = new function()
if (item.isRegularItem() && !item.isFeedItem) {
show.push(m.addNote, m.addAttachments, m.sep2);
// Create Note from Annotations
let popup = document.getElementById('create-note-from-annotations-popup');
popup.textContent = '';
let eligibleAttachments = Zotero.Items.get(item.getAttachments())
.filter(item => item.isPDFAttachment());
let attachmentsWithAnnotations = eligibleAttachments.filter(x => x.numAnnotations());
if (attachmentsWithAnnotations.length) {
// Display submenu if there's more than one PDF attachment, even if
// there's only attachment with annotations, so it's clear which one
// the annotations are coming from
if (eligibleAttachments.length > 1) {
show.push(m.createNoteFromAnnotationsMenu);
for (let attachment of attachmentsWithAnnotations) {
let menuitem = document.createElement('menuitem');
menuitem.setAttribute('label', attachment.getDisplayTitle());
menuitem.onclick = () => {
ZoteroPane.createNoteFromAnnotationsForAttachment(attachment);
};
popup.appendChild(menuitem);
}
}
// Single attachment with annotations
else {
show.push(m.createNoteFromAnnotations);
}
}
}
if (Zotero.Attachments.canFindPDFForItem(item)) {
@ -3462,7 +3537,7 @@ var ZoteroPane = new function()
var items = this.getSelectedItems();
if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) {
var note = items[0].getNote()
var note = items[0].note;
items[0].setNote(note + text);
yield items[0].saveTx();
@ -3514,13 +3589,13 @@ var ZoteroPane = new function()
}
}
};
this.onNoteWindowClosed = async function (itemID, noteText) {
var item = Zotero.Items.get(itemID);
item.setNote(noteText);
await item.saveTx();
// If note is still selected, show the editor again when the note window closes
var selectedItems = this.getSelectedItems(true);
if (selectedItems.length == 1 && itemID == selectedItems[0]) {
@ -3529,6 +3604,46 @@ var ZoteroPane = new function()
};
this.openBackupNoteWindow = function (itemID) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var name = null;
if (itemID) {
let w = this.findBackupNoteWindow(itemID);
if (w) {
w.focus();
return;
}
// Create a name for this window so we can focus it later
//
// Collection is only used on new notes, so we don't need to
// include it in the name
name = 'zotero-backup-note-' + itemID;
}
var io = { itemID: itemID };
window.openDialog('chrome://zotero/content/noteBackup.xul', name, 'chrome,resizable,centerscreen,dialog=false', io);
}
this.findBackupNoteWindow = function (itemID) {
var name = 'zotero-backup-note-' + itemID;
var wm = Services.wm;
var e = wm.getEnumerator('zotero:note');
while (e.hasMoreElements()) {
var w = e.getNext();
if (w.name == name) {
return w;
}
}
};
this.addAttachmentFromURI = Zotero.Promise.method(function (link, itemID) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
@ -4000,9 +4115,27 @@ var ZoteroPane = new function()
}
}
var launchFile = async function (path, contentType) {
var launchFile = async (path, contentType, itemID) => {
// Fix blank PDF attachment MIME type
if (!contentType) {
let item = await Zotero.Items.getAsync(itemID);
let path = await item.getFilePathAsync();
let type = 'application/pdf';
if (Zotero.MIME.sniffForMIMEType(await Zotero.File.getSample(path)) == type) {
contentType = type;
item.attachmentContentType = type;
await item.saveTx();
}
}
// Custom PDF handler
if (contentType === 'application/pdf') {
let item = await Zotero.Items.getAsync(itemID);
let library = Zotero.Libraries.get(item.libraryID);
// TEMP
if (Zotero.isPDFBuild && library.libraryType == 'user') {
this.viewPDF(itemID, event && event.shiftKey);
return;
}
let pdfHandler = Zotero.Prefs.get("fileHandler.pdf");
if (pdfHandler) {
if (await OS.File.exists(pdfHandler)) {
@ -4031,7 +4164,7 @@ var ZoteroPane = new function()
continue;
}
let isLinkedFile = !item.isImportedAttachment();
let isLinkedFile = !item.isStoredFileAttachment();
let path = item.getFilePath();
if (!path) {
ZoteroPane_Local.showAttachmentNotFoundDialog(
@ -4058,7 +4191,7 @@ var ZoteroPane = new function()
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
if (await OS.File.exists(iCloudPath)) {
Zotero.debug("Triggering download of iCloud file");
await launchFile(iCloudPath, item.attachmentContentType);
await launchFile(iCloudPath, item.attachmentContentType, itemID);
let time = new Date();
let maxTime = 5000;
let revealed = false;
@ -4118,7 +4251,7 @@ var ZoteroPane = new function()
if (fileExists && !redownload) {
Zotero.debug("Opening " + path);
Zotero.Notifier.trigger('open', 'file', item.id);
launchFile(path, item.attachmentContentType);
await launchFile(path, item.attachmentContentType, item.id);
continue;
}
@ -4164,10 +4297,14 @@ var ZoteroPane = new function()
Zotero.debug("Opening " + path);
Zotero.Notifier.trigger('open', 'file', item.id);
launchFile(path, item.attachmentContentType);
await launchFile(path, item.attachmentContentType, item.id);
}
});
this.viewPDF = function (itemID, openWindow) {
Zotero.Reader.open(itemID, null, openWindow);
};
/**
* @deprecated
@ -4203,7 +4340,7 @@ var ZoteroPane = new function()
var fileExists = await OS.File.exists(path);
// If file doesn't exist but an evicted iCloud Drive file does, reveal that instead
if (!fileExists && Zotero.isMac && !attachment.isImportedAttachment()) {
if (!fileExists && Zotero.isMac && !attachment.isStoredFileAttachment()) {
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
if (await OS.File.exists(iCloudPath)) {
path = iCloudPath;
@ -4563,8 +4700,40 @@ var ZoteroPane = new function()
}
}
};
this.createNoteFromAnnotationsForAttachment = async function (attachment) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var note = await Zotero.EditorInstance.createNoteFromAnnotations(
attachment.getAnnotations(), attachment.parentID
);
await this.selectItem(note.id);
};
this.createNoteFromAnnotationsFromSelected = async function () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
var item = this.getSelectedItems()[0];
var attachment;
if (item.isRegularItem()) {
attachment = Zotero.Items.get(item.getAttachments())
.find(x => x.isPDFAttachment() && x.numAnnotations());
}
else if (item.isFileAttachment()) {
attachment = item;
}
else {
throw new Error("Not a regular item or file attachment");
}
return this.createNoteFromAnnotationsForAttachment(attachment);
};
this.createEmptyParent = async function (item) {
await Zotero.DB.executeTransaction(async function () {
// TODO: remove once there are no top-level web attachments
@ -4585,7 +4754,27 @@ var ZoteroPane = new function()
await item.save();
});
};
this.exportPDF = async function (itemID) {
let item = await Zotero.Items.getAsync(itemID);
if (!item || !item.isAttachment()) {
throw new Error('Item ' + itemID + ' is not attachment');
}
let filename = item.attachmentFilename;
var fp = new FilePicker();
fp.init(window, Zotero.getString('styles.editor.save'), fp.modeSave);
fp.appendFilter("PDF", "*.pdf");
fp.defaultString = filename;
var rv = await fp.show();
if (rv === fp.returnOK || rv === fp.returnReplace) {
let outputFile = fp.file;
await Zotero.PDFWorker.export(item.id, outputFile, true);
}
};
this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
// TEMP: fix
@ -4947,6 +5136,7 @@ var ZoteroPane = new function()
this.updateToolbarPosition();
this.updateTagsBoxSize();
ZoteroContextPane.update();
}
@ -5095,6 +5285,8 @@ var ZoteroPane = new function()
* Revisit when we're all HTML.
*/
this.updateTagsBoxSize = function () {
// TODO: We can probably remove this function
return;
var pane = document.querySelector('#zotero-item-pane');
var header = document.querySelector('#zotero-item-pane .tags-box-header');
var list = document.querySelector('#zotero-item-pane .tags-box-list');

File diff suppressed because it is too large Load diff

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Show in Library">
<!ENTITY zotero.items.menu.attach.note "Add Note">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Add Attachment">
<!ENTITY zotero.items.menu.attach.link.uri "Attach Link to URI…">
<!ENTITY zotero.items.menu.attach.file "Attach Stored Copy of File...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Store Copy of File...">
<!ENTITY zotero.toolbar.attachment.weblink "Save Link to Current Page">
<!ENTITY zotero.toolbar.attachment.snapshot "Take Snapshot of Current Page">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Attach Link to URI">
<!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Title:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Briefly describe the problem:
general.nMegabytes=%S MB
general.item=Item
general.pdf=PDF
general.back=Back
general.operationInProgress=A Zotero operation is currently in progress.
general.operationInProgress.waitUntilFinished=Please wait until it has finished.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Switch to two fields
pane.item.creator.moveToTop=Move to Top
pane.item.creator.moveUp=Move Up
pane.item.creator.moveDown=Move Down
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Untitled Note
pane.item.notes.delete.confirm=Are you sure you want to delete this note?
pane.item.notes.count.zero=%S notes:
pane.item.notes.count.singular=%S note:
pane.item.notes.count.plural=%S notes:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=New title:
pane.item.attachments.rename.renameAssociatedFile=Rename associated file
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S related:
pane.item.related.count.plural=%S related:
pane.item.parentItem=Parent Item:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Edit Note
itemTypes.note=Note
itemTypes.annotation=Annotation
itemTypes.attachment=Attachment
itemTypes.book=Book
itemTypes.bookSection=Book Section
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=No PDFs found
findPDF.noPDFFound=No PDF found
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Full Text
attachment.acceptedVersion=Accepted Version
attachment.submittedVersion=Submitted Version
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
searchConditions.fulltextContent=Attachment Content
searchConditions.programmingLanguage=Programming Language
searchConditions.fileTypeID=Attachment File Type
searchConditions.annotation=Annotation
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexed
fulltext.indexState.unavailable=Unknown
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Export Notes
exportOptions.exportFileData=Export Files
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
charset.autoDetect=(auto detect)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
integration.exportDocument.description2=You should make a backup of the document before proceeding.
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Install Style
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "عرض في المكتبة">
<!ENTITY zotero.items.menu.attach.note "اضافة ملاحظة">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "اضافة مرفق">
<!ENTITY zotero.items.menu.attach.link.uri "ربط الرابط بالمسار...">
<!ENTITY zotero.items.menu.attach.file "إرفاق نسخة من ملف مخزن...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "تخزين نسخة من ملف...">
<!ENTITY zotero.toolbar.attachment.weblink "حفظ رابط للصفحة الحالية">
<!ENTITY zotero.toolbar.attachment.snapshot "اخذ لقطة من الصفحة الحالية">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "لاتوجد اوسمة لعرضها">
<!ENTITY zotero.tagSelector.loadingTags "تحميل الوسوم...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "ارفاق رابط لمحتوى على الانترنت">
<!ENTITY zotero.attachLink.label.link "رابط:">
<!ENTITY zotero.attachLink.label.title "عنوان:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=صف المشكلة باختصار:
general.nMegabytes=%S MB
general.item=عنصر
general.pdf=بي دي إف
general.back=Back
general.operationInProgress=عملية زوتيرو حالياً تحت الإجراء.
general.operationInProgress.waitUntilFinished=يرجى الانتظار حتى انتهائها.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
app.standalone=النسخة المستقلة من زوتيرو
app.firefox=نسخة زوتيرو لمتصفح الفايرفوكس
startupError=هناك خطأ عند بدء تشغيل زوتيرو.
startupError=There was an error starting %S.
startupError.databaseInUse=قاعدة بيانات زوتيرو لديك قيد الاستخدام. يمكن فتح نسخة واحدة فقط من زوتيرو تستخدم نفس قاعدة بيانات في وقت واحد حاليا.
startupError.closeStandalone=إذا كانت النسخة المستقلة من زوتيرو مفتوحة، من فضلك قم بإغلاقها وإعادة تشغيل متصفح فايرفوكس.
startupError.closeFirefox=إذا كان متصفح فايرفوكس مع امتداد زوتيرو مفتوح، فمن فضلك قم بإغلاقه وإعادة تشغيل النسخة المستقلة من زوتيرو.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=تحويل لحقل ثنائي
pane.item.creator.moveToTop=تحريك لأعلى
pane.item.creator.moveUp=تحريك لأعلى
pane.item.creator.moveDown=تحريك لأسفل
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=ملاحظة بدون عنوان
pane.item.notes.delete.confirm=هل ترغب في حذف هذه الملاحظة؟
pane.item.notes.count.zero=لا توجد ملاحظات:
pane.item.notes.count.singular=%S ملاحظات:
pane.item.notes.count.plural=%S ملاحظة:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=عنوان جديد:
pane.item.attachments.rename.renameAssociatedFile=إعادة تسمية الملف المرتبط
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S ارتباطات:
pane.item.related.count.plural=%S ارتباط:
pane.item.parentItem=عنصر رئيسي:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=تحرير ملاحظة
itemTypes.note=ملاحظة
itemTypes.annotation=Annotation
itemTypes.attachment=مرفق
itemTypes.book=كتاب
itemTypes.bookSection=قسم في كتاب
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=بي دي إف (%S)
findPDF.noPDFsFound=لا يوجد ملفات بي دي إف
findPDF.noPDFFound=لم يتم العثور على بي دي إف
note.annotationsWithDate=Annotations (%S)
attachment.fullText=نص كامل
attachment.acceptedVersion=نسخة مقبولة
attachment.submittedVersion=نسخة مقدمة
@ -788,7 +796,8 @@ searchConditions.dateModified=تاريخ التعديل
searchConditions.fulltextContent=محتويات المرفق
searchConditions.programmingLanguage=لغة البرمجة
searchConditions.fileTypeID=نوع الملف المرفق
searchConditions.annotation=تعليق
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=مكشف
fulltext.indexState.unavailable=غير معروف
@ -797,6 +806,7 @@ fulltext.indexState.queued=في الانتظار
exportOptions.exportNotes=تصدير الملاحظات
exportOptions.exportFileData=تصدير الملفات
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=استخدم اختصار الدورية
charset.UTF8withoutBOM=يونيكود (UTF-8 بدون BOM)
charset.autoDetect=(التعرف التلقائي)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
integration.exportDocument.description2=You should make a backup of the document before proceeding.
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=ثبت نمط
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Показване в библиотеката">
<!ENTITY zotero.items.menu.attach.note "Добавяне на бележка">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Добавяне на прикачен файл">
<!ENTITY zotero.items.menu.attach.link.uri "Добавяне на връзка към URI…">
<!ENTITY zotero.items.menu.attach.file "Добавяне на съхранено копие на файл от твърдия диск...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Запазване на копие от файл">
<!ENTITY zotero.toolbar.attachment.weblink "Запазване на връзка към настоящата страница">
<!ENTITY zotero.toolbar.attachment.snapshot "Направи &quot;моментна снимка&quot; на настоящата страница">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Няма маркери, които да бъдат показани">
<!ENTITY zotero.tagSelector.loadingTags "Зареждане на маркери...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Прикачване на линк към URI">
<!ENTITY zotero.attachLink.label.link "Линк:">
<!ENTITY zotero.attachLink.label.title "Заглавие:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Опишете накратко проблема /In Eng
general.nMegabytes=%S МБ
general.item=Елемент
general.pdf=PDF
general.back=Back
general.operationInProgress=Операция на Зотеро е активна в момента.
general.operationInProgress.waitUntilFinished=Моля изчакайте докато приключи.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
app.standalone=Zotero Standalone
app.firefox=Zotero for Firefox
startupError=Появи се грешка при стартирането на Зотеро.
startupError=There was an error starting %S.
startupError.databaseInUse=Your Zotero database is currently in use. Only one instance of Zotero using the same database may be opened simultaneously at this time.
startupError.closeStandalone=If Zotero Standalone is open, please close it and restart Firefox.
startupError.closeFirefox=If Firefox with the Zotero extension is open, please close it and restart Zotero Standalone.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Превключва към две полета
pane.item.creator.moveToTop=Move to Top
pane.item.creator.moveUp=Move Up
pane.item.creator.moveDown=Move Down
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Бележка без име
pane.item.notes.delete.confirm=Сигурни ли сте, че искате да изтриете тази бележка?
pane.item.notes.count.zero=%S бележки:
pane.item.notes.count.singular=%S бележка:
pane.item.notes.count.plural=%S бележки:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=Ново заглавие:
pane.item.attachments.rename.renameAssociatedFile=Преименува приложеният файл
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S близък:
pane.item.related.count.plural=%S близки:
pane.item.parentItem=Parent Item:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Редактира бележка
itemTypes.note=Бележка
itemTypes.annotation=Annotation
itemTypes.attachment=Приложение
itemTypes.book=Книга
itemTypes.bookSection=Глава от книга
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=No PDFs found
findPDF.noPDFFound=No PDF found
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Full Text
attachment.acceptedVersion=Accepted Version
attachment.submittedVersion=Submitted Version
@ -788,7 +796,8 @@ searchConditions.dateModified=Променен на
searchConditions.fulltextContent=Съдържание на приложението
searchConditions.programmingLanguage=Програмен език
searchConditions.fileTypeID=Вид приложен файл
searchConditions.annotation=Анотация
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Индексиран
fulltext.indexState.unavailable=Неизвестен
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Износ на бележки
exportOptions.exportFileData=Износ на Файлове
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Уникод (UTF-8 без BOM)
charset.autoDetect=(автоматично откриване)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
integration.exportDocument.description2=You should make a backup of the document before proceeding.
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Install Style
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Diskouez el levraoueg:">
<!ENTITY zotero.items.menu.attach.note "Ouzhpennañ un notenn">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Ouzhpennañ ur pezh-stag">
<!ENTITY zotero.items.menu.attach.link.uri "Stagañ ul liamm betek un URI…">
<!ENTITY zotero.items.menu.attach.file "Stagañ un eilenn enrollet eus ur restr...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Stokañ un eilenn eus ar restr...">
<!ENTITY zotero.toolbar.attachment.weblink "Enrollañ al liamm d'ar bajenn a-vremañ">
<!ENTITY zotero.toolbar.attachment.snapshot "Kemer un dapadenn-skramm eus ar bajenn a-vremañ">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Baliz ebet da ziskouez">
<!ENTITY zotero.tagSelector.loadingTags "O kargañ ar balizoù...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Stagañ ul liamm betek un URI">
<!ENTITY zotero.attachLink.label.link "Liamm:">
<!ENTITY zotero.attachLink.label.title "Titl:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Deskrivit ar gudenn e berr gomzoù:
general.nMegabytes=%S Mo
general.item=Elfenn
general.pdf=PDF
general.back=Back
general.operationInProgress=Un oberiadenn a-berzh Zotero a zo war ober er mare-mañ.
general.operationInProgress.waitUntilFinished=Gortozit betek ma vo echuet.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Diskouez div vaezienn
pane.item.creator.moveToTop=Fiñval d'al laez
pane.item.creator.moveUp=War-grec'h
pane.item.creator.moveDown=War-draoñ
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Notenn hep-titl
pane.item.notes.delete.confirm=Ha fellout a ra deoc'h dilemel an notenn-mañ da vat?
pane.item.notes.count.zero=%S a notennoù:
pane.item.notes.count.singular=%S notenn:
pane.item.notes.count.plural=%S a notennoù:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Oc'h aozañ en ur prenestr nevez
pane.item.attachments.rename.title=Titl nevez:
pane.item.attachments.rename.renameAssociatedFile=Adenvel ar restr kevelet
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S kevreet:
pane.item.related.count.plural=%S kevreet:
pane.item.parentItem=Elfenn kar:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Aozañ an notenn
itemTypes.note=Notenn
itemTypes.annotation=Annotation
itemTypes.attachment=Pezh-stag
itemTypes.book=Levr
itemTypes.bookSection=Chabistr levr
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=PDF ebet kavet
findPDF.noPDFFound=PDF ebet kavet
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Testenn a-bezh
attachment.acceptedVersion=Stumm asantet
attachment.submittedVersion=Stumm kaset
@ -788,7 +796,8 @@ searchConditions.dateModified=Deiziad kemmañ
searchConditions.fulltextContent=Endalc'had ar pezh-stag
searchConditions.programmingLanguage=Langaj programiñ
searchConditions.fileTypeID=Doare restr-stag
searchConditions.annotation=Ennotadur
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indekset
fulltext.indexState.unavailable=Dianav
@ -797,6 +806,7 @@ fulltext.indexState.queued=War-c'hortoz
exportOptions.exportNotes=Ezporzhiañ notennoù
exportOptions.exportFileData=Ezporzhiañ restroù
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Implijout berradurioù ar c'helaouennoù
charset.UTF8withoutBOM=Unikod (UTF-8 hep BOM)
charset.autoDetect=(em-ziguzhiñ)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prientiñ an arroudennoù evit an dreuzkasadenn
integration.exportDocument.description1=Zotero a emdroio arroudennoù an teuliad en ur furmad hag a c'hall bezañ treuzkaset en un doare sur davet un treter testennoù all.
integration.exportDocument.description2=Rankout a rafec'h ober un enrolladenn eus an teuliad a-raok kenderc'hel.
integration.importInstructions=An arroudennoù Zotero en teuliad-mañ a zo bet emdroet en ur furmad a c'hall bezañ treuzkaset en un doare sur kenetre an tretourioù testennoù. Digorit an teuliad-mañ e-barzh an treter testennoù kemeret e kont ha pouezit war Freskaat e-barzh al lugant Zotero evit kenderc'hel da labourat gant an arroudennoù.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Staliañ ar stil
styles.install.unexpectedError=Ur fazi dic'hortoz a zo c'hoarvezet en ur staliañ "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Mostra a la biblioteca">
<!ENTITY zotero.items.menu.attach.note "Afegeix una nota">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Afegeix un adjunt">
<!ENTITY zotero.items.menu.attach.link.uri "Adjunta un enllaç a l'URI...">
<!ENTITY zotero.items.menu.attach.file "Adjunta la còpia emmagatzemada del fitxer...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Emmagatzema una còpia del fitxer...">
<!ENTITY zotero.toolbar.attachment.weblink "Desa l'enllaç a la pàgina actual">
<!ENTITY zotero.toolbar.attachment.snapshot "Captura la pàgina actual">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "No hi ha etiquetes per mostrar">
<!ENTITY zotero.tagSelector.loadingTags "S'estan carregant les etiquetes…">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Adjunta l'enllaç a l'URI">
<!ENTITY zotero.attachLink.label.link "Enllaç:">
<!ENTITY zotero.attachLink.label.title "Títol:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Descriviu breument el problema:
general.nMegabytes=%S MB
general.item=Element
general.pdf=PDF
general.back=Back
general.operationInProgress=Una operació del Zotero està actualment en curs.
general.operationInProgress.waitUntilFinished=Espereu fins que hagi acabat.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Mostra el directori a
app.standalone=Zotero Independent
app.firefox=Zotero per Firefox
startupError=Hi ha hagut un error en iniciar Zotero.
startupError=There was an error starting %S.
startupError.databaseInUse=La teva base de dades de Zotero està actualment en ús. En aquest moment i simultàniament només pot estar oberta una instància de Zotero amb la mateixa base de dades.
startupError.closeStandalone=Si el Zotero Independent és obert, tanqueu-lo i reinicieu Firefox.
startupError.closeFirefox=Si Firefox amb l'extensió Zotero és obert, tanqueu i reinicieu Zotero Independent.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Canvia a dos camps
pane.item.creator.moveToTop=Mou a dalt
pane.item.creator.moveUp=Mou amunt
pane.item.creator.moveDown=Mou avall
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Nota sense títol
pane.item.notes.delete.confirm=Segur que voleu eliminar aquesta nota?
pane.item.notes.count.zero=Cap nota:
pane.item.notes.count.singular=%S nota:
pane.item.notes.count.plural=%S notes:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Edició en finestra separada
pane.item.attachments.rename.title=Nou títol:
pane.item.attachments.rename.renameAssociatedFile=Canvia el nom del fitxer associat
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S element relacionat:
pane.item.related.count.plural=%S elements relacionats:
pane.item.parentItem=Element ascendent
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Edita la nota
itemTypes.note=Nota
itemTypes.annotation=Annotation
itemTypes.attachment=Fitxer adjunt
itemTypes.book=Llibre
itemTypes.bookSection=Capítol d'un llibre
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=No s'ha trobat cap PDF
findPDF.noPDFFound=No s'ha trobat cap PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Text complet
attachment.acceptedVersion=Versió acceptada
attachment.submittedVersion=Versió enviada
@ -788,7 +796,8 @@ searchConditions.dateModified=Data de modificació
searchConditions.fulltextContent=Contingut de l'arxiu adjunt
searchConditions.programmingLanguage=Llengua de programació
searchConditions.fileTypeID=Tipus de fitxer adjunt
searchConditions.annotation=Anotació
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexat
fulltext.indexState.unavailable=Desconegut
@ -797,6 +806,7 @@ fulltext.indexState.queued=En cua
exportOptions.exportNotes=Exporta les notes
exportOptions.exportFileData=Exporta els fitxers adjunts
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Utilitza l'abreviació de la publicació
charset.UTF8withoutBOM=Unicode (UTF-8 sense BOM)
charset.autoDetect=(Detecció automàtica)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepara les cites per transferir
integration.exportDocument.description1=El Zotero convertirà les cites del document a un format que pot transferir-se de forma segura a un altre processador de text acceptat.
integration.exportDocument.description2=Caldria que féssiu una còpia de seguretat del document abans de procedir.
integration.importInstructions=Les cites de Zotero d'aquest document s'han convertit a un format que pot transferir-se de forma segura entre diferents processadors de text. Obriu el document en un processador de text que ho accepti i premeu Refresca des del connector del Zotero per a continuar treballant amb les cites.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Instal·la l'estil
styles.install.unexpectedError=S'ha produït un error inesperat durant la instal·lació de "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Ukázat v knihovně">
<!ENTITY zotero.items.menu.attach.note "Přidat poznámku">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Přidat přílohu">
<!ENTITY zotero.items.menu.attach.link.uri "Připojit odkaz na URI..,">
<!ENTITY zotero.items.menu.attach.file "Přiložit uloženou kopii souboru...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Uložit kopii souboru...">
<!ENTITY zotero.toolbar.attachment.weblink "Uložit odkaz na aktuální stránku">
<!ENTITY zotero.toolbar.attachment.snapshot "Vytvořit snímek aktuální stránky">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Žádné štítky k zobrazení">
<!ENTITY zotero.tagSelector.loadingTags "Nahrávám štítky...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Připojit odkaz na URI">
<!ENTITY zotero.attachLink.label.link "Odkaz:">
<!ENTITY zotero.attachLink.label.title "Název:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Stručně popište problém:
general.nMegabytes=%S MB
general.item=Položka
general.pdf=PDF
general.back=Back
general.operationInProgress=Právě probíhá operace se Zoterem.
general.operationInProgress.waitUntilFinished=Počkejte prosím, dokud neskončí.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Zobrazit současný a
app.standalone=Samostatné Zotero
app.firefox=Zotero pro Firefox
startupError=Při spouštění aplikace Zotero nastala chyba.
startupError=There was an error starting %S.
startupError.databaseInUse=Vaše databáze Zotera je právě používána. V jednom okamžiku je možné mít otevřenu pouze jednu instanci Zotera používající stejnou databázi.
startupError.closeStandalone=Pokud je otevřené Samostatné Zotero, zavřete ho prosím a restartujte Firefox.
startupError.closeFirefox=Pokud je otevřen Firefox s přídavkem Zotero, zavřete ho prosím a restartujte Firefox.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Přepnout na více polí
pane.item.creator.moveToTop=Přesunout nahoru
pane.item.creator.moveUp=Posunout nahoru
pane.item.creator.moveDown=Posunout dolů
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Nepojmenovaná poznámka
pane.item.notes.delete.confirm=Jste si jistý, že chcete smazat tuto poznámku?
pane.item.notes.count.zero=%S poznámek:
pane.item.notes.count.singular=%S poznámka:
pane.item.notes.count.plural=%S poznámek:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Editace v samostatném okně
pane.item.attachments.rename.title=Nový název:
pane.item.attachments.rename.renameAssociatedFile=Přejmenovat asociovaný soubor
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S související:
pane.item.related.count.plural=%S souvisejících:
pane.item.parentItem=Rodičovská položka:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Upravit poznámku
itemTypes.note=Poznámka
itemTypes.annotation=Annotation
itemTypes.attachment=Příloha
itemTypes.book=Kniha
itemTypes.bookSection=Kapitola knihy
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Nenalezena žádná PDF
findPDF.noPDFFound=Nenalezeno žádné PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Plný text
attachment.acceptedVersion=Přijatá verze
attachment.submittedVersion=Odeslaná verze
@ -788,7 +796,8 @@ searchConditions.dateModified=Datum změny
searchConditions.fulltextContent=Obsah přílohy
searchConditions.programmingLanguage=Programovací jazyk
searchConditions.fileTypeID=Typ souboru přílohy
searchConditions.annotation=Anotace
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexováno
fulltext.indexState.unavailable=Neznámé
@ -797,6 +806,7 @@ fulltext.indexState.queued=Ve frontě
exportOptions.exportNotes=Exportovat poznámky
exportOptions.exportFileData=Exportovat soubory
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Použít zkrácený název časopisu
charset.UTF8withoutBOM=Unicode (UTF-8 bez BOM)
charset.autoDetect=(automaticky detekovat)
@ -930,6 +940,7 @@ integration.exportDocument.title=Připravit citace na přesun
integration.exportDocument.description1=Zotero převede citace v dokumentu do formátu, který může být bezpečně přenesen do jiného podporovaného textového editoru.
integration.exportDocument.description2=Než budete pokračovat, je vhodné vytvořit zálohu dokumentu.
integration.importInstructions=Zotero citace v tomto dokumentu byly převedeny do formátu, v němž mohou být bezpečně přeneseny mezi textovými editory. Abyste mohli pokračovat v práci s citacemi, otevřete tento dokument v podporovaném textovém procesoru a v doplňku Zotero stiskněte Obnovit.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=instalovat Styl
styles.install.unexpectedError=Při instalaci "%1$S" došlo k neočekávané chybě

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Vis i bibliotek">
<!ENTITY zotero.items.menu.attach.note "Tilføj note">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Vedhæft">
<!ENTITY zotero.items.menu.attach.link.uri "Vedhæft link til URI...">
<!ENTITY zotero.items.menu.attach.file "Vedhæft en gemt kopi af ...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Gem kopi af filen...">
<!ENTITY zotero.toolbar.attachment.weblink "Gem link til den aktuelle side">
<!ENTITY zotero.toolbar.attachment.snapshot "Tag kopi af den aktuelle side">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Der er ingen mærker at vise">
<!ENTITY zotero.tagSelector.loadingTags "Indlæser mærker...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Tilføj link til URI">
<!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Titel:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Beskriv kort problemet:
general.nMegabytes=%S MB
general.item=Element
general.pdf=PDF
general.back=Back
general.operationInProgress=En handling i Zotero er ved at blive udført.
general.operationInProgress.waitUntilFinished=Vent venligst til den er færdig.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Vis nuværende mappe
app.standalone=Zotero Standalone
app.firefox=Zotero til Firefox
startupError=Der opstod en fejl under opstarten af Zotero.
startupError=There was an error starting %S.
startupError.databaseInUse=Din Zotero-database er allerede i brug. Det er ikke muligt at lade flere kopier af Zotero bruge databasen samtidig.
startupError.closeStandalone=Hvis Zotero Standalone er aktiv, så luk den ned og genstart Firefox.
startupError.closeFirefox=Hvis Zotero er aktiv i Firefox, så luk Zotero ned der og genstart Zotero Standalone.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Skift til delt navnefelt
pane.item.creator.moveToTop=Flyt øverst
pane.item.creator.moveUp=Flyt op
pane.item.creator.moveDown=Flyt ned
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Note uden titel
pane.item.notes.delete.confirm=Er du sikker på, du vil slette denne note?
pane.item.notes.count.zero=%S noter:
pane.item.notes.count.singular=%S note:
pane.item.notes.count.plural=%S noter:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=Ny titel:
pane.item.attachments.rename.renameAssociatedFile=Omdøb en tilhørende fil
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S relateret:
pane.item.related.count.plural=%S relaterede:
pane.item.parentItem=Overordnet element:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Redigér note
itemTypes.note=Note
itemTypes.annotation=Annotation
itemTypes.attachment=Vedhæftning
itemTypes.book=Bog
itemTypes.bookSection=Bidrag til bog
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Fandt ingen PDF'er
findPDF.noPDFFound=Fandt ingen PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Fuldtekst
attachment.acceptedVersion=Antaget version
attachment.submittedVersion=Indsendt version
@ -788,7 +796,8 @@ searchConditions.dateModified=Ændringsdato
searchConditions.fulltextContent=Indhold i Vedhæftning
searchConditions.programmingLanguage=Programmeringssprog
searchConditions.fileTypeID=Filtype (vedhæftn.)
searchConditions.annotation=Annotering
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indekseret
fulltext.indexState.unavailable=Ukendt
@ -797,6 +806,7 @@ fulltext.indexState.queued=Sat i kø
exportOptions.exportNotes=Eksportér noter
exportOptions.exportFileData=Eksportér filer
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Anvend tidsskriftforkortelse
charset.UTF8withoutBOM=Unicode (UTF-8 uden BOM)
charset.autoDetect=(auto-genkend.)
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
integration.exportDocument.description2=You should make a backup of the document before proceeding.
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Installér bibliografisk format
styles.install.unexpectedError=Der opstod en uventet fejl under forsøget på at installere "%1$S"

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "In Bibliothek anzeigen">
<!ENTITY zotero.items.menu.attach.note "Notiz hinzufügen">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Anhang hinzufügen">
<!ENTITY zotero.items.menu.attach.link.uri "Link zu URI hinzufügen...">
<!ENTITY zotero.items.menu.attach.file "Gespeicherte Kopie der Datei anhängen...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Kopie einer Datei speichern...">
<!ENTITY zotero.toolbar.attachment.weblink "Link zur aktuellen Seite speichern">
<!ENTITY zotero.toolbar.attachment.snapshot "Schnappschuss von aktueller Seite machen">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Keine Tags vorhanden">
<!ENTITY zotero.tagSelector.loadingTags "Lade Tags...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Link zu einer URI anhängen">
<!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Titel:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Beschreiben Sie kurz das Problem:
general.nMegabytes=%S MB
general.item=Eintrag
general.pdf=PDF
general.back=Back
general.operationInProgress=Zotero ist beschäftigt.
general.operationInProgress.waitUntilFinished=Bitte warten Sie, bis der Vorgang abgeschlossen ist.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Aktuelles Verzeichnis
app.standalone=Zotero Standalone
app.firefox=Zotero für Firefox
startupError=Es gab einen Fehler beim Start von Zotero.
startupError=There was an error starting %S.
startupError.databaseInUse=Ihre Zotero-Datenbank wird bereits benutzt. Sie können nur je eine Instanz von Zotero mit derselben Datenbank gleichzeitig geöffnet haben.
startupError.closeStandalone=Wenn Sie Zotero Standalone geöffnet haben, bitte schließen Sie die Anwendung und starten Sie Firefox neu.
startupError.closeFirefox=Wenn Firefox mit der Zotero-Erweiterung offen ist, schließen Sie bitte Firefox und starten Sie Zotero Standalone neu.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Zu zwei Feldern wechseln
pane.item.creator.moveToTop=An den Anfang verschieben
pane.item.creator.moveUp=Nach oben verschieben
pane.item.creator.moveDown=Nach unten verschieben
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Notiz ohne Titel
pane.item.notes.delete.confirm=Sind Sie sicher, dass Sie diese Notiz löschen möchten?
pane.item.notes.count.zero=%S Notizen:
pane.item.notes.count.singular=%S Notiz:
pane.item.notes.count.plural=%S Notizen:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=In einem neuen Fenster bearbeiten
pane.item.attachments.rename.title=Neuer Titel:
pane.item.attachments.rename.renameAssociatedFile=Zugehörige Datei umbenennen
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S verwandter Eintrag:
pane.item.related.count.plural=%S verwandte Einträge:
pane.item.parentItem=Übergeordneter Eintrag:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Notiz bearbeiten
itemTypes.note=Notiz
itemTypes.annotation=Annotation
itemTypes.attachment=Anhang
itemTypes.book=Buch
itemTypes.bookSection=Buchteil
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Keine PDFs gefunden
findPDF.noPDFFound=Kein PDF gefunden
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Volltext
attachment.acceptedVersion=Akzeptierte Version
attachment.submittedVersion=Eingereichte Version
@ -788,7 +796,8 @@ searchConditions.dateModified=verändert am
searchConditions.fulltextContent=Inhalt des Anhangs
searchConditions.programmingLanguage=Programmiersprache
searchConditions.fileTypeID=Dateityp des Anhangs
searchConditions.annotation=Anmerkung
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indiziert
fulltext.indexState.unavailable=Unbekannt
@ -797,6 +806,7 @@ fulltext.indexState.queued=in Warteschlange
exportOptions.exportNotes=Notizen exportieren
exportOptions.exportFileData=Dateien exportieren
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Abgekürzte Zeitschriftentitel verwenden
charset.UTF8withoutBOM=Unicode (UTF-8 ohne BOM)
charset.autoDetect=(automatisch erkennen)
@ -930,6 +940,7 @@ integration.exportDocument.title=Zitationen zum Transfer vorbereiten
integration.exportDocument.description1=Zotero wird die Zitationen in diesem Dokument in ein Format konvertieren, in dem Sie es in einer anderen von Zotero unterstützten Textverarbeitung öffnen können
integration.exportDocument.description2=Sie sollten ein Backup des Dokuments erstellen, bevor Sie fortfahren.
integration.importInstructions=Die Zotero Zitationen in diesem Dokument sind konvertiert und können in von Zotero unterstützten Textverarbeitungsprogrammen genutzt werden. Öffnen Sie das Dokument und drücken Sie Refresh im Zotero-Plugin, um es zu importieren.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Zitierstil installieren
styles.install.unexpectedError=Bei der Installation von "%1$S" trat ein unerwarteter Fehler auf

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Εμφάνιση στην Βιβλιοθήκη">
<!ENTITY zotero.items.menu.attach.note "Προσθήκη σημείωσης">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Προσθήκη προσαρτήματος">
<!ENTITY zotero.items.menu.attach.link.uri "Προσάρτηση Συνδέσμου προς το URI...">
<!ENTITY zotero.items.menu.attach.file "Προσκόληση αποθηκευμένου αντιγράφου αρχείου...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Αποθήκευση αντιγράφου του αρχείου...">
<!ENTITY zotero.toolbar.attachment.weblink "Αποθήκευση Συνδέσμου στην Τρέχουσα Σελίδα">
<!ENTITY zotero.toolbar.attachment.snapshot "Λήψη Στιγμιότυπου της Τρέχουσας Σελίδας">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Δεν υπάρχουν ετικέτες προς εμφάνιση">
<!ENTITY zotero.tagSelector.loadingTags "Φόρτωση ετικετών...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Προσάρτηση συνδέσμου στο URI">
<!ENTITY zotero.attachLink.label.link "Σύνδεσμος:">
<!ENTITY zotero.attachLink.label.title "Τίτλος:">
<!ENTITY zotero.context.addChildNote "Add Item Note">
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">

View file

@ -76,6 +76,7 @@ general.describeProblem=Περιγράψτε συνοπτικά το πρόβλ
general.nMegabytes=%S MB
general.item=Είδος
general.pdf=PDF
general.back=Back
general.operationInProgress=Αυτή τη στιγμή εκτελείται κάποια λειτουργία Zotero.
general.operationInProgress.waitUntilFinished=Παρακαλώ περιμένετε έως ότου ολοκληρωθεί.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Μετάβαση σε δύο πεδία
pane.item.creator.moveToTop=Μετακίνηση προς τα επάνω
pane.item.creator.moveUp=Μετακίνηση Επάνω
pane.item.creator.moveDown=Μετακίνηση Κάτω
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Σημείωση χωρίς τίτλο
pane.item.notes.delete.confirm=Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη σημείωση;
pane.item.notes.count.zero=%S σημειώσεις:
pane.item.notes.count.singular=%S σημείωση:
pane.item.notes.count.plural=%S σημειώσεις:
pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.editingInWindow=Επεξεργασία σε ξεχωριστό παράθυρο
pane.item.attachments.rename.title=Νέος τίτλος:
pane.item.attachments.rename.renameAssociatedFile=Μετονομασία συσχετισμένου αρχείου
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S σχετικό:
pane.item.related.count.plural=%S σχετικό:
pane.item.parentItem=Γονικό Στοιχείο:
pane.context.noParent=No parent item
pane.context.itemNotes=Item Notes
pane.context.allNotes=All Notes
pane.context.noNotes=No notes
noteEditor.editNote=Επεξεργασία Σημείωσης
itemTypes.note=Σημείωση
itemTypes.annotation=Annotation
itemTypes.attachment=Συνημμένο
itemTypes.book=Βιβλίο
itemTypes.bookSection=Ενότητα Βιβλίου
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Δεν βρέθηκαν αρχεία PDF
findPDF.noPDFFound=Δεν βρέθηκε PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Πλήρες Κείμενο
attachment.acceptedVersion=Αποδεκτή Έκδοση
attachment.submittedVersion=Υποβληθείσα Έκδοση
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
searchConditions.fulltextContent=Attachment Content
searchConditions.programmingLanguage=Programming Language
searchConditions.fileTypeID=Attachment File Type
searchConditions.annotation=Annotation
searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexed
fulltext.indexState.unavailable=Unknown
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Export Notes
exportOptions.exportFileData=Export Files
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
charset.autoDetect=(auto detect)
@ -930,6 +940,7 @@ integration.exportDocument.title=Προετοιμάστε τις παραπομ
integration.exportDocument.description1=Το Zotero θα μετατρέψει τις παραπομπές στο έγγραφο σε μορφή που μπορεί να μεταφερθεί με ασφάλεια σε άλλο υποστηριζόμενο επεξεργαστή κειμένου.
integration.exportDocument.description2=Πριν προχωρήσετε, θα πρέπει να δημιουργήσετε αντίγραφο ασφαλείας του εγγράφου.
integration.importInstructions=Οι αναφορές Zotero σε αυτό το έγγραφο έχουν μετατραπεί σε μορφή που μπορεί να μεταφερθεί με ασφάλεια μεταξύ επεξεργαστών κειμένου. Ανοίξτε αυτό το έγγραφο σε υποστηριζόμενο επεξεργαστή κειμένου και πατήστε Ανανέωση -Refresh- στο πρόσθετο Zotero για να συνεχίσετε να εργάζεστε με τις παραπομπές.
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
styles.install.title=Εγκατάσταση Στυλ
styles.install.unexpectedError=Παρουσιάστηκε μη αναμενόμενο σφάλμα κατά την εγκατάσταση του "%1$S"

Some files were not shown because too many files have changed in this diff Show more