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"] [submodule "resource/SingleFile"]
path = resource/SingleFile path = resource/SingleFile
url = https://github.com/gildas-lormeau/SingleFile.git 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 */ /* Use standard tab appearance for item pane tabs */
#zotero-pane #zotero-view-tabbox > tabs > tab { .zotero-view-tabbox > tabs > tab {
-moz-appearance: tab; -moz-appearance: tab;
} }
/* Active tab label color in item pane and elsewhere */ /* 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), #zotero-prefs tab[visuallyselected="true"]:not(:-moz-window-inactive),
tabs > tab[visuallyselected="true"] hbox > .tab-text { tabs > tab[visuallyselected="true"] hbox > .tab-text {
color: black !important; 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, which is based on window corners, which are different on Big Sur */
#quick-format-dialog #zotero-icon {
margin-top: 2px;
}
#quick-format-iframe { #quick-format-iframe {
margin-top: 2px; margin-top: 2px;
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -76,3 +76,9 @@ tab {
background-color: transparent; background-color: transparent;
background-image: none; 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; background: transparent;
padding: 0; padding: 0;
-moz-appearance: none;
} }
#quick-format-search { .citation-dialog.search {
background: white; background: white;
padding: 2px 2px 2px 0; padding: 2px 2px 2px 0;
border: 1px solid rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.5);
@ -11,22 +12,20 @@
-moz-appearance: none; -moz-appearance: none;
} }
#quick-format-dialog { .citation-dialog.search:not([multiline="true"]) {
background: transparent;
-moz-appearance: none;
padding: 0;
}
#quick-format-search:not([multiline="true"]) {
height: 28px !important; 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%); background: -moz-linear-gradient(-90deg, rgb(243,123,119) 0, rgb(180,47,38) 50%, rgb(156,36,27) 50%);
padding: 10px; 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; -moz-border-radius: 15px;
border-radius: 15px; border-radius: 15px;
} }

View file

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

View file

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

View file

@ -652,7 +652,7 @@
for (var i=0; i<itemTypes.length; i++) { for (var i=0; i<itemTypes.length; i++) {
var name = itemTypes[i].name; 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); this.itemTypeMenu.appendItem(itemTypes[i].localized, itemTypes[i].id);
} }
} }

View file

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

View file

@ -1,99 +1,205 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- <!--
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA George Mason University, Fairfax, Virginia, USA
http://zotero.org http://zotero.org
This file is part of Zotero. This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify 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 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 the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Zotero is distributed in the hope that it will be useful, Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>. along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
--> -->
<bindings xmlns="http://www.mozilla.org/xbl" <bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl" xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="note-editor"> <binding id="note-editor">
<resources> <resources>
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/> <stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/> <stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
</resources> </resources>
<implementation> <implementation>
<!-- <!--
Public properties Public properties
--> -->
<field name="editable">false</field> <field name="editable">false</field>
<field name="saveOnEdit">false</field>
<field name="displayTags">false</field> <field name="displayTags">false</field>
<field name="displayRelated">false</field> <field name="displayRelated">false</field>
<field name="displayButton">false</field> <field name="displayButton">false</field>
<field name="hideLinksContainer"/>
<field name="buttonCaption"/> <field name="buttonCaption"/>
<field name="parentClickHandler"/> <field name="parentClickHandler"/>
<field name="keyDownHandler"/> <field name="keyDownHandler"/>
<field name="commandHandler"/> <field name="commandHandler"/>
<field name="clickHandler"/> <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 --> <!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field> <field name="_mode">"view"</field>
<property name="mode" onget="return this._mode;"> <property name="mode" onget="return this._mode;">
<setter> <setter>
<![CDATA[ <![CDATA[
// Duplicate default property settings here // Duplicate default property settings here
this.editable = false; this.editable = false;
this.saveOnEdit = false; this.displayTags = false;
this.displayTags = false; this.displayRelated = false;
this.displayRelated = false; this.displayButton = false;
this.displayButton = false;
switch (val) {
switch (val) { case 'view':
case 'view': case 'merge':
case 'merge': this.editable = false;
if (this.noteField) { break;
this.noteField.onInit(ed => ed.setMode('readonly'));
} case 'edit':
break; this.editable = true;
this.parentClickHandler = this.selectParent;
case 'edit': this.keyDownHandler = this.handleKeyDown;
if (this.noteField) { this.commandHandler = this.save;
this.noteField.onInit(ed => ed.setMode('design')); this.displayTags = true;
} this.displayRelated = true;
this.editable = true; break;
this.saveOnEdit = true;
this.parentClickHandler = this.selectParent; default:
this.keyDownHandler = this.handleKeyDown; throw ("Invalid mode '" + val + "' in noteeditor.xml");
this.commandHandler = this.save; }
this.displayTags = true;
this.displayRelated = true; this._mode = val;
break; document.getAnonymousNodes(this)[0].setAttribute('mode', val);
this._id('links-box').mode = val;
default: this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
throw ("Invalid mode '" + val + "' in noteeditor.xml"); this._id('links-box').refresh();
} ]]>
this._mode = val;
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
this._id('links-box').mode = val;
]]>
</setter> </setter>
</property> </property>
<field name="returnHandler"/>
<property name="returnHandler" onget="return this._returnHandler;">
<setter>
<![CDATA[
this._returnHandler = val;
]]>
</setter>
</property>
<field name="_parentItem"/> <field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;"> <property name="parentItem" onget="return this._parentItem;">
<setter> <setter>
@ -102,301 +208,156 @@
]]> ]]>
</setter> </setter>
</property> </property>
<field name="_mtime"/>
<field name="_item"/> <field name="_item"/>
<property name="item" onget="return this._item;"> <property name="item" onget="return this._item;">
<setter><![CDATA[ <setter><![CDATA[
this._item = val; return (async () => {
// TODO: use clientDateModified instead // `item` field can be set before the constructor is called
this._mtime = val.getField('dateModified'); // or noteditor is attached to dom (which happens in the
// merge dialog i.e.), therefore we wait for the initialization
var parentKey = this.item.parentKey; let n = 0;
if (parentKey) { while (!this._initialized && !this._destroyed) {
this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey); if (n >= 1000) {
} throw new Error('Waiting for noteeditor initialization failed');
}
this._id('links-box').item = this.item; await Zotero.Promise.delay(10);
n++;
this.refresh(); }
]]></setter>
// 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>
<property name="linksOnTop"> <property name="linksOnTop">
<setter> <setter>
<![CDATA[ <![CDATA[
if(val) { // if (val) {
var container = this._id('links-container'); // var container = this._id('links-container');
var parent = container.parentNode; // var parent = container.parentNode;
var sib = container.nextSibling; // var sib = container.nextSibling;
while (parent.firstChild !== container) { // while (parent.firstChild !== container) {
parent.insertBefore(parent.removeChild(parent.firstChild), sib); // 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> </setter>
</property> </property>
<property name="note" <property name="hideLinksContainer">
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item" <setter>
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/> <![CDATA[
<property name="ref" onget="return this._item" onset="this.item = val"/> this._hideLinksContainer = val;
this._id('links-container').hidden = val;
]]>
</setter>
</property>
<field name="collection"/> <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> <destructor>
<![CDATA[ <![CDATA[
Zotero.Notifier.unregisterObserver(this._notifierID); Zotero.Notifier.unregisterObserver(this._notifierID);
if (this._editorInstance) {
this._editorInstance.uninit();
}
this._destroyed = true;
this._initialized = false;
this._editorInstance = null;
]]> ]]>
</destructor> </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"> <method name="save">
<body><![CDATA[ <body><![CDATA[
return Zotero.spawn(function* () { return (async () => {
try {
if (this._mode == 'view') { })();
Zotero.debug("Not saving read-only note"); ]]></body>
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> </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"> <method name="focus">
<body> <body>
<![CDATA[ <![CDATA[
this._id('noteField').focus(); setTimeout(() => {
if (this._iframe && this._iframe.contentWindow) {
this._iframe.focus();
this._editorInstance.focus();
}
}, 500);
]]> ]]>
</body> </body>
</method> </method>
<method name="clearUndo">
<body>
<![CDATA[
this._id('noteField').clearUndo();
]]>
</body>
</method>
<method name="_id"> <method name="_id">
<parameter name="id"/> <parameter name="id"/>
<body> <body>
<![CDATA[ <![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0]; return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]> ]]>
</body> </body>
</method> </method>
</implementation> </implementation>
<content> <content>
<xul:vbox xbl:inherits="flex"> <xul:vbox xbl:inherits="flex" style="display: flex;flex-direction: column;flex-grow: 1;">
<xul:textbox id="noteField" type="styled" mode="note" <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;"
timeout="1000" flex="1" hidden="true"/> frameBorder="0" src="resource://zotero/note-editor/editor.html" type="content"/>
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
readonly="true" flex="1" hidden="true"/>
<xul:hbox id="links-container" hidden="true"> <xul:hbox id="links-container" hidden="true">
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/> <xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
</xul:hbox> </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> </xul:vbox>
</content> </content>
</binding> </binding>
<binding id="links-box"> <binding id="links-box">
<implementation> <implementation>
<field name="itemRef"/> <field name="itemRef"/>
<property name="item" onget="return this.itemRef;"> <property name="item" onget="return this.itemRef;">
<setter> <setter>
<![CDATA[ <![CDATA[
this.itemRef = val; this.itemRef = val;
this.id('tags').item = this.item; this.id('tags').item = this.item;
this.id('related').item = this.item; this.id('related').item = this.item;
this.refresh(); this.refresh();
@ -406,127 +367,131 @@
<property name="mode"> <property name="mode">
<setter> <setter>
<![CDATA[ <![CDATA[
this.id('related').mode = val; this.id('related').mode = val;
this.id('tags').mode = val; this.id('tags').mode = val;
]]> ]]>
</setter> </setter>
</property> </property>
<field name="_parentItem"/> <field name="_parentItem"/>
<property name="parentItem" onget="return this._parentItem;"> <property name="parentItem" onget="return this._parentItem;">
<setter> <setter>
<![CDATA[ <![CDATA[
this._parentItem = val; this._parentItem = val;
var parentText = this.id('parentText'); var parentText = this.id('parentText');
if (parentText.firstChild) { if (parentText.firstChild) {
parentText.removeChild(parentText.firstChild); parentText.removeChild(parentText.firstChild);
} }
if (this._parentItem && this.getAttribute('notitle') != '1') { if (this._parentItem && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined; this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem'); this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true))); parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
} }
]]> ]]>
</setter> </setter>
</property> </property>
<method name="tagsClick"> <method name="tagsClick">
<body><![CDATA[ <body><![CDATA[
this.id('tags').reload(); this.id('tags').reload();
var x = this.boxObject.screenX; var x = this.boxObject.screenX;
var y = this.boxObject.screenY; var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false); this.id('tagsPopup').openPopupAtScreen(x, y, false);
// If editable and no existing tags, open new empty row // If editable and no existing tags, open new empty row
var tagsBox = this.id('tags'); var tagsBox = this.id('tags');
if (tagsBox.mode == 'edit' && tagsBox.count == 0) { if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
this.id('tags').newTag(); this.id('tags').newTag();
} }
]]></body> ]]></body>
</method> </method>
<method name="refresh"> <method name="refresh">
<body><![CDATA[ <body><![CDATA[
this.updateTagsSummary(); this.updateTagsSummary();
this.updateRelatedSummary(); this.updateRelatedSummary();
]]></body> ]]></body>
</method> </method>
<method name="updateTagsSummary"> <method name="updateTagsSummary">
<body><![CDATA[ <body><![CDATA[
var v = this.id('tags').summary; var v = this.id('tags').summary;
if (!v || v == "") { if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]"; v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
} }
this.id('tagsLabel').value = Zotero.getString('itemFields.tags') this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon'); + Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v; this.id('tagsClick').value = v;
]]></body> ]]></body>
</method> </method>
<method name="relatedClick"> <method name="relatedClick">
<body><![CDATA[ <body><![CDATA[
var relatedList = this.item.relatedItems; var relatedList = this.item.relatedItems;
if (relatedList.length > 0) { if (relatedList.length > 0) {
var x = this.boxObject.screenX; var x = this.boxObject.screenX;
var y = this.boxObject.screenY; var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false); this.id('relatedPopup').openPopupAtScreen(x, y, false);
} }
else { else {
this.id('related').add(); this.id('related').add();
} }
]]></body> ]]></body>
</method> </method>
<method name="updateRelatedSummary"> <method name="updateRelatedSummary">
<body><![CDATA[ <body><![CDATA[
var v = this.id('related').summary; var v = this.id('related').summary;
if (!v || v == "") { if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]"; v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
} }
this.id('relatedLabel').value = Zotero.getString('itemFields.related') this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon'); + Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v; this.id('relatedClick').value = v;
]]></body> ]]></body>
</method> </method>
<method name="parentClick"> <method name="parentClick">
<body> <body>
<![CDATA[ <![CDATA[
if (!this.item || !this.item.id) { if (!this.item || !this.item.id) {
return; 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; if (document.getElementById('zotero-pane')) {
} var zp = ZoteroPane;
}
Zotero.spawn(function* () { else {
var parentID = this.item.parentID; var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
yield zp.clearQuicksearch(); .getService(Components.interfaces.nsIWindowMediator);
zp.selectItem(parentID);
}, this); 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> </body>
</method> </method>
<method name="id"> <method name="id">
<parameter name="id"/> <parameter name="id"/>
<body> <body>
<![CDATA[ <![CDATA[
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0]; return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
]]> ]]>
</body> </body>
</method> </method>
@ -541,15 +506,18 @@
<xul:rows> <xul:rows>
<xul:row id="parent-row" hidden="true"> <xul:row id="parent-row" hidden="true">
<xul:label id="parentLabel"/> <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:row> <xul:row>
<xul:label id="relatedLabel"/> <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:row> <xul:row>
<xul:label id="tagsLabel"/> <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:row>
</xul:rows> </xul:rows>
</xul:grid> </xul:grid>
@ -562,17 +530,17 @@
seems to get triggered by these events for reasons that are less than 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 clear) so that we can manually refresh the popup if it's open after
autocomplete is used to prevent it from becoming unresponsive autocomplete is used to prevent it from becoming unresponsive
Note: Code in tagsbox.xml is dependent on the DOM path between the 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 tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
--> -->
<xul:menupopup id="tagsPopup" ignorekeys="true" <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'); }" 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'); }"> onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
<xul:tagsbox id="tags" flex="1" mode="edit"/> <xul:tagsbox id="tags" flex="1" mode="edit" style="display: flex"/>
</xul:menupopup> </xul:menupopup>
</xul:popupset> </xul:popupset>
</xul:vbox> </xul:vbox>
</content> </content>
</binding> </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('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png")
i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.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 // Class to provide options for export
var Zotero_File_Interface_Export = new function() { var Zotero_File_Interface_Export = new function() {
this.init = init;
this.updateOptions = updateOptions;
this.accept = accept; this.accept = accept;
this.cancel = cancel; this.cancel = cancel;
@ -44,7 +42,7 @@ var Zotero_File_Interface_Export = new function() {
/* /*
* add options to export * add options to export
*/ */
function init() { this.init = function () {
// Set font size from pref // Set font size from pref
var sbc = document.getElementById('zotero-export-options-container'); var sbc = document.getElementById('zotero-export-options-container');
Zotero.setFontSize(sbc); Zotero.setFontSize(sbc);
@ -82,10 +80,25 @@ var Zotero_File_Interface_Export = new function() {
// right now, option interface supports only boolean values, which // right now, option interface supports only boolean values, which
// it interprets as checkboxes // it interprets as checkboxes
if(typeof(translators[i].displayOptions[option]) == "boolean") { 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("id", OPTION_PREFIX+option);
checkbox.setAttribute("label", optionLabel); checkbox.setAttribute("label", optionLabel);
optionsBox.insertBefore(checkbox, charsetBox); 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; 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); _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 * update translator-specific options
*/ */
function updateOptions(optionString) { this.updateOptions = function (optionString) {
// get selected translator // get selected translator
var index = document.getElementById("format-menu").selectedIndex; var index = document.getElementById("format-menu").selectedIndex;
var translatorOptions = window.arguments[0].translators[index].displayOptions; var translatorOptions = window.arguments[0].translators[index].displayOptions;
@ -133,7 +146,9 @@ var Zotero_File_Interface_Export = new function() {
var node = optionsBox.childNodes[i]; var node = optionsBox.childNodes[i];
// skip non-options // skip non-options
if(node.id.length <= OPTION_PREFIX.length 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; continue;
} }
@ -161,6 +176,10 @@ var Zotero_File_Interface_Export = new function() {
} }
} }
this.updateAnnotationsCheckbox(
(options && options.includeAnnotations) ? options.includeAnnotations : false
);
// handle charset popup // handle charset popup
if(_charsets && translatorOptions && translatorOptions.exportCharset) { if(_charsets && translatorOptions && translatorOptions.exportCharset) {
optionsBox.hidden = undefined; optionsBox.hidden = undefined;
@ -181,6 +200,21 @@ var Zotero_File_Interface_Export = new function() {
window.sizeToContent(); 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 * 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 // save options
var optionString = JSON.stringify(displayOptions); var optionString = JSON.stringify(displayOptions);
Zotero.Prefs.set("export.translatorSettings", optionString); Zotero.Prefs.set("export.translatorSettings", optionString);

View file

@ -1,5 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
<!DOCTYPE window [ <!DOCTYPE window [
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd" > <!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd" >
%zoteroDTD; %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); io.onLoad(_onProgress);
} }
if (io.isNote) {
document.documentElement.classList.add('note-dialog');
}
// Only hide chrome on Windows or Mac // Only hide chrome on Windows or Mac
if(Zotero.isMac) { if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true); document.documentElement.setAttribute("drawintitlebar", true);
@ -45,12 +49,12 @@ var Zotero_ProgressBar = new function () {
document.documentElement.setAttribute("hidechrome", true); 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 // With fx60 and drawintitlebar=true Firefox calculates the minHeight
// as titlebar+maincontent, so we have hack around that here. // as titlebar+maincontent, so we have hack around that here.
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) { if (Zotero.isMac) {
document.getElementById("quick-format-entry").style.marginBottom = "-22px"; document.querySelector(".citation-dialog.entry").style.marginBottom = "-28px";
} }
} }
@ -75,7 +79,7 @@ var Zotero_ProgressBar = new function () {
* Called when progress changes * Called when progress changes
*/ */
function _onProgress(percent) { function _onProgress(percent) {
var meter = document.getElementById("quick-format-progress-meter"); var meter = document.querySelector(".citation-dialog.progress-meter");
if(percent === null) { if(percent === null) {
meter.mode = "undetermined"; meter.mode = "undetermined";
} else { } else {

View file

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

View file

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

View file

@ -33,6 +33,7 @@
<window <window
id="quick-format-dialog" id="quick-format-dialog"
class="citation-dialog"
orient="vertical" orient="vertical"
title="&zotero.integration.quickFormatDialog.title;" title="&zotero.integration.quickFormatDialog.title;"
xmlns:html="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml"
@ -44,40 +45,41 @@
<script src="../include.js"/> <script src="../include.js"/>
<script src="windowDraggingUtils.js" type="text/javascript"/> <script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="quickFormat.js" type="text/javascript"/> <script src="quickFormat.js" type="text/javascript"/>
<box orient="horizontal" id="quick-format-entry"> <box orient="horizontal" class="citation-dialog entry">
<deck id="quick-format-deck" selectedIndex="0" flex="1"> <deck class="citation-dialog deck" selectedIndex="0" flex="1">
<hbox id="quick-format-search" flex="1" align="start"> <hbox class="citation-dialog search" flex="1" align="start">
<hbox flex="1"> <hbox flex="1">
<toolbarbutton id="zotero-icon" type="menu"> <toolbarbutton id="zotero-icon" type="menu">
<menupopup> <menupopup>
<menuitem id="keep-sorted" label="&zotero.citation.keepSorted.label;" <menuitem id="keep-sorted" label="&zotero.citation.keepSorted.label;"
oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox" oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox"
hidden="true"/> hidden="true"/>
<menuitem id="show-editor" label="&zotero.integration.showEditor.label;" <menuitem id="show-editor" label="&zotero.integration.showEditor.label;"
oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox" oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox"
hidden="true"/> hidden="true"/>
<menuitem id="classic-view" label="&zotero.integration.classicView.label;" <menuitem id="classic-view" label="&zotero.integration.classicView.label;"
oncommand="Zotero_QuickFormat.onClassicViewCommand()"/> oncommand="Zotero_QuickFormat.onClassicViewCommand()"/>
</menupopup> </menupopup>
</toolbarbutton> </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"/> <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"
<vbox id="quick-format-spinner" style="visibility: hidden"> tabindex="1" flex="1"/>
<vbox class="citation-dialog spinner" style="visibility: hidden">
<image class="zotero-spinner-16"/> <image class="zotero-spinner-16"/>
</vbox> </vbox>
</hbox> </hbox>
</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> </deck>
</box> </box>
<panel id="quick-format-reference-panel" noautofocus="true" norestorefocus="true" <panel class="citation-dialog reference-panel" noautofocus="true" norestorefocus="true"
height="0" width="0"> height="0" width="0">
<richlistbox id="quick-format-reference-list" flex="1"/> <richlistbox class="citation-dialog reference-list" flex="1"/>
</panel> </panel>
<panel id="citation-properties" type="arrow" orient="vertical" <panel id="citation-properties" type="arrow" orient="vertical"
onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)" onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)"
onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)"> onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)">
<vbox flex="1"> <vbox flex="1">
<description id="citation-properties-title"/> <description id="citation-properties-title"/>
<hbox id="citation-properties-info"/> <hbox id="citation-properties-info"/>
@ -90,25 +92,25 @@
<rows> <rows>
<row align="center"> <row align="center">
<menulist id="locator-label" sizetopopup="none" <menulist id="locator-label" sizetopopup="none"
oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)"> oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)">
<menupopup id="locator-label-popup"/> <menupopup id="locator-label-popup"/>
</menulist> </menulist>
<textbox id="locator" flex="1" <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>
<row align="center"> <row align="center">
<label value="&zotero.citation.prefix.label;"/> <label value="&zotero.citation.prefix.label;"/>
<textbox class="citation-textbox" id="prefix" flex="1" <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>
<row align="center"> <row align="center">
<label value="&zotero.citation.suffix.label;"/> <label value="&zotero.citation.suffix.label;"/>
<textbox class="citation-textbox" id="suffix" flex="1" <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> </row>
<html:div> <html:div>
<html:input type="checkbox" id="suppress-author" <html:input type="checkbox" id="suppress-author"
onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/> onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/>
<html:label for="suppress-author"> <html:label for="suppress-author">
&zotero.citation.suppressAuthor.label; &zotero.citation.suppressAuthor.label;
</html:label> </html:label>
@ -119,6 +121,6 @@
<button id="citation-properties-library-link" onclick="Zotero_QuickFormat.showInLibrary()"/> <button id="citation-properties-library-link" onclick="Zotero_QuickFormat.showInLibrary()"/>
</vbox> </vbox>
</panel> </panel>
<zoteroguidancepanel id="quick-format-guidance" about="quickFormat" <zoteroguidancepanel class="citation-dialog guidance" about="quickFormat"
for="zotero-icon" x="26"/> for="zotero-icon" x="26"/>
</window> </window>

View file

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

View file

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

View file

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

View file

@ -21,6 +21,9 @@
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/> <key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
</keyset> </keyset>
<command id="cmd_close" oncommand="window.close();"/> <command id="cmd_close" oncommand="window.close();"/>
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="onError()"/> <deck id="zotero-note-editor-switcher" flex="1">
</window> <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"); 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(); ZoteroPane.init();
} }
catch (e) { catch (e) {

View file

@ -38,9 +38,12 @@ Zotero_Preferences.General = {
'zotero.preferences.launchNonNativeFiles', Zotero.appName 'zotero.preferences.launchNonNativeFiles', Zotero.appName
); );
} }
var menuitem = document.getElementById('fileHandler-internal');
menuitem.setAttribute('label', Zotero.appName);
this.updateAutoRenameFilesUI(); this.updateAutoRenameFilesUI();
this._updateFileHandlerUI(); this._updateFileHandlerUI();
this._updateZotero6BetaCheckbox();
}, },
updateAutoRenameFilesUI: function () { updateAutoRenameFilesUI: function () {
@ -88,6 +91,16 @@ Zotero_Preferences.General = {
var handler = Zotero.Prefs.get('fileHandler.pdf'); var handler = Zotero.Prefs.get('fileHandler.pdf');
var menulist = document.getElementById('fileHandler-pdf'); var menulist = document.getElementById('fileHandler-pdf');
var customMenuItem = document.getElementById('fileHandler-custom'); 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) { if (handler) {
let icon; let icon;
try { try {
@ -113,11 +126,12 @@ Zotero_Preferences.General = {
customMenuItem.className = ''; customMenuItem.className = '';
} }
customMenuItem.hidden = false; customMenuItem.hidden = false;
menulist.selectedIndex = 0; menulist.selectedIndex = 1;
} }
// System default
else { else {
customMenuItem.hidden = true; customMenuItem.hidden = true;
menulist.selectedIndex = 1; menulist.selectedIndex = 2;
} }
}, },
@ -126,5 +140,38 @@ Zotero_Preferences.General = {
throw new Error(`Unknown file type ${type}`); throw new Error(`Unknown file type ${type}`);
} }
return 'fileHandler.pdf'; 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-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
<preference id="pref-groups-copyChildLinks" name="extensions.zotero.groups.copyChildLinks" 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"/> <preference id="pref-groups-copyTags" name="extensions.zotero.groups.copyTags" type="bool"/>
</preferences> </preferences>
<groupbox id="zotero-prefpane-file-handling-groupbox"> <groupbox id="zotero-prefpane-file-handling-groupbox">
@ -67,6 +66,9 @@
<label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/> <label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/>
<menulist id="fileHandler-pdf" class="fileHandler-menu"> <menulist id="fileHandler-pdf" class="fileHandler-menu">
<menupopup> <menupopup>
<menuitem id="fileHandler-internal"
oncommand="Zotero_Preferences.General.setFileHandler('pdf', 'zotero')"
hidden="true"/>
<menuitem id="fileHandler-custom"/> <menuitem id="fileHandler-custom"/>
<menuitem label="&zotero.preferences.fileHandler.systemDefault;" <menuitem label="&zotero.preferences.fileHandler.systemDefault;"
oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/> oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/>
@ -75,6 +77,13 @@
</menupopup> </menupopup>
</menulist> </menulist>
</hbox> </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>
<groupbox id="zotero-prefpane-miscellaneous-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'); 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 () { Zotero.Promise.try(function () {
if(!Zotero) { if(!Zotero) {
throw true; throw true;
@ -59,6 +81,13 @@ const ZoteroStandalone = new function() {
ZoteroStandalone.DebugOutput.init(); 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(); Zotero.hideZoteroPaneOverlays();
ZoteroPane.init(); ZoteroPane.init();
ZoteroPane.makeVisible(); ZoteroPane.makeVisible();
@ -90,7 +119,16 @@ const ZoteroStandalone = new function() {
return; 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 () { this.onFileMenuOpen = function () {
var active = false; var active = false;
@ -422,7 +460,6 @@ const ZoteroStandalone = new function() {
this.updateNoteFontSize = function (event) { this.updateNoteFontSize = function (event) {
var size = event.originalTarget.getAttribute('label'); var size = event.originalTarget.getAttribute('label');
Zotero.Prefs.set('note.fontSize', size); Zotero.Prefs.set('note.fontSize', size);
this.promptForRestart();
}; };

View file

@ -43,6 +43,7 @@
<window id="main-window" <window id="main-window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
windowtype="navigator:browser" windowtype="navigator:browser"
title="&brandShortName;" title="&brandShortName;"
width="1000" height="600" width="1000" height="600"
@ -111,7 +112,15 @@
accesskey="&selectAllCmd.accesskey;" accesskey="&selectAllCmd.accesskey;"
command="cmd_selectAll"/> command="cmd_selectAll"/>
</menupopup> </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"> <toolbox id="navigator-toolbox" class="toolbox-top" mode="icons" defaultmode="icons">
<!-- Menu --> <!-- Menu -->
<toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true" <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
@ -119,28 +128,38 @@
mode="icons" iconsize="small" defaulticonsize="small" mode="icons" iconsize="small" defaulticonsize="small"
context="toolbar-context-menu"> context="toolbar-context-menu">
<toolbaritem id="menubar-items" align="center"> <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" <menubar id="main-menubar"
style="border:0px;padding:0px;margin:0px;-moz-appearance:none"> style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
<menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;" <menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;"
onpopupshowing="ZoteroStandalone.onFileMenuOpen()"> onpopupshowing="ZoteroStandalone.onFileMenuOpen()">
<menupopup id="menu_FilePopup"> <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" <menupopup id="menu_NewItemPopup"
onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/> onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/>
</menu> </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"/> 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"/> 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/> <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"/> accesskey="&closeCmd.accesskey;" command="cmd_close"/>
<menuseparator/> <menuitem id="menu_close_tab" class="menu-type-reader" label="&closeCmd.label;" key="key_close"
<menuitem id="menu_import" label="&importCmd.label;" 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"/> 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"/> 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"/> command="cmd_zotero_exportLibrary"/>
</menupopup> </menupopup>
</menu> </menu>
@ -154,29 +173,33 @@
<menuitem id="menu_cut"/> <menuitem id="menu_cut"/>
<menuitem id="menu_copy"/> <menuitem id="menu_copy"/>
<menuitem id="menu_copyCitation" <menuitem id="menu_copyCitation"
class="menu-type-library"
label="&copyCitationCmd.label;" label="&copyCitationCmd.label;"
command="cmd_zotero_copyCitation" command="cmd_zotero_copyCitation"
key="key_copyCitation" key="key_copyCitation"
hidden="true"/> hidden="true"/>
<menuitem id="menu_copyBibliography" <menuitem id="menu_copyBibliography"
class="menu-type-library"
label="&copyBibliographyCmd.label;" label="&copyBibliographyCmd.label;"
command="cmd_zotero_copyBibliography" command="cmd_zotero_copyBibliography"
key="key_copyBibliography" key="key_copyBibliography"
hidden="true"/> hidden="true"/>
<menuitem id="menu_copyExport" <menuitem id="menu_copyExport"
class="menu-type-library"
key="key_copyBibliography" key="key_copyBibliography"
command="cmd_zotero_copyBibliography" command="cmd_zotero_copyBibliography"
hidden="true"/> hidden="true"/>
<menuitem id="menu_paste"/> <menuitem id="menu_paste"/>
<menuitem id="menu_delete"/> <menuitem id="menu_delete"/>
<menuseparator/> <menuseparator class="menu-type-library"/>
<menuitem id="menu_selectAll"/> <menuitem id="menu_selectAll" class="menu-type-library"/>
<menuseparator/> <menuseparator class="menu-type-library"/>
<menuitem id="menu_find"/> <menuitem id="menu_find" class="menu-type-library"/>
<menuitem id="menu_advancedSearch" <menuitem id="menu_advancedSearch"
class="menu-type-library"
label="&zotero.toolbar.advancedSearch;" label="&zotero.toolbar.advancedSearch;"
command="cmd_zotero_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" <menuitem id="textfieldDirection-swap"
command="cmd_switchTextDirection" command="cmd_switchTextDirection"
key="key_switchTextDirection" key="key_switchTextDirection"
@ -190,8 +213,27 @@
label="&viewMenu.label;" label="&viewMenu.label;"
onpopupshowing="ZoteroStandalone.onViewMenuOpen()"> onpopupshowing="ZoteroStandalone.onViewMenuOpen()">
<menupopup id="menu_viewPopup"> <menupopup id="menu_viewPopup">
<menu id="layout-menu" <!-- <menuitem class="menu-type-reader" label="Switch to Presentation Mode" oncommand="ZoteroStandalone.onReaderCmd('presentationmode')"/>-->
label="&layout.label;"> <!-- <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)"> <menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
<menuitem <menuitem
id="view-menuitem-standard" id="view-menuitem-standard"
@ -259,6 +301,7 @@
</menu> </menu>
<menuseparator/> <menuseparator/>
<menuitem id="view-menuitem-recursive-collections" <menuitem id="view-menuitem-recursive-collections"
class="menu-type-library"
label="&recursiveCollections.label;" label="&recursiveCollections.label;"
oncommand="ZoteroStandalone.onViewMenuItemClick(event)" oncommand="ZoteroStandalone.onViewMenuItemClick(event)"
type="checkbox" type="checkbox"
@ -268,16 +311,17 @@
<menu id="toolsMenu" label="&toolsMenu.label;" accesskey="&toolsMenu.accesskey;"> <menu id="toolsMenu" label="&toolsMenu.label;" accesskey="&toolsMenu.accesskey;">
<menupopup id="menu_ToolsPopup"> <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"/> 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"/> command="cmd_zotero_rtfScan"/>
<menuseparator/> <menuseparator/>
<menuitem id="installConnector" <menuitem id="installConnector"
class="menu-type-library"
accessKey="&installConnector.accesskey;" accessKey="&installConnector.accesskey;"
label="&installConnector.label;" label="&installConnector.label;"
oncommand="ZoteroStandalone.openHelp('connectors');"/> 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)"/> oncommand="Zotero.openInViewer('chrome://mozapps/content/extensions/extensions.xul', ZoteroStandalone.updateAddonsPane)"/>
<menu id="developer-menu" <menu id="developer-menu"
label="&developer.label;"> label="&developer.label;">
@ -289,7 +333,7 @@
</menupopup> </menupopup>
</menu> </menu>
<menuseparator/> <menuseparator/>
<menu id="manage-attachments-menu" label="&manageAttachments.label;" <menu id="manage-attachments-menu" class="menu-type-library" label="&manageAttachments.label;"
onpopupshowing="ZoteroStandalone.onManageAttachmentsMenuOpen()" onpopupshowing="ZoteroStandalone.onManageAttachmentsMenuOpen()"
oncommand="ZoteroStandalone.onManageAttachmentsMenuItemClick(event)"> oncommand="ZoteroStandalone.onManageAttachmentsMenuItemClick(event)">
<menupopup id="manage-attachments-menupopup"> <menupopup id="manage-attachments-menupopup">
@ -372,6 +416,8 @@
</toolbaritem> </toolbaritem>
</toolbar> </toolbar>
</toolbox> </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/> <!--<toolbarpalette/>
<toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar" <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(){ 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_FILE = 0;
this.LINK_MODE_IMPORTED_URL = 1; this.LINK_MODE_IMPORTED_URL = 1;
this.LINK_MODE_LINKED_FILE = 2; this.LINK_MODE_LINKED_FILE = 2;
this.LINK_MODE_LINKED_URL = 3; this.LINK_MODE_LINKED_URL = 3;
this.LINK_MODE_EMBEDDED_IMAGE = 4;
this.BASE_PATH_PLACEHOLDER = 'attachments:'; this.BASE_PATH_PLACEHOLDER = 'attachments:';
var _findPDFQueue = []; 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 {Object} options
* @param {Integer} options.libraryID * @param {Integer} options.libraryID
@ -2102,6 +2196,9 @@ Zotero.Attachments = new function(){
if (!(item instanceof Zotero.Item)) { if (!(item instanceof Zotero.Item)) {
throw new Error("'item' must be a 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); return this.getStorageDirectoryByLibraryAndKey(item.libraryID, item.key);
} }
@ -2211,6 +2308,9 @@ Zotero.Attachments = new function(){
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
break; break;
case Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE:
return false;
default: default:
throw new Error("Invalid attachment link mode"); throw new Error("Invalid attachment link mode");
} }
@ -2367,7 +2467,7 @@ Zotero.Attachments = new function(){
Zotero.DB.requireTransaction(); Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID); var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) { if (attachment.isStoredFileAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different // Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath; newAttachment.attachmentPath = attachment.attachmentPath;
} }
@ -2379,7 +2479,7 @@ Zotero.Attachments = new function(){
// Move files over if they exist // Move files over if they exist
var oldDir; var oldDir;
var newDir; var newDir;
if (newAttachment.isImportedAttachment()) { if (newAttachment.isStoredFileAttachment()) {
oldDir = this.getStorageDirectory(attachment).path; oldDir = this.getStorageDirectory(attachment).path;
if (await OS.File.exists(oldDir)) { if (await OS.File.exists(oldDir)) {
newDir = this.getStorageDirectory(newAttachment).path; newDir = this.getStorageDirectory(newAttachment).path;
@ -2405,7 +2505,7 @@ Zotero.Attachments = new function(){
} }
catch (e) { catch (e) {
// Move files back if old item can't be deleted // Move files back if old item can't be deleted
if (newAttachment.isImportedAttachment()) { if (newAttachment.isStoredFileAttachment()) {
try { try {
await OS.File.move(newDir, oldDir); await OS.File.move(newDir, oldDir);
} }
@ -2431,7 +2531,7 @@ Zotero.Attachments = new function(){
Zotero.DB.requireTransaction(); Zotero.DB.requireTransaction();
var newAttachment = attachment.clone(libraryID); var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) { if (attachment.isStoredFileAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different // Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath; newAttachment.attachmentPath = attachment.attachmentPath;
} }
@ -2441,7 +2541,7 @@ Zotero.Attachments = new function(){
yield newAttachment.save(); yield newAttachment.save();
// Copy over files if they exist // 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 dir = Zotero.Attachments.getStorageDirectory(attachment);
let newDir = yield Zotero.Attachments.createDirectoryForItem(newAttachment); let newDir = yield Zotero.Attachments.createDirectoryForItem(newAttachment);
yield Zotero.File.copyDirectory(dir, newDir); yield Zotero.File.copyDirectory(dir, newDir);
@ -2787,6 +2887,8 @@ Zotero.Attachments = new function(){
return 'linked_file'; return 'linked_file';
case this.LINK_MODE_LINKED_URL: case this.LINK_MODE_LINKED_URL:
return 'linked_url'; return 'linked_url';
case this.LINK_MODE_EMBEDDED_IMAGE:
return 'embedded_image';
default: default:
throw new Error(`Invalid link mode ${linkMode}`); throw new Error(`Invalid link mode ${linkMode}`);
} }

View file

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

View file

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

View file

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

View file

@ -31,8 +31,7 @@ Zotero.Collection = function(params = {}) {
this._childCollections = new Set(); this._childCollections = new Set();
this._childItems = new Set(); this._childItems = new Set();
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID', Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID', 'parentKey']);
'parentKey', 'lastSync']);
} }
Zotero.extendClass(Zotero.DataObject, Zotero.Collection); 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) { this._loadChildCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID " var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID "
+ "FROM collections C1 LEFT JOIN collections C2 ON (C1.collectionID=C2.parentCollectionID) " + "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) throw new Error(this._ObjectType + " " + (id ? id : libraryID + "/" + key)
+ " not found in Zotero." + this._ObjectType + ".loadPrimaryData()"); + " not found in Zotero." + this._ObjectType + ".loadPrimaryData()");
} }
this._clearChanged('primaryData');
// If object doesn't exist, mark all data types as loaded // If object doesn't exist, mark all data types as loaded
this._markAllDataTypeLoadStates(true); 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 * Get either the unsaved value of a field or the saved value if unchanged since the last save
*/ */
Zotero.DataObject.prototype._getLatestField = function (field) { 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) { Zotero.DataObject.prototype._markFieldChange = function (field, value) {
// New method (changedData) // New method (changedData)
if (['deleted', 'tags'].includes(field)) { if (['deleted', 'tags'].includes(field) || field.startsWith('annotation')) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
this._changedData[field] = [...value]; this._changedData[field] = [...value];
} }
else if (typeof value === 'object' && value !== null) {
this._changedData[field] = Object.assign({}, value);
}
else { else {
this._changedData[field] = value; 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.skipEditCheck) this.editCheck();
if (env.options.skipDeleteLog) {
env.notifierData[this.id].skipDeleteLog = true;
}
return true; return true;
}); });
@ -1277,6 +1290,10 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
this.ObjectsClass.unload(env.deletedObjectIDs || this.id); this.ObjectsClass.unload(env.deletedObjectIDs || this.id);
}.bind(this)); }.bind(this));
if (env.options.skipDeleteLog) {
env.notifierData[this.id].skipDeleteLog = true;
}
if (!env.options.skipNotifier) { if (!env.options.skipNotifier) {
Zotero.Notifier.queue( Zotero.Notifier.queue(
'delete', 'delete',

View file

@ -486,9 +486,14 @@ Zotero.DataObjects.prototype.loadDataTypes = Zotero.Promise.coroutine(function*
* @param {Integer[]} [ids] * @param {Integer[]} [ids]
*/ */
Zotero.DataObjects.prototype._loadDataTypeInLibrary = Zotero.Promise.coroutine(function* (dataType, libraryID, 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()') // 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]) { if (!this[funcName]) {
throw new Error(`Zotero.${this._ZDO_Objects}.${funcName} is not a function`); 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 * 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); var baseFieldID = this.getID(baseField);
if (!baseFieldID) { if (!baseFieldID) {
throw new Error("Invalid field '" + baseField + '" for base field'); throw new Error("Invalid field '" + baseField + "' for base field");
} }
if (fieldID == baseFieldID) { if (fieldID == baseFieldID) {
@ -277,7 +277,7 @@ Zotero.ItemFields = new function() {
var baseFieldID = this.getID(baseField); var baseFieldID = this.getID(baseField);
if (!baseFieldID) { 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 // 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); var fieldID = Zotero.ItemFields.getID(field);
if (!fieldID) { if (!fieldID) {
Zotero.debug((new Error).stack, 1); Zotero.debug((new Error).stack, 1);
throw new Error(`Invalid field '${field}`); throw new Error(`Invalid field '${field}'`);
} }
return fieldID; return fieldID;
} }
@ -518,7 +518,9 @@ Zotero.ItemFields = new function() {
var rows = yield Zotero.DB.queryAsync(sql); var rows = yield Zotero.DB.queryAsync(sql);
_itemTypeFields = { _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++) { for (let i=0; i<rows.length; i++) {

View file

@ -38,6 +38,7 @@ Zotero.Items = function() {
get: function () { get: function () {
var itemTypeAttachment = Zotero.ItemTypes.getID('attachment'); var itemTypeAttachment = Zotero.ItemTypes.getID('attachment');
var itemTypeNote = Zotero.ItemTypes.getID('note'); var itemTypeNote = Zotero.ItemTypes.getID('note');
var itemTypeAnnotation = Zotero.ItemTypes.getID('annotation');
return { return {
itemID: "O.itemID", itemID: "O.itemID",
@ -49,16 +50,25 @@ Zotero.Items = function() {
version: "O.version", version: "O.version",
synced: "O.synced", synced: "O.synced",
createdByUserID: "createdByUserID",
lastModifiedByUserID: "lastModifiedByUserID",
firstCreator: _getFirstCreatorSQL(), firstCreator: _getFirstCreatorSQL(),
sortCreator: _getSortCreatorSQL(), sortCreator: _getSortCreatorSQL(),
deleted: "DI.itemID IS NOT NULL AS deleted", deleted: "DI.itemID IS NOT NULL AS deleted",
inPublications: "PI.itemID IS NOT NULL AS inPublications", inPublications: "PI.itemID IS NOT NULL AS inPublications",
parentID: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.itemID ` parentID: `(CASE O.itemTypeID `
+ `WHEN ${itemTypeNote} THEN INoP.itemID END) AS parentID`, + `WHEN ${itemTypeAttachment} THEN IAP.itemID `
parentKey: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.key ` + `WHEN ${itemTypeNote} THEN INoP.itemID `
+ `WHEN ${itemTypeNote} THEN INoP.key END) AS parentKey`, + `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", attachmentCharset: "CS.charset AS attachmentCharset",
attachmentLinkMode: "IA.linkMode AS attachmentLinkMode", attachmentLinkMode: "IA.linkMode AS attachmentLinkMode",
@ -66,7 +76,8 @@ Zotero.Items = function() {
attachmentPath: "IA.path AS attachmentPath", attachmentPath: "IA.path AS attachmentPath",
attachmentSyncState: "IA.syncState AS attachmentSyncState", attachmentSyncState: "IA.syncState AS attachmentSyncState",
attachmentSyncedModificationTime: "IA.storageModTime AS attachmentSyncedModificationTime", attachmentSyncedModificationTime: "IA.storageModTime AS attachmentSyncedModificationTime",
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash" attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash",
attachmentLastProcessedModificationTime: "IA.lastProcessedModificationTime AS attachmentLastProcessedModificationTime",
}; };
} }
}, {lazy: true}); }, {lazy: true});
@ -77,9 +88,12 @@ Zotero.Items = function() {
+ "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) " + "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) " + "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.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 deletedItems DI ON (O.itemID=DI.itemID) "
+ "LEFT JOIN publicationsItems PI ON (O.itemID=PI.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"; 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) { this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var params = [libraryID]; var params = [libraryID];
var rows = []; var rows = [];
@ -499,6 +595,9 @@ Zotero.Items = function() {
}); });
}; };
//
// Attachments
//
var sql = "SELECT parentItemID, A.itemID, value AS title, " var sql = "SELECT parentItemID, A.itemID, value AS title, "
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed " + "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemAttachments A " + "FROM itemAttachments A "
@ -602,9 +701,56 @@ Zotero.Items = function() {
ids.forEach(id => setNoteItem(id, [])); 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 " // Annotations
+ "(SELECT itemID FROM itemAttachments UNION SELECT itemID FROM itemNotes)"; //
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( yield Zotero.DB.queryAsync(
sql, sql,
params, params,

View file

@ -25,7 +25,7 @@
Zotero.Notes = new function() { Zotero.Notes = new function() {
this.noteToTitle = noteToTitle; this._editorInstances = [];
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; }); this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; });
this.__defineGetter__("defaultNote", function () { return '<div class="zotero-note znv1"></div>'; }); 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 * Return first line (or first MAX_LENGTH characters) of note content
**/ **/
function noteToTitle(text) { this.noteToTitle = function(text) {
var origText = text; var origText = text;
text = text.trim(); text = text.trim();
text = Zotero.Utilities.unescapeHTML(text); text = Zotero.Utilities.unescapeHTML(text);
@ -59,9 +59,101 @@ Zotero.Notes = new function() {
t = t.substring(0, ln); t = t.substring(0, ln);
} }
return t; 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); var parts = Zotero.SearchConditions.parseSearchString(value);
for (let part of parts) { 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 // Allow searching for exact object key
if (operator == 'contains' && Zotero.Utilities.isValidObjectKey(part.text)) { if (operator == 'contains' && Zotero.Utilities.isValidObjectKey(part.text)) {
this.addCondition('key', 'is', part.text, false); 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('title', operator, part.text, false);
this.addCondition('publicationTitle', operator, part.text, false); this.addCondition('publicationTitle', operator, part.text, false);
this.addCondition('shortTitle', 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); this.addCondition('creator', operator, part.text, false);
if (condition == 'quicksearch-everything') { 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) { if (part.inQuotes) {
this.addCondition('fulltextContent', operator, part.text, false); this.addCondition('fulltextContent', operator, part.text, false);
@ -347,6 +353,9 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
if (condition == 'quicksearch-titleCreatorYear') { if (condition == 'quicksearch-titleCreatorYear') {
this.addCondition('noChildren', 'true'); this.addCondition('noChildren', 'true');
} }
else if (condition == 'quicksearch-titleCreatorYearNote') {
this.addCondition('itemType', 'is', 'note');
}
return false; return false;
} }

View file

@ -168,6 +168,17 @@ Zotero.SearchConditions = new function(){
noLoad: true noLoad: true
}, },
{
name: 'quicksearch-titleCreatorYearNote',
operators: {
is: true,
isNot: true,
contains: true,
doesNotContain: true
},
noLoad: true
},
{ {
name: 'quicksearch-fields', name: 'quicksearch-fields',
operators: { operators: {
@ -490,13 +501,25 @@ Zotero.SearchConditions = new function(){
}, },
{ {
name: 'annotation', name: 'annotationText',
operators: { operators: {
contains: true, contains: true,
doesNotContain: true doesNotContain: true
}, },
table: 'annotations', table: 'itemAnnotations',
field: 'text' 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){ function getFileDateString(file){
var date = new Date(); var date = new Date();
date.setTime(file.lastModifiedTime); 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_ZFS_FILE_EDITING_DENIED = 7;
Zotero.Error.ERROR_INVALID_ITEM_TYPE = 8; Zotero.Error.ERROR_INVALID_ITEM_TYPE = 8;
Zotero.Error.ERROR_USER_NOT_AVAILABLE = 9; 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_EMPTY_RESPONSE_FROM_SERVER = 6;
//Zotero.Error.ERROR_SYNC_INVALID_RESPONSE_FROM_SERVER = 7; //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) { this.generateDataURI = async function (file, contentType) {
var contentType = Components.classes["@mozilla.org/mime;1"] if (!contentType) {
.getService(Components.interfaces.nsIMIMEService) throw new Error("contentType not provided");
.getTypeFromFile(file); }
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream); var buf = await OS.File.read(file, {});
inputStream.init(file, 0x01, 0o600, 0); var bytes = new Uint8Array(buf);
var stream = Components.classes["@mozilla.org/binaryinputstream;1"] var binary = '';
.createInstance(Components.interfaces.nsIBinaryInputStream); var len = bytes.byteLength;
stream.setInputStream(inputStream); for (let i = 0; i < len; i++) {
var encoded = btoa(stream.readBytes(stream.available())); binary += String.fromCharCode(bytes[i]);
return "data:" + contentType + ";base64," + encoded; }
} return 'data:' + contentType + ';base64,' + btoa(binary);
};
this.setNormalFilePermissions = function (file) { this.setNormalFilePermissions = function (file) {
@ -1008,14 +1010,17 @@ Zotero.File = new function(){
} }
this.createDirectoryIfMissingAsync = async function (path) { this.createDirectoryIfMissingAsync = async function (path, options = {}) {
try { try {
await OS.File.makeDir( await OS.File.makeDir(
path, path,
{ Object.assign(
ignoreExisting: false, {
unixMode: 0o755 ignoreExisting: false,
} unixMode: 0o755
},
options
)
) )
} }
catch (e) { 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(); Zotero.CollectionTreeCache.clear();
// Get the full set of items we want to show // Get the full set of items we want to show
let newSearchItems = yield this.collectionTreeRow.getItems(); 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 // Remove notes and attachments if necessary
if (this.regularOnly) { if (this.regularOnly) {
newSearchItems = newSearchItems.filter(item => item.isRegularItem()); newSearchItems = newSearchItems.filter(item => item.isRegularItem());
@ -3505,7 +3509,7 @@ Zotero.ItemTreeRow.prototype.numNotes = function() {
return 0; return 0;
} }
if (this.ref.isAttachment()) { if (this.ref.isAttachment()) {
return this.ref.getNote() !== '' ? 1 : 0; return this.ref.note !== '' ? 1 : 0;
} }
return this.ref.numNotes(false, true) || 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 = [ var _types = [
'collection', 'search', 'share', 'share-items', 'item', 'file', 'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', '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 _transactionID = false;
var _queue = {}; var _queue = {};

View file

@ -26,7 +26,32 @@
/* eslint-disable array-element-newline */ /* eslint-disable array-element-newline */
Zotero.OpenPDF = { 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 handler = Zotero.Prefs.get("fileHandler.pdf");
var opened = false; 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.saveIcon');
this.clear('firstRunGuidanceShown.saveButton'); this.clear('firstRunGuidanceShown.saveButton');
break; 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); this.set('prefVersion', toVersion);

View file

@ -292,7 +292,7 @@ Zotero.QuickCopy = new function() {
div.className = "zotero-note"; div.className = "zotero-note";
// AMO reviewer: This documented is never rendered (and the inserted markup // AMO reviewer: This documented is never rendered (and the inserted markup
// is sanitized anyway) // is sanitized anyway)
div.insertAdjacentHTML('afterbegin', notes[i].getNote()); div.insertAdjacentHTML('afterbegin', notes[i].note);
container.appendChild(div); container.appendChild(div);
textContainer.appendChild(textDoc.importNode(div, true)); 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 // 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. // 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 _dbVersions = [];
var _schemaVersions = []; var _schemaVersions = [];
// Update when adding _updateCompatibility() line to schema update step // Update when adding _updateCompatibility() line to schema update step
var _maxCompatibility = 6; var _maxCompatibility = 7;
var _repositoryTimerID; var _repositoryTimerID;
var _repositoryNotificationTimerID; var _repositoryNotificationTimerID;
@ -1790,6 +1790,9 @@ Zotero.Schema = new function(){
var noteID = parseInt(yield Zotero.DB.valueQueryAsync( var noteID = parseInt(yield Zotero.DB.valueQueryAsync(
"SELECT itemTypeID FROM itemTypes WHERE typeName='note'" "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 // 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 // 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)`, `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)`, `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}))`, `SELECT COUNT(*) > 0 FROM itemAttachments `
`UPDATE itemAttachments SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`, + `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}))`, `SELECT COUNT(*) > 0 FROM itemAttachments `
`UPDATE itemNotes SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`, + `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 // Delete empty creators
// This may cause itemCreator gaps, but that's better than 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 // Invalid link mode -- set to imported url
[ [
"SELECT COUNT(*) > 0 FROM itemAttachments 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)" "UPDATE itemAttachments SET linkMode=1 WHERE linkMode NOT IN (0,1,2,3,4)"
], ],
// Creators with first name can't be fieldMode 1 // 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)"); 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 // If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
} }

View file

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

View file

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

View file

@ -149,6 +149,7 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
throw e; throw e;
} }
Zotero.debug("Upload failed -- performing download", 2); Zotero.debug("Upload failed -- performing download", 2);
Zotero.debug(e, 1);
downloadResult = yield this._startDownload(); downloadResult = yield this._startDownload();
Zotero.debug("Download result is " + downloadResult, 4); Zotero.debug("Download result is " + downloadResult, 4);
throw e; throw e;
@ -223,6 +224,15 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
skipNotifier: true skipNotifier: true
}); });
if (this.library.libraryType == 'group') {
try {
yield this._updateGroupItemUsers();
}
catch (e) {
Zotero.logError(e);
}
}
Zotero.debug("Done syncing " + this.library.name); 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 = {}) { Zotero.Sync.Data.Engine.prototype._getJSONForObject = function (objectType, id, options = {}) {
return Zotero.DB.executeTransaction(function* () { return Zotero.DB.executeTransaction(function* () {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType); var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);

View file

@ -124,24 +124,39 @@ Zotero.Sync.EventListeners.AutoSyncListener = {
} }
// Only trigger sync for certain types // Only trigger sync for certain types
// // TODO: full text
// TODO: settings, full text if (![...Zotero.DataObjectUtilities.getTypes(), 'setting'].includes(type)) {
if (!Zotero.DataObjectUtilities.getTypes().includes(type)) {
return; return;
} }
// Determine affected libraries so only those can be synced // Determine affected libraries so only those can be synced
let libraries = []; let libraries = [];
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
ids.forEach(id => { if (type == 'setting') {
let lk = objectsClass.getLibraryAndKeyFromID(id); for (let id of ids) {
if (lk) { // E.g., '1/lastPageIndex_u_ABCD2345'
let library = Zotero.Libraries.get(lk.libraryID); let libraryID = parseInt(id.split('/')[0]);
let library = Zotero.Libraries.get(libraryID);
if (library.syncable) { if (library.syncable) {
libraries.push(library); 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); libraries = Zotero.Sync.Data.Local.filterSkippedLibraries(libraries);
if (!libraries.length) { 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 // Replace local user key with libraryID, in case duplicates were merged before the
// first sync // first sync
yield Zotero.Relations.updateUser(null, userID); 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"; var sql = "SELECT O." + objectsClass.idColumn + " FROM " + objectsClass.table + " O";
if (objectType == 'item') { if (objectType == 'item') {
sql += " LEFT JOIN itemAttachments IA USING (itemID) " 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"; sql += " WHERE libraryID=? AND synced=0";
// Sort child items last // Don't sync external annotations
if (objectType == 'item') { 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]); var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
@ -555,12 +557,12 @@ Zotero.Sync.Data.Local = {
// Sort descendent collections last // Sort descendent collections last
if (objectType == 'collection') { if (objectType == 'collection') {
try { try {
ids = Zotero.Collections.sortByLevel(ids); ids = Zotero.Collections.sortByLevel(ids.map(id => Zotero.Collections.get(id))).map(o => o.id);
} }
catch (e) { catch (e) {
Zotero.logError(e); Zotero.logError(e);
// If collections were incorrectly nested, fix and try again // 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); let c = Zotero.Collections.get(e.collectionID);
Zotero.debug(`Removing parent collection ${c.parentKey} from collection ${c.key}`); Zotero.debug(`Removing parent collection ${c.parentKey} from collection ${c.key}`);
c.parentID = null; 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; return ids;
}), }),
isSyncItem: function (item) {
if (item.itemType == 'annotation' && item.annotationIsExternal) {
return false;
}
return true;
},
// //
// Cache management // Cache management
// //
@ -883,7 +896,7 @@ Zotero.Sync.Data.Local = {
// For items, check if mtime or file hash changed in metadata, // For items, check if mtime or file hash changed in metadata,
// which would indicate that a remote storage sync took place and // which would indicate that a remote storage sync took place and
// a download is needed // a download is needed
if (objectType == 'item' && obj.isImportedAttachment()) { if (objectType == 'item' && obj.isStoredFileAttachment()) {
if (jsonDataLocal.mtime != jsonData.mtime if (jsonDataLocal.mtime != jsonData.mtime
|| jsonDataLocal.md5 != jsonData.md5) { || jsonDataLocal.md5 != jsonData.md5) {
saveOptions.storageDetailsChanged = true; saveOptions.storageDetailsChanged = true;
@ -1459,8 +1472,23 @@ Zotero.Sync.Data.Local = {
if (!options.skipData) { if (!options.skipData) {
obj.fromJSON(json.data, { strict: true }); obj.fromJSON(json.data, { strict: true });
} }
if (obj.objectType == 'item' && obj.isImportedAttachment()) { if (obj.objectType == 'item') {
yield this._checkAttachmentForDownload(obj, json.data.mtime, options.isNewObject); // 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; obj.version = json.data.version;
if (!options.saveAsUnsynced) { if (!options.saveAsUnsynced) {
@ -1493,7 +1521,7 @@ Zotero.Sync.Data.Local = {
yield this._removeObjectFromSyncQueue(obj.objectType, obj.libraryID, json.key); yield this._removeObjectFromSyncQueue(obj.objectType, obj.libraryID, json.key);
// Mark updated attachments for download // 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 // If storage changes were made (attachment mtime or hash), mark
// library as requiring download // library as requiring download
if (options.isNewObject || options.storageDetailsChanged) { if (options.isNewObject || options.storageDetailsChanged) {

View file

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

View file

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

View file

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

View file

@ -28,10 +28,11 @@ Zotero.Users = new function () {
var _libraryID; var _libraryID;
var _username; var _username;
var _localUserKey; 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 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 = {}; let settings = {};
for (let i=0; i<rows.length; i++) { for (let i=0; i<rows.length; i++) {
@ -56,11 +57,16 @@ Zotero.Users = new function () {
let key = Zotero.randomString(8); let key = Zotero.randomString(8);
sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)"; sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
yield Zotero.DB.queryAsync(sql, key); await Zotero.DB.queryAsync(sql, key);
_localUserKey = 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 }; this.getCurrentUserID = function() { return _userID };
@ -87,4 +93,18 @@ Zotero.Users = new function () {
this.getLocalUserKey = function () { this.getLocalUserKey = function () {
return _localUserKey; 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. * Fixes author name capitalization.
* Currently for all uppercase names only * Currently for all uppercase names only

View file

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

View file

@ -69,6 +69,13 @@ var ZoteroPane = new function()
this.init = function () { this.init = function () {
Zotero.debug("Initializing Zotero pane"); 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 // Set key down handler
document.getElementById('appcontent').addEventListener('keydown', ZoteroPane_Local.handleKeyDown, true); document.getElementById('appcontent').addEventListener('keydown', ZoteroPane_Local.handleKeyDown, true);
@ -144,6 +151,7 @@ var ZoteroPane = new function()
Zotero.hiDPI = window.devicePixelRatio > 1; Zotero.hiDPI = window.devicePixelRatio > 1;
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : ""; Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
Zotero_Tabs.init();
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading')); ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
// Add a default progress window // Add a default progress window
@ -525,6 +533,21 @@ var ZoteroPane = new function()
* Trigger actions based on keyboard shortcuts * Trigger actions based on keyboard shortcuts
*/ */
function handleKeyDown(event, from) { 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 { try {
// Ignore keystrokes outside of Zotero pane // Ignore keystrokes outside of Zotero pane
if (!(event.originalTarget.ownerDocument instanceof XULDocument)) { if (!(event.originalTarget.ownerDocument instanceof XULDocument)) {
@ -541,6 +564,20 @@ var ZoteroPane = new function()
} }
if (from == 'zotero-pane') { 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 // Highlight collections containing selected items
// //
// We use Control (17) on Windows because Alt triggers the menubar; // We use Control (17) on Windows because Alt triggers the menubar;
@ -1166,6 +1203,14 @@ var ZoteroPane = new function()
return; 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 // Clear quick search and tag selector when switching views
document.getElementById('zotero-tb-search').value = ""; document.getElementById('zotero-tb-search').value = "";
if (ZoteroPane.tagSelector) { if (ZoteroPane.tagSelector) {
@ -2690,6 +2735,8 @@ var ZoteroPane = new function()
'showInLibrary', 'showInLibrary',
'sep1', 'sep1',
'addNote', 'addNote',
'createNoteFromAnnotations',
'createNoteFromAnnotationsMenu',
'addAttachments', 'addAttachments',
'sep2', 'sep2',
'findPDF', 'findPDF',
@ -2858,6 +2905,7 @@ var ZoteroPane = new function()
} }
} }
} }
} }
// Single item selected // Single item selected
@ -2875,6 +2923,33 @@ var ZoteroPane = new function()
if (item.isRegularItem() && !item.isFeedItem) { if (item.isRegularItem() && !item.isFeedItem) {
show.push(m.addNote, m.addAttachments, m.sep2); 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)) { if (Zotero.Attachments.canFindPDFForItem(item)) {
@ -3462,7 +3537,7 @@ var ZoteroPane = new function()
var items = this.getSelectedItems(); var items = this.getSelectedItems();
if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) { 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); items[0].setNote(note + text);
yield items[0].saveTx(); yield items[0].saveTx();
@ -3514,13 +3589,13 @@ var ZoteroPane = new function()
} }
} }
}; };
this.onNoteWindowClosed = async function (itemID, noteText) { this.onNoteWindowClosed = async function (itemID, noteText) {
var item = Zotero.Items.get(itemID); var item = Zotero.Items.get(itemID);
item.setNote(noteText); item.setNote(noteText);
await item.saveTx(); await item.saveTx();
// If note is still selected, show the editor again when the note window closes // If note is still selected, show the editor again when the note window closes
var selectedItems = this.getSelectedItems(true); var selectedItems = this.getSelectedItems(true);
if (selectedItems.length == 1 && itemID == selectedItems[0]) { 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) { this.addAttachmentFromURI = Zotero.Promise.method(function (link, itemID) {
if (!this.canEdit()) { if (!this.canEdit()) {
this.displayCannotEditLibraryMessage(); 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 // Custom PDF handler
if (contentType === 'application/pdf') { 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"); let pdfHandler = Zotero.Prefs.get("fileHandler.pdf");
if (pdfHandler) { if (pdfHandler) {
if (await OS.File.exists(pdfHandler)) { if (await OS.File.exists(pdfHandler)) {
@ -4031,7 +4164,7 @@ var ZoteroPane = new function()
continue; continue;
} }
let isLinkedFile = !item.isImportedAttachment(); let isLinkedFile = !item.isStoredFileAttachment();
let path = item.getFilePath(); let path = item.getFilePath();
if (!path) { if (!path) {
ZoteroPane_Local.showAttachmentNotFoundDialog( ZoteroPane_Local.showAttachmentNotFoundDialog(
@ -4058,7 +4191,7 @@ var ZoteroPane = new function()
let iCloudPath = Zotero.File.getEvictedICloudPath(path); let iCloudPath = Zotero.File.getEvictedICloudPath(path);
if (await OS.File.exists(iCloudPath)) { if (await OS.File.exists(iCloudPath)) {
Zotero.debug("Triggering download of iCloud file"); Zotero.debug("Triggering download of iCloud file");
await launchFile(iCloudPath, item.attachmentContentType); await launchFile(iCloudPath, item.attachmentContentType, itemID);
let time = new Date(); let time = new Date();
let maxTime = 5000; let maxTime = 5000;
let revealed = false; let revealed = false;
@ -4118,7 +4251,7 @@ var ZoteroPane = new function()
if (fileExists && !redownload) { if (fileExists && !redownload) {
Zotero.debug("Opening " + path); Zotero.debug("Opening " + path);
Zotero.Notifier.trigger('open', 'file', item.id); Zotero.Notifier.trigger('open', 'file', item.id);
launchFile(path, item.attachmentContentType); await launchFile(path, item.attachmentContentType, item.id);
continue; continue;
} }
@ -4164,10 +4297,14 @@ var ZoteroPane = new function()
Zotero.debug("Opening " + path); Zotero.debug("Opening " + path);
Zotero.Notifier.trigger('open', 'file', item.id); 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 * @deprecated
@ -4203,7 +4340,7 @@ var ZoteroPane = new function()
var fileExists = await OS.File.exists(path); var fileExists = await OS.File.exists(path);
// If file doesn't exist but an evicted iCloud Drive file does, reveal that instead // 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); let iCloudPath = Zotero.File.getEvictedICloudPath(path);
if (await OS.File.exists(iCloudPath)) { if (await OS.File.exists(iCloudPath)) {
path = 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) { this.createEmptyParent = async function (item) {
await Zotero.DB.executeTransaction(async function () { await Zotero.DB.executeTransaction(async function () {
// TODO: remove once there are no top-level web attachments // TODO: remove once there are no top-level web attachments
@ -4585,7 +4754,27 @@ var ZoteroPane = new function()
await item.save(); 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* () { this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
// TEMP: fix // TEMP: fix
@ -4947,6 +5136,7 @@ var ZoteroPane = new function()
this.updateToolbarPosition(); this.updateToolbarPosition();
this.updateTagsBoxSize(); this.updateTagsBoxSize();
ZoteroContextPane.update();
} }
@ -5095,6 +5285,8 @@ var ZoteroPane = new function()
* Revisit when we're all HTML. * Revisit when we're all HTML.
*/ */
this.updateTagsBoxSize = function () { this.updateTagsBoxSize = function () {
// TODO: We can probably remove this function
return;
var pane = document.querySelector('#zotero-item-pane'); var pane = document.querySelector('#zotero-item-pane');
var header = document.querySelector('#zotero-item-pane .tags-box-header'); var header = document.querySelector('#zotero-item-pane .tags-box-header');
var list = document.querySelector('#zotero-item-pane .tags-box-list'); 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.showInLibrary "Show in Library">
<!ENTITY zotero.items.menu.attach.note "Add Note"> <!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 "Add Attachment">
<!ENTITY zotero.items.menu.attach.link.uri "Attach Link to URI…"> <!ENTITY zotero.items.menu.attach.link.uri "Attach Link to URI…">
<!ENTITY zotero.items.menu.attach.file "Attach Stored Copy of File..."> <!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.add "Store Copy of File...">
<!ENTITY zotero.toolbar.attachment.weblink "Save Link to Current Page"> <!ENTITY zotero.toolbar.attachment.weblink "Save Link to Current Page">
<!ENTITY zotero.toolbar.attachment.snapshot "Take Snapshot of 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.noTagsToDisplay "No tags to display">
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…"> <!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Attach Link to URI"> <!ENTITY zotero.attachLink.title "Attach Link to URI">
<!ENTITY zotero.attachLink.label.link "Link:"> <!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Title:"> <!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.nMegabytes=%S MB
general.item=Item general.item=Item
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=A Zotero operation is currently in progress. general.operationInProgress=A Zotero operation is currently in progress.
general.operationInProgress.waitUntilFinished=Please wait until it has finished. 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.moveToTop=Move to Top
pane.item.creator.moveUp=Move Up pane.item.creator.moveUp=Move Up
pane.item.creator.moveDown=Move Down pane.item.creator.moveDown=Move Down
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Untitled Note pane.item.notes.untitled=Untitled Note
pane.item.notes.delete.confirm=Are you sure you want to delete this note? pane.item.notes.delete.confirm=Are you sure you want to delete this note?
pane.item.notes.count.zero=%S notes: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S note:
pane.item.notes.count.plural=%S notes:
pane.item.notes.editingInWindow=Editing in separate window pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=New title: pane.item.attachments.rename.title=New title:
pane.item.attachments.rename.renameAssociatedFile=Rename associated file pane.item.attachments.rename.renameAssociatedFile=Rename associated file
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S related:
pane.item.related.count.plural=%S related: pane.item.related.count.plural=%S related:
pane.item.parentItem=Parent Item: 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 noteEditor.editNote=Edit Note
itemTypes.note=Note itemTypes.note=Note
itemTypes.annotation=Annotation
itemTypes.attachment=Attachment itemTypes.attachment=Attachment
itemTypes.book=Book itemTypes.book=Book
itemTypes.bookSection=Book Section itemTypes.bookSection=Book Section
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=No PDFs found findPDF.noPDFsFound=No PDFs found
findPDF.noPDFFound=No PDF found findPDF.noPDFFound=No PDF found
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Full Text attachment.fullText=Full Text
attachment.acceptedVersion=Accepted Version attachment.acceptedVersion=Accepted Version
attachment.submittedVersion=Submitted Version attachment.submittedVersion=Submitted Version
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
searchConditions.fulltextContent=Attachment Content searchConditions.fulltextContent=Attachment Content
searchConditions.programmingLanguage=Programming Language searchConditions.programmingLanguage=Programming Language
searchConditions.fileTypeID=Attachment File Type searchConditions.fileTypeID=Attachment File Type
searchConditions.annotation=Annotation searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexed fulltext.indexState.indexed=Indexed
fulltext.indexState.unavailable=Unknown fulltext.indexState.unavailable=Unknown
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Export Notes exportOptions.exportNotes=Export Notes
exportOptions.exportFileData=Export Files exportOptions.exportFileData=Export Files
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM) charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
charset.autoDetect=(auto detect) 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.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.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.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.title=Install Style
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S" 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.showInLibrary "عرض في المكتبة">
<!ENTITY zotero.items.menu.attach.note "اضافة ملاحظة"> <!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 "اضافة مرفق">
<!ENTITY zotero.items.menu.attach.link.uri "ربط الرابط بالمسار..."> <!ENTITY zotero.items.menu.attach.link.uri "ربط الرابط بالمسار...">
<!ENTITY zotero.items.menu.attach.file "إرفاق نسخة من ملف مخزن..."> <!ENTITY zotero.items.menu.attach.file "إرفاق نسخة من ملف مخزن...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "تخزين نسخة من ملف..."> <!ENTITY zotero.toolbar.attachment.add "تخزين نسخة من ملف...">
<!ENTITY zotero.toolbar.attachment.weblink "حفظ رابط للصفحة الحالية"> <!ENTITY zotero.toolbar.attachment.weblink "حفظ رابط للصفحة الحالية">
<!ENTITY zotero.toolbar.attachment.snapshot "اخذ لقطة من الصفحة الحالية"> <!ENTITY zotero.toolbar.attachment.snapshot "اخذ لقطة من الصفحة الحالية">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "لاتوجد اوسمة لعرضها"> <!ENTITY zotero.tagSelector.noTagsToDisplay "لاتوجد اوسمة لعرضها">
<!ENTITY zotero.tagSelector.loadingTags "تحميل الوسوم..."> <!ENTITY zotero.tagSelector.loadingTags "تحميل الوسوم...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "ارفاق رابط لمحتوى على الانترنت"> <!ENTITY zotero.attachLink.title "ارفاق رابط لمحتوى على الانترنت">
<!ENTITY zotero.attachLink.label.link "رابط:"> <!ENTITY zotero.attachLink.label.link "رابط:">
<!ENTITY zotero.attachLink.label.title "عنوان:"> <!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.nMegabytes=%S MB
general.item=عنصر general.item=عنصر
general.pdf=بي دي إف general.pdf=بي دي إف
general.back=Back
general.operationInProgress=عملية زوتيرو حالياً تحت الإجراء. general.operationInProgress=عملية زوتيرو حالياً تحت الإجراء.
general.operationInProgress.waitUntilFinished=يرجى الانتظار حتى انتهائها. general.operationInProgress.waitUntilFinished=يرجى الانتظار حتى انتهائها.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
app.standalone=النسخة المستقلة من زوتيرو app.standalone=النسخة المستقلة من زوتيرو
app.firefox=نسخة زوتيرو لمتصفح الفايرفوكس app.firefox=نسخة زوتيرو لمتصفح الفايرفوكس
startupError=هناك خطأ عند بدء تشغيل زوتيرو. startupError=There was an error starting %S.
startupError.databaseInUse=قاعدة بيانات زوتيرو لديك قيد الاستخدام. يمكن فتح نسخة واحدة فقط من زوتيرو تستخدم نفس قاعدة بيانات في وقت واحد حاليا. startupError.databaseInUse=قاعدة بيانات زوتيرو لديك قيد الاستخدام. يمكن فتح نسخة واحدة فقط من زوتيرو تستخدم نفس قاعدة بيانات في وقت واحد حاليا.
startupError.closeStandalone=إذا كانت النسخة المستقلة من زوتيرو مفتوحة، من فضلك قم بإغلاقها وإعادة تشغيل متصفح فايرفوكس. startupError.closeStandalone=إذا كانت النسخة المستقلة من زوتيرو مفتوحة، من فضلك قم بإغلاقها وإعادة تشغيل متصفح فايرفوكس.
startupError.closeFirefox=إذا كان متصفح فايرفوكس مع امتداد زوتيرو مفتوح، فمن فضلك قم بإغلاقه وإعادة تشغيل النسخة المستقلة من زوتيرو. startupError.closeFirefox=إذا كان متصفح فايرفوكس مع امتداد زوتيرو مفتوح، فمن فضلك قم بإغلاقه وإعادة تشغيل النسخة المستقلة من زوتيرو.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=تحويل لحقل ثنائي
pane.item.creator.moveToTop=تحريك لأعلى pane.item.creator.moveToTop=تحريك لأعلى
pane.item.creator.moveUp=تحريك لأعلى pane.item.creator.moveUp=تحريك لأعلى
pane.item.creator.moveDown=تحريك لأسفل pane.item.creator.moveDown=تحريك لأسفل
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=ملاحظة بدون عنوان pane.item.notes.untitled=ملاحظة بدون عنوان
pane.item.notes.delete.confirm=هل ترغب في حذف هذه الملاحظة؟ pane.item.notes.delete.confirm=هل ترغب في حذف هذه الملاحظة؟
pane.item.notes.count.zero=لا توجد ملاحظات: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S ملاحظات:
pane.item.notes.count.plural=%S ملاحظة:
pane.item.notes.editingInWindow=Editing in separate window pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=عنوان جديد: pane.item.attachments.rename.title=عنوان جديد:
pane.item.attachments.rename.renameAssociatedFile=إعادة تسمية الملف المرتبط pane.item.attachments.rename.renameAssociatedFile=إعادة تسمية الملف المرتبط
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S ارتباطات:
pane.item.related.count.plural=%S ارتباط: pane.item.related.count.plural=%S ارتباط:
pane.item.parentItem=عنصر رئيسي: 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=تحرير ملاحظة noteEditor.editNote=تحرير ملاحظة
itemTypes.note=ملاحظة itemTypes.note=ملاحظة
itemTypes.annotation=Annotation
itemTypes.attachment=مرفق itemTypes.attachment=مرفق
itemTypes.book=كتاب itemTypes.book=كتاب
itemTypes.bookSection=قسم في كتاب itemTypes.bookSection=قسم في كتاب
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=بي دي إف (%S)
findPDF.noPDFsFound=لا يوجد ملفات بي دي إف findPDF.noPDFsFound=لا يوجد ملفات بي دي إف
findPDF.noPDFFound=لم يتم العثور على بي دي إف findPDF.noPDFFound=لم يتم العثور على بي دي إف
note.annotationsWithDate=Annotations (%S)
attachment.fullText=نص كامل attachment.fullText=نص كامل
attachment.acceptedVersion=نسخة مقبولة attachment.acceptedVersion=نسخة مقبولة
attachment.submittedVersion=نسخة مقدمة attachment.submittedVersion=نسخة مقدمة
@ -788,7 +796,8 @@ searchConditions.dateModified=تاريخ التعديل
searchConditions.fulltextContent=محتويات المرفق searchConditions.fulltextContent=محتويات المرفق
searchConditions.programmingLanguage=لغة البرمجة searchConditions.programmingLanguage=لغة البرمجة
searchConditions.fileTypeID=نوع الملف المرفق searchConditions.fileTypeID=نوع الملف المرفق
searchConditions.annotation=تعليق searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=مكشف fulltext.indexState.indexed=مكشف
fulltext.indexState.unavailable=غير معروف fulltext.indexState.unavailable=غير معروف
@ -797,6 +806,7 @@ fulltext.indexState.queued=في الانتظار
exportOptions.exportNotes=تصدير الملاحظات exportOptions.exportNotes=تصدير الملاحظات
exportOptions.exportFileData=تصدير الملفات exportOptions.exportFileData=تصدير الملفات
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=استخدم اختصار الدورية exportOptions.useJournalAbbreviation=استخدم اختصار الدورية
charset.UTF8withoutBOM=يونيكود (UTF-8 بدون BOM) charset.UTF8withoutBOM=يونيكود (UTF-8 بدون BOM)
charset.autoDetect=(التعرف التلقائي) 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.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.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.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.title=ثبت نمط
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S" 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.showInLibrary "Показване в библиотеката">
<!ENTITY zotero.items.menu.attach.note "Добавяне на бележка"> <!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 "Добавяне на прикачен файл">
<!ENTITY zotero.items.menu.attach.link.uri "Добавяне на връзка към URI…"> <!ENTITY zotero.items.menu.attach.link.uri "Добавяне на връзка към URI…">
<!ENTITY zotero.items.menu.attach.file "Добавяне на съхранено копие на файл от твърдия диск..."> <!ENTITY zotero.items.menu.attach.file "Добавяне на съхранено копие на файл от твърдия диск...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Запазване на копие от файл"> <!ENTITY zotero.toolbar.attachment.add "Запазване на копие от файл">
<!ENTITY zotero.toolbar.attachment.weblink "Запазване на връзка към настоящата страница"> <!ENTITY zotero.toolbar.attachment.weblink "Запазване на връзка към настоящата страница">
<!ENTITY zotero.toolbar.attachment.snapshot "Направи &quot;моментна снимка&quot; на настоящата страница"> <!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.noTagsToDisplay "Няма маркери, които да бъдат показани">
<!ENTITY zotero.tagSelector.loadingTags "Зареждане на маркери..."> <!ENTITY zotero.tagSelector.loadingTags "Зареждане на маркери...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Прикачване на линк към URI"> <!ENTITY zotero.attachLink.title "Прикачване на линк към URI">
<!ENTITY zotero.attachLink.label.link "Линк:"> <!ENTITY zotero.attachLink.label.link "Линк:">
<!ENTITY zotero.attachLink.label.title "Заглавие:"> <!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.nMegabytes=%S МБ
general.item=Елемент general.item=Елемент
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Операция на Зотеро е активна в момента. general.operationInProgress=Операция на Зотеро е активна в момента.
general.operationInProgress.waitUntilFinished=Моля изчакайте докато приключи. general.operationInProgress.waitUntilFinished=Моля изчакайте докато приключи.
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
app.standalone=Zotero Standalone app.standalone=Zotero Standalone
app.firefox=Zotero for Firefox 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.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.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. 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.moveToTop=Move to Top
pane.item.creator.moveUp=Move Up pane.item.creator.moveUp=Move Up
pane.item.creator.moveDown=Move Down pane.item.creator.moveDown=Move Down
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Бележка без име pane.item.notes.untitled=Бележка без име
pane.item.notes.delete.confirm=Сигурни ли сте, че искате да изтриете тази бележка? pane.item.notes.delete.confirm=Сигурни ли сте, че искате да изтриете тази бележка?
pane.item.notes.count.zero=%S бележки: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S бележка:
pane.item.notes.count.plural=%S бележки:
pane.item.notes.editingInWindow=Editing in separate window pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=Ново заглавие: pane.item.attachments.rename.title=Ново заглавие:
pane.item.attachments.rename.renameAssociatedFile=Преименува приложеният файл pane.item.attachments.rename.renameAssociatedFile=Преименува приложеният файл
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S близък:
pane.item.related.count.plural=%S близки: pane.item.related.count.plural=%S близки:
pane.item.parentItem=Parent Item: 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=Редактира бележка noteEditor.editNote=Редактира бележка
itemTypes.note=Бележка itemTypes.note=Бележка
itemTypes.annotation=Annotation
itemTypes.attachment=Приложение itemTypes.attachment=Приложение
itemTypes.book=Книга itemTypes.book=Книга
itemTypes.bookSection=Глава от книга itemTypes.bookSection=Глава от книга
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=No PDFs found findPDF.noPDFsFound=No PDFs found
findPDF.noPDFFound=No PDF found findPDF.noPDFFound=No PDF found
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Full Text attachment.fullText=Full Text
attachment.acceptedVersion=Accepted Version attachment.acceptedVersion=Accepted Version
attachment.submittedVersion=Submitted Version attachment.submittedVersion=Submitted Version
@ -788,7 +796,8 @@ searchConditions.dateModified=Променен на
searchConditions.fulltextContent=Съдържание на приложението searchConditions.fulltextContent=Съдържание на приложението
searchConditions.programmingLanguage=Програмен език searchConditions.programmingLanguage=Програмен език
searchConditions.fileTypeID=Вид приложен файл searchConditions.fileTypeID=Вид приложен файл
searchConditions.annotation=Анотация searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Индексиран fulltext.indexState.indexed=Индексиран
fulltext.indexState.unavailable=Неизвестен fulltext.indexState.unavailable=Неизвестен
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Износ на бележки exportOptions.exportNotes=Износ на бележки
exportOptions.exportFileData=Износ на Файлове exportOptions.exportFileData=Износ на Файлове
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Уникод (UTF-8 без BOM) charset.UTF8withoutBOM=Уникод (UTF-8 без BOM)
charset.autoDetect=(автоматично откриване) 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.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.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.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.title=Install Style
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S" 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.showInLibrary "Diskouez el levraoueg:">
<!ENTITY zotero.items.menu.attach.note "Ouzhpennañ un notenn"> <!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 "Ouzhpennañ ur pezh-stag">
<!ENTITY zotero.items.menu.attach.link.uri "Stagañ ul liamm betek un URI…"> <!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..."> <!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.add "Stokañ un eilenn eus ar restr...">
<!ENTITY zotero.toolbar.attachment.weblink "Enrollañ al liamm d'ar bajenn a-vremañ"> <!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.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.noTagsToDisplay "Baliz ebet da ziskouez">
<!ENTITY zotero.tagSelector.loadingTags "O kargañ ar balizoù..."> <!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.title "Stagañ ul liamm betek un URI">
<!ENTITY zotero.attachLink.label.link "Liamm:"> <!ENTITY zotero.attachLink.label.link "Liamm:">
<!ENTITY zotero.attachLink.label.title "Titl:"> <!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.nMegabytes=%S Mo
general.item=Elfenn general.item=Elfenn
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Un oberiadenn a-berzh Zotero a zo war ober er mare-mañ. general.operationInProgress=Un oberiadenn a-berzh Zotero a zo war ober er mare-mañ.
general.operationInProgress.waitUntilFinished=Gortozit betek ma vo echuet. 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.moveToTop=Fiñval d'al laez
pane.item.creator.moveUp=War-grec'h pane.item.creator.moveUp=War-grec'h
pane.item.creator.moveDown=War-draoñ pane.item.creator.moveDown=War-draoñ
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Notenn hep-titl 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.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=%1$S note;%1$S notes
pane.item.notes.count.singular=%S notenn:
pane.item.notes.count.plural=%S a notennoù:
pane.item.notes.editingInWindow=Oc'h aozañ en ur prenestr nevez pane.item.notes.editingInWindow=Oc'h aozañ en ur prenestr nevez
pane.item.attachments.rename.title=Titl nevez: pane.item.attachments.rename.title=Titl nevez:
pane.item.attachments.rename.renameAssociatedFile=Adenvel ar restr kevelet 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.related.count.plural=%S kevreet:
pane.item.parentItem=Elfenn kar: 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 noteEditor.editNote=Aozañ an notenn
itemTypes.note=Notenn itemTypes.note=Notenn
itemTypes.annotation=Annotation
itemTypes.attachment=Pezh-stag itemTypes.attachment=Pezh-stag
itemTypes.book=Levr itemTypes.book=Levr
itemTypes.bookSection=Chabistr levr itemTypes.bookSection=Chabistr levr
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=PDF ebet kavet findPDF.noPDFsFound=PDF ebet kavet
findPDF.noPDFFound=PDF ebet kavet findPDF.noPDFFound=PDF ebet kavet
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Testenn a-bezh attachment.fullText=Testenn a-bezh
attachment.acceptedVersion=Stumm asantet attachment.acceptedVersion=Stumm asantet
attachment.submittedVersion=Stumm kaset attachment.submittedVersion=Stumm kaset
@ -788,7 +796,8 @@ searchConditions.dateModified=Deiziad kemmañ
searchConditions.fulltextContent=Endalc'had ar pezh-stag searchConditions.fulltextContent=Endalc'had ar pezh-stag
searchConditions.programmingLanguage=Langaj programiñ searchConditions.programmingLanguage=Langaj programiñ
searchConditions.fileTypeID=Doare restr-stag searchConditions.fileTypeID=Doare restr-stag
searchConditions.annotation=Ennotadur searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indekset fulltext.indexState.indexed=Indekset
fulltext.indexState.unavailable=Dianav fulltext.indexState.unavailable=Dianav
@ -797,6 +806,7 @@ fulltext.indexState.queued=War-c'hortoz
exportOptions.exportNotes=Ezporzhiañ notennoù exportOptions.exportNotes=Ezporzhiañ notennoù
exportOptions.exportFileData=Ezporzhiañ restroù exportOptions.exportFileData=Ezporzhiañ restroù
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Implijout berradurioù ar c'helaouennoù exportOptions.useJournalAbbreviation=Implijout berradurioù ar c'helaouennoù
charset.UTF8withoutBOM=Unikod (UTF-8 hep BOM) charset.UTF8withoutBOM=Unikod (UTF-8 hep BOM)
charset.autoDetect=(em-ziguzhiñ) 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.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.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.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.title=Staliañ ar stil
styles.install.unexpectedError=Ur fazi dic'hortoz a zo c'hoarvezet en ur staliañ "%1$S" 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.showInLibrary "Mostra a la biblioteca">
<!ENTITY zotero.items.menu.attach.note "Afegeix una nota"> <!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 "Afegeix un adjunt">
<!ENTITY zotero.items.menu.attach.link.uri "Adjunta un enllaç a l'URI..."> <!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..."> <!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.add "Emmagatzema una còpia del fitxer...">
<!ENTITY zotero.toolbar.attachment.weblink "Desa l'enllaç a la pàgina actual"> <!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.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.noTagsToDisplay "No hi ha etiquetes per mostrar">
<!ENTITY zotero.tagSelector.loadingTags "S'estan carregant les etiquetes…"> <!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.title "Adjunta l'enllaç a l'URI">
<!ENTITY zotero.attachLink.label.link "Enllaç:"> <!ENTITY zotero.attachLink.label.link "Enllaç:">
<!ENTITY zotero.attachLink.label.title "Títol:"> <!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.nMegabytes=%S MB
general.item=Element general.item=Element
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Una operació del Zotero està actualment en curs. general.operationInProgress=Una operació del Zotero està actualment en curs.
general.operationInProgress.waitUntilFinished=Espereu fins que hagi acabat. 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.standalone=Zotero Independent
app.firefox=Zotero per Firefox 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.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.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. 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.moveToTop=Mou a dalt
pane.item.creator.moveUp=Mou amunt pane.item.creator.moveUp=Mou amunt
pane.item.creator.moveDown=Mou avall pane.item.creator.moveDown=Mou avall
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Nota sense títol pane.item.notes.untitled=Nota sense títol
pane.item.notes.delete.confirm=Segur que voleu eliminar aquesta nota? pane.item.notes.delete.confirm=Segur que voleu eliminar aquesta nota?
pane.item.notes.count.zero=Cap nota: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S nota:
pane.item.notes.count.plural=%S notes:
pane.item.notes.editingInWindow=Edició en finestra separada pane.item.notes.editingInWindow=Edició en finestra separada
pane.item.attachments.rename.title=Nou títol: pane.item.attachments.rename.title=Nou títol:
pane.item.attachments.rename.renameAssociatedFile=Canvia el nom del fitxer associat 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.related.count.plural=%S elements relacionats:
pane.item.parentItem=Element ascendent 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 noteEditor.editNote=Edita la nota
itemTypes.note=Nota itemTypes.note=Nota
itemTypes.annotation=Annotation
itemTypes.attachment=Fitxer adjunt itemTypes.attachment=Fitxer adjunt
itemTypes.book=Llibre itemTypes.book=Llibre
itemTypes.bookSection=Capítol d'un 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.noPDFsFound=No s'ha trobat cap PDF
findPDF.noPDFFound=No s'ha trobat cap PDF findPDF.noPDFFound=No s'ha trobat cap PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Text complet attachment.fullText=Text complet
attachment.acceptedVersion=Versió acceptada attachment.acceptedVersion=Versió acceptada
attachment.submittedVersion=Versió enviada attachment.submittedVersion=Versió enviada
@ -788,7 +796,8 @@ searchConditions.dateModified=Data de modificació
searchConditions.fulltextContent=Contingut de l'arxiu adjunt searchConditions.fulltextContent=Contingut de l'arxiu adjunt
searchConditions.programmingLanguage=Llengua de programació searchConditions.programmingLanguage=Llengua de programació
searchConditions.fileTypeID=Tipus de fitxer adjunt searchConditions.fileTypeID=Tipus de fitxer adjunt
searchConditions.annotation=Anotació searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexat fulltext.indexState.indexed=Indexat
fulltext.indexState.unavailable=Desconegut fulltext.indexState.unavailable=Desconegut
@ -797,6 +806,7 @@ fulltext.indexState.queued=En cua
exportOptions.exportNotes=Exporta les notes exportOptions.exportNotes=Exporta les notes
exportOptions.exportFileData=Exporta els fitxers adjunts exportOptions.exportFileData=Exporta els fitxers adjunts
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Utilitza l'abreviació de la publicació exportOptions.useJournalAbbreviation=Utilitza l'abreviació de la publicació
charset.UTF8withoutBOM=Unicode (UTF-8 sense BOM) charset.UTF8withoutBOM=Unicode (UTF-8 sense BOM)
charset.autoDetect=(Detecció automàtica) 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.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.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.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.title=Instal·la l'estil
styles.install.unexpectedError=S'ha produït un error inesperat durant la instal·lació de "%1$S" 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.showInLibrary "Ukázat v knihovně">
<!ENTITY zotero.items.menu.attach.note "Přidat poznámku"> <!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 "Přidat přílohu">
<!ENTITY zotero.items.menu.attach.link.uri "Připojit odkaz na URI..,"> <!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..."> <!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.add "Uložit kopii souboru...">
<!ENTITY zotero.toolbar.attachment.weblink "Uložit odkaz na aktuální stránku"> <!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.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.noTagsToDisplay "Žádné štítky k zobrazení">
<!ENTITY zotero.tagSelector.loadingTags "Nahrávám štítky..."> <!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.title "Připojit odkaz na URI">
<!ENTITY zotero.attachLink.label.link "Odkaz:"> <!ENTITY zotero.attachLink.label.link "Odkaz:">
<!ENTITY zotero.attachLink.label.title "Název:"> <!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.nMegabytes=%S MB
general.item=Položka general.item=Položka
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Právě probíhá operace se Zoterem. general.operationInProgress=Právě probíhá operace se Zoterem.
general.operationInProgress.waitUntilFinished=Počkejte prosím, dokud neskončí. 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.standalone=Samostatné Zotero
app.firefox=Zotero pro Firefox 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.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.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. 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.moveToTop=Přesunout nahoru
pane.item.creator.moveUp=Posunout nahoru pane.item.creator.moveUp=Posunout nahoru
pane.item.creator.moveDown=Posunout dolů pane.item.creator.moveDown=Posunout dolů
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Nepojmenovaná poznámka pane.item.notes.untitled=Nepojmenovaná poznámka
pane.item.notes.delete.confirm=Jste si jistý, že chcete smazat tuto poznámku? 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=%1$S note;%1$S notes
pane.item.notes.count.singular=%S poznámka:
pane.item.notes.count.plural=%S poznámek:
pane.item.notes.editingInWindow=Editace v samostatném okně pane.item.notes.editingInWindow=Editace v samostatném okně
pane.item.attachments.rename.title=Nový název: pane.item.attachments.rename.title=Nový název:
pane.item.attachments.rename.renameAssociatedFile=Přejmenovat asociovaný soubor 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.related.count.plural=%S souvisejících:
pane.item.parentItem=Rodičovská položka: 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 noteEditor.editNote=Upravit poznámku
itemTypes.note=Poznámka itemTypes.note=Poznámka
itemTypes.annotation=Annotation
itemTypes.attachment=Příloha itemTypes.attachment=Příloha
itemTypes.book=Kniha itemTypes.book=Kniha
itemTypes.bookSection=Kapitola knihy itemTypes.bookSection=Kapitola knihy
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Nenalezena žádná PDF findPDF.noPDFsFound=Nenalezena žádná PDF
findPDF.noPDFFound=Nenalezeno žádné PDF findPDF.noPDFFound=Nenalezeno žádné PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Plný text attachment.fullText=Plný text
attachment.acceptedVersion=Přijatá verze attachment.acceptedVersion=Přijatá verze
attachment.submittedVersion=Odeslaná verze attachment.submittedVersion=Odeslaná verze
@ -788,7 +796,8 @@ searchConditions.dateModified=Datum změny
searchConditions.fulltextContent=Obsah přílohy searchConditions.fulltextContent=Obsah přílohy
searchConditions.programmingLanguage=Programovací jazyk searchConditions.programmingLanguage=Programovací jazyk
searchConditions.fileTypeID=Typ souboru přílohy searchConditions.fileTypeID=Typ souboru přílohy
searchConditions.annotation=Anotace searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexováno fulltext.indexState.indexed=Indexováno
fulltext.indexState.unavailable=Neznámé fulltext.indexState.unavailable=Neznámé
@ -797,6 +806,7 @@ fulltext.indexState.queued=Ve frontě
exportOptions.exportNotes=Exportovat poznámky exportOptions.exportNotes=Exportovat poznámky
exportOptions.exportFileData=Exportovat soubory exportOptions.exportFileData=Exportovat soubory
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Použít zkrácený název časopisu exportOptions.useJournalAbbreviation=Použít zkrácený název časopisu
charset.UTF8withoutBOM=Unicode (UTF-8 bez BOM) charset.UTF8withoutBOM=Unicode (UTF-8 bez BOM)
charset.autoDetect=(automaticky detekovat) 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.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.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.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.title=instalovat Styl
styles.install.unexpectedError=Při instalaci "%1$S" došlo k neočekávané chybě 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.showInLibrary "Vis i bibliotek">
<!ENTITY zotero.items.menu.attach.note "Tilføj note"> <!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 "Vedhæft">
<!ENTITY zotero.items.menu.attach.link.uri "Vedhæft link til URI..."> <!ENTITY zotero.items.menu.attach.link.uri "Vedhæft link til URI...">
<!ENTITY zotero.items.menu.attach.file "Vedhæft en gemt kopi af ..."> <!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.add "Gem kopi af filen...">
<!ENTITY zotero.toolbar.attachment.weblink "Gem link til den aktuelle side"> <!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.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.noTagsToDisplay "Der er ingen mærker at vise">
<!ENTITY zotero.tagSelector.loadingTags "Indlæser mærker..."> <!ENTITY zotero.tagSelector.loadingTags "Indlæser mærker...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Tilføj link til URI"> <!ENTITY zotero.attachLink.title "Tilføj link til URI">
<!ENTITY zotero.attachLink.label.link "Link:"> <!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Titel:"> <!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.nMegabytes=%S MB
general.item=Element general.item=Element
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=En handling i Zotero er ved at blive udført. general.operationInProgress=En handling i Zotero er ved at blive udført.
general.operationInProgress.waitUntilFinished=Vent venligst til den er færdig. 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.standalone=Zotero Standalone
app.firefox=Zotero til Firefox 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.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.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. 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.moveToTop=Flyt øverst
pane.item.creator.moveUp=Flyt op pane.item.creator.moveUp=Flyt op
pane.item.creator.moveDown=Flyt ned pane.item.creator.moveDown=Flyt ned
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Note uden titel pane.item.notes.untitled=Note uden titel
pane.item.notes.delete.confirm=Er du sikker på, du vil slette denne note? 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=%1$S note;%1$S notes
pane.item.notes.count.singular=%S note:
pane.item.notes.count.plural=%S noter:
pane.item.notes.editingInWindow=Editing in separate window pane.item.notes.editingInWindow=Editing in separate window
pane.item.attachments.rename.title=Ny titel: pane.item.attachments.rename.title=Ny titel:
pane.item.attachments.rename.renameAssociatedFile=Omdøb en tilhørende fil 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.related.count.plural=%S relaterede:
pane.item.parentItem=Overordnet element: 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 noteEditor.editNote=Redigér note
itemTypes.note=Note itemTypes.note=Note
itemTypes.annotation=Annotation
itemTypes.attachment=Vedhæftning itemTypes.attachment=Vedhæftning
itemTypes.book=Bog itemTypes.book=Bog
itemTypes.bookSection=Bidrag til bog itemTypes.bookSection=Bidrag til bog
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Fandt ingen PDF'er findPDF.noPDFsFound=Fandt ingen PDF'er
findPDF.noPDFFound=Fandt ingen PDF findPDF.noPDFFound=Fandt ingen PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Fuldtekst attachment.fullText=Fuldtekst
attachment.acceptedVersion=Antaget version attachment.acceptedVersion=Antaget version
attachment.submittedVersion=Indsendt version attachment.submittedVersion=Indsendt version
@ -788,7 +796,8 @@ searchConditions.dateModified=Ændringsdato
searchConditions.fulltextContent=Indhold i Vedhæftning searchConditions.fulltextContent=Indhold i Vedhæftning
searchConditions.programmingLanguage=Programmeringssprog searchConditions.programmingLanguage=Programmeringssprog
searchConditions.fileTypeID=Filtype (vedhæftn.) searchConditions.fileTypeID=Filtype (vedhæftn.)
searchConditions.annotation=Annotering searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indekseret fulltext.indexState.indexed=Indekseret
fulltext.indexState.unavailable=Ukendt fulltext.indexState.unavailable=Ukendt
@ -797,6 +806,7 @@ fulltext.indexState.queued=Sat i kø
exportOptions.exportNotes=Eksportér noter exportOptions.exportNotes=Eksportér noter
exportOptions.exportFileData=Eksportér filer exportOptions.exportFileData=Eksportér filer
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Anvend tidsskriftforkortelse exportOptions.useJournalAbbreviation=Anvend tidsskriftforkortelse
charset.UTF8withoutBOM=Unicode (UTF-8 uden BOM) charset.UTF8withoutBOM=Unicode (UTF-8 uden BOM)
charset.autoDetect=(auto-genkend.) 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.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.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.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.title=Installér bibliografisk format
styles.install.unexpectedError=Der opstod en uventet fejl under forsøget på at installere "%1$S" 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.showInLibrary "In Bibliothek anzeigen">
<!ENTITY zotero.items.menu.attach.note "Notiz hinzufügen"> <!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 "Anhang hinzufügen">
<!ENTITY zotero.items.menu.attach.link.uri "Link zu URI 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..."> <!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.add "Kopie einer Datei speichern...">
<!ENTITY zotero.toolbar.attachment.weblink "Link zur aktuellen Seite speichern"> <!ENTITY zotero.toolbar.attachment.weblink "Link zur aktuellen Seite speichern">
<!ENTITY zotero.toolbar.attachment.snapshot "Schnappschuss von aktueller Seite machen"> <!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.noTagsToDisplay "Keine Tags vorhanden">
<!ENTITY zotero.tagSelector.loadingTags "Lade Tags..."> <!ENTITY zotero.tagSelector.loadingTags "Lade Tags...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Link zu einer URI anhängen"> <!ENTITY zotero.attachLink.title "Link zu einer URI anhängen">
<!ENTITY zotero.attachLink.label.link "Link:"> <!ENTITY zotero.attachLink.label.link "Link:">
<!ENTITY zotero.attachLink.label.title "Titel:"> <!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.nMegabytes=%S MB
general.item=Eintrag general.item=Eintrag
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Zotero ist beschäftigt. general.operationInProgress=Zotero ist beschäftigt.
general.operationInProgress.waitUntilFinished=Bitte warten Sie, bis der Vorgang abgeschlossen ist. 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.standalone=Zotero Standalone
app.firefox=Zotero für Firefox 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.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.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. 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.moveToTop=An den Anfang verschieben
pane.item.creator.moveUp=Nach oben verschieben pane.item.creator.moveUp=Nach oben verschieben
pane.item.creator.moveDown=Nach unten verschieben pane.item.creator.moveDown=Nach unten verschieben
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Notiz ohne Titel 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.delete.confirm=Sind Sie sicher, dass Sie diese Notiz löschen möchten?
pane.item.notes.count.zero=%S Notizen: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S Notiz:
pane.item.notes.count.plural=%S Notizen:
pane.item.notes.editingInWindow=In einem neuen Fenster bearbeiten pane.item.notes.editingInWindow=In einem neuen Fenster bearbeiten
pane.item.attachments.rename.title=Neuer Titel: pane.item.attachments.rename.title=Neuer Titel:
pane.item.attachments.rename.renameAssociatedFile=Zugehörige Datei umbenennen 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.related.count.plural=%S verwandte Einträge:
pane.item.parentItem=Übergeordneter Eintrag: 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 noteEditor.editNote=Notiz bearbeiten
itemTypes.note=Notiz itemTypes.note=Notiz
itemTypes.annotation=Annotation
itemTypes.attachment=Anhang itemTypes.attachment=Anhang
itemTypes.book=Buch itemTypes.book=Buch
itemTypes.bookSection=Buchteil itemTypes.bookSection=Buchteil
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Keine PDFs gefunden findPDF.noPDFsFound=Keine PDFs gefunden
findPDF.noPDFFound=Kein PDF gefunden findPDF.noPDFFound=Kein PDF gefunden
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Volltext attachment.fullText=Volltext
attachment.acceptedVersion=Akzeptierte Version attachment.acceptedVersion=Akzeptierte Version
attachment.submittedVersion=Eingereichte Version attachment.submittedVersion=Eingereichte Version
@ -788,7 +796,8 @@ searchConditions.dateModified=verändert am
searchConditions.fulltextContent=Inhalt des Anhangs searchConditions.fulltextContent=Inhalt des Anhangs
searchConditions.programmingLanguage=Programmiersprache searchConditions.programmingLanguage=Programmiersprache
searchConditions.fileTypeID=Dateityp des Anhangs searchConditions.fileTypeID=Dateityp des Anhangs
searchConditions.annotation=Anmerkung searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indiziert fulltext.indexState.indexed=Indiziert
fulltext.indexState.unavailable=Unbekannt fulltext.indexState.unavailable=Unbekannt
@ -797,6 +806,7 @@ fulltext.indexState.queued=in Warteschlange
exportOptions.exportNotes=Notizen exportieren exportOptions.exportNotes=Notizen exportieren
exportOptions.exportFileData=Dateien exportieren exportOptions.exportFileData=Dateien exportieren
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Abgekürzte Zeitschriftentitel verwenden exportOptions.useJournalAbbreviation=Abgekürzte Zeitschriftentitel verwenden
charset.UTF8withoutBOM=Unicode (UTF-8 ohne BOM) charset.UTF8withoutBOM=Unicode (UTF-8 ohne BOM)
charset.autoDetect=(automatisch erkennen) 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.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.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.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.title=Zitierstil installieren
styles.install.unexpectedError=Bei der Installation von "%1$S" trat ein unerwarteter Fehler auf 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.showInLibrary "Εμφάνιση στην Βιβλιοθήκη">
<!ENTITY zotero.items.menu.attach.note "Προσθήκη σημείωσης"> <!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 "Προσθήκη προσαρτήματος">
<!ENTITY zotero.items.menu.attach.link.uri "Προσάρτηση Συνδέσμου προς το URI..."> <!ENTITY zotero.items.menu.attach.link.uri "Προσάρτηση Συνδέσμου προς το URI...">
<!ENTITY zotero.items.menu.attach.file "Προσκόληση αποθηκευμένου αντιγράφου αρχείου..."> <!ENTITY zotero.items.menu.attach.file "Προσκόληση αποθηκευμένου αντιγράφου αρχείου...">
@ -155,6 +156,8 @@
<!ENTITY zotero.toolbar.attachment.add "Αποθήκευση αντιγράφου του αρχείου..."> <!ENTITY zotero.toolbar.attachment.add "Αποθήκευση αντιγράφου του αρχείου...">
<!ENTITY zotero.toolbar.attachment.weblink "Αποθήκευση Συνδέσμου στην Τρέχουσα Σελίδα"> <!ENTITY zotero.toolbar.attachment.weblink "Αποθήκευση Συνδέσμου στην Τρέχουσα Σελίδα">
<!ENTITY zotero.toolbar.attachment.snapshot "Λήψη Στιγμιότυπου της Τρέχουσας Σελίδας"> <!ENTITY zotero.toolbar.attachment.snapshot "Λήψη Στιγμιότυπου της Τρέχουσας Σελίδας">
<!ENTITY zotero.toolbar.context.item "Item">
<!ENTITY zotero.toolbar.context.notes "Notes">
<!ENTITY zotero.tagSelector.noTagsToDisplay "Δεν υπάρχουν ετικέτες προς εμφάνιση"> <!ENTITY zotero.tagSelector.noTagsToDisplay "Δεν υπάρχουν ετικέτες προς εμφάνιση">
<!ENTITY zotero.tagSelector.loadingTags "Φόρτωση ετικετών..."> <!ENTITY zotero.tagSelector.loadingTags "Φόρτωση ετικετών...">
@ -325,3 +328,8 @@
<!ENTITY zotero.attachLink.title "Προσάρτηση συνδέσμου στο URI"> <!ENTITY zotero.attachLink.title "Προσάρτηση συνδέσμου στο URI">
<!ENTITY zotero.attachLink.label.link "Σύνδεσμος:"> <!ENTITY zotero.attachLink.label.link "Σύνδεσμος:">
<!ENTITY zotero.attachLink.label.title "Τίτλος:"> <!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.nMegabytes=%S MB
general.item=Είδος general.item=Είδος
general.pdf=PDF general.pdf=PDF
general.back=Back
general.operationInProgress=Αυτή τη στιγμή εκτελείται κάποια λειτουργία Zotero. general.operationInProgress=Αυτή τη στιγμή εκτελείται κάποια λειτουργία Zotero.
general.operationInProgress.waitUntilFinished=Παρακαλώ περιμένετε έως ότου ολοκληρωθεί. general.operationInProgress.waitUntilFinished=Παρακαλώ περιμένετε έως ότου ολοκληρωθεί.
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Μετάβαση σε δύο πεδία
pane.item.creator.moveToTop=Μετακίνηση προς τα επάνω pane.item.creator.moveToTop=Μετακίνηση προς τα επάνω
pane.item.creator.moveUp=Μετακίνηση Επάνω pane.item.creator.moveUp=Μετακίνηση Επάνω
pane.item.creator.moveDown=Μετακίνηση Κάτω pane.item.creator.moveDown=Μετακίνηση Κάτω
pane.item.notes.allNotes=All Notes
pane.item.notes.untitled=Σημείωση χωρίς τίτλο pane.item.notes.untitled=Σημείωση χωρίς τίτλο
pane.item.notes.delete.confirm=Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη σημείωση; pane.item.notes.delete.confirm=Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη σημείωση;
pane.item.notes.count.zero=%S σημειώσεις: pane.item.notes.count=%1$S note;%1$S notes
pane.item.notes.count.singular=%S σημείωση:
pane.item.notes.count.plural=%S σημειώσεις:
pane.item.notes.editingInWindow=Επεξεργασία σε ξεχωριστό παράθυρο pane.item.notes.editingInWindow=Επεξεργασία σε ξεχωριστό παράθυρο
pane.item.attachments.rename.title=Νέος τίτλος: pane.item.attachments.rename.title=Νέος τίτλος:
pane.item.attachments.rename.renameAssociatedFile=Μετονομασία συσχετισμένου αρχείου pane.item.attachments.rename.renameAssociatedFile=Μετονομασία συσχετισμένου αρχείου
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S σχετικό:
pane.item.related.count.plural=%S σχετικό: pane.item.related.count.plural=%S σχετικό:
pane.item.parentItem=Γονικό Στοιχείο: 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=Επεξεργασία Σημείωσης noteEditor.editNote=Επεξεργασία Σημείωσης
itemTypes.note=Σημείωση itemTypes.note=Σημείωση
itemTypes.annotation=Annotation
itemTypes.attachment=Συνημμένο itemTypes.attachment=Συνημμένο
itemTypes.book=Βιβλίο itemTypes.book=Βιβλίο
itemTypes.bookSection=Ενότητα Βιβλίου itemTypes.bookSection=Ενότητα Βιβλίου
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
findPDF.noPDFsFound=Δεν βρέθηκαν αρχεία PDF findPDF.noPDFsFound=Δεν βρέθηκαν αρχεία PDF
findPDF.noPDFFound=Δεν βρέθηκε PDF findPDF.noPDFFound=Δεν βρέθηκε PDF
note.annotationsWithDate=Annotations (%S)
attachment.fullText=Πλήρες Κείμενο attachment.fullText=Πλήρες Κείμενο
attachment.acceptedVersion=Αποδεκτή Έκδοση attachment.acceptedVersion=Αποδεκτή Έκδοση
attachment.submittedVersion=Υποβληθείσα Έκδοση attachment.submittedVersion=Υποβληθείσα Έκδοση
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
searchConditions.fulltextContent=Attachment Content searchConditions.fulltextContent=Attachment Content
searchConditions.programmingLanguage=Programming Language searchConditions.programmingLanguage=Programming Language
searchConditions.fileTypeID=Attachment File Type searchConditions.fileTypeID=Attachment File Type
searchConditions.annotation=Annotation searchConditions.annotationText=Annotation Text
searchConditions.annotationComment=Annotation Comment
fulltext.indexState.indexed=Indexed fulltext.indexState.indexed=Indexed
fulltext.indexState.unavailable=Unknown fulltext.indexState.unavailable=Unknown
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
exportOptions.exportNotes=Export Notes exportOptions.exportNotes=Export Notes
exportOptions.exportFileData=Export Files exportOptions.exportFileData=Export Files
exportOptions.includeAnnotations=Include Annotations
exportOptions.useJournalAbbreviation=Use Journal Abbreviation exportOptions.useJournalAbbreviation=Use Journal Abbreviation
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM) charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
charset.autoDetect=(auto detect) charset.autoDetect=(auto detect)
@ -930,6 +940,7 @@ integration.exportDocument.title=Προετοιμάστε τις παραπομ
integration.exportDocument.description1=Το Zotero θα μετατρέψει τις παραπομπές στο έγγραφο σε μορφή που μπορεί να μεταφερθεί με ασφάλεια σε άλλο υποστηριζόμενο επεξεργαστή κειμένου. integration.exportDocument.description1=Το Zotero θα μετατρέψει τις παραπομπές στο έγγραφο σε μορφή που μπορεί να μεταφερθεί με ασφάλεια σε άλλο υποστηριζόμενο επεξεργαστή κειμένου.
integration.exportDocument.description2=Πριν προχωρήσετε, θα πρέπει να δημιουργήσετε αντίγραφο ασφαλείας του εγγράφου. integration.exportDocument.description2=Πριν προχωρήσετε, θα πρέπει να δημιουργήσετε αντίγραφο ασφαλείας του εγγράφου.
integration.importInstructions=Οι αναφορές Zotero σε αυτό το έγγραφο έχουν μετατραπεί σε μορφή που μπορεί να μεταφερθεί με ασφάλεια μεταξύ επεξεργαστών κειμένου. Ανοίξτε αυτό το έγγραφο σε υποστηριζόμενο επεξεργαστή κειμένου και πατήστε Ανανέωση -Refresh- στο πρόσθετο Zotero για να συνεχίσετε να εργάζεστε με τις παραπομπές. 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.title=Εγκατάσταση Στυλ
styles.install.unexpectedError=Παρουσιάστηκε μη αναμενόμενο σφάλμα κατά την εγκατάσταση του "%1$S" styles.install.unexpectedError=Παρουσιάστηκε μη αναμενόμενο σφάλμα κατά την εγκατάσταση του "%1$S"

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