Merge PDF reader branch
This commit is contained in:
commit
f0992f959c
254 changed files with 13206 additions and 4178 deletions
12
.gitmodules
vendored
12
.gitmodules
vendored
|
@ -29,3 +29,15 @@
|
|||
[submodule "resource/SingleFile"]
|
||||
path = resource/SingleFile
|
||||
url = https://github.com/gildas-lormeau/SingleFile.git
|
||||
[submodule "pdf-reader"]
|
||||
path = pdf-reader
|
||||
url = ssh://git@github.com/zotero/pdf-reader.git
|
||||
branch = master
|
||||
[submodule "pdf-worker"]
|
||||
path = pdf-worker
|
||||
url = ssh://git@github.com/zotero/pdf-worker.git
|
||||
branch = master
|
||||
[submodule "note-editor"]
|
||||
path = note-editor
|
||||
url = ssh://git@github.com/zotero/note-editor.git
|
||||
branch = master
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* Use standard tab appearance for item pane tabs */
|
||||
#zotero-pane #zotero-view-tabbox > tabs > tab {
|
||||
.zotero-view-tabbox > tabs > tab {
|
||||
-moz-appearance: tab;
|
||||
}
|
||||
|
||||
/* Active tab label color in item pane and elsewhere */
|
||||
#zotero-pane tabs#zotero-editpane-tabs > tab[visuallyselected="true"][selected="true"] hbox > .tab-text,
|
||||
tabs.zotero-editpane-tabs > tab[visuallyselected="true"][selected="true"] hbox > .tab-text,
|
||||
#zotero-prefs tab[visuallyselected="true"]:not(:-moz-window-inactive),
|
||||
tabs > tab[visuallyselected="true"] hbox > .tab-text {
|
||||
color: black !important;
|
||||
|
@ -12,10 +12,6 @@ tabs > tab[visuallyselected="true"] hbox > .tab-text {
|
|||
}
|
||||
|
||||
/* Quick Format dialog, which is based on window corners, which are different on Big Sur */
|
||||
#quick-format-dialog #zotero-icon {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#quick-format-iframe {
|
||||
margin-top: 2px;
|
||||
}
|
|
@ -4,7 +4,7 @@ html > body {
|
|||
}
|
||||
|
||||
body {
|
||||
line-height: 1.45em;
|
||||
line-height: 1.6em;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
|
@ -12,45 +12,42 @@ body[multiline="true"] {
|
|||
line-height: 26px;
|
||||
}
|
||||
|
||||
#quick-format-dialog {
|
||||
window.citation-dialog {
|
||||
background: transparent;
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
#quick-format-dialog.progress-bar #quick-format-deck {
|
||||
.citation-dialog.progress-bar .citation-dialog.deck {
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
#quick-format-search {
|
||||
.citation-dialog.search {
|
||||
background: white;
|
||||
-moz-appearance: searchfield;
|
||||
}
|
||||
|
||||
#quick-format-search[multiline="true"] {
|
||||
padding: 2px 2px 0 19.5px;
|
||||
padding: 2px 2px 0 2px;
|
||||
margin: 2.5px 3.5px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
-moz-appearance: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#quick-format-search:not([multiline="true"]) {
|
||||
padding-top: 3.5px;
|
||||
height: 37px !important;
|
||||
.citation-dialog.search:not([multiline="true"]) {
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
#quick-format-entry {
|
||||
.citation-dialog.entry {
|
||||
background: -moz-linear-gradient(-90deg, rgb(243,123,119) 0, rgb(180,47,38) 50%, rgb(156,36,27) 50%);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#zotero-icon {
|
||||
margin: -2px 0 3px -6px;
|
||||
.note-dialog .citation-dialog.entry {
|
||||
background: -moz-linear-gradient(-90deg, rgb(249, 231, 179) 0, rgb(228, 193, 94) 50%, rgb(221, 184, 81) 50%);
|
||||
}
|
||||
|
||||
#quick-format-search[multiline="true"] #zotero-icon {
|
||||
margin: 0 0 1px -13px;
|
||||
#zotero-icon {
|
||||
margin: -1px 0 0 4px;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#citation-properties menulist {
|
||||
|
@ -106,7 +103,6 @@ panel button:-moz-focusring {
|
|||
box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
|
||||
}
|
||||
|
||||
.quick-format-bubble {
|
||||
margin-top: 0;
|
||||
.citation-dialog.bubble {
|
||||
padding: 1px 6px 1px 6px;
|
||||
}
|
|
@ -107,12 +107,12 @@ input {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox {
|
||||
.zotero-view-tabbox {
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zotero-item-pane-content .groupbox-body {
|
||||
.zotero-item-pane-content .groupbox-body {
|
||||
-moz-appearance: none;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
@ -121,13 +121,13 @@ input {
|
|||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabpanels {
|
||||
.zotero-view-tabbox > tabpanels {
|
||||
margin: 12px 0 0 0;
|
||||
padding: 0;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabs {
|
||||
.zotero-editpane-tabs {
|
||||
-moz-appearance: none;
|
||||
background: -moz-linear-gradient(top, #ededed, #cccccc);
|
||||
border-style: solid;
|
||||
|
@ -136,15 +136,15 @@ input {
|
|||
padding: 2px 0 2px 0;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabs > tab > hbox {
|
||||
.zotero-editpane-tabs > tab > hbox {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabs > tab > hbox > .tab-icon {
|
||||
.zotero-editpane-tabs > tab > hbox > .tab-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zotero-pane #zotero-view-tabbox > tabs > tab {
|
||||
.zotero-editpane-tabs > tab {
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-align: center;
|
||||
-moz-appearance: toolbarbutton;
|
||||
|
@ -153,7 +153,7 @@ input {
|
|||
padding: 3px 1px 3px 1px;
|
||||
}
|
||||
|
||||
#zotero-pane #zotero-view-tabbox > tabs > tab > hbox .tab-text {
|
||||
.zotero-editpane-tabs > tab > hbox .tab-text {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin: 2px 7px 2px 9px !important;
|
||||
|
@ -161,11 +161,11 @@ input {
|
|||
}
|
||||
|
||||
/* This seems to be necessary to center the tabs. Not sure why. */
|
||||
#zotero-pane #zotero-view-tabbox > tabs > tab:last-of-type > hbox .tab-text {
|
||||
.zotero-editpane-tabs > tab:last-of-type > hbox .tab-text {
|
||||
margin: 2px 9px 2px 9px !important;
|
||||
}
|
||||
|
||||
#zotero-pane #zotero-view-tabbox > tabs > tab[selected=true] > hbox .tab-text {
|
||||
.zotero-editpane-tabs > tab[selected=true] > hbox .tab-text {
|
||||
color: #FFF !important;
|
||||
text-shadow: rgba(0, 0, 0, 0.4) 0 1px;
|
||||
}
|
||||
|
@ -217,18 +217,21 @@ input {
|
|||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed])[orient=horizontal]
|
||||
#zotero-items-splitter:not([state=collapsed])[orient=horizontal],
|
||||
#zotero-context-splitter:not([state=collapsed])[orient=horizontal]
|
||||
{
|
||||
-moz-appearance: none;
|
||||
border-inline-start: 1px solid #bdbdbd;
|
||||
margin-inline-end: -4px;
|
||||
width: 5px !important;
|
||||
min-width: 5px;
|
||||
position: relative;
|
||||
/* Create a separate stacking context to be on top */
|
||||
opacity: 0.99;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#zotero-items-splitter[orient=vertical]
|
||||
#zotero-items-splitter[orient=vertical],
|
||||
#zotero-context-splitter-stacked
|
||||
{
|
||||
-moz-border-start: none !important;
|
||||
-moz-border-end: none !important;
|
||||
|
@ -239,17 +242,25 @@ input {
|
|||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-items-splitter:not([state=collapsed]) > grippy
|
||||
#zotero-items-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-context-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-context-splitter-stacked:not([state=collapsed]) > grippy
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed], #zotero-items-splitter[state=collapsed] {
|
||||
#zotero-collections-splitter[state=collapsed],
|
||||
#zotero-items-splitter[state=collapsed],
|
||||
#zotero-context-splitter[state=collapsed],
|
||||
#zotero-context-splitter-stacked[state=collapsed]
|
||||
{
|
||||
border: 0 solid #d6d6d6 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed], #zotero-items-splitter[state=collapsed][orient=horizontal]
|
||||
#zotero-collections-splitter[state=collapsed],
|
||||
#zotero-items-splitter[state=collapsed][orient=horizontal],
|
||||
#zotero-context-splitter[state=collapsed][orient=horizontal]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/mac/vsplitter.png");
|
||||
background-repeat: repeat-y;
|
||||
|
@ -258,7 +269,8 @@ input {
|
|||
width: 8px !important;
|
||||
}
|
||||
|
||||
#zotero-items-splitter[state=collapsed][orient=vertical]
|
||||
#zotero-items-splitter[state=collapsed][orient=vertical],
|
||||
#zotero-context-splitter-stacked[state=collapsed][orient=vertical]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/mac/hsplitter.png");
|
||||
background-repeat: repeat-x;
|
||||
|
@ -271,11 +283,17 @@ input {
|
|||
border-right-width: 1px !important;
|
||||
}
|
||||
|
||||
#zotero-items-splitter[state=collapsed] {
|
||||
#zotero-items-splitter[state=collapsed],
|
||||
#zotero-context-splitter[state=collapsed],
|
||||
#zotero-context-splitter-stacked[state=collapsed]
|
||||
{
|
||||
border-left-width: 1px !important;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter[state=collapsed] > grippy, #zotero-items-splitter[state=collapsed] > grippy
|
||||
#zotero-collections-splitter[state=collapsed] > grippy,
|
||||
#zotero-items-splitter[state=collapsed] > grippy,
|
||||
#zotero-context-splitter[state=collapsed] > grippy,
|
||||
#zotero-context-splitter-stacked[state=collapsed] > grippy
|
||||
{
|
||||
-moz-appearance: none;
|
||||
background: url(chrome://zotero/skin/mac/vgrippy.png) center/auto 8px no-repeat;
|
||||
|
@ -301,11 +319,20 @@ input {
|
|||
height: 8px;
|
||||
}
|
||||
|
||||
#zotero-tags-splitter > grippy:hover, #zotero-collections-splitter > grippy:hover, #zotero-items-splitter > grippy:hover
|
||||
#zotero-tags-splitter > grippy:hover,
|
||||
#zotero-collections-splitter > grippy:hover,
|
||||
#zotero-items-splitter > grippy:hover,
|
||||
#zotero-context-splitter > grippy:hover,
|
||||
#zotero-context-splitter-stacked > grippy:hover
|
||||
{
|
||||
background-color:transparent;
|
||||
}
|
||||
|
||||
#zotero-context-toolbar-extension {
|
||||
/* To cover #zotero-context-splitter 1px border */
|
||||
margin-inline-start: -1px;
|
||||
}
|
||||
|
||||
#zotero-items-tree
|
||||
{
|
||||
-moz-appearance: none;
|
||||
|
|
|
@ -2,18 +2,18 @@ body {
|
|||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
#quick-format-search:not([multiline="true"]) {
|
||||
.citation-dialog.search:not([multiline="true"]) {
|
||||
height: 29px !important;
|
||||
}
|
||||
|
||||
#quick-format-search {
|
||||
.citation-dialog.search {
|
||||
background: white;
|
||||
padding: 0 2px 0 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
#quick-format-dialog {
|
||||
window.citation-dialog {
|
||||
-moz-appearance: none;
|
||||
padding: 5px;
|
||||
}
|
||||
|
|
|
@ -11,20 +11,20 @@
|
|||
visibility: visible;
|
||||
}
|
||||
|
||||
#zotero-item-pane-content {
|
||||
.zotero-item-pane-content {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Make the item pane appear white (same colour as treeview), making the UI more consistent */
|
||||
#zotero-item-pane-content tab, #zotero-item-pane-content tabpanels {
|
||||
.zotero-item-pane-content tab, .zotero-item-pane-content tabpanels {
|
||||
background-color: -moz-Field; /* Same as background colour for treeview */
|
||||
}
|
||||
|
||||
/* Possibly irrelevant if mozilla fixes https://bugzilla.mozilla.org/show_bug.cgi?id=1306425 */
|
||||
#zotero-view-tabbox tabs tab[visuallyselected=true] {
|
||||
.zotero-view-tabbox tabs tab[visuallyselected=true] {
|
||||
margin-top: 0px !important; /* Importanter than ./itemPane.css:20 */
|
||||
margin-bottom: -2px !important; /* Importanter than skin/itemPane.css:12 */
|
||||
}
|
||||
#zotero-view-tabbox tabs tab {
|
||||
.zotero-view-tabbox tabs tab {
|
||||
margin-top: 2px !important; /* Importanter than skin/itemPane.css:11 */
|
||||
}
|
||||
|
|
|
@ -76,3 +76,9 @@ tab {
|
|||
background-color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#zotero-context-splitter-stacked {
|
||||
-moz-appearance: none;
|
||||
background-color: #ececec;
|
||||
border-top: 1px solid hsla(0, 0%, 0%, 0.2);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#quick-format-dialog {
|
||||
window.citation-dialog {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#quick-format-search {
|
||||
.citation-dialog.search {
|
||||
background: white;
|
||||
padding: 2px 2px 2px 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
|
@ -11,22 +12,20 @@
|
|||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#quick-format-dialog {
|
||||
background: transparent;
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#quick-format-search:not([multiline="true"]) {
|
||||
.citation-dialog.search:not([multiline="true"]) {
|
||||
height: 28px !important;
|
||||
}
|
||||
|
||||
#quick-format-entry {
|
||||
.citation-dialog.entry {
|
||||
background: -moz-linear-gradient(-90deg, rgb(243,123,119) 0, rgb(180,47,38) 50%, rgb(156,36,27) 50%);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#quick-format-entry:not([square="true"]) {
|
||||
.note-dialog .citation-dialog.entry {
|
||||
background: -moz-linear-gradient(-90deg, rgb(249, 231, 179) 0, rgb(228, 193, 94) 50%, rgb(221, 184, 81) 50%);
|
||||
}
|
||||
|
||||
.citation-dialog.entry:not([square="true"]) {
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
padding-top: 4px;
|
||||
}
|
||||
|
||||
#zotero-view-tabbox tab {
|
||||
.zotero-view-tabbox tab {
|
||||
padding-left: .7em;
|
||||
padding-right: .7em;
|
||||
}
|
||||
|
@ -61,7 +61,9 @@
|
|||
|
||||
#zotero-collections-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed]),
|
||||
#zotero-tags-splitter:not([state=collapsed]) {
|
||||
#zotero-tags-splitter:not([state=collapsed]),
|
||||
#zotero-context-splitter:not([state=collapsed]),
|
||||
#zotero-context-splitter-stacked:not([state=collapsed]) {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
|
@ -70,9 +72,15 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
#zotero-context-splitter:not([state=collapsed]),
|
||||
#zotero-context-splitter-stacked:not([state=collapsed]) {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed]):not([orient=vertical]),
|
||||
#zotero-tags-splitter:not([state=collapsed]) {
|
||||
#zotero-tags-splitter:not([state=collapsed]),
|
||||
#zotero-context-splitter:not([state=collapsed]) {
|
||||
border-inline-end: 1px solid var(--theme-border-color);
|
||||
min-width: 0;
|
||||
width: 3px;
|
||||
|
@ -80,7 +88,8 @@
|
|||
}
|
||||
|
||||
#zotero-tags-splitter:not([state=collapsed]),
|
||||
#zotero-items-splitter:not([state=collapsed])[orient=vertical] {
|
||||
#zotero-items-splitter:not([state=collapsed])[orient=vertical],
|
||||
#zotero-context-splitter-stacked:not([state=collapsed]) {
|
||||
border-block-end: 1px solid var(--theme-border-color);
|
||||
min-height: 0;
|
||||
height: 3px;
|
||||
|
@ -89,17 +98,20 @@
|
|||
|
||||
#zotero-collections-splitter > grippy,
|
||||
#zotero-items-splitter > grippy,
|
||||
#zotero-tags-splitter > grippy {
|
||||
#zotero-tags-splitter > grippy,
|
||||
#zotero-context-splitter > grippy {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#zotero-collections-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-items-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-tags-splitter:not([state=collapsed]) > grippy {
|
||||
#zotero-tags-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-context-splitter:not([state=collapsed]) > grippy,
|
||||
#zotero-context-splitter-stacked:not([state=collapsed]) > grippy {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#zotero-collections-tree, #zotero-items-tree, #zotero-view-item {
|
||||
#zotero-collections-tree, #zotero-items-tree, .zotero-view-item {
|
||||
-moz-appearance: none;
|
||||
border-style: solid;
|
||||
border-color: #818790;
|
||||
|
@ -142,11 +154,11 @@ tree {
|
|||
margin: .04em 0 0 .15em !important;
|
||||
}
|
||||
|
||||
#zotero-editpane-tabs spacer {
|
||||
.zotero-editpane-tabs spacer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#zotero-view-item {
|
||||
.zotero-view-item {
|
||||
padding: 0 !important;
|
||||
-moz-appearance: none;
|
||||
background-color: -moz-field;
|
||||
|
@ -154,7 +166,7 @@ tree {
|
|||
border-color: var(--theme-border-color);
|
||||
}
|
||||
|
||||
#zotero-view-tabbox > tabs {
|
||||
.zotero-editpane-tabs {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
|
@ -163,8 +175,8 @@ tree {
|
|||
border-width: 0;
|
||||
}
|
||||
|
||||
#zotero-editpane-item-box > scrollbox, #zotero-view-item > tabpanel > vbox,
|
||||
#zotero-editpane-tags > scrollbox, #zotero-editpane-related {
|
||||
.zotero-editpane-item-box > scrollbox, .zotero-view-item > tabpanel > vbox,
|
||||
#zotero-editpane-tags > scrollbox, .zotero-editpane-related {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
|
@ -172,6 +184,6 @@ tree {
|
|||
padding-left: 5px;
|
||||
}
|
||||
|
||||
#zotero-view-item > tabpanel > vbox {
|
||||
.zotero-view-item > tabpanel > vbox {
|
||||
padding-left: 5px;
|
||||
}
|
|
@ -360,9 +360,19 @@
|
|||
indexStatusRow.hidden = true;
|
||||
}
|
||||
|
||||
var type = Zotero.Libraries.get(this.item.libraryID).libraryType;
|
||||
var switherDeck = this._id('attachment-note-editor-switcher');
|
||||
// Note editor
|
||||
var noteEditor = this._id('attachment-note-editor');
|
||||
if (this.displayNote && (this.displayNoteIfEmpty || this.item.getNote() != '')) {
|
||||
if (type == 'group' || !Zotero.isPDFBuild) {
|
||||
var noteEditor = this._id('attachment-note-editor-old');
|
||||
switherDeck.selectedIndex = 0;
|
||||
}
|
||||
else {
|
||||
var noteEditor = this._id('attachment-note-editor');
|
||||
switherDeck.selectedIndex = 1;
|
||||
}
|
||||
|
||||
if (this.displayNote && (this.displayNoteIfEmpty || this.item.note != '')) {
|
||||
noteEditor.linksOnTop = true;
|
||||
noteEditor.hidden = false;
|
||||
|
||||
|
@ -624,7 +634,10 @@
|
|||
</grid>
|
||||
</vbox>
|
||||
|
||||
<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
|
||||
<deck id="attachment-note-editor-switcher" flex="1">
|
||||
<oldzoteronoteeditor id="attachment-note-editor-old" notitle="1" flex="1"/>
|
||||
<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
|
||||
</deck>
|
||||
|
||||
<button id="select-button" hidden="true"/>
|
||||
</vbox>
|
||||
|
|
|
@ -652,7 +652,7 @@
|
|||
|
||||
for (var i=0; i<itemTypes.length; i++) {
|
||||
var name = itemTypes[i].name;
|
||||
if (name != 'attachment' && name != 'note') {
|
||||
if (name != 'attachment' && name != 'note' && name != 'annotation') {
|
||||
this.itemTypeMenu.appendItem(itemTypes[i].localized, itemTypes[i].id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -335,7 +335,9 @@
|
|||
break;
|
||||
|
||||
case 'note':
|
||||
elementName = 'zoteronoteeditor';
|
||||
var type = Zotero.Libraries.get(this.libraryID).libraryType;
|
||||
var useOld = type == 'group' || !Zotero.isPDFBuild;
|
||||
elementName = useOld ? 'oldzoteronoteeditor' : 'zoteronoteeditor';
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -1,99 +1,205 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="note-editor">
|
||||
<resources>
|
||||
<stylesheet src="chrome://zotero/skin/bindings/noteeditor.css"/>
|
||||
<stylesheet src="chrome://zotero-platform/content/noteeditor.css"/>
|
||||
</resources>
|
||||
|
||||
|
||||
<implementation>
|
||||
<!--
|
||||
Public properties
|
||||
-->
|
||||
<field name="editable">false</field>
|
||||
<field name="saveOnEdit">false</field>
|
||||
<field name="displayTags">false</field>
|
||||
<field name="displayRelated">false</field>
|
||||
<field name="displayButton">false</field>
|
||||
<field name="hideLinksContainer"/>
|
||||
|
||||
<field name="buttonCaption"/>
|
||||
<field name="parentClickHandler"/>
|
||||
<field name="keyDownHandler"/>
|
||||
<field name="commandHandler"/>
|
||||
<field name="clickHandler"/>
|
||||
<field name="navigateHandler"/>
|
||||
|
||||
<field name="returnHandler"/>
|
||||
|
||||
<constructor><![CDATA[
|
||||
this._destroyed = false;
|
||||
this._noteEditorID = Zotero.Utilities.randomString();
|
||||
this._iframe = document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view');
|
||||
this._iframe.addEventListener('DOMContentLoaded', (e) => {
|
||||
// For iframes without chrome priviledges, for unknown reasons,
|
||||
// dataTransfer.getData() returns empty value for `drop` event
|
||||
// when dragging something from the outside of Zotero
|
||||
this._iframe.contentWindow.addEventListener('drop', (event) => {
|
||||
this._iframe.contentWindow.wrappedJSObject.droppedData = Components.utils.cloneInto({
|
||||
'text/plain': event.dataTransfer.getData('text/plain'),
|
||||
'text/html': event.dataTransfer.getData('text/html'),
|
||||
'zotero/annotation': event.dataTransfer.getData('zotero/annotation'),
|
||||
'zotero/item': event.dataTransfer.getData('zotero/item')
|
||||
}, this._iframe.contentWindow);
|
||||
}, true);
|
||||
this._initialized = true;
|
||||
});
|
||||
|
||||
window.fillTooltip = (tooltip) => {
|
||||
let node = window.document.tooltipNode.closest('*[title]');
|
||||
if (!node || !node.getAttribute('title')) {
|
||||
return false;
|
||||
}
|
||||
tooltip.setAttribute('label', node.getAttribute('title'));
|
||||
return true;
|
||||
}
|
||||
|
||||
this.saveSync = () => {
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
this.getCurrentInstance = () => {
|
||||
return this._editorInstance;
|
||||
}
|
||||
|
||||
this.initEditor = async (state) => {
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.uninit();
|
||||
}
|
||||
this._editorInstance = new Zotero.EditorInstance();
|
||||
await this._editorInstance.init({
|
||||
state,
|
||||
item: this._item,
|
||||
iframeWindow: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-view').contentWindow,
|
||||
popup: document.getAnonymousElementByAttribute(this, 'anonid', 'editor-menu'),
|
||||
onNavigate: this._navigateHandler,
|
||||
readOnly: !this.editable,
|
||||
disableUI: this.mode == 'merge',
|
||||
onReturn: this._returnHandler,
|
||||
placeholder: this.placeholder
|
||||
});
|
||||
if (this._onInitCallback) {
|
||||
this._onInitCallback();
|
||||
}
|
||||
}
|
||||
|
||||
this.onInit = (callback) => {
|
||||
if (this._editorInstance) {
|
||||
return callback();
|
||||
}
|
||||
this._onInitCallback = callback;
|
||||
}
|
||||
|
||||
this.notify = async (event, type, ids, extraData) => {
|
||||
if (this._editorInstance) {
|
||||
await this._editorInstance.notify(ids);
|
||||
}
|
||||
|
||||
if (!this.item) return;
|
||||
// Try to use the state from the item save event
|
||||
let id = this.item.id;
|
||||
if (ids.includes(id)) {
|
||||
let state = extraData && extraData[id] && extraData[id].state;
|
||||
if (state) {
|
||||
if (extraData[id].noteEditorID !== this._editorInstance.instanceID) {
|
||||
this.initEditor(state);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let curValue = this.item.note;
|
||||
if (curValue !== this._lastHtmlValue) {
|
||||
this.initEditor();
|
||||
}
|
||||
}
|
||||
this._lastHtmlValue = this.item.note;
|
||||
}
|
||||
|
||||
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
||||
this._id('links-box').refresh();
|
||||
}
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
|
||||
]]></constructor>
|
||||
|
||||
<property name="editorInstance" onget="return this._editorInstance"/>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
<field name="_mode">"view"</field>
|
||||
<property name="mode" onget="return this._mode;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
// Duplicate default property settings here
|
||||
this.editable = false;
|
||||
this.saveOnEdit = false;
|
||||
this.displayTags = false;
|
||||
this.displayRelated = false;
|
||||
this.displayButton = false;
|
||||
|
||||
switch (val) {
|
||||
case 'view':
|
||||
case 'merge':
|
||||
if (this.noteField) {
|
||||
this.noteField.onInit(ed => ed.setMode('readonly'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
if (this.noteField) {
|
||||
this.noteField.onInit(ed => ed.setMode('design'));
|
||||
}
|
||||
this.editable = true;
|
||||
this.saveOnEdit = true;
|
||||
this.parentClickHandler = this.selectParent;
|
||||
this.keyDownHandler = this.handleKeyDown;
|
||||
this.commandHandler = this.save;
|
||||
this.displayTags = true;
|
||||
this.displayRelated = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid mode '" + val + "' in noteeditor.xml");
|
||||
}
|
||||
|
||||
this._mode = val;
|
||||
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
||||
this._id('links-box').mode = val;
|
||||
]]>
|
||||
// Duplicate default property settings here
|
||||
this.editable = false;
|
||||
this.displayTags = false;
|
||||
this.displayRelated = false;
|
||||
this.displayButton = false;
|
||||
|
||||
switch (val) {
|
||||
case 'view':
|
||||
case 'merge':
|
||||
this.editable = false;
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
this.editable = true;
|
||||
this.parentClickHandler = this.selectParent;
|
||||
this.keyDownHandler = this.handleKeyDown;
|
||||
this.commandHandler = this.save;
|
||||
this.displayTags = true;
|
||||
this.displayRelated = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid mode '" + val + "' in noteeditor.xml");
|
||||
}
|
||||
|
||||
this._mode = val;
|
||||
document.getAnonymousNodes(this)[0].setAttribute('mode', val);
|
||||
this._id('links-box').mode = val;
|
||||
this._id('links-container').hidden = !(this.displayTags && this.displayRelated) || this._hideLinksContainer;
|
||||
this._id('links-box').refresh();
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
|
||||
<field name="returnHandler"/>
|
||||
<property name="returnHandler" onget="return this._returnHandler;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this._returnHandler = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<field name="_parentItem"/>
|
||||
<property name="parentItem" onget="return this._parentItem;">
|
||||
<setter>
|
||||
|
@ -102,301 +208,156 @@
|
|||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<field name="_mtime"/>
|
||||
|
||||
|
||||
<field name="_item"/>
|
||||
<property name="item" onget="return this._item;">
|
||||
<setter><![CDATA[
|
||||
this._item = val;
|
||||
// TODO: use clientDateModified instead
|
||||
this._mtime = val.getField('dateModified');
|
||||
|
||||
var parentKey = this.item.parentKey;
|
||||
if (parentKey) {
|
||||
this.parentItem = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
|
||||
}
|
||||
|
||||
this._id('links-box').item = this.item;
|
||||
|
||||
this.refresh();
|
||||
]]></setter>
|
||||
return (async () => {
|
||||
// `item` field can be set before the constructor is called
|
||||
// or noteditor is attached to dom (which happens in the
|
||||
// merge dialog i.e.), therefore we wait for the initialization
|
||||
let n = 0;
|
||||
while (!this._initialized && !this._destroyed) {
|
||||
if (n >= 1000) {
|
||||
throw new Error('Waiting for noteeditor initialization failed');
|
||||
}
|
||||
await Zotero.Promise.delay(10);
|
||||
n++;
|
||||
}
|
||||
|
||||
// The binding can also be immediately destructed
|
||||
// (which also happens in the merge dialog)
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!val) this._item = null;
|
||||
if (this._item && this._item.id === val.id) return;
|
||||
|
||||
this._lastHtmlValue = val.note;
|
||||
|
||||
this._item = val;
|
||||
|
||||
this.initEditor();
|
||||
|
||||
// var parentKey = this._item.parentKey;
|
||||
// if (parentKey) {
|
||||
// this.parentItem = Zotero.Items.getByLibraryAndKey(this._item.libraryID, parentKey);
|
||||
// }
|
||||
|
||||
this._id('links-box').item = this._item;
|
||||
})();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
|
||||
<property name="linksOnTop">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if(val) {
|
||||
var container = this._id('links-container');
|
||||
var parent = container.parentNode;
|
||||
var sib = container.nextSibling;
|
||||
while (parent.firstChild !== container) {
|
||||
parent.insertBefore(parent.removeChild(parent.firstChild), sib);
|
||||
}
|
||||
// if (val) {
|
||||
// var container = this._id('links-container');
|
||||
// var parent = container.parentNode;
|
||||
// var sib = container.nextSibling;
|
||||
// while (parent.firstChild !== container) {
|
||||
// parent.insertBefore(parent.removeChild(parent.firstChild), sib);
|
||||
// }
|
||||
// }
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="navigateHandler">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.onNavigate = val;
|
||||
}
|
||||
this._navigateHandler = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="note"
|
||||
onget="Zotero.debug('Getting note with .note deprecated -- use .item in zoteronoteeditor'); return this._item"
|
||||
onset="Zotero.debug('Setting note with .note deprecated -- use .item in zoteronoteeditor'); this.item = val"/>
|
||||
<property name="ref" onget="return this._item" onset="this.item = val"/>
|
||||
|
||||
<property name="hideLinksContainer">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this._hideLinksContainer = val;
|
||||
this._id('links-container').hidden = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<field name="collection"/>
|
||||
|
||||
<property name="noteField" onget="return this._id('noteField')" readonly="true"/>
|
||||
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
|
||||
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.instanceID = Zotero.Utilities.randomString();
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'noteeditor');
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
<![CDATA[
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
if (this._editorInstance) {
|
||||
this._editorInstance.uninit();
|
||||
}
|
||||
this._destroyed = true;
|
||||
this._initialized = false;
|
||||
this._editorInstance = null;
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<method name="notify">
|
||||
<parameter name="event"/>
|
||||
<parameter name="type"/>
|
||||
<parameter name="ids"/>
|
||||
<parameter name="extraData"/>
|
||||
<body><![CDATA[
|
||||
if (event != 'modify' || !this.item || !this.item.id) return;
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
let id = ids[i];
|
||||
if (id != this.item.id) {
|
||||
continue;
|
||||
}
|
||||
if (extraData && extraData[id] && extraData[id].noteEditorID == this.instanceID) {
|
||||
//Zotero.debug("Skipping notification from current note field");
|
||||
continue;
|
||||
}
|
||||
if (this.noteField.changed) {
|
||||
//Zotero.debug("Note has changed since last save -- skipping refresh");
|
||||
return;
|
||||
}
|
||||
this.refresh();
|
||||
break;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="refresh">
|
||||
<body><![CDATA[
|
||||
Zotero.debug('Refreshing note editor');
|
||||
|
||||
var textbox = this.noteField;
|
||||
var textboxReadOnly = this._id('noteFieldReadOnly');
|
||||
var button = this._id('goButton');
|
||||
|
||||
if (this.editable) {
|
||||
textbox.hidden = false;
|
||||
textboxReadOnly.hidden = true;
|
||||
}
|
||||
else {
|
||||
textbox.hidden = true;
|
||||
textboxReadOnly.hidden = false;
|
||||
textbox = textboxReadOnly;
|
||||
}
|
||||
|
||||
//var scrollPos = textbox.inputField.scrollTop;
|
||||
if (this.item) {
|
||||
// For sanity check in save()
|
||||
textbox.setAttribute('itemID', this.item.id);
|
||||
textbox.value = this.item.getNote();
|
||||
}
|
||||
else {
|
||||
textbox.value = '';
|
||||
textbox.removeAttribute('itemID');
|
||||
}
|
||||
//textbox.inputField.scrollTop = scrollPos;
|
||||
|
||||
this._id('links-container').hidden = !(this.displayTags && this.displayRelated);
|
||||
this._id('links-box').refresh();
|
||||
|
||||
if (this.keyDownHandler) {
|
||||
textbox.setAttribute('onkeydown',
|
||||
'document.getBindingParent(this).handleKeyDown(event)');
|
||||
}
|
||||
else {
|
||||
textbox.removeAttribute('onkeydown');
|
||||
}
|
||||
|
||||
if (this.commandHandler) {
|
||||
textbox.setAttribute('oncommand',
|
||||
'document.getBindingParent(this).commandHandler()');
|
||||
}
|
||||
else {
|
||||
textbox.removeAttribute('oncommand');
|
||||
}
|
||||
|
||||
if (this.displayButton) {
|
||||
button.label = this.buttonCaption;
|
||||
button.hidden = false;
|
||||
button.setAttribute('oncommand',
|
||||
'document.getBindingParent(this).clickHandler(this)');
|
||||
}
|
||||
else {
|
||||
button.hidden = true;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="save">
|
||||
<body><![CDATA[
|
||||
return Zotero.spawn(function* () {
|
||||
try {
|
||||
if (this._mode == 'view') {
|
||||
Zotero.debug("Not saving read-only note");
|
||||
return;
|
||||
}
|
||||
|
||||
var noteField = this._id('noteField');
|
||||
var value = noteField.value;
|
||||
if (value === null) {
|
||||
Zotero.debug("Note value not available -- not saving", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update note
|
||||
if (this.item) {
|
||||
// If note field doesn't match item, abort save and run error handler
|
||||
if (noteField.getAttribute('itemID') != this.item.id) {
|
||||
throw new Error("Note field doesn't match current item");
|
||||
}
|
||||
|
||||
let changed = this.item.setNote(value);
|
||||
if (changed && this.saveOnEdit) {
|
||||
this.noteField.changed = false;
|
||||
yield this.item.saveTx({
|
||||
notifierData: {
|
||||
noteEditorID: this.instanceID
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new note
|
||||
var item = new Zotero.Item('note');
|
||||
if (this.parentItem) {
|
||||
item.libraryID = this.parentItem.libraryID;
|
||||
}
|
||||
item.setNote(value);
|
||||
if (this.parentItem) {
|
||||
item.parentKey = this.parentItem.key;
|
||||
}
|
||||
if (this.saveOnEdit) {
|
||||
var id = yield item.saveTx();
|
||||
|
||||
if (!this.parentItem && this.collection) {
|
||||
this.collection.addItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
this.item = item;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
|
||||
if (this.hasAttribute('onerror')) {
|
||||
let fn = new Function("", this.getAttribute('onerror'));
|
||||
fn.call(this)
|
||||
}
|
||||
if (this.onError) {
|
||||
this.onError(e);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
]]></body>
|
||||
return (async () => {
|
||||
|
||||
})();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- Used to insert a tab manually -->
|
||||
<method name="handleKeyDown">
|
||||
<parameter name="event"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var noteField = this._id('noteField');
|
||||
|
||||
switch (event.keyCode) {
|
||||
case 9:
|
||||
// On Shift-Tab, if focus was moved out of the note, focus the element
|
||||
// specified in the 'previousfocus' attribute. We check for focus
|
||||
// because Shift-Tab doesn't and shouldn't move focus out of the note if
|
||||
// the cursor is in a list.
|
||||
if (event.shiftKey) {
|
||||
let id = this.getAttribute('previousfocus');
|
||||
if (id) {
|
||||
setTimeout(() => {
|
||||
if (!noteField.hasFocus()) {
|
||||
document.getElementById(id).focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="focus">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._id('noteField').focus();
|
||||
setTimeout(() => {
|
||||
if (this._iframe && this._iframe.contentWindow) {
|
||||
this._iframe.focus();
|
||||
this._editorInstance.focus();
|
||||
}
|
||||
|
||||
}, 500);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearUndo">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this._id('noteField').clearUndo();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="_id">
|
||||
<parameter name="id"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
|
||||
<content>
|
||||
<xul:vbox xbl:inherits="flex">
|
||||
<xul:textbox id="noteField" type="styled" mode="note"
|
||||
timeout="1000" flex="1" hidden="true"/>
|
||||
<xul:textbox id="noteFieldReadOnly" type="styled" mode="note"
|
||||
readonly="true" flex="1" hidden="true"/>
|
||||
<xul:vbox xbl:inherits="flex" style="display: flex;flex-direction: column;flex-grow: 1;">
|
||||
<xul:iframe tooltip="editor-tooltip" anonid="editor-view" flex="1" overflow="auto" style="width: 100%;margin-right: 5px;border: 0;width: 100%;margin-right: 5px;border: 0;flex-grow: 1;"
|
||||
frameBorder="0" src="resource://zotero/note-editor/editor.html" type="content"/>
|
||||
<xul:hbox id="links-container" hidden="true">
|
||||
<xul:linksbox id="links-box" flex="1" xbl:inherits="notitle"/>
|
||||
</xul:hbox>
|
||||
<xul:button id="goButton" hidden="true"/>
|
||||
|
||||
<xul:popupset>
|
||||
<xul:tooltip id="editor-tooltip" onpopupshowing="return fillTooltip(this);"/>
|
||||
<xul:menupopup anonid="editor-menu" id="editor-menu" flex="1">
|
||||
</xul:menupopup>
|
||||
</xul:popupset>
|
||||
</xul:vbox>
|
||||
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
|
||||
|
||||
|
||||
<binding id="links-box">
|
||||
<implementation>
|
||||
<implementation>
|
||||
<field name="itemRef"/>
|
||||
<property name="item" onget="return this.itemRef;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this.itemRef = val;
|
||||
|
||||
|
||||
this.id('tags').item = this.item;
|
||||
this.id('related').item = this.item;
|
||||
this.refresh();
|
||||
|
@ -406,127 +367,131 @@
|
|||
<property name="mode">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this.id('related').mode = val;
|
||||
this.id('tags').mode = val;
|
||||
]]>
|
||||
this.id('related').mode = val;
|
||||
this.id('tags').mode = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
<field name="_parentItem"/>
|
||||
<property name="parentItem" onget="return this._parentItem;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
this._parentItem = val;
|
||||
|
||||
var parentText = this.id('parentText');
|
||||
if (parentText.firstChild) {
|
||||
parentText.removeChild(parentText.firstChild);
|
||||
}
|
||||
|
||||
if (this._parentItem && this.getAttribute('notitle') != '1') {
|
||||
this.id('parent-row').hidden = undefined;
|
||||
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
|
||||
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
||||
}
|
||||
]]>
|
||||
this._parentItem = val;
|
||||
|
||||
var parentText = this.id('parentText');
|
||||
if (parentText.firstChild) {
|
||||
parentText.removeChild(parentText.firstChild);
|
||||
}
|
||||
|
||||
if (this._parentItem && this.getAttribute('notitle') != '1') {
|
||||
this.id('parent-row').hidden = undefined;
|
||||
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
|
||||
parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
|
||||
}
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
<method name="tagsClick">
|
||||
<body><![CDATA[
|
||||
this.id('tags').reload();
|
||||
var x = this.boxObject.screenX;
|
||||
var y = this.boxObject.screenY;
|
||||
this.id('tagsPopup').openPopupAtScreen(x, y, false);
|
||||
|
||||
// If editable and no existing tags, open new empty row
|
||||
var tagsBox = this.id('tags');
|
||||
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
|
||||
this.id('tags').newTag();
|
||||
}
|
||||
]]></body>
|
||||
this.id('tags').reload();
|
||||
var x = this.boxObject.screenX;
|
||||
var y = this.boxObject.screenY;
|
||||
this.id('tagsPopup').openPopupAtScreen(x, y, false);
|
||||
|
||||
// If editable and no existing tags, open new empty row
|
||||
var tagsBox = this.id('tags');
|
||||
if (tagsBox.mode == 'edit' && tagsBox.count == 0) {
|
||||
this.id('tags').newTag();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="refresh">
|
||||
<body><![CDATA[
|
||||
this.updateTagsSummary();
|
||||
this.updateRelatedSummary();
|
||||
]]></body>
|
||||
this.updateTagsSummary();
|
||||
this.updateRelatedSummary();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="updateTagsSummary">
|
||||
<body><![CDATA[
|
||||
var v = this.id('tags').summary;
|
||||
|
||||
if (!v || v == "") {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this.id('tagsClick').value = v;
|
||||
]]></body>
|
||||
var v = this.id('tags').summary;
|
||||
|
||||
if (!v || v == "") {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this.id('tagsClick').value = v;
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="relatedClick">
|
||||
<body><![CDATA[
|
||||
var relatedList = this.item.relatedItems;
|
||||
if (relatedList.length > 0) {
|
||||
var x = this.boxObject.screenX;
|
||||
var y = this.boxObject.screenY;
|
||||
this.id('relatedPopup').openPopupAtScreen(x, y, false);
|
||||
}
|
||||
else {
|
||||
this.id('related').add();
|
||||
}
|
||||
]]></body>
|
||||
var relatedList = this.item.relatedItems;
|
||||
if (relatedList.length > 0) {
|
||||
var x = this.boxObject.screenX;
|
||||
var y = this.boxObject.screenY;
|
||||
this.id('relatedPopup').openPopupAtScreen(x, y, false);
|
||||
}
|
||||
else {
|
||||
this.id('related').add();
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="updateRelatedSummary">
|
||||
<body><![CDATA[
|
||||
var v = this.id('related').summary;
|
||||
|
||||
if (!v || v == "") {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this.id('relatedClick').value = v;
|
||||
]]></body>
|
||||
var v = this.id('related').summary;
|
||||
|
||||
if (!v || v == "") {
|
||||
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
|
||||
}
|
||||
|
||||
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
|
||||
+ Zotero.getString('punctuation.colon');
|
||||
this.id('relatedClick').value = v;
|
||||
]]></body>
|
||||
</method>
|
||||
<method name="parentClick">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.item || !this.item.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById('zotero-pane')) {
|
||||
var zp = ZoteroPane;
|
||||
}
|
||||
else {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (!lastWin) {
|
||||
var lastWin = window.open();
|
||||
if (!this.item || !this.item.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
var zp = lastWin.ZoteroPane;
|
||||
}
|
||||
|
||||
Zotero.spawn(function* () {
|
||||
var parentID = this.item.parentID;
|
||||
yield zp.clearQuicksearch();
|
||||
zp.selectItem(parentID);
|
||||
}, this);
|
||||
]]>
|
||||
|
||||
if (document.getElementById('zotero-pane')) {
|
||||
var zp = ZoteroPane;
|
||||
}
|
||||
else {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (!lastWin) {
|
||||
var lastWin = window.open();
|
||||
}
|
||||
|
||||
if (lastWin.ZoteroOverlay && !lastWin.ZoteroPane.isShowing()) {
|
||||
lastWin.ZoteroOverlay.toggleDisplay(true);
|
||||
}
|
||||
|
||||
var zp = lastWin.ZoteroPane;
|
||||
}
|
||||
|
||||
Zotero.spawn(function* () {
|
||||
var parentID = this.item.parentID;
|
||||
yield zp.clearQuicksearch();
|
||||
zp.selectItem(parentID);
|
||||
}, this);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="id">
|
||||
<parameter name="id"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
|
||||
return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -541,15 +506,18 @@
|
|||
<xul:rows>
|
||||
<xul:row id="parent-row" hidden="true">
|
||||
<xul:label id="parentLabel"/>
|
||||
<xul:label id="parentText" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).parentClick();"/>
|
||||
<xul:label id="parentText" class="zotero-clicky" crop="end"
|
||||
onclick="document.getBindingParent(this).parentClick();"/>
|
||||
</xul:row>
|
||||
<xul:row>
|
||||
<xul:label id="relatedLabel"/>
|
||||
<xul:label id="relatedClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).relatedClick();"/>
|
||||
<xul:label id="relatedClick" class="zotero-clicky" crop="end"
|
||||
onclick="document.getBindingParent(this).relatedClick();"/>
|
||||
</xul:row>
|
||||
<xul:row>
|
||||
<xul:label id="tagsLabel"/>
|
||||
<xul:label id="tagsClick" class="zotero-clicky" crop="end" onclick="document.getBindingParent(this).tagsClick();"/>
|
||||
<xul:label id="tagsClick" class="zotero-clicky" crop="end"
|
||||
onclick="document.getBindingParent(this).tagsClick();"/>
|
||||
</xul:row>
|
||||
</xul:rows>
|
||||
</xul:grid>
|
||||
|
@ -562,17 +530,17 @@
|
|||
seems to get triggered by these events for reasons that are less than
|
||||
clear) so that we can manually refresh the popup if it's open after
|
||||
autocomplete is used to prevent it from becoming unresponsive
|
||||
|
||||
|
||||
Note: Code in tagsbox.xml is dependent on the DOM path between the
|
||||
tagsbox and tagsLabel above, so be sure to update fixPopup() if it changes
|
||||
-->
|
||||
<xul:menupopup id="tagsPopup" ignorekeys="true"
|
||||
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
|
||||
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
||||
<xul:tagsbox id="tags" flex="1" mode="edit"/>
|
||||
onpopupshown="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ /* DEBUG: it would be nice to make this work -- if (this.firstChild.count==0){ this.firstChild.newTag(); } */ this.setAttribute('showing', 'true'); }"
|
||||
onpopuphidden="if (!document.commandDispatcher.focusedElement || document.commandDispatcher.focusedElement.tagName=='xul:label'){ this.setAttribute('showing', 'false'); }">
|
||||
<xul:tagsbox id="tags" flex="1" mode="edit" style="display: flex"/>
|
||||
</xul:menupopup>
|
||||
</xul:popupset>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
||||
</bindings>
|
||||
|
|
578
chrome/content/zotero/bindings/oldnoteeditor.xml
Normal file
578
chrome/content/zotero/bindings/oldnoteeditor.xml
Normal 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>
|
|
@ -55,4 +55,5 @@ function i(name, svgOrSrc, hasDPI=true) {
|
|||
|
||||
i('TagSelectorMenu', "chrome://zotero/skin/tag-selector-menu.png")
|
||||
i('DownChevron', "chrome://zotero/skin/searchbar-dropmarker.png")
|
||||
i('Xmark', "chrome://zotero/skin/xmark.png")
|
||||
|
||||
|
|
93
chrome/content/zotero/components/itemPane/notesList.jsx
Normal file
93
chrome/content/zotero/components/itemPane/notesList.jsx
Normal 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;
|
129
chrome/content/zotero/components/tabBar.jsx
Normal file
129
chrome/content/zotero/components/tabBar.jsx
Normal 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;
|
718
chrome/content/zotero/contextPane.js
Normal file
718
chrome/content/zotero/contextPane.js
Normal 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);
|
|
@ -34,8 +34,6 @@ const OPTION_PREFIX = "export-option-";
|
|||
// Class to provide options for export
|
||||
|
||||
var Zotero_File_Interface_Export = new function() {
|
||||
this.init = init;
|
||||
this.updateOptions = updateOptions;
|
||||
this.accept = accept;
|
||||
this.cancel = cancel;
|
||||
|
||||
|
@ -44,7 +42,7 @@ var Zotero_File_Interface_Export = new function() {
|
|||
/*
|
||||
* add options to export
|
||||
*/
|
||||
function init() {
|
||||
this.init = function () {
|
||||
// Set font size from pref
|
||||
var sbc = document.getElementById('zotero-export-options-container');
|
||||
Zotero.setFontSize(sbc);
|
||||
|
@ -82,10 +80,25 @@ var Zotero_File_Interface_Export = new function() {
|
|||
// right now, option interface supports only boolean values, which
|
||||
// it interprets as checkboxes
|
||||
if(typeof(translators[i].displayOptions[option]) == "boolean") {
|
||||
var checkbox = document.createElement("checkbox");
|
||||
let checkbox = document.createElement("checkbox");
|
||||
checkbox.setAttribute("id", OPTION_PREFIX+option);
|
||||
checkbox.setAttribute("label", optionLabel);
|
||||
optionsBox.insertBefore(checkbox, charsetBox);
|
||||
|
||||
// Add "Include Annotations" after "Export Files"
|
||||
if (option == 'exportFileData') {
|
||||
checkbox.onclick = () => {
|
||||
setTimeout(() => this.updateAnnotationsCheckbox());
|
||||
};
|
||||
|
||||
checkbox = document.createElement("checkbox");
|
||||
checkbox.setAttribute("id", OPTION_PREFIX + 'includeAnnotations');
|
||||
checkbox.setAttribute(
|
||||
"label",
|
||||
Zotero.getString('exportOptions.includeAnnotations')
|
||||
);
|
||||
optionsBox.insertBefore(checkbox, charsetBox);
|
||||
}
|
||||
}
|
||||
|
||||
addedOptions[option] = true;
|
||||
|
@ -108,13 +121,13 @@ var Zotero_File_Interface_Export = new function() {
|
|||
_charsets = Zotero_Charset_Menu.populate(document.getElementById(OPTION_PREFIX+"exportCharset"), true);
|
||||
}
|
||||
|
||||
updateOptions(Zotero.Prefs.get("export.translatorSettings"));
|
||||
this.updateOptions(Zotero.Prefs.get("export.translatorSettings"));
|
||||
}
|
||||
|
||||
/*
|
||||
* update translator-specific options
|
||||
*/
|
||||
function updateOptions(optionString) {
|
||||
this.updateOptions = function (optionString) {
|
||||
// get selected translator
|
||||
var index = document.getElementById("format-menu").selectedIndex;
|
||||
var translatorOptions = window.arguments[0].translators[index].displayOptions;
|
||||
|
@ -133,7 +146,9 @@ var Zotero_File_Interface_Export = new function() {
|
|||
var node = optionsBox.childNodes[i];
|
||||
// skip non-options
|
||||
if(node.id.length <= OPTION_PREFIX.length
|
||||
|| node.id.substr(0, OPTION_PREFIX.length) != OPTION_PREFIX) {
|
||||
|| node.id.substr(0, OPTION_PREFIX.length) != OPTION_PREFIX
|
||||
// Handled separately by updateAnnotationsCheckbox()
|
||||
|| node.id == 'export-option-includeAnnotations') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -161,6 +176,10 @@ var Zotero_File_Interface_Export = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
this.updateAnnotationsCheckbox(
|
||||
(options && options.includeAnnotations) ? options.includeAnnotations : false
|
||||
);
|
||||
|
||||
// handle charset popup
|
||||
if(_charsets && translatorOptions && translatorOptions.exportCharset) {
|
||||
optionsBox.hidden = undefined;
|
||||
|
@ -181,6 +200,21 @@ var Zotero_File_Interface_Export = new function() {
|
|||
window.sizeToContent();
|
||||
}
|
||||
|
||||
this.updateAnnotationsCheckbox = function (defaultValue) {
|
||||
var filesCheckbox = document.getElementById(OPTION_PREFIX + 'exportFileData');
|
||||
var annotationsCheckbox = document.getElementById(OPTION_PREFIX + 'includeAnnotations');
|
||||
if (filesCheckbox.hidden) {
|
||||
annotationsCheckbox.hidden = true;
|
||||
annotationsCheckbox.checked = false;
|
||||
return;
|
||||
}
|
||||
annotationsCheckbox.hidden = false;
|
||||
annotationsCheckbox.disabled = !filesCheckbox.checked;
|
||||
if (defaultValue !== undefined) {
|
||||
annotationsCheckbox.checked = defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* make option array reflect status
|
||||
*/
|
||||
|
@ -210,6 +244,13 @@ var Zotero_File_Interface_Export = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// If "Export Files" is shown, add "Include Annotations" checkbox value
|
||||
if (optionsAvailable && optionsAvailable.exportFileData !== undefined) {
|
||||
let elem1 = document.getElementById(OPTION_PREFIX + 'exportFileData');
|
||||
let elem2 = document.getElementById(OPTION_PREFIX + 'includeAnnotations');
|
||||
displayOptions.includeAnnotations = elem1.checked && elem2.checked;
|
||||
}
|
||||
|
||||
// save options
|
||||
var optionString = JSON.stringify(displayOptions);
|
||||
Zotero.Prefs.set("export.translatorSettings", optionString);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd" >
|
||||
%zoteroDTD;
|
||||
|
|
31
chrome/content/zotero/integration/insertNoteDialog.js
Normal file
31
chrome/content/zotero/integration/insertNoteDialog.js
Normal 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();
|
||||
}
|
67
chrome/content/zotero/integration/insertNoteDialog.xul
Normal file
67
chrome/content/zotero/integration/insertNoteDialog.xul
Normal 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>
|
|
@ -38,6 +38,10 @@ var Zotero_ProgressBar = new function () {
|
|||
io.onLoad(_onProgress);
|
||||
}
|
||||
|
||||
if (io.isNote) {
|
||||
document.documentElement.classList.add('note-dialog');
|
||||
}
|
||||
|
||||
// Only hide chrome on Windows or Mac
|
||||
if(Zotero.isMac) {
|
||||
document.documentElement.setAttribute("drawintitlebar", true);
|
||||
|
@ -45,12 +49,12 @@ var Zotero_ProgressBar = new function () {
|
|||
document.documentElement.setAttribute("hidechrome", true);
|
||||
}
|
||||
|
||||
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
|
||||
new WindowDraggingElement(document.querySelector(".citation-dialog"), window);
|
||||
|
||||
// With fx60 and drawintitlebar=true Firefox calculates the minHeight
|
||||
// as titlebar+maincontent, so we have hack around that here.
|
||||
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
|
||||
document.getElementById("quick-format-entry").style.marginBottom = "-22px";
|
||||
if (Zotero.isMac) {
|
||||
document.querySelector(".citation-dialog.entry").style.marginBottom = "-28px";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,7 +79,7 @@ var Zotero_ProgressBar = new function () {
|
|||
* Called when progress changes
|
||||
*/
|
||||
function _onProgress(percent) {
|
||||
var meter = document.getElementById("quick-format-progress-meter");
|
||||
var meter = document.querySelector(".citation-dialog.progress-meter");
|
||||
if(percent === null) {
|
||||
meter.mode = "undetermined";
|
||||
} else {
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<window
|
||||
id="quick-format-dialog"
|
||||
class="progress-bar"
|
||||
id="progress-bar"
|
||||
class="citation-dialog progress-bar"
|
||||
orient="vertical"
|
||||
title="&zotero.progress.title;"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
|
@ -43,9 +43,9 @@
|
|||
<script src="windowDraggingUtils.js" type="text/javascript"/>
|
||||
<script src="progressBar.js" type="text/javascript"/>
|
||||
|
||||
<box orient="horizontal" id="quick-format-entry">
|
||||
<deck id="quick-format-deck" selectedIndex="0" flex="1">
|
||||
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
|
||||
<box orient="horizontal" class="citation-dialog entry">
|
||||
<deck class="citation-dialog deck" selectedIndex="0" flex="1">
|
||||
<progressmeter class="citation-dialog progress-meter" mode="undetermined" value="0" flex="1"/>
|
||||
</deck>
|
||||
</box>
|
||||
</window>
|
||||
|
|
|
@ -34,7 +34,7 @@ var Zotero_QuickFormat = new function () {
|
|||
const numRe = /^[0-9\-–]+$/;
|
||||
|
||||
var initialized, io, qfs, qfi, qfiWindow, qfiDocument, qfe, qfb, qfbHeight, qfGuidance,
|
||||
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
|
||||
keepSorted, showEditor, referencePanel, referenceBox, referenceHeight = 0,
|
||||
separatorHeight = 0, currentLocator, currentLocatorLabel, currentSearchTime, dragging,
|
||||
panel, panelPrefix, panelSuffix, panelSuppressAuthor, panelLocatorLabel, panelLocator,
|
||||
panelLibraryLink, panelInfo, panelRefersToBubble, panelFrameHeight = 0, accepted = false;
|
||||
|
@ -56,6 +56,10 @@ var Zotero_QuickFormat = new function () {
|
|||
Zotero.debug(`Quick Format received citation:`);
|
||||
Zotero.debug(JSON.stringify(io.citation.toJSON()));
|
||||
|
||||
if (io.disableClassicDialog) {
|
||||
document.getElementById('classic-view').hidden = true;
|
||||
}
|
||||
|
||||
// Only hide chrome on Windows or Mac
|
||||
if(Zotero.isMac) {
|
||||
document.documentElement.setAttribute("drawintitlebar", true);
|
||||
|
@ -65,18 +69,18 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// Include a different key combo in message on Mac
|
||||
if(Zotero.isMac) {
|
||||
var qf = document.getElementById('quick-format-guidance');
|
||||
qf.setAttribute('about', qf.getAttribute('about') + "Mac");
|
||||
var qf = document.querySelector('.citation-dialog.guidance');
|
||||
qf && qf.setAttribute('about', qf.getAttribute('about') + "Mac");
|
||||
}
|
||||
|
||||
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
|
||||
new WindowDraggingElement(document.querySelector("window.citation-dialog"), window);
|
||||
|
||||
qfs = document.getElementById("quick-format-search");
|
||||
qfi = document.getElementById("quick-format-iframe");
|
||||
qfb = document.getElementById("quick-format-entry");
|
||||
qfs = document.querySelector(".citation-dialog.search");
|
||||
qfi = document.querySelector(".citation-dialog.iframe");
|
||||
qfb = document.querySelector(".citation-dialog.entry");
|
||||
qfbHeight = qfb.scrollHeight;
|
||||
referencePanel = document.getElementById("quick-format-reference-panel");
|
||||
referenceBox = document.getElementById("quick-format-reference-list");
|
||||
referencePanel = document.querySelector(".citation-dialog.reference-panel");
|
||||
referenceBox = document.querySelector(".citation-dialog.reference-list");
|
||||
|
||||
if (Zotero.isWin) {
|
||||
referencePanel.style.marginTop = "-29px";
|
||||
|
@ -84,31 +88,15 @@ var Zotero_QuickFormat = new function () {
|
|||
qfb.setAttribute("square", "true");
|
||||
}
|
||||
}
|
||||
|
||||
// With fx60 and drawintitlebar=true Firefox calculates the minHeight
|
||||
// as titlebar+maincontent, so we have hack around that here.
|
||||
if (Zotero.isMac && Zotero.platformMajorVersion >= 60) {
|
||||
else if (Zotero.isMac) {
|
||||
qfb.style.marginBottom = "-28px";
|
||||
}
|
||||
|
||||
// add labels to popup
|
||||
var locators = Zotero.Cite.labels;
|
||||
var menu = document.getElementById("locator-label");
|
||||
var labelList = document.getElementById("locator-label-popup");
|
||||
for(var locator of locators) {
|
||||
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
|
||||
|
||||
// add to list of labels
|
||||
var child = document.createElement("menuitem");
|
||||
child.setAttribute("value", locator);
|
||||
child.setAttribute("label", locatorLabel);
|
||||
labelList.appendChild(child);
|
||||
}
|
||||
menu.selectedIndex = 0;
|
||||
|
||||
keepSorted = document.getElementById("keep-sorted");
|
||||
showEditor = document.getElementById("show-editor");
|
||||
if(io.sortable) {
|
||||
if(keepSorted && io.sortable) {
|
||||
keepSorted.hidden = false;
|
||||
if(!io.citation.properties.unsorted) {
|
||||
keepSorted.setAttribute("checked", "true");
|
||||
|
@ -117,13 +105,31 @@ var Zotero_QuickFormat = new function () {
|
|||
|
||||
// Nodes for citation properties panel
|
||||
panel = document.getElementById("citation-properties");
|
||||
panelPrefix = document.getElementById("prefix");
|
||||
panelSuffix = document.getElementById("suffix");
|
||||
panelSuppressAuthor = document.getElementById("suppress-author");
|
||||
panelLocatorLabel = document.getElementById("locator-label");
|
||||
panelLocator = document.getElementById("locator");
|
||||
panelInfo = document.getElementById("citation-properties-info");
|
||||
panelLibraryLink = document.getElementById("citation-properties-library-link");
|
||||
if (panel) {
|
||||
panelPrefix = document.getElementById("prefix");
|
||||
panelSuffix = document.getElementById("suffix");
|
||||
panelSuppressAuthor = document.getElementById("suppress-author");
|
||||
panelLocatorLabel = document.getElementById("locator-label");
|
||||
panelLocator = document.getElementById("locator");
|
||||
panelInfo = document.getElementById("citation-properties-info");
|
||||
panelLibraryLink = document.getElementById("citation-properties-library-link");
|
||||
|
||||
// add labels to popup
|
||||
var locators = Zotero.Cite.labels;
|
||||
var menu = document.getElementById("locator-label");
|
||||
var labelList = document.getElementById("locator-label-popup");
|
||||
for(var locator of locators) {
|
||||
var locatorLabel = Zotero.getString('citation.locator.'+locator.replace(/\s/g,''));
|
||||
|
||||
// add to list of labels
|
||||
var child = document.createElement("menuitem");
|
||||
child.setAttribute("value", locator);
|
||||
child.setAttribute("label", locatorLabel);
|
||||
labelList.appendChild(child);
|
||||
}
|
||||
menu.selectedIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
// Don't need to set noautohide dynamically on these platforms, so do it now
|
||||
if(Zotero.isMac || Zotero.isWin) {
|
||||
|
@ -134,9 +140,12 @@ var Zotero_QuickFormat = new function () {
|
|||
qfiDocument = qfi.contentDocument;
|
||||
qfb.addEventListener("click", _onQuickSearchClick, false);
|
||||
qfb.addEventListener("keypress", _onQuickSearchKeyPress, false);
|
||||
qfe = qfiDocument.getElementById("quick-format-editor");
|
||||
qfe = qfiDocument.querySelector(".citation-dialog.editor");
|
||||
qfe.addEventListener("drop", _onBubbleDrop, false);
|
||||
qfe.addEventListener("paste", _onPaste, false);
|
||||
if (Zotero_QuickFormat.citingNotes) {
|
||||
_quickFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,8 +169,8 @@ var Zotero_QuickFormat = new function () {
|
|||
Zotero.debug(`Moving window to ${targetX}, ${targetY}`);
|
||||
window.moveTo(targetX, targetY);
|
||||
}
|
||||
qfGuidance = document.getElementById('quick-format-guidance');
|
||||
qfGuidance.show();
|
||||
qfGuidance = document.querySelector('.citation-dialog.guidance');
|
||||
qfGuidance && qfGuidance.show();
|
||||
_refocusQfe();
|
||||
})();
|
||||
|
||||
|
@ -224,7 +233,21 @@ var Zotero_QuickFormat = new function () {
|
|||
var node = _getCurrentEditorTextNode();
|
||||
return node ? node.wholeText : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates currentLocator based on a string
|
||||
* @param {String} str String to search for locator
|
||||
* @return {String} str without locator
|
||||
*/
|
||||
function _updateLocator(str) {
|
||||
m = locatorRe.exec(str);
|
||||
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
|
||||
currentLocator = m[3];
|
||||
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the dirty work of figuring out what the user meant to type
|
||||
*/
|
||||
|
@ -307,63 +330,86 @@ var Zotero_QuickFormat = new function () {
|
|||
// Exclude feeds
|
||||
Zotero.Feeds.getAll()
|
||||
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
|
||||
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
|
||||
s.addCondition("itemType", "isNot", "attachment");
|
||||
if (Zotero_QuickFormat.citingNotes) {
|
||||
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
|
||||
}
|
||||
else {
|
||||
s.addCondition("quicksearch-titleCreatorYear", "contains", str);
|
||||
s.addCondition("itemType", "isNot", "attachment");
|
||||
if (io.filterLibraryIDs) {
|
||||
io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
|
||||
}
|
||||
}
|
||||
haveConditions = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(haveConditions) {
|
||||
if (!haveConditions && Zotero_QuickFormat.citingNotes) {
|
||||
s = new Zotero.Search();
|
||||
str = "";
|
||||
s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
|
||||
haveConditions = true;
|
||||
}
|
||||
|
||||
if (haveConditions) {
|
||||
var searchResultIDs = (haveConditions ? (yield s.search()) : []);
|
||||
|
||||
// Show items list without cited items to start
|
||||
yield _updateItemList(false, false, str, searchResultIDs);
|
||||
yield _updateItemList({ searchString: str, searchResultIDs });
|
||||
|
||||
// Check to see which search results match items already in the document
|
||||
var citedItems, completed = false, isAsync = false;
|
||||
var citedItems, completed = !!Zotero_QuickFormat.citingNotes, isAsync = false;
|
||||
// Save current search time so that when we get items, we know whether it's too late to
|
||||
// process them or not
|
||||
var lastSearchTime = currentSearchTime = Date.now();
|
||||
// This may or may not be synchronous
|
||||
io.getItems().then(function(citedItems) {
|
||||
// Don't do anything if panel is already closed
|
||||
if(isAsync &&
|
||||
((referencePanel.state !== "open" && referencePanel.state !== "showing")
|
||||
|| lastSearchTime !== currentSearchTime)) return;
|
||||
|
||||
completed = true;
|
||||
|
||||
if(str.toLowerCase() === Zotero.getString("integration.ibid").toLowerCase()) {
|
||||
// If "ibid" is entered, show all cited items
|
||||
citedItemsMatchingSearch = citedItems;
|
||||
} else {
|
||||
Zotero.debug("Searching cited items");
|
||||
// Search against items. We do this here because it's possible that some of these
|
||||
// items are only in the doc, and not in the DB.
|
||||
var splits = Zotero.Fulltext.semanticSplitter(str),
|
||||
citedItemsMatchingSearch = [];
|
||||
for(var i=0, iCount=citedItems.length; i<iCount; i++) {
|
||||
// Generate a string to search for each item
|
||||
let item = citedItems[i];
|
||||
let itemStr = item.getCreators()
|
||||
.map(creator => creator.firstName + " " + creator.lastName)
|
||||
.concat([item.getField("title"), item.getField("date", true, true).substr(0, 4)])
|
||||
.join(" ");
|
||||
|
||||
// See if words match
|
||||
for(var j=0, jCount=splits.length; j<jCount; j++) {
|
||||
var split = splits[j];
|
||||
if(itemStr.toLowerCase().indexOf(split) === -1) break;
|
||||
if (!Zotero_QuickFormat.citingNotes) {
|
||||
io.getItems().then(function(citedItems) {
|
||||
// Don't do anything if panel is already closed
|
||||
if(isAsync &&
|
||||
((referencePanel.state !== "open" && referencePanel.state !== "showing")
|
||||
|| lastSearchTime !== currentSearchTime)) return;
|
||||
|
||||
completed = true;
|
||||
|
||||
if(str.toLowerCase() === Zotero.getString("integration.ibid").toLowerCase()) {
|
||||
// If "ibid" is entered, show all cited items
|
||||
citedItemsMatchingSearch = citedItems;
|
||||
} else {
|
||||
Zotero.debug("Searching cited items");
|
||||
// Search against items. We do this here because it's possible that some of these
|
||||
// items are only in the doc, and not in the DB.
|
||||
var splits = Zotero.Fulltext.semanticSplitter(str),
|
||||
citedItemsMatchingSearch = [];
|
||||
for(var i=0, iCount=citedItems.length; i<iCount; i++) {
|
||||
// Generate a string to search for each item
|
||||
let item = citedItems[i];
|
||||
let itemStr = item.getCreators()
|
||||
.map(creator => creator.firstName + " " + creator.lastName)
|
||||
.concat([item.getField("title"), item.getField("date", true, true).substr(0, 4)])
|
||||
.join(" ");
|
||||
|
||||
// See if words match
|
||||
for(var j=0, jCount=splits.length; j<jCount; j++) {
|
||||
var split = splits[j];
|
||||
if(itemStr.toLowerCase().indexOf(split) === -1) break;
|
||||
}
|
||||
|
||||
// If matched, add to citedItemsMatchingSearch
|
||||
if(j === jCount) citedItemsMatchingSearch.push(item);
|
||||
}
|
||||
|
||||
// If matched, add to citedItemsMatchingSearch
|
||||
if(j === jCount) citedItemsMatchingSearch.push(item);
|
||||
Zotero.debug("Searched cited items");
|
||||
}
|
||||
Zotero.debug("Searched cited items");
|
||||
}
|
||||
|
||||
_updateItemList(citedItems, citedItemsMatchingSearch, str, searchResultIDs, isAsync);
|
||||
});
|
||||
|
||||
_updateItemList({
|
||||
citedItems,
|
||||
citedItemsMatchingSearch,
|
||||
searchString: str,
|
||||
searchResultIDs,
|
||||
preserveSelection: isAsync
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if(!completed) {
|
||||
// We are going to have to wait until items have been retrieved from the document.
|
||||
|
@ -374,30 +420,26 @@ var Zotero_QuickFormat = new function () {
|
|||
}
|
||||
} else {
|
||||
// No search conditions, so just clear the box
|
||||
_updateItemList([], [], "", []);
|
||||
_updateItemList({ citedItems: [] });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates currentLocator based on a string
|
||||
* @param {String} str String to search for locator
|
||||
* @return {String} str without locator
|
||||
*/
|
||||
function _updateLocator(str) {
|
||||
m = locatorRe.exec(str);
|
||||
if(m && (m[1] || m[2] || m[3].length !== 4) && m.index > 0) {
|
||||
currentLocator = m[3];
|
||||
str = str.substr(0, m.index)+str.substring(m.index+m[0].length);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the item list
|
||||
*/
|
||||
var _updateItemList = Zotero.Promise.coroutine(function* (citedItems, citedItemsMatchingSearch,
|
||||
searchString, searchResultIDs, preserveSelection) {
|
||||
var _updateItemList = async function (options = {}) {
|
||||
options = Object.assign({
|
||||
citedItems: false,
|
||||
citedItemsMatchingSearch: false,
|
||||
searchString: "",
|
||||
searchResultIDs: [],
|
||||
preserveSelection: false
|
||||
}, options);
|
||||
let { citedItems, citedItemsMatchingSearch, searchString,
|
||||
searchResultIDs, preserveSelection } = options
|
||||
|
||||
var selectedIndex = 1, previousItemID;
|
||||
if (Zotero_QuickFormat.citingNotes) citedItems = [];
|
||||
|
||||
// Do this so we can preserve the selected item after cited items have been loaded
|
||||
if(preserveSelection && referenceBox.selectedIndex !== -1 && referenceBox.selectedIndex !== 2) {
|
||||
|
@ -454,13 +496,13 @@ var Zotero_QuickFormat = new function () {
|
|||
if(searchResultIDs.length && (!citedItemsMatchingSearch || citedItemsMatchingSearch.length < 50)) {
|
||||
// Search results might be in an unloaded library, so get items asynchronously and load
|
||||
// necessary data
|
||||
var items = yield Zotero.Items.getAsync(searchResultIDs);
|
||||
yield Zotero.Items.loadDataTypes(items);
|
||||
var items = await Zotero.Items.getAsync(searchResultIDs);
|
||||
await Zotero.Items.loadDataTypes(items);
|
||||
|
||||
searchString = searchString.toLowerCase();
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
|
||||
items.sort(function _itemSort(a, b) {
|
||||
function _itemSort(a, b) {
|
||||
var firstCreatorA = a.firstCreator, firstCreatorB = b.firstCreator;
|
||||
|
||||
// Favor left-bound name matches (e.g., "Baum" < "Appelbaum"),
|
||||
|
@ -506,7 +548,15 @@ var Zotero_QuickFormat = new function () {
|
|||
var yearA = a.getField("date", true, true).substr(0, 4),
|
||||
yearB = b.getField("date", true, true).substr(0, 4);
|
||||
return yearA - yearB;
|
||||
});
|
||||
}
|
||||
|
||||
function _noteSort(a, b) {
|
||||
return collation.compareString(
|
||||
1, b.getField('dateModified'), a.getField('dateModified')
|
||||
);
|
||||
}
|
||||
|
||||
items.sort(Zotero_QuickFormat.citingNotes ? _noteSort : _itemSort);
|
||||
|
||||
var previousLibrary = -1;
|
||||
for(var i=0, n=Math.min(items.length, citedItemsMatchingSearch ? 50-citedItemsMatchingSearch.length : 50); i<n; i++) {
|
||||
|
@ -532,70 +582,85 @@ var Zotero_QuickFormat = new function () {
|
|||
referenceBox.selectedIndex = selectedIndex;
|
||||
referenceBox.ensureIndexIsVisible(selectedIndex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a string describing an item. We avoid CSL here for speed.
|
||||
*/
|
||||
function _buildItemDescription(item, infoHbox) {
|
||||
var nodes = [];
|
||||
|
||||
var author, authorDate = "";
|
||||
if(item.firstCreator) author = authorDate = item.firstCreator;
|
||||
var date = item.getField("date", true, true);
|
||||
if(date && (date = date.substr(0, 4)) !== "0000") {
|
||||
authorDate += " (" + parseInt(date) + ")";
|
||||
}
|
||||
authorDate = authorDate.trim();
|
||||
if(authorDate) nodes.push(authorDate);
|
||||
|
||||
var publicationTitle = item.getField("publicationTitle", false, true);
|
||||
if(publicationTitle) {
|
||||
var label = document.createElement("label");
|
||||
label.setAttribute("value", publicationTitle);
|
||||
label.setAttribute("crop", "end");
|
||||
label.style.fontStyle = "italic";
|
||||
nodes.push(label);
|
||||
}
|
||||
|
||||
var volumeIssue = item.getField("volume");
|
||||
var issue = item.getField("issue");
|
||||
if(issue) volumeIssue += "("+issue+")";
|
||||
if(volumeIssue) nodes.push(volumeIssue);
|
||||
|
||||
var publisherPlace = [], field;
|
||||
if((field = item.getField("publisher"))) publisherPlace.push(field);
|
||||
if((field = item.getField("place"))) publisherPlace.push(field);
|
||||
if(publisherPlace.length) nodes.push(publisherPlace.join(": "));
|
||||
|
||||
var pages = item.getField("pages");
|
||||
if(pages) nodes.push(pages);
|
||||
|
||||
if(!nodes.length) {
|
||||
var url = item.getField("url");
|
||||
if(url) nodes.push(url);
|
||||
}
|
||||
|
||||
// compile everything together
|
||||
var str = "";
|
||||
for(var i=0, n=nodes.length; i<n; i++) {
|
||||
var node = nodes[i];
|
||||
|
||||
if (item.isNote()) {
|
||||
var date = Zotero.Date.sqlToDate(item.dateModified);
|
||||
date = Zotero.Date.toFriendlyDate(date);
|
||||
str += date;
|
||||
|
||||
if(i != 0) str += ", ";
|
||||
|
||||
if(typeof node === "object") {
|
||||
var label = document.createElement("label");
|
||||
label.setAttribute("value", str);
|
||||
label.setAttribute("crop", "end");
|
||||
infoHbox.appendChild(label);
|
||||
infoHbox.appendChild(node);
|
||||
str = "";
|
||||
} else {
|
||||
str += node;
|
||||
var text = item.note;
|
||||
text = Zotero.Utilities.unescapeHTML(text);
|
||||
text = text.trim();
|
||||
text = text.slice(0, 500);
|
||||
var parts = text.split('\n').map(x => x.trim()).filter(x => x.length);
|
||||
if (parts[1]) str += " " + parts[1];
|
||||
}
|
||||
else {
|
||||
var author, authorDate = "";
|
||||
if(item.firstCreator) author = authorDate = item.firstCreator;
|
||||
var date = item.getField("date", true, true);
|
||||
if(date && (date = date.substr(0, 4)) !== "0000") {
|
||||
authorDate += " (" + parseInt(date) + ")";
|
||||
}
|
||||
authorDate = authorDate.trim();
|
||||
if(authorDate) nodes.push(authorDate);
|
||||
|
||||
var publicationTitle = item.getField("publicationTitle", false, true);
|
||||
if(publicationTitle) {
|
||||
var label = document.createElement("label");
|
||||
label.setAttribute("value", publicationTitle);
|
||||
label.setAttribute("crop", "end");
|
||||
label.style.fontStyle = "italic";
|
||||
nodes.push(label);
|
||||
}
|
||||
|
||||
var volumeIssue = item.getField("volume");
|
||||
var issue = item.getField("issue");
|
||||
if(issue) volumeIssue += "("+issue+")";
|
||||
if(volumeIssue) nodes.push(volumeIssue);
|
||||
|
||||
var publisherPlace = [], field;
|
||||
if((field = item.getField("publisher"))) publisherPlace.push(field);
|
||||
if((field = item.getField("place"))) publisherPlace.push(field);
|
||||
if(publisherPlace.length) nodes.push(publisherPlace.join(": "));
|
||||
|
||||
var pages = item.getField("pages");
|
||||
if(pages) nodes.push(pages);
|
||||
|
||||
if(!nodes.length) {
|
||||
var url = item.getField("url");
|
||||
if(url) nodes.push(url);
|
||||
}
|
||||
|
||||
// compile everything together
|
||||
for(var i=0, n=nodes.length; i<n; i++) {
|
||||
var node = nodes[i];
|
||||
|
||||
if(i != 0) str += ", ";
|
||||
|
||||
if(typeof node === "object") {
|
||||
var label = document.createElement("label");
|
||||
label.setAttribute("value", str);
|
||||
label.setAttribute("crop", "end");
|
||||
infoHbox.appendChild(label);
|
||||
infoHbox.appendChild(node);
|
||||
str = "";
|
||||
} else {
|
||||
str += node;
|
||||
}
|
||||
}
|
||||
|
||||
if(nodes.length && (!str.length || str[str.length-1] !== ".")) str += ".";
|
||||
}
|
||||
|
||||
if(nodes.length && (!str.length || str[str.length-1] !== ".")) str += ".";
|
||||
var label = document.createElement("label");
|
||||
label.setAttribute("value", str);
|
||||
label.setAttribute("crop", "end");
|
||||
|
@ -608,23 +673,23 @@ var Zotero_QuickFormat = new function () {
|
|||
*/
|
||||
function _buildListItem(item) {
|
||||
var titleNode = document.createElement("label");
|
||||
titleNode.setAttribute("class", "quick-format-title");
|
||||
titleNode.setAttribute("class", "citation-dialog title");
|
||||
titleNode.setAttribute("flex", "1");
|
||||
titleNode.setAttribute("crop", "end");
|
||||
titleNode.setAttribute("value", item.getDisplayTitle());
|
||||
|
||||
var infoNode = document.createElement("hbox");
|
||||
infoNode.setAttribute("class", "quick-format-info");
|
||||
infoNode.setAttribute("class", "citation-dialog info");
|
||||
_buildItemDescription(item, infoNode);
|
||||
|
||||
// add to rich list item
|
||||
var rll = document.createElement("richlistitem");
|
||||
rll.setAttribute("orient", "vertical");
|
||||
rll.setAttribute("class", "quick-format-item");
|
||||
rll.setAttribute("class", "citation-dialog item");
|
||||
rll.setAttribute("zotero-item", item.cslItemID ? item.cslItemID : item.id);
|
||||
rll.appendChild(titleNode);
|
||||
rll.appendChild(infoNode);
|
||||
rll.addEventListener("click", _bubbleizeSelected, false);
|
||||
rll.addEventListener("click", Zotero_QuickFormat._bubbleizeSelected, false);
|
||||
|
||||
return rll;
|
||||
}
|
||||
|
@ -634,7 +699,7 @@ var Zotero_QuickFormat = new function () {
|
|||
*/
|
||||
function _buildListSeparator(labelText, loading) {
|
||||
var titleNode = document.createElement("label");
|
||||
titleNode.setAttribute("class", "quick-format-separator-title");
|
||||
titleNode.setAttribute("class", "citation-dialog separator-title");
|
||||
titleNode.setAttribute("flex", "1");
|
||||
titleNode.setAttribute("crop", "end");
|
||||
titleNode.setAttribute("value", labelText);
|
||||
|
@ -643,7 +708,7 @@ var Zotero_QuickFormat = new function () {
|
|||
var rll = document.createElement("richlistitem");
|
||||
rll.setAttribute("orient", "vertical");
|
||||
rll.setAttribute("disabled", true);
|
||||
rll.setAttribute("class", loading ? "quick-format-loading" : "quick-format-separator");
|
||||
rll.setAttribute("class", loading ? "citation-dialog loading" : "citation-dialog separator");
|
||||
rll.appendChild(titleNode);
|
||||
rll.addEventListener("mousedown", _ignoreClick, true);
|
||||
rll.addEventListener("click", _ignoreClick, true);
|
||||
|
@ -663,8 +728,12 @@ var Zotero_QuickFormat = new function () {
|
|||
var str = item.getField("firstCreator");
|
||||
|
||||
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
|
||||
if(!str) {
|
||||
str = Zotero.getString("punctuation.openingQMark") + item.getDisplayTitle() + Zotero.getString("punctuation.closingQMark");
|
||||
title = item.getDisplayTitle();
|
||||
if (item.isNote()) {
|
||||
title = title.substr(0, 24) + '…';
|
||||
}
|
||||
if (!str) {
|
||||
str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
|
||||
}
|
||||
|
||||
// Date
|
||||
|
@ -713,7 +782,7 @@ var Zotero_QuickFormat = new function () {
|
|||
// a XUL label for these things works best. A regular span causes issues with moving the
|
||||
// cursor.
|
||||
var bubble = qfiDocument.createElement("span");
|
||||
bubble.setAttribute("class", "quick-format-bubble");
|
||||
bubble.setAttribute("class", "citation-dialog bubble");
|
||||
bubble.setAttribute("draggable", "true");
|
||||
bubble.textContent = str;
|
||||
bubble.addEventListener("click", _onBubbleClick, false);
|
||||
|
@ -744,12 +813,11 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Converts the selected item to a bubble
|
||||
*/
|
||||
var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
|
||||
this._bubbleizeSelected = Zotero.Promise.coroutine(function* () {
|
||||
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
|
||||
|
||||
|
||||
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
|
||||
if (typeof citationItem.id === "string" && citationItem.id.indexOf("/") !== -1) {
|
||||
var item = Zotero.Cite.getItem(citationItem.id);
|
||||
citationItem.uris = item.cslURIs;
|
||||
citationItem.itemData = item.cslItemData;
|
||||
}
|
||||
|
@ -824,13 +892,13 @@ var Zotero_QuickFormat = new function () {
|
|||
var childNodes = referenceBox.childNodes, numReferences = 0, numSeparators = 0,
|
||||
firstReference, firstSeparator, height;
|
||||
for(var i=0, n=childNodes.length; i<n && numReferences < SHOWN_REFERENCES; i++) {
|
||||
if(childNodes[i].className === "quick-format-item") {
|
||||
if(childNodes[i].className === "citation-dialog item") {
|
||||
numReferences++;
|
||||
if(!firstReference) {
|
||||
firstReference = childNodes[i];
|
||||
if(referenceBox.selectedIndex === -1) referenceBox.selectedIndex = i;
|
||||
}
|
||||
} else if(childNodes[i].className === "quick-format-separator") {
|
||||
} else if(childNodes[i].className === "citation-dialog separator") {
|
||||
numSeparators++;
|
||||
if(!firstSeparator) firstSeparator = childNodes[i];
|
||||
}
|
||||
|
@ -885,7 +953,6 @@ var Zotero_QuickFormat = new function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
referencePanel.sizeTo(window.outerWidth-30,
|
||||
numReferences*referenceHeight+numSeparators*separatorHeight+panelFrameHeight);
|
||||
if(!panelShowing) _openReferencePanel();
|
||||
|
@ -900,29 +967,29 @@ var Zotero_QuickFormat = new function () {
|
|||
* Opens the reference panel and potentially refocuses the main text box
|
||||
*/
|
||||
function _openReferencePanel() {
|
||||
if(!Zotero.isMac && !Zotero.isWin) {
|
||||
var panelShowing = referencePanel.state === "open" || referencePanel.state === "showing";
|
||||
|
||||
if (!panelShowing && !Zotero.isMac && !Zotero.isWin) {
|
||||
// noautohide and noautofocus are incompatible on Linux
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=545265
|
||||
referencePanel.setAttribute("noautohide", "false");
|
||||
}
|
||||
|
||||
referencePanel.openPopup(document.documentElement, "after_start", 15,
|
||||
qfb.clientHeight-window.clientHeight, false, false, null);
|
||||
|
||||
if(!Zotero.isMac && !Zotero.isWin) {
|
||||
|
||||
// reinstate noautohide after the window is shown
|
||||
referencePanel.addEventListener("popupshowing", function() {
|
||||
referencePanel.removeEventListener("popupshowing", arguments.callee, false);
|
||||
referencePanel.setAttribute("noautohide", "true");
|
||||
}, false);
|
||||
}
|
||||
|
||||
referencePanel.openPopup(document.documentElement, "after_start", 15,
|
||||
qfb.clientHeight-window.clientHeight, false, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all citations
|
||||
*/
|
||||
function _clearCitation() {
|
||||
var citations = qfe.getElementsByClassName("quick-format-bubble");
|
||||
var citations = qfe.getElementsByClassName("citation-dialog bubble");
|
||||
while(citations.length) {
|
||||
citations[0].parentNode.removeChild(citations[0]);
|
||||
}
|
||||
|
@ -933,7 +1000,7 @@ var Zotero_QuickFormat = new function () {
|
|||
*/
|
||||
function _showCitation(insertBefore) {
|
||||
if(!io.citation.properties.unsorted
|
||||
&& keepSorted.hasAttribute("checked")
|
||||
&& keepSorted && keepSorted.hasAttribute("checked")
|
||||
&& io.citation.sortedItems
|
||||
&& io.citation.sortedItems.length) {
|
||||
for(var i=0, n=io.citation.sortedItems.length; i<n; i++) {
|
||||
|
@ -965,7 +1032,7 @@ var Zotero_QuickFormat = new function () {
|
|||
}
|
||||
|
||||
if(io.sortable) {
|
||||
if(keepSorted.hasAttribute("checked")) {
|
||||
if(keepSorted && keepSorted.hasAttribute("checked")) {
|
||||
delete io.citation.properties.unsorted;
|
||||
} else {
|
||||
io.citation.properties.unsorted = true;
|
||||
|
@ -990,8 +1057,8 @@ var Zotero_QuickFormat = new function () {
|
|||
* Generates the preview and sorts citations
|
||||
*/
|
||||
var _previewAndSort = Zotero.Promise.coroutine(function* () {
|
||||
var shouldKeepSorted = keepSorted.hasAttribute("checked"),
|
||||
editorShowing = showEditor.hasAttribute("checked");
|
||||
var shouldKeepSorted = keepSorted && keepSorted.hasAttribute("checked"),
|
||||
editorShowing = showEditor && showEditor.hasAttribute("checked");
|
||||
if(!shouldKeepSorted && !editorShowing) return;
|
||||
|
||||
_updateCitationObject();
|
||||
|
@ -1002,7 +1069,7 @@ var Zotero_QuickFormat = new function () {
|
|||
_showCitation();
|
||||
|
||||
// select past last citation
|
||||
var lastBubble = qfe.getElementsByClassName("quick-format-bubble");
|
||||
var lastBubble = qfe.getElementsByClassName("citation-dialog bubble");
|
||||
lastBubble = lastBubble[lastBubble.length-1];
|
||||
|
||||
_moveCursorToEnd();
|
||||
|
@ -1052,7 +1119,7 @@ var Zotero_QuickFormat = new function () {
|
|||
* Called when progress changes
|
||||
*/
|
||||
function _onProgress(percent) {
|
||||
var meter = document.getElementById("quick-format-progress-meter");
|
||||
var meter = document.querySelector(".citation-dialog .progress-meter");
|
||||
if(percent === null) {
|
||||
meter.mode = "undetermined";
|
||||
} else {
|
||||
|
@ -1064,12 +1131,12 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Accepts current selection and adds citation
|
||||
*/
|
||||
function _accept() {
|
||||
this._accept = function() {
|
||||
if(accepted) return;
|
||||
accepted = true;
|
||||
try {
|
||||
_updateCitationObject();
|
||||
document.getElementById("quick-format-deck").selectedIndex = 1;
|
||||
document.querySelector(".citation-dialog.deck").selectedIndex = 1;
|
||||
io.accept(_onProgress);
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
|
@ -1089,14 +1156,14 @@ var Zotero_QuickFormat = new function () {
|
|||
/**
|
||||
* Handle escape for entire window
|
||||
*/
|
||||
this.onKeyPress = function(event) {
|
||||
this.onKeyPress = function (event) {
|
||||
var keyCode = event.keyCode;
|
||||
if(keyCode === event.DOM_VK_ESCAPE && !accepted) {
|
||||
if (keyCode === event.DOM_VK_ESCAPE && !accepted) {
|
||||
accepted = true;
|
||||
io.citation.citationItems = [];
|
||||
io.accept();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get bubbles within the current selection
|
||||
|
@ -1141,7 +1208,7 @@ var Zotero_QuickFormat = new function () {
|
|||
*/
|
||||
function _resetSearchTimer() {
|
||||
// Show spinner
|
||||
var spinner = document.getElementById('quick-format-spinner');
|
||||
var spinner = document.querySelector('.citation-dialog.spinner');
|
||||
spinner.style.visibility = '';
|
||||
// Cancel current search if active
|
||||
if (_searchPromise && _searchPromise.isPending()) {
|
||||
|
@ -1187,8 +1254,8 @@ var Zotero_QuickFormat = new function () {
|
|||
var keyCode = event.keyCode;
|
||||
if (keyCode === event.DOM_VK_RETURN) {
|
||||
event.preventDefault();
|
||||
if(!(yield _bubbleizeSelected()) && !_getEditorContent()) {
|
||||
_accept();
|
||||
if(!(yield Zotero_QuickFormat._bubbleizeSelected()) && !_getEditorContent()) {
|
||||
Zotero_QuickFormat._accept();
|
||||
}
|
||||
} else if (keyCode === event.DOM_VK_ESCAPE) {
|
||||
// Handled in the event handler up, but we have to cancel it here
|
||||
|
@ -1196,7 +1263,7 @@ var Zotero_QuickFormat = new function () {
|
|||
return;
|
||||
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
|
||||
event.preventDefault();
|
||||
_bubbleizeSelected();
|
||||
Zotero_QuickFormat._bubbleizeSelected();
|
||||
} else if(keyCode === event.DOM_VK_BACK_SPACE || keyCode === event.DOM_VK_DELETE) {
|
||||
var bubble = _getSelectedBubble(keyCode === event.DOM_VK_DELETE);
|
||||
|
||||
|
@ -1331,7 +1398,7 @@ var Zotero_QuickFormat = new function () {
|
|||
var bubble = _insertBubble(JSON.parse(dragging.dataset.citationItem), range);
|
||||
|
||||
// If moved out of order, turn off "Keep Sources Sorted"
|
||||
if(io.sortable && keepSorted.hasAttribute("checked") && oldPosition !== -1 &&
|
||||
if(io.sortable && keepSorted && keepSorted.hasAttribute("checked") && oldPosition !== -1 &&
|
||||
oldPosition != _getBubbleIndex(bubble)) {
|
||||
keepSorted.removeAttribute("checked");
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
<window
|
||||
id="quick-format-dialog"
|
||||
class="citation-dialog"
|
||||
orient="vertical"
|
||||
title="&zotero.integration.quickFormatDialog.title;"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
|
@ -44,40 +45,41 @@
|
|||
<script src="../include.js"/>
|
||||
<script src="windowDraggingUtils.js" type="text/javascript"/>
|
||||
<script src="quickFormat.js" type="text/javascript"/>
|
||||
|
||||
<box orient="horizontal" id="quick-format-entry">
|
||||
<deck id="quick-format-deck" selectedIndex="0" flex="1">
|
||||
<hbox id="quick-format-search" flex="1" align="start">
|
||||
|
||||
<box orient="horizontal" class="citation-dialog entry">
|
||||
<deck class="citation-dialog deck" selectedIndex="0" flex="1">
|
||||
<hbox class="citation-dialog search" flex="1" align="start">
|
||||
<hbox flex="1">
|
||||
<toolbarbutton id="zotero-icon" type="menu">
|
||||
<menupopup>
|
||||
<menuitem id="keep-sorted" label="&zotero.citation.keepSorted.label;"
|
||||
oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox"
|
||||
hidden="true"/>
|
||||
oncommand="Zotero_QuickFormat.onKeepSortedCommand()" type="checkbox"
|
||||
hidden="true"/>
|
||||
<menuitem id="show-editor" label="&zotero.integration.showEditor.label;"
|
||||
oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox"
|
||||
hidden="true"/>
|
||||
oncommand="Zotero_QuickFormat.onShowEditorCommand()" type="checkbox"
|
||||
hidden="true"/>
|
||||
<menuitem id="classic-view" label="&zotero.integration.classicView.label;"
|
||||
oncommand="Zotero_QuickFormat.onClassicViewCommand()"/>
|
||||
oncommand="Zotero_QuickFormat.onClassicViewCommand()"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
<iframe id="quick-format-iframe" ondragstart="event.stopPropagation()" src="data:application/xhtml+xml,%3C!DOCTYPE%20html%20PUBLIC%20%22-//W3C//DTD%20XHTML%201.0%20Strict//EN%22%20%22http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd%22%3E%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Chead%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero/skin/integration.css%22/%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero-platform/content/integration.css%22/%3E%3C/head%3E%3Cbody%20contenteditable=%22true%22%20spellcheck=%22false%22%20id=%22quick-format-editor%22/%3E%3C/html%3E"
|
||||
tabindex="1" flex="1"/>
|
||||
<vbox id="quick-format-spinner" style="visibility: hidden">
|
||||
|
||||
<iframe class="citation-dialog iframe" ondragstart="event.stopPropagation()" src="data:application/xhtml+xml,%3C!DOCTYPE%20html%20PUBLIC%20%22-//W3C//DTD%20XHTML%201.0%20Strict//EN%22%20%22http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd%22%3E%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Chead%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero/skin/integration.css%22/%3E%3Clink%20rel=%22stylesheet%22%20type=%22text/css%22%20href=%22chrome://zotero-platform/content/integration.css%22/%3E%3C/head%3E%3Cbody%20contenteditable=%22true%22%20spellcheck=%22false%22%20class=%22citation-dialog%20editor%22/%3E%3C/html%3E"
|
||||
tabindex="1" flex="1"/>
|
||||
<vbox class="citation-dialog spinner" style="visibility: hidden">
|
||||
<image class="zotero-spinner-16"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
|
||||
<progressmeter class="citation-dialog progress-meter" mode="undetermined" value="0" flex="1"/>
|
||||
</deck>
|
||||
</box>
|
||||
<panel id="quick-format-reference-panel" noautofocus="true" norestorefocus="true"
|
||||
height="0" width="0">
|
||||
<richlistbox id="quick-format-reference-list" flex="1"/>
|
||||
<panel class="citation-dialog reference-panel" noautofocus="true" norestorefocus="true"
|
||||
height="0" width="0">
|
||||
<richlistbox class="citation-dialog reference-list" flex="1"/>
|
||||
</panel>
|
||||
<panel id="citation-properties" type="arrow" orient="vertical"
|
||||
onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)"
|
||||
onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)">
|
||||
onkeypress="Zotero_QuickFormat.onPanelKeyPress(event)"
|
||||
onpopuphidden="Zotero_QuickFormat.onCitationPropertiesClosed(event)">
|
||||
<vbox flex="1">
|
||||
<description id="citation-properties-title"/>
|
||||
<hbox id="citation-properties-info"/>
|
||||
|
@ -90,25 +92,25 @@
|
|||
<rows>
|
||||
<row align="center">
|
||||
<menulist id="locator-label" sizetopopup="none"
|
||||
oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)">
|
||||
oncommand="Zotero_QuickFormat.onCitationPropertiesChanged(event)">
|
||||
<menupopup id="locator-label-popup"/>
|
||||
</menulist>
|
||||
<textbox id="locator" flex="1"
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
</row>
|
||||
<row align="center">
|
||||
<label value="&zotero.citation.prefix.label;"/>
|
||||
<textbox class="citation-textbox" id="prefix" flex="1"
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
</row>
|
||||
<row align="center">
|
||||
<label value="&zotero.citation.suffix.label;"/>
|
||||
<textbox class="citation-textbox" id="suffix" flex="1"
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
oninput="window.setTimeout(function(event) { Zotero_QuickFormat.onCitationPropertiesChanged(event) }, 0)"/>
|
||||
</row>
|
||||
<html:div>
|
||||
<html:input type="checkbox" id="suppress-author"
|
||||
onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/>
|
||||
onchange="Zotero_QuickFormat.onCitationPropertiesChanged(event)"/>
|
||||
<html:label for="suppress-author">
|
||||
&zotero.citation.suppressAuthor.label;
|
||||
</html:label>
|
||||
|
@ -119,6 +121,6 @@
|
|||
<button id="citation-properties-library-link" onclick="Zotero_QuickFormat.showInLibrary()"/>
|
||||
</vbox>
|
||||
</panel>
|
||||
<zoteroguidancepanel id="quick-format-guidance" about="quickFormat"
|
||||
for="zotero-icon" x="26"/>
|
||||
<zoteroguidancepanel class="citation-dialog guidance" about="quickFormat"
|
||||
for="zotero-icon" x="26"/>
|
||||
</window>
|
||||
|
|
|
@ -253,30 +253,45 @@ var ZoteroItemPane = new function() {
|
|||
tree.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.switchEditorEngine = function (useOld) {
|
||||
var switherDeck = document.getElementById('zotero-note-editor-switcher');
|
||||
switherDeck.selectedIndex = useOld ? 0 : 1;
|
||||
};
|
||||
|
||||
|
||||
this.onNoteSelected = function (item, editable) {
|
||||
_selectedNoteID = item.id;
|
||||
|
||||
// If an external note window is open for this item, don't show the editor
|
||||
if (ZoteroPane.findNoteWindow(item.id)) {
|
||||
this.showNoteWindowMessage();
|
||||
return;
|
||||
var type = Zotero.Libraries.get(item.libraryID).libraryType;
|
||||
if (type == 'group' || !Zotero.isPDFBuild) {
|
||||
// If an external note window is open for this item, don't show the editor
|
||||
if (ZoteroPane.findNoteWindow(item.id)) {
|
||||
this.showNoteWindowMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
var noteEditor = document.getElementById('zotero-note-editor-old');
|
||||
|
||||
// If loading new or different note, disable undo while we repopulate the text field
|
||||
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
|
||||
// undo content from another note into the current one.
|
||||
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
|
||||
|
||||
noteEditor.mode = editable ? 'edit' : 'view';
|
||||
noteEditor.parent = null;
|
||||
noteEditor.item = item;
|
||||
|
||||
if (clearUndo) {
|
||||
noteEditor.clearUndo();
|
||||
}
|
||||
}
|
||||
|
||||
var noteEditor = document.getElementById('zotero-note-editor');
|
||||
|
||||
// If loading new or different note, disable undo while we repopulate the text field
|
||||
// so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
|
||||
// undo content from another note into the current one.
|
||||
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
|
||||
|
||||
noteEditor.mode = editable ? 'edit' : 'view';
|
||||
noteEditor.parent = null;
|
||||
noteEditor.item = item;
|
||||
|
||||
if (clearUndo) {
|
||||
noteEditor.clearUndo();
|
||||
else {
|
||||
var noteEditor = document.getElementById('zotero-note-editor');
|
||||
noteEditor.mode = editable ? 'edit' : 'view';
|
||||
noteEditor.parent = null;
|
||||
noteEditor.item = item;
|
||||
}
|
||||
|
||||
document.getElementById('zotero-view-note-button').hidden = !editable;
|
||||
|
@ -295,16 +310,19 @@ var ZoteroItemPane = new function() {
|
|||
this.openNoteWindow = async function () {
|
||||
var selectedNote = Zotero.Items.get(_selectedNoteID);
|
||||
|
||||
// We don't want to show the note in two places, since it causes unnecessary UI updates
|
||||
// and can result in weird bugs where note content gets lost.
|
||||
//
|
||||
// If this is a child note, select the parent
|
||||
if (selectedNote.parentID) {
|
||||
await ZoteroPane.selectItem(selectedNote.parentID);
|
||||
}
|
||||
// Otherwise, hide note and replace with a message that we're editing externally
|
||||
else {
|
||||
this.showNoteWindowMessage();
|
||||
var type = Zotero.Libraries.get(selectedNote.libraryID).libraryType;
|
||||
if (type == 'group' || !Zotero.isPDFBuild) {
|
||||
// We don't want to show the note in two places, since it causes unnecessary UI updates
|
||||
// and can result in weird bugs where note content gets lost.
|
||||
//
|
||||
// If this is a child note, select the parent
|
||||
if (selectedNote.parentID) {
|
||||
await ZoteroPane.selectItem(selectedNote.parentID);
|
||||
}
|
||||
// Otherwise, hide note and replace with a message that we're editing externally
|
||||
else {
|
||||
this.showNoteWindowMessage();
|
||||
}
|
||||
}
|
||||
ZoteroPane.openNoteWindow(selectedNote.id);
|
||||
};
|
||||
|
@ -438,21 +456,7 @@ var ZoteroItemPane = new function() {
|
|||
|
||||
function _updateNoteCount() {
|
||||
var c = _notesList.childNodes.length;
|
||||
|
||||
var str = 'pane.item.notes.count.';
|
||||
switch (c){
|
||||
case 0:
|
||||
str += 'zero';
|
||||
break;
|
||||
case 1:
|
||||
str += 'singular';
|
||||
break;
|
||||
default:
|
||||
str += 'plural';
|
||||
break;
|
||||
}
|
||||
|
||||
_notesLabel.value = Zotero.getString(str, [c]);
|
||||
_notesLabel.value = Zotero.getString('pane.item.notes.count', c, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,23 +62,23 @@
|
|||
<button id="zotero-item-show-original" label="Show Original"
|
||||
oncommand="ZoteroPane_Local.showOriginalItem()" hidden="true"/>
|
||||
|
||||
<deck id="zotero-item-pane-content" selectedIndex="0" flex="1">
|
||||
<deck id="zotero-item-pane-content" class="zotero-item-pane-content" selectedIndex="0" flex="1">
|
||||
<!-- Center label (for zero or multiple item selection) -->
|
||||
<groupbox id="zotero-item-pane-groupbox" pack="center" align="center">
|
||||
<vbox id="zotero-item-pane-message-box"/>
|
||||
</groupbox>
|
||||
|
||||
<!-- Regular item -->
|
||||
<tabbox id="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
|
||||
<tabs id="zotero-editpane-tabs">
|
||||
<tabbox id="zotero-view-tabbox" class="zotero-view-tabbox" flex="1" onselect="if (!ZoteroPane_Local.collectionsView.selection || event.originalTarget.localName != 'tabpanels') { return; }; ZoteroItemPane.viewItem(ZoteroPane_Local.getSelectedItems()[0], ZoteroPane_Local.collectionsView.editable ? 'edit' : 'view', this.selectedIndex)">
|
||||
<tabs id="zotero-editpane-tabs" class="zotero-editpane-tabs">
|
||||
<tab id="zotero-editpane-info-tab" label="&zotero.tabs.info.label;"/>
|
||||
<tab id="zotero-editpane-notes-tab" label="&zotero.tabs.notes.label;"/>
|
||||
<tab id="zotero-editpane-tags-tab" label="&zotero.tabs.tags.label;"/>
|
||||
<tab id="zotero-editpane-related-tab" label="&zotero.tabs.related.label;"/>
|
||||
</tabs>
|
||||
<tabpanels id="zotero-view-item" flex="1">
|
||||
<tabpanel>
|
||||
<zoteroitembox id="zotero-editpane-item-box" flex="1"/>
|
||||
<tabpanels id="zotero-view-item" class="zotero-view-item" flex="1">
|
||||
<tabpanel flex="1">
|
||||
<zoteroitembox id="zotero-editpane-item-box" class="zotero-editpane-item-box" flex="1"/>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel flex="1" orient="vertical">
|
||||
|
@ -97,12 +97,12 @@
|
|||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="tags-pane" orient="vertical" context="tags-context-menu">
|
||||
<html:div id="tags-box-container"></html:div>
|
||||
<tabpanel id="tags-pane" class="tags-pane" orient="vertical" context="tags-context-menu">
|
||||
<html:div id="tags-box-container" class="tags-box-container"></html:div>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel>
|
||||
<relatedbox id="zotero-editpane-related" flex="1"/>
|
||||
<relatedbox id="zotero-editpane-related" class="zotero-editpane-related" flex="1"/>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
|
@ -113,9 +113,13 @@
|
|||
'onerror' handler crashes the app on a save error to prevent typing in notes
|
||||
while they're not being saved
|
||||
-->
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
|
||||
previousfocus="zotero-items-tree"
|
||||
onerror="ZoteroPane.displayErrorMessage(); this.mode = 'view'"/>
|
||||
<deck id="zotero-note-editor-switcher" flex="1">
|
||||
<oldzoteronoteeditor id="zotero-note-editor-old" flex="1" notitle="1"
|
||||
previousfocus="zotero-items-tree"
|
||||
onerror="ZoteroPane.displayErrorMessage(); this.mode = 'view'"/>
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" notitle="1"
|
||||
previousfocus="zotero-items-tree"/>
|
||||
</deck>
|
||||
<button id="zotero-view-note-button"
|
||||
label="&zotero.notes.separate;"
|
||||
oncommand="ZoteroItemPane.openNoteWindow()"/>
|
||||
|
|
|
@ -25,14 +25,14 @@
|
|||
|
||||
var noteEditor;
|
||||
var notifierUnregisterID;
|
||||
var type;
|
||||
|
||||
function switchEditorEngine(useOld) {
|
||||
var switherDeck = document.getElementById('zotero-note-editor-switcher');
|
||||
switherDeck.selectedIndex = useOld ? 0 : 1;
|
||||
}
|
||||
|
||||
async function onLoad() {
|
||||
noteEditor = document.getElementById('zotero-note-editor');
|
||||
noteEditor.mode = 'edit';
|
||||
|
||||
// Set font size from pref
|
||||
Zotero.setFontSize(noteEditor);
|
||||
|
||||
if (window.arguments) {
|
||||
var io = window.arguments[0];
|
||||
}
|
||||
|
@ -41,6 +41,35 @@ async function onLoad() {
|
|||
var collectionID = parseInt(io.collectionID);
|
||||
var parentItemKey = io.parentItemKey;
|
||||
|
||||
if (itemID) {
|
||||
var ref = await Zotero.Items.getAsync(itemID);
|
||||
var libraryID = ref.libraryID;
|
||||
}
|
||||
else {
|
||||
if (parentItemKey) {
|
||||
var ref = Zotero.Items.getByLibraryAndKey(parentItemKey);
|
||||
var libraryID = ref.libraryID;
|
||||
}
|
||||
else {
|
||||
if (collectionID && collectionID != '' && collectionID != 'undefined') {
|
||||
var collection = Zotero.Collections.get(collectionID);
|
||||
var libraryID = collection.libraryID;
|
||||
}
|
||||
}
|
||||
}
|
||||
type = Zotero.Libraries.get(libraryID).libraryType;
|
||||
switchEditorEngine(type == 'group' || !Zotero.isPDFBuild);
|
||||
if (type == 'group' || !Zotero.isPDFBuild) {
|
||||
noteEditor = document.getElementById('zotero-note-editor-old');
|
||||
}
|
||||
else {
|
||||
noteEditor = document.getElementById('zotero-note-editor');
|
||||
}
|
||||
noteEditor.mode = 'edit';
|
||||
|
||||
// Set font size from pref
|
||||
Zotero.setFontSize(noteEditor);
|
||||
|
||||
if (itemID) {
|
||||
var ref = await Zotero.Items.getAsync(itemID);
|
||||
noteEditor.item = ref;
|
||||
|
@ -77,9 +106,13 @@ function onError() {
|
|||
|
||||
function onUnload() {
|
||||
Zotero.Notifier.unregisterObserver(notifierUnregisterID);
|
||||
|
||||
if (noteEditor.item) {
|
||||
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteEditor.value);
|
||||
if (type == 'group' || !Zotero.isPDFBuild) {
|
||||
if (noteEditor.item) {
|
||||
window.opener.ZoteroPane.onNoteWindowClosed(noteEditor.item.id, noteEditor.value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
noteEditor.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,9 +120,7 @@ var NotifyCallback = {
|
|||
notify: function(action, type, ids){
|
||||
if (noteEditor.item && ids.includes(noteEditor.item.id)) {
|
||||
var noteTitle = noteEditor.item.getNoteTitle();
|
||||
if (!document.title && noteTitle != '') {
|
||||
document.title = noteTitle;
|
||||
}
|
||||
document.title = noteTitle;
|
||||
|
||||
// Update the window name (used for focusing) in case this is a new note
|
||||
window.name = 'zotero-note-' + noteEditor.item.id;
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
<key id="key_close" key="W" modifiers="accel" command="cmd_close"/>
|
||||
</keyset>
|
||||
<command id="cmd_close" oncommand="window.close();"/>
|
||||
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="onError()"/>
|
||||
</window>
|
||||
|
||||
<deck id="zotero-note-editor-switcher" flex="1">
|
||||
<oldzoteronoteeditor id="zotero-note-editor-old" flex="1" onerror="onError()"/>
|
||||
<zoteronoteeditor id="zotero-note-editor" flex="1" onerror="return;onError()"/>
|
||||
</deck>
|
||||
</window>
|
||||
|
|
57
chrome/content/zotero/noteBackup.js
Normal file
57
chrome/content/zotero/noteBackup.js
Normal 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);
|
26
chrome/content/zotero/noteBackup.xul
Normal file
26
chrome/content/zotero/noteBackup.xul
Normal 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>
|
|
@ -41,6 +41,15 @@ var ZoteroOverlay = new function () {
|
|||
throw new Error("Skipping loading");
|
||||
}
|
||||
|
||||
// Keep in sync with standalone.xul
|
||||
if (Zotero.test) {
|
||||
let ns = 'http://www.w3.org/1999/xhtml';
|
||||
let div = document.createElementNS(ns, 'div');
|
||||
let beforeNode = document.getElementById('browser');
|
||||
div.id = 'tab-bar-container';
|
||||
beforeNode.parentNode.insertBefore(div, beforeNode);
|
||||
}
|
||||
|
||||
ZoteroPane.init();
|
||||
}
|
||||
catch (e) {
|
||||
|
|
|
@ -38,9 +38,12 @@ Zotero_Preferences.General = {
|
|||
'zotero.preferences.launchNonNativeFiles', Zotero.appName
|
||||
);
|
||||
}
|
||||
var menuitem = document.getElementById('fileHandler-internal');
|
||||
menuitem.setAttribute('label', Zotero.appName);
|
||||
|
||||
this.updateAutoRenameFilesUI();
|
||||
this._updateFileHandlerUI();
|
||||
this._updateZotero6BetaCheckbox();
|
||||
},
|
||||
|
||||
updateAutoRenameFilesUI: function () {
|
||||
|
@ -88,6 +91,16 @@ Zotero_Preferences.General = {
|
|||
var handler = Zotero.Prefs.get('fileHandler.pdf');
|
||||
var menulist = document.getElementById('fileHandler-pdf');
|
||||
var customMenuItem = document.getElementById('fileHandler-custom');
|
||||
|
||||
// TEMP: Use separate checkbox for now
|
||||
/*if (handler == 'zotero') {
|
||||
let menuitem = document.getElementById('fileHandler-internal');
|
||||
menulist.selectedIndex = 0;
|
||||
customMenuItem.hidden = true;
|
||||
return;
|
||||
}*/
|
||||
|
||||
// Custom handler
|
||||
if (handler) {
|
||||
let icon;
|
||||
try {
|
||||
|
@ -113,11 +126,12 @@ Zotero_Preferences.General = {
|
|||
customMenuItem.className = '';
|
||||
}
|
||||
customMenuItem.hidden = false;
|
||||
menulist.selectedIndex = 0;
|
||||
menulist.selectedIndex = 1;
|
||||
}
|
||||
// System default
|
||||
else {
|
||||
customMenuItem.hidden = true;
|
||||
menulist.selectedIndex = 1;
|
||||
menulist.selectedIndex = 2;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -126,5 +140,38 @@ Zotero_Preferences.General = {
|
|||
throw new Error(`Unknown file type ${type}`);
|
||||
}
|
||||
return 'fileHandler.pdf';
|
||||
},
|
||||
|
||||
|
||||
handleZotero6BetaChange: function (event) {
|
||||
var ps = Services.prompt;
|
||||
var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
|
||||
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
|
||||
var index = ps.confirmEx(
|
||||
window,
|
||||
Zotero.getString('general.restartRequired'),
|
||||
Zotero.getString('general.restartRequiredForChange', Zotero.appName),
|
||||
buttonFlags,
|
||||
Zotero.getString('general.restartApp', Zotero.appName),
|
||||
null, null, null, {}
|
||||
);
|
||||
if (index == 0) {
|
||||
Zotero.Prefs.set('beta.zotero6', !event.target.checked);
|
||||
Zotero.Utilities.Internal.quitZotero(true);
|
||||
return;
|
||||
}
|
||||
// Set to opposite so the click changes it back to what it was before
|
||||
event.target.checked = !event.target.checked;
|
||||
},
|
||||
|
||||
|
||||
_updateZotero6BetaCheckbox: function () {
|
||||
var checkbox = document.getElementById('zotero6-checkbox');
|
||||
if (Zotero.Prefs.get('beta.zotero6')) {
|
||||
checkbox.setAttribute('checked', true);
|
||||
}
|
||||
else {
|
||||
checkbox.removeAttribute('checked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
<preference id="pref-groups-copyChildFileAttachments" name="extensions.zotero.groups.copyChildFileAttachments" type="bool"/>
|
||||
<preference id="pref-groups-copyChildLinks" name="extensions.zotero.groups.copyChildLinks" type="bool"/>
|
||||
<preference id="pref-groups-copyTags" name="extensions.zotero.groups.copyTags" type="bool"/>
|
||||
|
||||
</preferences>
|
||||
|
||||
<groupbox id="zotero-prefpane-file-handling-groupbox">
|
||||
|
@ -67,6 +66,9 @@
|
|||
<label value="&zotero.preferences.fileHandler.openPDFsUsing;" control="file-handler-pdf"/>
|
||||
<menulist id="fileHandler-pdf" class="fileHandler-menu">
|
||||
<menupopup>
|
||||
<menuitem id="fileHandler-internal"
|
||||
oncommand="Zotero_Preferences.General.setFileHandler('pdf', 'zotero')"
|
||||
hidden="true"/>
|
||||
<menuitem id="fileHandler-custom"/>
|
||||
<menuitem label="&zotero.preferences.fileHandler.systemDefault;"
|
||||
oncommand="Zotero_Preferences.General.setFileHandler('pdf', false)"/>
|
||||
|
@ -75,6 +77,13 @@
|
|||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<checkbox
|
||||
id="zotero6-checkbox"
|
||||
label="Enable the Zotero PDF reader and new note editor (preview, My Library only)"
|
||||
style="margin-top: 5px"
|
||||
onclick="Zotero_Preferences.General.handleZotero6BetaChange(event)"/>
|
||||
<label class="zotero-text-link" value="Learn more about the preview"
|
||||
style="margin-left: 23px; margin-top: 1px" href="https://www.zotero.org/support/pdf_reader_preview"/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
|
||||
|
|
120
chrome/content/zotero/reader.xul
Normal file
120
chrome/content/zotero/reader.xul
Normal 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>
|
|
@ -42,6 +42,28 @@ const ZoteroStandalone = new function() {
|
|||
window.document.documentElement.setAttribute('sizemode', 'normal');
|
||||
}
|
||||
|
||||
// Create tab bar by default
|
||||
if (Zotero.isMac) {
|
||||
document.documentElement.setAttribute('drawintitlebar', true);
|
||||
document.documentElement.setAttribute('tabsintitlebar', true);
|
||||
document.documentElement.setAttribute('chromemargin', '0,-1,-1,-1');
|
||||
}
|
||||
|
||||
this.switchMenuType('library');
|
||||
Zotero.Notifier.registerObserver(
|
||||
{
|
||||
notify: async (action, type, ids, extraData) => {
|
||||
if (action == 'select') {
|
||||
// "library" or "reader"
|
||||
this.switchMenuType(extraData[ids[0]].type);
|
||||
setTimeout(() => ZoteroPane.updateToolbarPosition(), 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
['tab'],
|
||||
'tab'
|
||||
);
|
||||
|
||||
Zotero.Promise.try(function () {
|
||||
if(!Zotero) {
|
||||
throw true;
|
||||
|
@ -59,6 +81,13 @@ const ZoteroStandalone = new function() {
|
|||
|
||||
ZoteroStandalone.DebugOutput.init();
|
||||
|
||||
// TEMP: Remove tab bar if not PDF build
|
||||
if (Zotero.isMac && !Zotero.isPDFBuild) {
|
||||
document.documentElement.removeAttribute('drawintitlebar');
|
||||
document.documentElement.removeAttribute('tabsintitlebar');
|
||||
document.documentElement.removeAttribute('chromemargin');
|
||||
}
|
||||
|
||||
Zotero.hideZoteroPaneOverlays();
|
||||
ZoteroPane.init();
|
||||
ZoteroPane.makeVisible();
|
||||
|
@ -90,7 +119,16 @@ const ZoteroStandalone = new function() {
|
|||
return;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.switchMenuType = function (type) {
|
||||
document.querySelectorAll('.menu-type-library, .menu-type-reader').forEach(el => el.collapsed = true);
|
||||
document.querySelectorAll('.menu-type-' + type).forEach(el => el.collapsed = false);
|
||||
};
|
||||
|
||||
this.onReaderCmd = function (cmd) {
|
||||
let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||
reader.menuCmd(cmd);
|
||||
};
|
||||
|
||||
this.onFileMenuOpen = function () {
|
||||
var active = false;
|
||||
|
@ -422,7 +460,6 @@ const ZoteroStandalone = new function() {
|
|||
this.updateNoteFontSize = function (event) {
|
||||
var size = event.originalTarget.getAttribute('label');
|
||||
Zotero.Prefs.set('note.fontSize', size);
|
||||
this.promptForRestart();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
<window id="main-window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="navigator:browser"
|
||||
title="&brandShortName;"
|
||||
width="1000" height="600"
|
||||
|
@ -111,7 +112,15 @@
|
|||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
</menupopup>
|
||||
|
||||
<vbox id="titlebar">
|
||||
<hbox id="titlebar-buttonbox-container" skipintoolbarset="true">
|
||||
<hbox id="titlebar-buttonbox">
|
||||
<toolbarbutton class="titlebar-button titlebar-min" oncommand="window.minimize();"/>
|
||||
<toolbarbutton class="titlebar-button titlebar-max" oncommand="onTitlebarMaxClick();"/>
|
||||
<toolbarbutton class="titlebar-button titlebar-close" command="cmd_closeWindow"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<toolbox id="navigator-toolbox" class="toolbox-top" mode="icons" defaultmode="icons">
|
||||
<!-- Menu -->
|
||||
<toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
|
||||
|
@ -119,28 +128,38 @@
|
|||
mode="icons" iconsize="small" defaulticonsize="small"
|
||||
context="toolbar-context-menu">
|
||||
<toolbaritem id="menubar-items" align="center">
|
||||
<!-- TODO: Localize labels -->
|
||||
<!--
|
||||
On macOS the document's top-most menubar node is used and
|
||||
there is no way to change it after the initialization, only
|
||||
the children can be modified. All non-menu nodes are ignored
|
||||
-->
|
||||
<menubar id="main-menubar"
|
||||
style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
|
||||
<menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;"
|
||||
onpopupshowing="ZoteroStandalone.onFileMenuOpen()">
|
||||
<menupopup id="menu_FilePopup">
|
||||
<menu id="menu_newItem" label="&zotero.toolbar.newItem.label;">
|
||||
<menu id="menu_newItem" class="menu-type-library" label="&zotero.toolbar.newItem.label;">
|
||||
<menupopup id="menu_NewItemPopup"
|
||||
onpopupshowing="ZoteroStandalone.buildNewItemMenu()"/>
|
||||
</menu>
|
||||
<menuitem id="menu_newNote" label="&zotero.toolbar.newNote;"
|
||||
<menuitem id="menu_newNote" class="menu-type-library" label="&zotero.toolbar.newNote;"
|
||||
command="cmd_zotero_newStandaloneNote"/>
|
||||
<menuitem id="menu_newCollection" label="&zotero.toolbar.newCollection.label;"
|
||||
<menuitem id="menu_newCollection" class="menu-type-library" label="&zotero.toolbar.newCollection.label;"
|
||||
command="cmd_zotero_newCollection"/>
|
||||
<menuitem label="Save As" class="menu-type-reader" oncommand="ZoteroStandalone.onReaderCmd('export')"/>
|
||||
<menuitem label="Print" class="menu-type-reader" oncommand="ZoteroStandalone.onReaderCmd('print')"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_close" label="&closeCmd.label;" key="key_close"
|
||||
<menuitem id="menu_close" class="menu-type-library" label="&closeCmd.label;" key="key_close"
|
||||
accesskey="&closeCmd.accesskey;" command="cmd_close"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_import" label="&importCmd.label;"
|
||||
<menuitem id="menu_close_tab" class="menu-type-reader" label="&closeCmd.label;" key="key_close"
|
||||
accesskey="&closeCmd.accesskey;" oncommand="Zotero_Tabs.close()"/>
|
||||
<menuseparator class="menu-type-library"/>
|
||||
<menuitem id="menu_import" class="menu-type-library" label="&importCmd.label;"
|
||||
command="cmd_zotero_import" key="key_import"/>
|
||||
<menuitem id="menu_importFromClipboard" label="&importFromClipboardCmd.label;"
|
||||
<menuitem id="menu_importFromClipboard" class="menu-type-library" label="&importFromClipboardCmd.label;"
|
||||
command="cmd_zotero_importFromClipboard" key="key_importFromClipboard"/>
|
||||
<menuitem id="menu_exportLibrary" label="&zotero.toolbar.export.label;"
|
||||
<menuitem id="menu_exportLibrary" class="menu-type-library" label="&zotero.toolbar.export.label;"
|
||||
command="cmd_zotero_exportLibrary"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
@ -154,29 +173,33 @@
|
|||
<menuitem id="menu_cut"/>
|
||||
<menuitem id="menu_copy"/>
|
||||
<menuitem id="menu_copyCitation"
|
||||
class="menu-type-library"
|
||||
label="©CitationCmd.label;"
|
||||
command="cmd_zotero_copyCitation"
|
||||
key="key_copyCitation"
|
||||
hidden="true"/>
|
||||
<menuitem id="menu_copyBibliography"
|
||||
class="menu-type-library"
|
||||
label="©BibliographyCmd.label;"
|
||||
command="cmd_zotero_copyBibliography"
|
||||
key="key_copyBibliography"
|
||||
hidden="true"/>
|
||||
<menuitem id="menu_copyExport"
|
||||
class="menu-type-library"
|
||||
key="key_copyBibliography"
|
||||
command="cmd_zotero_copyBibliography"
|
||||
hidden="true"/>
|
||||
<menuitem id="menu_paste"/>
|
||||
<menuitem id="menu_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_find"/>
|
||||
<menuseparator class="menu-type-library"/>
|
||||
<menuitem id="menu_selectAll" class="menu-type-library"/>
|
||||
<menuseparator class="menu-type-library"/>
|
||||
<menuitem id="menu_find" class="menu-type-library"/>
|
||||
<menuitem id="menu_advancedSearch"
|
||||
class="menu-type-library"
|
||||
label="&zotero.toolbar.advancedSearch;"
|
||||
command="cmd_zotero_advancedSearch"/>
|
||||
<menuseparator hidden="true" id="textfieldDirection-separator"/>
|
||||
<menuseparator class="menu-type-library" hidden="true" id="textfieldDirection-separator"/>
|
||||
<menuitem id="textfieldDirection-swap"
|
||||
command="cmd_switchTextDirection"
|
||||
key="key_switchTextDirection"
|
||||
|
@ -190,8 +213,27 @@
|
|||
label="&viewMenu.label;"
|
||||
onpopupshowing="ZoteroStandalone.onViewMenuOpen()">
|
||||
<menupopup id="menu_viewPopup">
|
||||
<menu id="layout-menu"
|
||||
label="&layout.label;">
|
||||
<!-- <menuitem class="menu-type-reader" label="Switch to Presentation Mode" oncommand="ZoteroStandalone.onReaderCmd('presentationmode')"/>-->
|
||||
<!-- <menuseparator class="menu-type-reader"/>-->
|
||||
<menuitem class="menu-type-reader" label="Go to First Page" oncommand="ZoteroStandalone.onReaderCmd('firstpage')"/>
|
||||
<menuitem class="menu-type-reader" label="Go to Last Page" oncommand="ZoteroStandalone.onReaderCmd('lastpage')"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem class="menu-type-reader" label="Rotate Clockwise" oncommand="ZoteroStandalone.onReaderCmd('rotatecw')"/>
|
||||
<menuitem class="menu-type-reader" label="Rotate Counterclockwise" oncommand="ZoteroStandalone.onReaderCmd('rotateccw')"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem class="menu-type-reader" label="Text Selection Tool" oncommand="ZoteroStandalone.onReaderCmd('switchcursortool_select')"/>
|
||||
<menuitem class="menu-type-reader" label="Hand Tool" oncommand="ZoteroStandalone.onReaderCmd('switchcursortool_hand')"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem class="menu-type-reader" label="Vertical Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_vertical')"/>
|
||||
<menuitem class="menu-type-reader" label="Horizontal Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_horizontal')"/>
|
||||
<menuitem class="menu-type-reader" label="Wrapped Scrolling" oncommand="ZoteroStandalone.onReaderCmd('switchscrollmode_wrapped')"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
<menuitem class="menu-type-reader" label="No Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_none')"/>
|
||||
<menuitem class="menu-type-reader" label="Odd Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_odd')"/>
|
||||
<menuitem class="menu-type-reader" label="Even Spreads" oncommand="ZoteroStandalone.onReaderCmd('switchspreadmode_even')"/>
|
||||
<menuseparator class="menu-type-reader"/>
|
||||
|
||||
<menu id="layout-menu" label="&layout.label;">
|
||||
<menupopup oncommand="ZoteroStandalone.onViewMenuItemClick(event)">
|
||||
<menuitem
|
||||
id="view-menuitem-standard"
|
||||
|
@ -259,6 +301,7 @@
|
|||
</menu>
|
||||
<menuseparator/>
|
||||
<menuitem id="view-menuitem-recursive-collections"
|
||||
class="menu-type-library"
|
||||
label="&recursiveCollections.label;"
|
||||
oncommand="ZoteroStandalone.onViewMenuItemClick(event)"
|
||||
type="checkbox"
|
||||
|
@ -268,16 +311,17 @@
|
|||
|
||||
<menu id="toolsMenu" label="&toolsMenu.label;" accesskey="&toolsMenu.accesskey;">
|
||||
<menupopup id="menu_ToolsPopup">
|
||||
<menuitem id="menu_createTimeline" label="&zotero.toolbar.timeline.label;"
|
||||
<menuitem id="menu_createTimeline" class="menu-type-library" label="&zotero.toolbar.timeline.label;"
|
||||
command="cmd_zotero_createTimeline"/>
|
||||
<menuitem id="menu_rtfScan" label="&zotero.toolbar.rtfScan.label;"
|
||||
<menuitem id="menu_rtfScan" class="menu-type-library" label="&zotero.toolbar.rtfScan.label;"
|
||||
command="cmd_zotero_rtfScan"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="installConnector"
|
||||
class="menu-type-library"
|
||||
accessKey="&installConnector.accesskey;"
|
||||
label="&installConnector.label;"
|
||||
oncommand="ZoteroStandalone.openHelp('connectors');"/>
|
||||
<menuitem id="menu_addons" label="&addons.label;"
|
||||
<menuitem id="menu_addons" class="menu-type-library" label="&addons.label;"
|
||||
oncommand="Zotero.openInViewer('chrome://mozapps/content/extensions/extensions.xul', ZoteroStandalone.updateAddonsPane)"/>
|
||||
<menu id="developer-menu"
|
||||
label="&developer.label;">
|
||||
|
@ -289,7 +333,7 @@
|
|||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator/>
|
||||
<menu id="manage-attachments-menu" label="&manageAttachments.label;"
|
||||
<menu id="manage-attachments-menu" class="menu-type-library" label="&manageAttachments.label;"
|
||||
onpopupshowing="ZoteroStandalone.onManageAttachmentsMenuOpen()"
|
||||
oncommand="ZoteroStandalone.onManageAttachmentsMenuItemClick(event)">
|
||||
<menupopup id="manage-attachments-menupopup">
|
||||
|
@ -372,6 +416,8 @@
|
|||
</toolbaritem>
|
||||
</toolbar>
|
||||
</toolbox>
|
||||
<!-- Keep in sync with Zotero.test conditional block in overlay.js -->
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" id="tab-bar-container"/>
|
||||
<!--<toolbarpalette/>
|
||||
|
||||
<toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"
|
||||
|
|
247
chrome/content/zotero/tabs.js
Normal file
247
chrome/content/zotero/tabs.js
Normal 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
210
chrome/content/zotero/xpcom/annotations.js
Normal file
210
chrome/content/zotero/xpcom/annotations.js
Normal 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;
|
||||
};
|
||||
};
|
|
@ -24,11 +24,13 @@
|
|||
*/
|
||||
|
||||
Zotero.Attachments = new function(){
|
||||
// Keep in sync with Zotero.Schema.integrityCheck()
|
||||
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
|
||||
this.LINK_MODE_IMPORTED_FILE = 0;
|
||||
this.LINK_MODE_IMPORTED_URL = 1;
|
||||
this.LINK_MODE_LINKED_FILE = 2;
|
||||
this.LINK_MODE_LINKED_URL = 3;
|
||||
this.LINK_MODE_EMBEDDED_IMAGE = 4;
|
||||
|
||||
this.BASE_PATH_PLACEHOLDER = 'attachments:';
|
||||
|
||||
var _findPDFQueue = [];
|
||||
|
@ -351,6 +353,98 @@ Zotero.Attachments = new function(){
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Saves an image for a parent note or image annotation
|
||||
*
|
||||
* Emerging formats like WebP and AVIF are supported here,
|
||||
* but should be filtered on the calling logic for now
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {Blob} params.blob - Image to save
|
||||
* @param {Integer} params.parentItemID - Note or annotation item to add item to
|
||||
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
|
||||
* @return {Promise<Zotero.Item>}
|
||||
*/
|
||||
this.importEmbeddedImage = async function ({ blob, parentItemID, saveOptions }) {
|
||||
Zotero.debug('Importing embedded image');
|
||||
|
||||
if (!parentItemID) {
|
||||
throw new Error("parentItemID must be provided");
|
||||
}
|
||||
|
||||
var contentType = blob.type;
|
||||
var fileExt;
|
||||
switch (contentType) {
|
||||
case 'image/apng':
|
||||
fileExt = 'apng';
|
||||
break;
|
||||
case 'image/avif': // Supported from FF 86
|
||||
fileExt = 'avif';
|
||||
break;
|
||||
case 'image/gif':
|
||||
fileExt = 'gif';
|
||||
break;
|
||||
case 'image/jpeg':
|
||||
fileExt = 'jpg';
|
||||
break;
|
||||
case 'image/png':
|
||||
fileExt = 'png';
|
||||
break;
|
||||
case 'image/svg+xml':
|
||||
fileExt = 'svg';
|
||||
break;
|
||||
case 'image/webp': // Supported from FF 65
|
||||
fileExt = 'webp';
|
||||
break;
|
||||
case 'image/bmp':
|
||||
fileExt = 'bmp';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported embedded image content type '${contentType}'`);
|
||||
}
|
||||
var filename = 'image.' + fileExt;
|
||||
|
||||
var attachmentItem;
|
||||
var destDir;
|
||||
try {
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
// Create a new attachment
|
||||
attachmentItem = new Zotero.Item('attachment');
|
||||
let { libraryID: parentLibraryID } = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
|
||||
attachmentItem.libraryID = parentLibraryID;
|
||||
attachmentItem.parentID = parentItemID;
|
||||
attachmentItem.attachmentLinkMode = this.LINK_MODE_EMBEDDED_IMAGE;
|
||||
attachmentItem.attachmentPath = 'storage:' + filename;
|
||||
attachmentItem.attachmentContentType = contentType;
|
||||
await attachmentItem.save(saveOptions);
|
||||
|
||||
// Write blob to file in attachment directory
|
||||
destDir = await this.createDirectoryForItem(attachmentItem);
|
||||
let file = OS.Path.join(destDir, filename);
|
||||
await Zotero.File.putContentsAsync(file, blob);
|
||||
await Zotero.File.setNormalFilePermissions(file);
|
||||
}.bind(this));
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError("Failed importing image:\n\n" + e);
|
||||
|
||||
// Clean up
|
||||
try {
|
||||
if (destDir) {
|
||||
await OS.File.removeDir(destDir, { ignoreAbsent: true });
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
return attachmentItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Integer} options.libraryID
|
||||
|
@ -2102,6 +2196,9 @@ Zotero.Attachments = new function(){
|
|||
if (!(item instanceof Zotero.Item)) {
|
||||
throw new Error("'item' must be a Zotero.Item");
|
||||
}
|
||||
if (!item.key) {
|
||||
throw new Error("Item key must be set");
|
||||
}
|
||||
return this.getStorageDirectoryByLibraryAndKey(item.libraryID, item.key);
|
||||
}
|
||||
|
||||
|
@ -2211,6 +2308,9 @@ Zotero.Attachments = new function(){
|
|||
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
|
||||
break;
|
||||
|
||||
case Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
throw new Error("Invalid attachment link mode");
|
||||
}
|
||||
|
@ -2367,7 +2467,7 @@ Zotero.Attachments = new function(){
|
|||
Zotero.DB.requireTransaction();
|
||||
|
||||
var newAttachment = attachment.clone(libraryID);
|
||||
if (attachment.isImportedAttachment()) {
|
||||
if (attachment.isStoredFileAttachment()) {
|
||||
// Attachment path isn't copied over by clone() if libraryID is different
|
||||
newAttachment.attachmentPath = attachment.attachmentPath;
|
||||
}
|
||||
|
@ -2379,7 +2479,7 @@ Zotero.Attachments = new function(){
|
|||
// Move files over if they exist
|
||||
var oldDir;
|
||||
var newDir;
|
||||
if (newAttachment.isImportedAttachment()) {
|
||||
if (newAttachment.isStoredFileAttachment()) {
|
||||
oldDir = this.getStorageDirectory(attachment).path;
|
||||
if (await OS.File.exists(oldDir)) {
|
||||
newDir = this.getStorageDirectory(newAttachment).path;
|
||||
|
@ -2405,7 +2505,7 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
catch (e) {
|
||||
// Move files back if old item can't be deleted
|
||||
if (newAttachment.isImportedAttachment()) {
|
||||
if (newAttachment.isStoredFileAttachment()) {
|
||||
try {
|
||||
await OS.File.move(newDir, oldDir);
|
||||
}
|
||||
|
@ -2431,7 +2531,7 @@ Zotero.Attachments = new function(){
|
|||
Zotero.DB.requireTransaction();
|
||||
|
||||
var newAttachment = attachment.clone(libraryID);
|
||||
if (attachment.isImportedAttachment()) {
|
||||
if (attachment.isStoredFileAttachment()) {
|
||||
// Attachment path isn't copied over by clone() if libraryID is different
|
||||
newAttachment.attachmentPath = attachment.attachmentPath;
|
||||
}
|
||||
|
@ -2441,7 +2541,7 @@ Zotero.Attachments = new function(){
|
|||
yield newAttachment.save();
|
||||
|
||||
// Copy over files if they exist
|
||||
if (newAttachment.isImportedAttachment() && (yield attachment.fileExists())) {
|
||||
if (newAttachment.isStoredFileAttachment() && (yield attachment.fileExists())) {
|
||||
let dir = Zotero.Attachments.getStorageDirectory(attachment);
|
||||
let newDir = yield Zotero.Attachments.createDirectoryForItem(newAttachment);
|
||||
yield Zotero.File.copyDirectory(dir, newDir);
|
||||
|
@ -2787,6 +2887,8 @@ Zotero.Attachments = new function(){
|
|||
return 'linked_file';
|
||||
case this.LINK_MODE_LINKED_URL:
|
||||
return 'linked_url';
|
||||
case this.LINK_MODE_EMBEDDED_IMAGE:
|
||||
return 'embedded_image';
|
||||
default:
|
||||
throw new Error(`Invalid link mode ${linkMode}`);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ Zotero.HTTPIntegrationClient.Application = function() {
|
|||
this.outputFormat = 'html';
|
||||
this.supportedNotes = ['footnotes'];
|
||||
this.supportsImportExport = false;
|
||||
this.supportsTextInsertion = false;
|
||||
this.processorName = "HTTP Integration";
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Application.prototype = {
|
||||
|
@ -68,6 +69,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = {
|
|||
this.outputFormat = result.outputFormat || this.outputFormat;
|
||||
this.supportedNotes = result.supportedNotes || this.supportedNotes;
|
||||
this.supportsImportExport = result.supportsImportExport || this.supportsImportExport;
|
||||
this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion;
|
||||
this.processorName = result.processorName || this.processorName;
|
||||
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
|
||||
}
|
||||
|
@ -80,7 +82,8 @@ Zotero.HTTPIntegrationClient.Document = function(documentID) {
|
|||
this._documentID = documentID;
|
||||
};
|
||||
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
|
||||
"setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument"]) {
|
||||
"setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument",
|
||||
"insertText"]) {
|
||||
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
|
||||
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
|
||||
[this._documentID].concat(Array.prototype.slice.call(arguments)));
|
||||
|
@ -146,6 +149,10 @@ Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields,
|
|||
fields = fields.map((f) => f._id);
|
||||
await Zotero.HTTPIntegrationClient.sendCommand("Document.convert", [this._documentID, fields, fieldType, noteTypes]);
|
||||
};
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.convertPlaceholdersToFields = async function(codes, placeholderIDs, noteType) {
|
||||
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.convertPlaceholdersToFields", [this._documentID, codes, placeholderIDs, noteType]);
|
||||
return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
|
||||
}
|
||||
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
|
||||
Zotero.HTTPIntegrationClient.inProgress = false;
|
||||
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);
|
||||
|
|
|
@ -1701,7 +1701,8 @@ Zotero.Server.Connector.Ping.prototype = {
|
|||
|
||||
let response = {
|
||||
prefs: {
|
||||
automaticSnapshots: Zotero.Prefs.get('automaticSnapshots')
|
||||
automaticSnapshots: Zotero.Prefs.get('automaticSnapshots'),
|
||||
googleDocsAddNoteEnabled: Zotero.isPDFBuild
|
||||
}
|
||||
};
|
||||
if (Zotero.QuickCopy.hasSiteSettings()) {
|
||||
|
|
|
@ -354,6 +354,8 @@ Zotero.ItemTypes = new function() {
|
|||
var _primaryTypeNames = ['book', 'bookSection', 'journalArticle', 'newspaperArticle', 'document'];
|
||||
var _primaryTypes;
|
||||
var _secondaryTypes;
|
||||
// Item types hidden from New Item menu
|
||||
var _hiddenTypeNames = ['webpage', 'attachment', 'note', 'annotation'];
|
||||
var _hiddenTypes;
|
||||
|
||||
var _numPrimary = 5;
|
||||
|
@ -371,12 +373,13 @@ Zotero.ItemTypes = new function() {
|
|||
|
||||
// Secondary types
|
||||
_secondaryTypes = yield this._getTypesFromDB(
|
||||
`WHERE display != 0 AND display NOT IN ('${_primaryTypeNames.join("', '")}')`
|
||||
+ " AND name != 'webpage'"
|
||||
`WHERE typeName NOT IN ('${_primaryTypeNames.concat(_hiddenTypeNames).join("', '")}')`
|
||||
);
|
||||
|
||||
// Hidden types
|
||||
_hiddenTypes = yield this._getTypesFromDB('WHERE display=0')
|
||||
_hiddenTypes = yield this._getTypesFromDB(
|
||||
`WHERE typeName IN ('${_hiddenTypeNames.join("', '")}')`
|
||||
);
|
||||
|
||||
// Custom labels and icons
|
||||
var sql = "SELECT customItemTypeID AS id, label, icon FROM customItemTypes";
|
||||
|
@ -402,8 +405,8 @@ Zotero.ItemTypes = new function() {
|
|||
mru.split(',')
|
||||
.slice(0, _numPrimary)
|
||||
.map(name => this.getName(name))
|
||||
// Ignore 'webpage' item type
|
||||
.filter(name => name && name != 'webpage')
|
||||
// Ignore hidden item types and 'webpage'
|
||||
.filter(name => name && !_hiddenTypeNames.concat('webpage').includes(name))
|
||||
);
|
||||
|
||||
// Add types from defaults until we reach our limit
|
||||
|
|
|
@ -31,8 +31,7 @@ Zotero.Collection = function(params = {}) {
|
|||
this._childCollections = new Set();
|
||||
this._childItems = new Set();
|
||||
|
||||
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID',
|
||||
'parentKey', 'lastSync']);
|
||||
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID', 'parentKey']);
|
||||
}
|
||||
|
||||
Zotero.extendClass(Zotero.DataObject, Zotero.Collection);
|
||||
|
|
|
@ -156,81 +156,6 @@ Zotero.Collections = function() {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort an array of collectionIDs from top-level to deepest
|
||||
*
|
||||
* Order within each level is undefined.
|
||||
*
|
||||
* This is used to sort higher-level collections first in upload JSON, since otherwise the API
|
||||
* would reject lower-level collections for having missing parents.
|
||||
*/
|
||||
this.sortByLevel = function (ids) {
|
||||
let levels = {};
|
||||
|
||||
// Get objects from ids
|
||||
let objs = {};
|
||||
ids.forEach(id => objs[id] = Zotero.Collections.get(id));
|
||||
|
||||
// Get top-level collections
|
||||
let top = ids.filter(id => !objs[id].parentID);
|
||||
levels["0"] = top.slice();
|
||||
ids = Zotero.Utilities.arrayDiff(ids, top);
|
||||
|
||||
// For each collection in list, walk up its parent tree. If a parent is present in the
|
||||
// list of ids, add it to the appropriate level bucket and remove it.
|
||||
while (ids.length) {
|
||||
let tree = [ids[0]];
|
||||
let keep = [ids[0]];
|
||||
let id = ids.shift();
|
||||
let seen = new Set([id]);
|
||||
while (true) {
|
||||
let c = Zotero.Collections.get(id);
|
||||
let parentID = c.parentID;
|
||||
if (!parentID) {
|
||||
break;
|
||||
}
|
||||
// Avoid an infinite loop if collections are incorrectly nested within each other
|
||||
if (seen.has(parentID)) {
|
||||
throw new Zotero.Error(
|
||||
"Incorrectly nested collections",
|
||||
Zotero.Error.ERROR_INVALID_COLLECTION_NESTING,
|
||||
{
|
||||
collectionID: id
|
||||
}
|
||||
);
|
||||
}
|
||||
seen.add(parentID);
|
||||
tree.push(parentID);
|
||||
// If parent is in list, remove it
|
||||
let pos = ids.indexOf(parentID);
|
||||
if (pos != -1) {
|
||||
keep.push(parentID);
|
||||
ids.splice(pos, 1);
|
||||
}
|
||||
id = parentID;
|
||||
}
|
||||
let level = tree.length - 1;
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
let currentLevel = level - i;
|
||||
for (let j = 0; j < keep.length; j++) {
|
||||
if (tree[i] != keep[j]) continue;
|
||||
|
||||
if (!levels[currentLevel]) {
|
||||
levels[currentLevel] = [];
|
||||
}
|
||||
levels[currentLevel].push(keep[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var orderedIDs = [];
|
||||
for (let level in levels) {
|
||||
orderedIDs = orderedIDs.concat(levels[level]);
|
||||
}
|
||||
return orderedIDs;
|
||||
};
|
||||
|
||||
|
||||
this._loadChildCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
|
||||
var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID "
|
||||
+ "FROM collections C1 LEFT JOIN collections C2 ON (C1.collectionID=C2.parentCollectionID) "
|
||||
|
|
|
@ -639,7 +639,6 @@ Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function*
|
|||
throw new Error(this._ObjectType + " " + (id ? id : libraryID + "/" + key)
|
||||
+ " not found in Zotero." + this._ObjectType + ".loadPrimaryData()");
|
||||
}
|
||||
this._clearChanged('primaryData');
|
||||
|
||||
// If object doesn't exist, mark all data types as loaded
|
||||
this._markAllDataTypeLoadStates(true);
|
||||
|
@ -735,11 +734,26 @@ Zotero.DataObject.prototype._markAllDataTypeLoadStates = function (loaded) {
|
|||
}
|
||||
}
|
||||
|
||||
Zotero.DataObject.prototype._hasFieldChanged = function (field) {
|
||||
return field in this._changedData;
|
||||
};
|
||||
|
||||
Zotero.DataObject.prototype._getChangedField = function (field) {
|
||||
return this._changedData[field];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get either the unsaved value of a field or the saved value if unchanged since the last save
|
||||
*/
|
||||
Zotero.DataObject.prototype._getLatestField = function (field) {
|
||||
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
|
||||
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get either the unsaved value of a field or the saved value if unchanged since the last save
|
||||
*/
|
||||
Zotero.DataObject.prototype._getLatestField = function (field) {
|
||||
return this._changedData[field] !== undefined ? this._changedData[field] : this['_' + field];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -749,10 +763,13 @@ Zotero.DataObject.prototype._getLatestField = function (field) {
|
|||
*/
|
||||
Zotero.DataObject.prototype._markFieldChange = function (field, value) {
|
||||
// New method (changedData)
|
||||
if (['deleted', 'tags'].includes(field)) {
|
||||
if (['deleted', 'tags'].includes(field) || field.startsWith('annotation')) {
|
||||
if (Array.isArray(value)) {
|
||||
this._changedData[field] = [...value];
|
||||
}
|
||||
else if (typeof value === 'object' && value !== null) {
|
||||
this._changedData[field] = Object.assign({}, value);
|
||||
}
|
||||
else {
|
||||
this._changedData[field] = value;
|
||||
}
|
||||
|
@ -1258,10 +1275,6 @@ Zotero.DataObject.prototype._initErase = Zotero.Promise.method(function (env) {
|
|||
|
||||
if (!env.options.skipEditCheck) this.editCheck();
|
||||
|
||||
if (env.options.skipDeleteLog) {
|
||||
env.notifierData[this.id].skipDeleteLog = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -1277,6 +1290,10 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
|
|||
this.ObjectsClass.unload(env.deletedObjectIDs || this.id);
|
||||
}.bind(this));
|
||||
|
||||
if (env.options.skipDeleteLog) {
|
||||
env.notifierData[this.id].skipDeleteLog = true;
|
||||
}
|
||||
|
||||
if (!env.options.skipNotifier) {
|
||||
Zotero.Notifier.queue(
|
||||
'delete',
|
||||
|
|
|
@ -486,9 +486,14 @@ Zotero.DataObjects.prototype.loadDataTypes = Zotero.Promise.coroutine(function*
|
|||
* @param {Integer[]} [ids]
|
||||
*/
|
||||
Zotero.DataObjects.prototype._loadDataTypeInLibrary = Zotero.Promise.coroutine(function* (dataType, libraryID, ids) {
|
||||
var funcName = "_load" + dataType[0].toUpperCase() + dataType.substr(1)
|
||||
// note → loadNotes
|
||||
// itemData → loadItemData
|
||||
// annotationDeferred → loadAnnotationsDeferred
|
||||
var baseDataType = dataType.replace('Deferred', '');
|
||||
var funcName = "_load" + dataType[0].toUpperCase() + baseDataType.substr(1)
|
||||
// Single data types need an 's' (e.g., 'note' -> 'loadNotes()')
|
||||
+ ((dataType.endsWith('s') || dataType.endsWith('Data') ? '' : 's'));
|
||||
+ ((baseDataType.endsWith('s') || baseDataType.endsWith('Data') ? '' : 's'))
|
||||
+ (dataType.endsWith('Deferred') ? 'Deferred' : '');
|
||||
if (!this[funcName]) {
|
||||
throw new Error(`Zotero.${this._ZDO_Objects}.${funcName} is not a function`);
|
||||
}
|
||||
|
@ -706,6 +711,150 @@ Zotero.DataObjects.prototype._loadRelations = Zotero.Promise.coroutine(function*
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Sort an array of collections or items from top-level to deepest, grouped by level
|
||||
*
|
||||
* All top-level objects are returned, followed by all second-level objects, followed by
|
||||
* third-level, etc. The order within each level is undefined.
|
||||
*
|
||||
* This is used to sort higher-level objects first in upload JSON, since otherwise the API would
|
||||
* reject lower-level objects for having missing parents.
|
||||
*
|
||||
* @param {Zotero.DataObject[]} objects - An array of objects
|
||||
* @return {Zotero.DataObject[]} - A sorted array of objects
|
||||
*/
|
||||
Zotero.DataObjects.prototype.sortByLevel = function (objects) {
|
||||
// Convert to ids
|
||||
var ids = objects.map(o => o.id);
|
||||
var levels = {};
|
||||
|
||||
// Get top-level objects
|
||||
var top = objects.filter(o => !o.parentID).map(o => o.id);
|
||||
levels["0"] = top.slice();
|
||||
ids = Zotero.Utilities.arrayDiff(ids, top);
|
||||
|
||||
// For each object in list, walk up its parent tree. If a parent is present in the
|
||||
// list of ids, add it to the appropriate level bucket and remove it.
|
||||
while (ids.length) {
|
||||
let tree = [ids[0]];
|
||||
let keep = [ids[0]];
|
||||
let id = ids.shift();
|
||||
let seen = new Set([id]);
|
||||
while (true) {
|
||||
let o = Zotero[this._ZDO_Objects].get(id);
|
||||
let parentID = o.parentID;
|
||||
if (!parentID) {
|
||||
break;
|
||||
}
|
||||
// Avoid an infinite loop if objects are incorrectly nested within each other
|
||||
if (seen.has(parentID)) {
|
||||
throw new Zotero.Error(
|
||||
`Incorrectly nested ${this._ZDO_objects}`,
|
||||
Zotero.Error.ERROR_INVALID_OBJECT_NESTING,
|
||||
{
|
||||
[this._ZDO_id]: id
|
||||
}
|
||||
);
|
||||
}
|
||||
seen.add(parentID);
|
||||
tree.push(parentID);
|
||||
// If parent is in list, remove it
|
||||
let pos = ids.indexOf(parentID);
|
||||
if (pos != -1) {
|
||||
keep.push(parentID);
|
||||
ids.splice(pos, 1);
|
||||
}
|
||||
id = parentID;
|
||||
}
|
||||
let level = tree.length - 1;
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
let currentLevel = level - i;
|
||||
for (let j = 0; j < keep.length; j++) {
|
||||
if (tree[i] != keep[j]) continue;
|
||||
|
||||
if (!levels[currentLevel]) {
|
||||
levels[currentLevel] = [];
|
||||
}
|
||||
levels[currentLevel].push(keep[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ordered = [];
|
||||
for (let level in levels) {
|
||||
ordered = ordered.concat(levels[level]);
|
||||
}
|
||||
// Convert back to objects
|
||||
return ordered.map(id => Zotero[this._ZDO_Objects].get(id));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sort an array of collections or items from top-level to deepest, grouped by parent
|
||||
*
|
||||
* Child objects are included before any sibling objects. The order within each level is undefined.
|
||||
*
|
||||
* This is used to sort higher-level objects first in upload JSON, since otherwise the API would
|
||||
* reject lower-level objects for having missing parents.
|
||||
*
|
||||
* @param {Zotero.DataObject[]} ids - An array of data objects
|
||||
* @return {Zotero.DataObject[]} - A sorted array of data objects
|
||||
*/
|
||||
Zotero.DataObjects.prototype.sortByParent = function (objects) {
|
||||
// Convert to ids
|
||||
var ids = objects.map(o => o.id);
|
||||
var ordered = [];
|
||||
|
||||
// For each object in list, walk up its parent tree. If a parent is present in the list of
|
||||
// objects, keep track of it and remove it from the list. When we get to a top-level object, add
|
||||
// all the objects we've kept to the ordered list.
|
||||
while (ids.length) {
|
||||
let id = ids.shift();
|
||||
let keep = [id];
|
||||
let seen = new Set([id]);
|
||||
while (true) {
|
||||
let o = Zotero[this._ZDO_Objects].get(id);
|
||||
let parentID = o.parentID;
|
||||
if (!parentID) {
|
||||
// We've reached a top-level object, so add any kept ids to the list
|
||||
ordered.push(...keep);
|
||||
break;
|
||||
}
|
||||
// Avoid an infinite loop if objects are incorrectly nested within each other
|
||||
if (seen.has(parentID)) {
|
||||
throw new Zotero.Error(
|
||||
`Incorrectly nested ${this._ZDO_objects}`,
|
||||
Zotero.Error.ERROR_INVALID_OBJECT_NESTING,
|
||||
{
|
||||
[this._ZDO_id]: id
|
||||
}
|
||||
);
|
||||
}
|
||||
seen.add(parentID);
|
||||
// If parent is in list of ids, keep it and remove it from list
|
||||
let pos = ids.indexOf(parentID);
|
||||
if (pos != -1) {
|
||||
keep.unshift(parentID);
|
||||
ids.splice(pos, 1);
|
||||
}
|
||||
// Otherwise, check if parent has already been added to the ordered list, in which case
|
||||
// we can slot in all kept ids after it
|
||||
else {
|
||||
pos = ordered.indexOf(parentID);
|
||||
if (pos != -1) {
|
||||
ordered.splice(pos + 1, 0, ...keep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
id = parentID;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to objects
|
||||
return ordered.map(id => Zotero[this._ZDO_Objects].get(id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flatten API JSON relations object into an array of unique predicate-object pairs
|
||||
*
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -240,7 +240,7 @@ Zotero.ItemFields = new function() {
|
|||
|
||||
var baseFieldID = this.getID(baseField);
|
||||
if (!baseFieldID) {
|
||||
throw new Error("Invalid field '" + baseField + '" for base field');
|
||||
throw new Error("Invalid field '" + baseField + "' for base field");
|
||||
}
|
||||
|
||||
if (fieldID == baseFieldID) {
|
||||
|
@ -277,7 +277,7 @@ Zotero.ItemFields = new function() {
|
|||
|
||||
var baseFieldID = this.getID(baseField);
|
||||
if (!baseFieldID) {
|
||||
throw new Error("Invalid field '" + baseField + '" for base field');
|
||||
throw new Error("Invalid field '" + baseField + "' for base field");
|
||||
}
|
||||
|
||||
// If field isn't a base field, return it if it's valid for the type
|
||||
|
@ -419,7 +419,7 @@ Zotero.ItemFields = new function() {
|
|||
var fieldID = Zotero.ItemFields.getID(field);
|
||||
if (!fieldID) {
|
||||
Zotero.debug((new Error).stack, 1);
|
||||
throw new Error(`Invalid field '${field}`);
|
||||
throw new Error(`Invalid field '${field}'`);
|
||||
}
|
||||
return fieldID;
|
||||
}
|
||||
|
@ -518,7 +518,9 @@ Zotero.ItemFields = new function() {
|
|||
var rows = yield Zotero.DB.queryAsync(sql);
|
||||
|
||||
_itemTypeFields = {
|
||||
[Zotero.ItemTypes.getID('note')]: [] // Notes have no fields
|
||||
// Notes and annotations have no fields
|
||||
[Zotero.ItemTypes.getID('note')]: [],
|
||||
[Zotero.ItemTypes.getID('annotation')]: []
|
||||
};
|
||||
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
|
|
|
@ -38,6 +38,7 @@ Zotero.Items = function() {
|
|||
get: function () {
|
||||
var itemTypeAttachment = Zotero.ItemTypes.getID('attachment');
|
||||
var itemTypeNote = Zotero.ItemTypes.getID('note');
|
||||
var itemTypeAnnotation = Zotero.ItemTypes.getID('annotation');
|
||||
|
||||
return {
|
||||
itemID: "O.itemID",
|
||||
|
@ -49,16 +50,25 @@ Zotero.Items = function() {
|
|||
version: "O.version",
|
||||
synced: "O.synced",
|
||||
|
||||
createdByUserID: "createdByUserID",
|
||||
lastModifiedByUserID: "lastModifiedByUserID",
|
||||
|
||||
firstCreator: _getFirstCreatorSQL(),
|
||||
sortCreator: _getSortCreatorSQL(),
|
||||
|
||||
deleted: "DI.itemID IS NOT NULL AS deleted",
|
||||
inPublications: "PI.itemID IS NOT NULL AS inPublications",
|
||||
|
||||
parentID: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.itemID `
|
||||
+ `WHEN ${itemTypeNote} THEN INoP.itemID END) AS parentID`,
|
||||
parentKey: `(CASE O.itemTypeID WHEN ${itemTypeAttachment} THEN IAP.key `
|
||||
+ `WHEN ${itemTypeNote} THEN INoP.key END) AS parentKey`,
|
||||
parentID: `(CASE O.itemTypeID `
|
||||
+ `WHEN ${itemTypeAttachment} THEN IAP.itemID `
|
||||
+ `WHEN ${itemTypeNote} THEN INoP.itemID `
|
||||
+ `WHEN ${itemTypeAnnotation} THEN IAnP.itemID `
|
||||
+ `END) AS parentID`,
|
||||
parentKey: `(CASE O.itemTypeID `
|
||||
+ `WHEN ${itemTypeAttachment} THEN IAP.key `
|
||||
+ `WHEN ${itemTypeNote} THEN INoP.key `
|
||||
+ `WHEN ${itemTypeAnnotation} THEN IAnP.key `
|
||||
+ `END) AS parentKey`,
|
||||
|
||||
attachmentCharset: "CS.charset AS attachmentCharset",
|
||||
attachmentLinkMode: "IA.linkMode AS attachmentLinkMode",
|
||||
|
@ -66,7 +76,8 @@ Zotero.Items = function() {
|
|||
attachmentPath: "IA.path AS attachmentPath",
|
||||
attachmentSyncState: "IA.syncState AS attachmentSyncState",
|
||||
attachmentSyncedModificationTime: "IA.storageModTime AS attachmentSyncedModificationTime",
|
||||
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash"
|
||||
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash",
|
||||
attachmentLastProcessedModificationTime: "IA.lastProcessedModificationTime AS attachmentLastProcessedModificationTime",
|
||||
};
|
||||
}
|
||||
}, {lazy: true});
|
||||
|
@ -77,9 +88,12 @@ Zotero.Items = function() {
|
|||
+ "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
|
||||
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
|
||||
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
|
||||
+ "LEFT JOIN itemAnnotations IAn ON (O.itemID=IAn.itemID) "
|
||||
+ "LEFT JOIN items IAnP ON (IAn.parentItemID=IAnP.itemID) "
|
||||
+ "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
|
||||
+ "LEFT JOIN publicationsItems PI ON (O.itemID=PI.itemID) "
|
||||
+ "LEFT JOIN charsets CS ON (IA.charsetID=CS.charsetID)";
|
||||
+ "LEFT JOIN charsets CS ON (IA.charsetID=CS.charsetID)"
|
||||
+ "LEFT JOIN groupItems GI ON (O.itemID=GI.itemID)";
|
||||
|
||||
this._relationsTable = "itemRelations";
|
||||
|
||||
|
@ -479,6 +493,88 @@ Zotero.Items = function() {
|
|||
});
|
||||
|
||||
|
||||
this._loadAnnotations = async function (libraryID, ids, idSQL) {
|
||||
var sql = "SELECT itemID, IA.parentItemID, IA.type, IA.text, IA.comment, IA.color, "
|
||||
+ "IA.sortIndex, IA.isExternal "
|
||||
+ "FROM items JOIN itemAnnotations IA USING (itemID) "
|
||||
+ "WHERE libraryID=?" + idSQL;
|
||||
var params = [libraryID];
|
||||
await Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
{
|
||||
noCache: ids.length != 1,
|
||||
onRow: function (row) {
|
||||
let itemID = row.getResultByIndex(0);
|
||||
|
||||
let item = this._objectCache[itemID];
|
||||
if (!item) {
|
||||
throw new Error("Item " + itemID + " not found");
|
||||
}
|
||||
|
||||
item._parentItemID = row.getResultByIndex(1);
|
||||
var typeID = row.getResultByIndex(2);
|
||||
var type;
|
||||
switch (typeID) {
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_HIGHLIGHT:
|
||||
type = 'highlight';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_NOTE:
|
||||
type = 'note';
|
||||
break;
|
||||
|
||||
case Zotero.Annotations.ANNOTATION_TYPE_IMAGE:
|
||||
type = 'image';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown annotation type id ${typeID}`);
|
||||
}
|
||||
item._annotationType = type;
|
||||
item._annotationText = row.getResultByIndex(3);
|
||||
item._annotationComment = row.getResultByIndex(4);
|
||||
item._annotationColor = row.getResultByIndex(5);
|
||||
item._annotationSortIndex = row.getResultByIndex(6);
|
||||
item._annotationIsExternal = !!row.getResultByIndex(7);
|
||||
|
||||
item._loaded.annotation = true;
|
||||
item._clearChanged('annotation');
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
this._loadAnnotationsDeferred = async function (libraryID, ids, idSQL) {
|
||||
var sql = "SELECT itemID, IA.position, IA.pageLabel FROM items "
|
||||
+ "JOIN itemAnnotations IA USING (itemID) "
|
||||
+ "WHERE libraryID=?" + idSQL;
|
||||
var params = [libraryID];
|
||||
await Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
{
|
||||
noCache: ids.length != 1,
|
||||
onRow: function (row) {
|
||||
let itemID = row.getResultByIndex(0);
|
||||
|
||||
let item = this._objectCache[itemID];
|
||||
if (!item) {
|
||||
throw new Error("Item " + itemID + " not found");
|
||||
}
|
||||
|
||||
item._annotationPosition = row.getResultByIndex(1);
|
||||
item._annotationPageLabel = row.getResultByIndex(2);
|
||||
|
||||
item._loaded.annotationDeferred = true;
|
||||
item._clearChanged('annotationDeferred');
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
|
||||
var params = [libraryID];
|
||||
var rows = [];
|
||||
|
@ -499,6 +595,9 @@ Zotero.Items = function() {
|
|||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Attachments
|
||||
//
|
||||
var sql = "SELECT parentItemID, A.itemID, value AS title, "
|
||||
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
|
||||
+ "FROM itemAttachments A "
|
||||
|
@ -602,9 +701,56 @@ Zotero.Items = function() {
|
|||
ids.forEach(id => setNoteItem(id, []));
|
||||
}
|
||||
|
||||
// Mark all top-level items as having child items loaded
|
||||
sql = "SELECT itemID FROM items I WHERE libraryID=?" + idSQL + " AND itemID NOT IN "
|
||||
+ "(SELECT itemID FROM itemAttachments UNION SELECT itemID FROM itemNotes)";
|
||||
//
|
||||
// Annotations
|
||||
//
|
||||
sql = "SELECT parentItemID, IAn.itemID, "
|
||||
+ "text || ' - ' || comment AS title, " // TODO: Make better
|
||||
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
|
||||
+ "FROM itemAnnotations IAn "
|
||||
+ "JOIN items I ON (IAn.parentItemID=I.itemID) "
|
||||
+ "LEFT JOIN deletedItems DI USING (itemID) "
|
||||
+ "WHERE libraryID=?"
|
||||
+ (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : "")
|
||||
+ " ORDER BY parentItemID, sortIndex";
|
||||
var setAnnotationItem = function (itemID, rows) {
|
||||
var item = this._objectCache[itemID];
|
||||
if (!item) {
|
||||
throw new Error("Item " + itemID + " not loaded");
|
||||
}
|
||||
rows.sort((a, b) => a.sortIndex - b.sortIndex);
|
||||
item._annotations = {
|
||||
rows,
|
||||
withTrashed: null,
|
||||
withoutTrashed: null
|
||||
};
|
||||
}.bind(this);
|
||||
lastItemID = null;
|
||||
rows = [];
|
||||
yield Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
{
|
||||
noCache: ids.length != 1,
|
||||
onRow: function (row) {
|
||||
onRow(row, setAnnotationItem);
|
||||
}
|
||||
}
|
||||
);
|
||||
// Process unprocessed rows
|
||||
if (lastItemID) {
|
||||
setAnnotationItem(lastItemID, rows);
|
||||
}
|
||||
// Otherwise clear existing entries for passed items
|
||||
else if (ids.length) {
|
||||
ids.forEach(id => setAnnotationItem(id, []));
|
||||
}
|
||||
|
||||
// Mark either all passed items or all items as having child items loaded
|
||||
sql = "SELECT itemID FROM items I WHERE libraryID=?";
|
||||
if (idSQL) {
|
||||
sql += idSQL;
|
||||
}
|
||||
yield Zotero.DB.queryAsync(
|
||||
sql,
|
||||
params,
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
|
||||
Zotero.Notes = new function() {
|
||||
this.noteToTitle = noteToTitle;
|
||||
this._editorInstances = [];
|
||||
|
||||
this.__defineGetter__("MAX_TITLE_LENGTH", function() { return 120; });
|
||||
this.__defineGetter__("defaultNote", function () { return '<div class="zotero-note znv1"></div>'; });
|
||||
|
@ -35,7 +35,7 @@ Zotero.Notes = new function() {
|
|||
/**
|
||||
* Return first line (or first MAX_LENGTH characters) of note content
|
||||
**/
|
||||
function noteToTitle(text) {
|
||||
this.noteToTitle = function(text) {
|
||||
var origText = text;
|
||||
text = text.trim();
|
||||
text = Zotero.Utilities.unescapeHTML(text);
|
||||
|
@ -59,9 +59,101 @@ Zotero.Notes = new function() {
|
|||
t = t.substring(0, ln);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.registerEditorInstance = function(instance) {
|
||||
this._editorInstances.push(instance);
|
||||
};
|
||||
|
||||
this.unregisterEditorInstance = async function(instance) {
|
||||
// Make sure the editor instance is not unregistered while
|
||||
// Zotero.Notes.updateUser is in progress, otherwise the
|
||||
// instance might not get the`disableSaving` flag set
|
||||
await Zotero.DB.executeTransaction(async () => {
|
||||
let index = this._editorInstances.indexOf(instance);
|
||||
if (index >= 0) {
|
||||
this._editorInstances.splice(index, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||
module.exports = Zotero.Notes;
|
||||
/**
|
||||
* Replace local URIs for citations and highlights
|
||||
* in all notes. Cut-off note saving for the opened
|
||||
* notes and then trigger notification to refresh
|
||||
*
|
||||
* @param {Number} fromUserID
|
||||
* @param {Number} toUserID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
this.updateUser = async function (fromUserID, toUserID) {
|
||||
if (!fromUserID) {
|
||||
fromUserID = 'local%2F' + Zotero.Users.getLocalUserKey();
|
||||
}
|
||||
if (!toUserID) {
|
||||
throw new Error('Invalid target userID ' + toUserID);
|
||||
}
|
||||
Zotero.DB.requireTransaction();
|
||||
|
||||
// `"http://zotero.org/users/${fromUserID}/items/`
|
||||
let from = `%22http%3A%2F%2Fzotero.org%2Fusers%2F${fromUserID}%2Fitems%2F`;
|
||||
// `"http://zotero.org/users/${toUserId}/items/`
|
||||
let to = `%22http%3A%2F%2Fzotero.org%2Fusers%2F${toUserID}%2Fitems%2F`;
|
||||
let sql = `UPDATE itemNotes SET note=REPLACE(note, '${from}', '${to}')`;
|
||||
await Zotero.DB.queryAsync(sql);
|
||||
|
||||
// Disable saving for each editor instance to make sure none
|
||||
// of the instances can overwrite our changes
|
||||
this._editorInstances.forEach(x => x.disableSaving = true);
|
||||
|
||||
let idsToRefresh = [];
|
||||
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
|
||||
let loadedObjects = objectsClass.getLoaded();
|
||||
for (let object of loadedObjects) {
|
||||
if (object.isNote()) {
|
||||
idsToRefresh.push(object.id);
|
||||
await object.reload(['note'], true);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.addCurrentCallback('commit', async () => {
|
||||
await Zotero.Notifier.trigger('refresh', 'item', idsToRefresh);
|
||||
});
|
||||
};
|
||||
|
||||
this.getExportableNote = async function(item) {
|
||||
if (!item.isNote()) {
|
||||
throw new Error('Item is not a note');
|
||||
}
|
||||
var note = item.getNote();
|
||||
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
var doc = parser.parseFromString(note, 'text/html');
|
||||
|
||||
var nodes = doc.querySelectorAll('img[data-attachment-key]');
|
||||
for (var node of nodes) {
|
||||
var attachmentKey = node.getAttribute('data-attachment-key');
|
||||
if (attachmentKey) {
|
||||
var attachment = Zotero.Items.getByLibraryAndKey(item.libraryID, attachmentKey);
|
||||
if (attachment && attachment.parentID == item.id) {
|
||||
var dataURI = await attachment.attachmentDataURI;
|
||||
node.setAttribute('src', dataURI);
|
||||
}
|
||||
}
|
||||
node.removeAttribute('data-attachment-key');
|
||||
}
|
||||
return doc.body.innerHTML;
|
||||
};
|
||||
|
||||
this.hasSchemaVersion = function (note) {
|
||||
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
let doc = parser.parseFromString(note, 'text/html');
|
||||
return !!doc.querySelector('body > div[data-schema-version]');
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]') {
|
||||
module.exports = Zotero.Notes;
|
||||
}
|
||||
|
|
|
@ -306,14 +306,19 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
|
|||
var parts = Zotero.SearchConditions.parseSearchString(value);
|
||||
|
||||
for (let part of parts) {
|
||||
this.addCondition('blockStart');
|
||||
if (condition == 'quicksearch-titleCreatorYearNote') {
|
||||
this.addCondition('note', operator, part.text, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.addCondition('blockStart');
|
||||
|
||||
// Allow searching for exact object key
|
||||
if (operator == 'contains' && Zotero.Utilities.isValidObjectKey(part.text)) {
|
||||
this.addCondition('key', 'is', part.text, false);
|
||||
}
|
||||
|
||||
if (condition == 'quicksearch-titleCreatorYear') {
|
||||
|
||||
if (condition.startsWith('quicksearch-titleCreatorYear')) {
|
||||
this.addCondition('title', operator, part.text, false);
|
||||
this.addCondition('publicationTitle', operator, part.text, false);
|
||||
this.addCondition('shortTitle', operator, part.text, false);
|
||||
|
@ -328,7 +333,8 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
|
|||
this.addCondition('creator', operator, part.text, false);
|
||||
|
||||
if (condition == 'quicksearch-everything') {
|
||||
this.addCondition('annotation', operator, part.text, false);
|
||||
this.addCondition('annotationText', operator, part.text, false);
|
||||
this.addCondition('annotationComment', operator, part.text, false);
|
||||
|
||||
if (part.inQuotes) {
|
||||
this.addCondition('fulltextContent', operator, part.text, false);
|
||||
|
@ -347,6 +353,9 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
|
|||
if (condition == 'quicksearch-titleCreatorYear') {
|
||||
this.addCondition('noChildren', 'true');
|
||||
}
|
||||
else if (condition == 'quicksearch-titleCreatorYearNote') {
|
||||
this.addCondition('itemType', 'is', 'note');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -168,6 +168,17 @@ Zotero.SearchConditions = new function(){
|
|||
noLoad: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'quicksearch-titleCreatorYearNote',
|
||||
operators: {
|
||||
is: true,
|
||||
isNot: true,
|
||||
contains: true,
|
||||
doesNotContain: true
|
||||
},
|
||||
noLoad: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'quicksearch-fields',
|
||||
operators: {
|
||||
|
@ -490,13 +501,25 @@ Zotero.SearchConditions = new function(){
|
|||
},
|
||||
|
||||
{
|
||||
name: 'annotation',
|
||||
name: 'annotationText',
|
||||
operators: {
|
||||
contains: true,
|
||||
doesNotContain: true
|
||||
},
|
||||
table: 'annotations',
|
||||
field: 'text'
|
||||
table: 'itemAnnotations',
|
||||
field: 'text',
|
||||
special: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'annotationComment',
|
||||
operators: {
|
||||
contains: true,
|
||||
doesNotContain: true
|
||||
},
|
||||
table: 'itemAnnotations',
|
||||
field: 'comment',
|
||||
special: true,
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
@ -853,6 +853,40 @@ Zotero.Date = new function(){
|
|||
}
|
||||
|
||||
|
||||
this.toFriendlyDate = function (date) {
|
||||
// 6:14:36 PM
|
||||
if (isToday(date)) {
|
||||
return date.toLocaleString(false, { hour: 'numeric', minute: 'numeric' })
|
||||
}
|
||||
// 'Thursday'
|
||||
if (isThisWeek(date)) {
|
||||
return date.toLocaleString(false, { weekday: 'long' });
|
||||
}
|
||||
return date.toLocaleDateString(false, { year: '2-digit', month: 'numeric', day: 'numeric' });
|
||||
};
|
||||
|
||||
|
||||
function isToday(date) {
|
||||
var d = new Date();
|
||||
return d.getDate() == date.getDate()
|
||||
&& d.getMonth() == d.getMonth()
|
||||
&& d.getFullYear() == d.getFullYear();
|
||||
}
|
||||
|
||||
|
||||
function isThisWeek(date) {
|
||||
var d = new Date();
|
||||
return d.getFullYear() == date.getFullYear() && getWeekNumber(d) == getWeekNumber(date);
|
||||
}
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/27125580
|
||||
function getWeekNumber(date) {
|
||||
let onejan = new Date(date.getFullYear(), 0, 1);
|
||||
return Math.ceil((((date.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
|
||||
}
|
||||
|
||||
|
||||
function getFileDateString(file){
|
||||
var date = new Date();
|
||||
date.setTime(file.lastModifiedTime);
|
||||
|
|
1019
chrome/content/zotero/xpcom/editorInstance.js
Normal file
1019
chrome/content/zotero/xpcom/editorInstance.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -51,7 +51,7 @@ Zotero.Error.ERROR_ZFS_UPLOAD_QUEUE_LIMIT = 6;
|
|||
Zotero.Error.ERROR_ZFS_FILE_EDITING_DENIED = 7;
|
||||
Zotero.Error.ERROR_INVALID_ITEM_TYPE = 8;
|
||||
Zotero.Error.ERROR_USER_NOT_AVAILABLE = 9;
|
||||
Zotero.Error.ERROR_INVALID_COLLECTION_NESTING = 10;
|
||||
Zotero.Error.ERROR_INVALID_OBJECT_NESTING = 10;
|
||||
//Zotero.Error.ERROR_SYNC_EMPTY_RESPONSE_FROM_SERVER = 6;
|
||||
//Zotero.Error.ERROR_SYNC_INVALID_RESPONSE_FROM_SERVER = 7;
|
||||
|
||||
|
|
|
@ -778,23 +778,25 @@ Zotero.File = new function(){
|
|||
|
||||
|
||||
/**
|
||||
* Generate a data: URI from an nsIFile
|
||||
* Generate a data: URI from a file path
|
||||
*
|
||||
* From https://developer.mozilla.org/en-US/docs/data_URIs
|
||||
* @param {String} path
|
||||
* @param {String} contentType
|
||||
*/
|
||||
this.generateDataURI = function (file) {
|
||||
var contentType = Components.classes["@mozilla.org/mime;1"]
|
||||
.getService(Components.interfaces.nsIMIMEService)
|
||||
.getTypeFromFile(file);
|
||||
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
inputStream.init(file, 0x01, 0o600, 0);
|
||||
var stream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
stream.setInputStream(inputStream);
|
||||
var encoded = btoa(stream.readBytes(stream.available()));
|
||||
return "data:" + contentType + ";base64," + encoded;
|
||||
}
|
||||
this.generateDataURI = async function (file, contentType) {
|
||||
if (!contentType) {
|
||||
throw new Error("contentType not provided");
|
||||
}
|
||||
|
||||
var buf = await OS.File.read(file, {});
|
||||
var bytes = new Uint8Array(buf);
|
||||
var binary = '';
|
||||
var len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return 'data:' + contentType + ';base64,' + btoa(binary);
|
||||
};
|
||||
|
||||
|
||||
this.setNormalFilePermissions = function (file) {
|
||||
|
@ -1008,14 +1010,17 @@ Zotero.File = new function(){
|
|||
}
|
||||
|
||||
|
||||
this.createDirectoryIfMissingAsync = async function (path) {
|
||||
this.createDirectoryIfMissingAsync = async function (path, options = {}) {
|
||||
try {
|
||||
await OS.File.makeDir(
|
||||
path,
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
}
|
||||
Object.assign(
|
||||
{
|
||||
ignoreExisting: false,
|
||||
unixMode: 0o755
|
||||
},
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
catch (e) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -328,6 +328,10 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
|
|||
Zotero.CollectionTreeCache.clear();
|
||||
// Get the full set of items we want to show
|
||||
let newSearchItems = yield this.collectionTreeRow.getItems();
|
||||
// TEMP: Hide annotations
|
||||
newSearchItems = newSearchItems.filter(item => !item.isAnnotation());
|
||||
// A temporary workaround to make item tree crash less often
|
||||
newSearchItems = newSearchItems.filter(item => !(item.isAttachment() && item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE));
|
||||
// Remove notes and attachments if necessary
|
||||
if (this.regularOnly) {
|
||||
newSearchItems = newSearchItems.filter(item => item.isRegularItem());
|
||||
|
@ -3505,7 +3509,7 @@ Zotero.ItemTreeRow.prototype.numNotes = function() {
|
|||
return 0;
|
||||
}
|
||||
if (this.ref.isAttachment()) {
|
||||
return this.ref.getNote() !== '' ? 1 : 0;
|
||||
return this.ref.note !== '' ? 1 : 0;
|
||||
}
|
||||
return this.ref.numNotes(false, true) || 0;
|
||||
}
|
||||
|
|
41
chrome/content/zotero/xpcom/noteBackups.js
Normal file
41
chrome/content/zotero/xpcom/noteBackups.js
Normal 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]);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -30,7 +30,7 @@ Zotero.Notifier = new function(){
|
|||
var _types = [
|
||||
'collection', 'search', 'share', 'share-items', 'item', 'file',
|
||||
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
|
||||
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key'
|
||||
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key', 'tab'
|
||||
];
|
||||
var _transactionID = false;
|
||||
var _queue = {};
|
||||
|
|
|
@ -26,7 +26,32 @@
|
|||
/* eslint-disable array-element-newline */
|
||||
|
||||
Zotero.OpenPDF = {
|
||||
openToPage: async function (path, page) {
|
||||
openToPage: async function (pathOrItem, page) {
|
||||
var path;
|
||||
if (pathOrItem == 'string') {
|
||||
Zotero.logError("Zotero.OpenPDF.openToPage() now takes a Zotero.Item rather than a path "
|
||||
+ "-- please update your code");
|
||||
path = pathOrItem;
|
||||
}
|
||||
else {
|
||||
let item = pathOrItem;
|
||||
let library = Zotero.Libraries.get(item.libraryID);
|
||||
// TEMP
|
||||
if (Zotero.isPDFBuild && library.libraryType == 'user') {
|
||||
let location = {
|
||||
pageIndex: page - 1
|
||||
};
|
||||
await Zotero.Reader.open(item.id, location);
|
||||
return true;
|
||||
}
|
||||
|
||||
path = await item.getFilePathAsync();
|
||||
if (!path) {
|
||||
Zotero.warn(`${path} not found`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var handler = Zotero.Prefs.get("fileHandler.pdf");
|
||||
var opened = false;
|
||||
|
||||
|
|
484
chrome/content/zotero/xpcom/pdfWorker/manager.js
Normal file
484
chrome/content/zotero/xpcom/pdfWorker/manager.js
Normal 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();
|
|
@ -73,6 +73,11 @@ Zotero.Prefs = new function(){
|
|||
this.clear('firstRunGuidanceShown.saveIcon');
|
||||
this.clear('firstRunGuidanceShown.saveButton');
|
||||
break;
|
||||
|
||||
// TEMP: Uncomment and set toVersion above to 3 when adding to prefs drop-down
|
||||
//case 3:
|
||||
// this.clear('fileHandler.pdf');
|
||||
// break;
|
||||
}
|
||||
}
|
||||
this.set('prefVersion', toVersion);
|
||||
|
|
|
@ -292,7 +292,7 @@ Zotero.QuickCopy = new function() {
|
|||
div.className = "zotero-note";
|
||||
// AMO reviewer: This documented is never rendered (and the inserted markup
|
||||
// is sanitized anyway)
|
||||
div.insertAdjacentHTML('afterbegin', notes[i].getNote());
|
||||
div.insertAdjacentHTML('afterbegin', notes[i].note);
|
||||
container.appendChild(div);
|
||||
textContainer.appendChild(textDoc.importNode(div, true));
|
||||
}
|
||||
|
|
815
chrome/content/zotero/xpcom/reader.js
Normal file
815
chrome/content/zotero/xpcom/reader.js
Normal 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();
|
|
@ -41,12 +41,12 @@ Zotero.Schema = new function(){
|
|||
|
||||
// If updating from this userdata version or later, don't show "Upgrading database…" and don't make
|
||||
// DB backup first. This should be set to false when breaking compatibility or making major changes.
|
||||
const minorUpdateFrom = 107;
|
||||
const minorUpdateFrom = false;
|
||||
|
||||
var _dbVersions = [];
|
||||
var _schemaVersions = [];
|
||||
// Update when adding _updateCompatibility() line to schema update step
|
||||
var _maxCompatibility = 6;
|
||||
var _maxCompatibility = 7;
|
||||
|
||||
var _repositoryTimerID;
|
||||
var _repositoryNotificationTimerID;
|
||||
|
@ -1790,6 +1790,9 @@ Zotero.Schema = new function(){
|
|||
var noteID = parseInt(yield Zotero.DB.valueQueryAsync(
|
||||
"SELECT itemTypeID FROM itemTypes WHERE typeName='note'"
|
||||
));
|
||||
var annotationID = parseInt((yield Zotero.DB.valueQueryAsync(
|
||||
"SELECT itemTypeID FROM itemTypes WHERE typeName='annotation'"
|
||||
)) || -1);
|
||||
|
||||
// The first position is for testing and the second is for repairing. Can be either SQL
|
||||
// statements or promise-returning functions. For statements, the repair entry can be either
|
||||
|
@ -1915,16 +1918,29 @@ Zotero.Schema = new function(){
|
|||
`SELECT COUNT(*) > 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`,
|
||||
`INSERT INTO itemAttachments (itemID, linkMode) SELECT itemID, 0 FROM items WHERE itemTypeID=${attachmentID} AND itemID NOT IN (SELECT itemID FROM itemAttachments)`,
|
||||
],
|
||||
// Note/child parents
|
||||
// Attachments with note parents, unless they're embedded-image attachments
|
||||
[
|
||||
`SELECT COUNT(*) > 0 FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
|
||||
`UPDATE itemAttachments SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
|
||||
`SELECT COUNT(*) > 0 FROM itemAttachments `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID=${noteID}) `
|
||||
+ `AND linkMode != ${Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE}`,
|
||||
`UPDATE itemAttachments SET parentItemID=NULL `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID=${noteID}) `
|
||||
+ `AND linkMode != ${Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE}`,
|
||||
],
|
||||
// Attachments with attachment or annotation parents
|
||||
[
|
||||
`SELECT COUNT(*) > 0 FROM itemNotes WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
|
||||
`UPDATE itemNotes SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}))`,
|
||||
`SELECT COUNT(*) > 0 FROM itemAttachments `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${attachmentID}, ${annotationID}))`,
|
||||
`UPDATE itemAttachments SET parentItemID=NULL `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${attachmentID}, ${annotationID}))`,
|
||||
],
|
||||
// Notes with note/attachment/annotation parents
|
||||
[
|
||||
`SELECT COUNT(*) > 0 FROM itemNotes `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}, ${annotationID}))`,
|
||||
`UPDATE itemNotes SET parentItemID=NULL `
|
||||
+ `WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (${noteID}, ${attachmentID}, ${annotationID}))`,
|
||||
],
|
||||
|
||||
// Delete empty creators
|
||||
// This may cause itemCreator gaps, but that's better than empty creators
|
||||
[
|
||||
|
@ -1944,8 +1960,8 @@ Zotero.Schema = new function(){
|
|||
],
|
||||
// Invalid link mode -- set to imported url
|
||||
[
|
||||
"SELECT COUNT(*) > 0 FROM itemAttachments WHERE linkMode NOT IN (0,1,2,3)",
|
||||
"UPDATE itemAttachments SET linkMode=1 WHERE linkMode NOT IN (0,1,2,3)"
|
||||
"SELECT COUNT(*) > 0 FROM itemAttachments WHERE linkMode NOT IN (0,1,2,3,4)",
|
||||
"UPDATE itemAttachments SET linkMode=1 WHERE linkMode NOT IN (0,1,2,3,4)"
|
||||
],
|
||||
// Creators with first name can't be fieldMode 1
|
||||
[
|
||||
|
@ -3218,6 +3234,22 @@ Zotero.Schema = new function(){
|
|||
yield Zotero.DB.queryAsync("CREATE INDEX deletedSearches_dateDeleted ON deletedSearches(dateDeleted)");
|
||||
}
|
||||
|
||||
else if (i == 112) {
|
||||
yield _updateCompatibility(7);
|
||||
|
||||
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS annotations");
|
||||
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS highlights");
|
||||
|
||||
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS users");
|
||||
yield Zotero.DB.queryAsync("CREATE TABLE users (\n userID INTEGER PRIMARY KEY,\n name TEXT NOT NULL\n)");
|
||||
|
||||
yield Zotero.DB.queryAsync("CREATE TABLE itemAnnotations (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT NOT NULL,\n type INTEGER NOT NULL,\n text TEXT,\n comment TEXT,\n color TEXT,\n pageLabel TEXT,\n sortIndex TEXT NOT NULL,\n position TEXT NOT NULL,\n isExternal INT NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
|
||||
yield Zotero.DB.queryAsync("CREATE INDEX itemAnnotations_parentItemID ON itemAnnotations(parentItemID)");
|
||||
|
||||
yield Zotero.DB.queryAsync("ALTER TABLE itemAttachments ADD COLUMN lastProcessedModificationTime INT");
|
||||
yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_lastProcessedModificationTime ON itemAttachments(lastProcessedModificationTime)");
|
||||
}
|
||||
|
||||
// If breaking compatibility or doing anything dangerous, clear minorUpdateFrom
|
||||
}
|
||||
|
||||
|
|
|
@ -502,13 +502,14 @@ Zotero.Sync.Storage.Local = {
|
|||
*/
|
||||
getFilesToUpload: function (libraryID) {
|
||||
var sql = "SELECT itemID FROM itemAttachments JOIN items USING (itemID) "
|
||||
+ "WHERE libraryID=? AND syncState IN (?,?) AND linkMode IN (?,?)";
|
||||
+ "WHERE libraryID=? AND syncState IN (?,?) AND linkMode IN (?,?,?)";
|
||||
var params = [
|
||||
libraryID,
|
||||
this.SYNC_STATE_TO_UPLOAD,
|
||||
this.SYNC_STATE_FORCE_UPLOAD,
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_URL
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
|
||||
Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE,
|
||||
];
|
||||
return Zotero.DB.columnQueryAsync(sql, params);
|
||||
},
|
||||
|
@ -566,12 +567,13 @@ Zotero.Sync.Storage.Local = {
|
|||
|
||||
return Zotero.DB.executeTransaction(async function () {
|
||||
var sql = "SELECT itemID FROM items JOIN itemAttachments USING (itemID) "
|
||||
+ "WHERE libraryID=? AND itemTypeID=? AND linkMode IN (?, ?)";
|
||||
+ "WHERE libraryID=? AND itemTypeID=? AND linkMode IN (?, ?, ?)";
|
||||
var params = [
|
||||
libraryID,
|
||||
Zotero.ItemTypes.getID('attachment'),
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
|
||||
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
|
||||
Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE,
|
||||
];
|
||||
var itemIDs = await Zotero.DB.columnQueryAsync(sql, params);
|
||||
for (let itemID of itemIDs) {
|
||||
|
|
|
@ -35,6 +35,7 @@ Zotero.Sync.APIClient = function (options) {
|
|||
this.baseURL = options.baseURL;
|
||||
this.apiVersion = options.apiVersion;
|
||||
this.apiKey = options.apiKey;
|
||||
this.schemaVersion = options.schemaVersion || Zotero.Schema.globalSchemaVersion;
|
||||
this.caller = options.caller;
|
||||
this.debugUploadPolicy = Zotero.Prefs.get('sync.debugUploadPolicy');
|
||||
this.cancellerReceiver = options.cancellerReceiver;
|
||||
|
@ -298,7 +299,6 @@ Zotero.Sync.APIClient.prototype = {
|
|||
target: objectTypePlural,
|
||||
libraryType: libraryType,
|
||||
libraryTypeID: libraryTypeID,
|
||||
format: 'json'
|
||||
};
|
||||
params[objectType + "Key"] = objectKeys.join(",");
|
||||
if (objectType == 'item') {
|
||||
|
@ -617,6 +617,7 @@ Zotero.Sync.APIClient.prototype = {
|
|||
if (this.apiKey) {
|
||||
newHeaders["Zotero-API-Key"] = this.apiKey;
|
||||
}
|
||||
newHeaders["Zotero-Schema-Version"] = this.schemaVersion;
|
||||
return newHeaders;
|
||||
},
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
|
|||
throw e;
|
||||
}
|
||||
Zotero.debug("Upload failed -- performing download", 2);
|
||||
Zotero.debug(e, 1);
|
||||
downloadResult = yield this._startDownload();
|
||||
Zotero.debug("Download result is " + downloadResult, 4);
|
||||
throw e;
|
||||
|
@ -223,6 +224,15 @@ Zotero.Sync.Data.Engine.prototype.start = Zotero.Promise.coroutine(function* ()
|
|||
skipNotifier: true
|
||||
});
|
||||
|
||||
if (this.library.libraryType == 'group') {
|
||||
try {
|
||||
yield this._updateGroupItemUsers();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.debug("Done syncing " + this.library.name);
|
||||
});
|
||||
|
||||
|
@ -1428,6 +1438,58 @@ Zotero.Sync.Data.Engine.prototype._uploadDeletions = Zotero.Promise.coroutine(fu
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Update createdByUserID/lastModifiedByUserID for previously downloaded group items
|
||||
*
|
||||
* TEMP: Currently only processes one batch of items, but before we start displaying the names,
|
||||
* we'll need to update it to fetch all
|
||||
*/
|
||||
Zotero.Sync.Data.Engine.prototype._updateGroupItemUsers = async function () {
|
||||
// TODO: Do more at once when we actually start showing these names
|
||||
var max = this.apiClient.MAX_OBJECTS_PER_REQUEST;
|
||||
|
||||
var sql = "SELECT key FROM items LEFT JOIN groupItems GI USING (itemID) "
|
||||
+ `WHERE libraryID=? AND GI.itemID IS NULL ORDER BY itemID LIMIT ${max}`;
|
||||
var keys = await Zotero.DB.columnQueryAsync(sql, this.libraryID);
|
||||
if (!keys.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.debug(`Updating item users in ${this.library.name}`);
|
||||
|
||||
var jsonItems = await this.apiClient.downloadObjects(
|
||||
this.library.libraryType, this.libraryTypeID, 'item', keys
|
||||
)[0];
|
||||
|
||||
if (!Array.isArray(jsonItems)) {
|
||||
Zotero.logError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let jsonItem of jsonItems) {
|
||||
let item = Zotero.Items.getByLibraryAndKey(this.libraryID, jsonItem.key);
|
||||
let params = [null, null];
|
||||
|
||||
// This should almost always exist, but maybe doesn't for some old items?
|
||||
if (jsonItem.meta.createdByUser) {
|
||||
let { id: userID, username, name } = jsonItem.meta.createdByUser;
|
||||
await Zotero.Users.setName(userID, name !== '' ? name : username);
|
||||
params[0] = userID;
|
||||
}
|
||||
|
||||
if (jsonItem.meta.lastModifiedByUser) {
|
||||
let { id: userID, username, name } = jsonItem.meta.lastModifiedByUser;
|
||||
await Zotero.Users.setName(userID, name !== '' ? name : username);
|
||||
params[1] = userID;
|
||||
}
|
||||
|
||||
await item.updateCreatedByUser.apply(item, params);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
Zotero.Sync.Data.Engine.prototype._getJSONForObject = function (objectType, id, options = {}) {
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
|
|
|
@ -124,24 +124,39 @@ Zotero.Sync.EventListeners.AutoSyncListener = {
|
|||
}
|
||||
|
||||
// Only trigger sync for certain types
|
||||
//
|
||||
// TODO: settings, full text
|
||||
if (!Zotero.DataObjectUtilities.getTypes().includes(type)) {
|
||||
// TODO: full text
|
||||
if (![...Zotero.DataObjectUtilities.getTypes(), 'setting'].includes(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine affected libraries so only those can be synced
|
||||
let libraries = [];
|
||||
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
|
||||
ids.forEach(id => {
|
||||
let lk = objectsClass.getLibraryAndKeyFromID(id);
|
||||
if (lk) {
|
||||
let library = Zotero.Libraries.get(lk.libraryID);
|
||||
|
||||
if (type == 'setting') {
|
||||
for (let id of ids) {
|
||||
// E.g., '1/lastPageIndex_u_ABCD2345'
|
||||
let libraryID = parseInt(id.split('/')[0]);
|
||||
let library = Zotero.Libraries.get(libraryID);
|
||||
if (library.syncable) {
|
||||
libraries.push(library);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (Zotero.DataObjectUtilities.getTypes().includes(type)) {
|
||||
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
|
||||
ids.forEach(id => {
|
||||
let lk = objectsClass.getLibraryAndKeyFromID(id);
|
||||
if (lk) {
|
||||
let library = Zotero.Libraries.get(lk.libraryID);
|
||||
if (library.syncable) {
|
||||
libraries.push(library);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
libraries = Zotero.Sync.Data.Local.filterSkippedLibraries(libraries);
|
||||
if (!libraries.length) {
|
||||
|
|
|
@ -175,6 +175,7 @@ Zotero.Sync.Data.Local = {
|
|||
// Replace local user key with libraryID, in case duplicates were merged before the
|
||||
// first sync
|
||||
yield Zotero.Relations.updateUser(null, userID);
|
||||
yield Zotero.Notes.updateUser(null, userID);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -542,12 +543,13 @@ Zotero.Sync.Data.Local = {
|
|||
var sql = "SELECT O." + objectsClass.idColumn + " FROM " + objectsClass.table + " O";
|
||||
if (objectType == 'item') {
|
||||
sql += " LEFT JOIN itemAttachments IA USING (itemID) "
|
||||
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) ";
|
||||
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
|
||||
+ "LEFT JOIN itemAnnotations IAn ON (O.itemID=IAn.itemID)";
|
||||
}
|
||||
sql += " WHERE libraryID=? AND synced=0";
|
||||
// Sort child items last
|
||||
// Don't sync external annotations
|
||||
if (objectType == 'item') {
|
||||
sql += " ORDER BY COALESCE(IA.parentItemID, INo.parentItemID)";
|
||||
sql += " AND (IAn.isExternal IS NULL OR IAN.isExternal=0)";
|
||||
}
|
||||
|
||||
var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
|
||||
|
@ -555,12 +557,12 @@ Zotero.Sync.Data.Local = {
|
|||
// Sort descendent collections last
|
||||
if (objectType == 'collection') {
|
||||
try {
|
||||
ids = Zotero.Collections.sortByLevel(ids);
|
||||
ids = Zotero.Collections.sortByLevel(ids.map(id => Zotero.Collections.get(id))).map(o => o.id);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
// If collections were incorrectly nested, fix and try again
|
||||
if (e instanceof Zotero.Error && e.error == Zotero.Error.ERROR_INVALID_COLLECTION_NESTING) {
|
||||
if (e instanceof Zotero.Error && e.error == Zotero.Error.ERROR_INVALID_OBJECT_NESTING) {
|
||||
let c = Zotero.Collections.get(e.collectionID);
|
||||
Zotero.debug(`Removing parent collection ${c.parentKey} from collection ${c.key}`);
|
||||
c.parentID = null;
|
||||
|
@ -572,11 +574,22 @@ Zotero.Sync.Data.Local = {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (objectType == 'item') {
|
||||
ids = Zotero.Items.sortByParent(ids.map(id => Zotero.Items.get(id))).map(o => o.id);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}),
|
||||
|
||||
|
||||
isSyncItem: function (item) {
|
||||
if (item.itemType == 'annotation' && item.annotationIsExternal) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Cache management
|
||||
//
|
||||
|
@ -883,7 +896,7 @@ Zotero.Sync.Data.Local = {
|
|||
// For items, check if mtime or file hash changed in metadata,
|
||||
// which would indicate that a remote storage sync took place and
|
||||
// a download is needed
|
||||
if (objectType == 'item' && obj.isImportedAttachment()) {
|
||||
if (objectType == 'item' && obj.isStoredFileAttachment()) {
|
||||
if (jsonDataLocal.mtime != jsonData.mtime
|
||||
|| jsonDataLocal.md5 != jsonData.md5) {
|
||||
saveOptions.storageDetailsChanged = true;
|
||||
|
@ -1459,8 +1472,23 @@ Zotero.Sync.Data.Local = {
|
|||
if (!options.skipData) {
|
||||
obj.fromJSON(json.data, { strict: true });
|
||||
}
|
||||
if (obj.objectType == 'item' && obj.isImportedAttachment()) {
|
||||
yield this._checkAttachmentForDownload(obj, json.data.mtime, options.isNewObject);
|
||||
if (obj.objectType == 'item') {
|
||||
// Update createdByUserID and lastModifiedByUserID
|
||||
for (let p of ['createdByUser', 'lastModifiedByUser']) {
|
||||
if (json.meta && json.meta[p]) {
|
||||
let { id: userID, username, name } = json.meta[p];
|
||||
obj[p + 'ID'] = userID;
|
||||
name = name !== '' ? name : username;
|
||||
// Update stored name if it changed
|
||||
if (Zotero.Users.getName(userID) != name) {
|
||||
yield Zotero.Users.setName(userID, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.isStoredFileAttachment()) {
|
||||
yield this._checkAttachmentForDownload(obj, json.data.mtime, options.isNewObject);
|
||||
}
|
||||
}
|
||||
obj.version = json.data.version;
|
||||
if (!options.saveAsUnsynced) {
|
||||
|
@ -1493,7 +1521,7 @@ Zotero.Sync.Data.Local = {
|
|||
yield this._removeObjectFromSyncQueue(obj.objectType, obj.libraryID, json.key);
|
||||
|
||||
// Mark updated attachments for download
|
||||
if (obj.objectType == 'item' && obj.isImportedAttachment()) {
|
||||
if (obj.objectType == 'item' && obj.isStoredFileAttachment()) {
|
||||
// If storage changes were made (attachment mtime or hash), mark
|
||||
// library as requiring download
|
||||
if (options.isNewObject || options.storageDetailsChanged) {
|
||||
|
|
|
@ -95,6 +95,7 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
return new Zotero.Sync.APIClient({
|
||||
baseURL: this.baseURL,
|
||||
apiVersion: this.apiVersion,
|
||||
schemaVersion: this.globalSchemaVersion,
|
||||
apiKey: options.apiKey,
|
||||
caller: this.caller,
|
||||
cancellerReceiver: _cancellerReceiver,
|
||||
|
|
|
@ -2541,8 +2541,14 @@ Zotero.Translate.Export.prototype._prepareTranslation = Zotero.Promise.method(fu
|
|||
|
||||
function rest() {
|
||||
// export file data, if requested
|
||||
if(this._displayOptions["exportFileData"]) {
|
||||
this.location = this._itemGetter.exportFiles(this.location, this.translator[0].target);
|
||||
if (this._displayOptions.exportFileData) {
|
||||
this.location = this._itemGetter.exportFiles(
|
||||
this.location,
|
||||
this.translator[0].target,
|
||||
{
|
||||
includeAnnotations: this._displayOptions.includeAnnotations
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// initialize IO
|
||||
|
|
|
@ -1038,7 +1038,15 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
},
|
||||
|
||||
"setAll": Zotero.Promise.coroutine(function* (libraryID, getChildCollections) {
|
||||
this._itemsLeft = yield Zotero.Items.getAll(libraryID, true);
|
||||
this._itemsLeft = (yield Zotero.Items.getAll(libraryID, true))
|
||||
.filter((item) => {
|
||||
// Don't export annotations
|
||||
switch (item.itemType) {
|
||||
case 'annotation':
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if(getChildCollections) {
|
||||
this._collectionsLeft = Zotero.Collections.getByLibrary(libraryID);
|
||||
|
@ -1048,9 +1056,10 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
this.numItems = this._itemsLeft.length;
|
||||
}),
|
||||
|
||||
"exportFiles":function(dir, extension) {
|
||||
exportFiles: function (dir, extension, { includeAnnotations }) {
|
||||
// generate directory
|
||||
this._exportFileDirectory = dir.parent.clone();
|
||||
this._includeAnnotations = includeAnnotations;
|
||||
|
||||
// delete this file if it exists
|
||||
if(dir.exists()) {
|
||||
|
@ -1079,6 +1088,7 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
var attachmentArray = Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy);
|
||||
var linkMode = attachment.attachmentLinkMode;
|
||||
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
let includeAnnotations = attachment.isPDFAttachment() && this._includeAnnotations;
|
||||
attachmentArray.localPath = attachment.getFilePath();
|
||||
|
||||
if(this._exportFileDirectory) {
|
||||
|
@ -1114,7 +1124,7 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
* file to be overwritten. If true, the file will be silently overwritten.
|
||||
* defaults to false if not provided.
|
||||
*/
|
||||
attachmentArray.saveFile = function(attachPath, overwriteExisting) {
|
||||
attachmentArray.saveFile = async function (attachPath, overwriteExisting) {
|
||||
// Ensure a valid path is specified
|
||||
if(attachPath === undefined || attachPath == "") {
|
||||
throw new Error("ERROR_EMPTY_PATH");
|
||||
|
@ -1162,18 +1172,13 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
|
||||
var directory = targetFile.parent;
|
||||
|
||||
// The only attachments that can have multiple supporting files are imported
|
||||
// attachments of mime type text/html
|
||||
// For snapshots with supporting files, check if any of the supporting
|
||||
// files would cause a name conflict, and build a list of transfers
|
||||
// that should be performed
|
||||
//
|
||||
// TEMP: This used to check getNumFiles() here, but that's now async.
|
||||
// It could be restored (using hasMultipleFiles()) when this is made
|
||||
// async, but it's probably not necessary. (The below can also be changed
|
||||
// to use OS.File.DirectoryIterator.)
|
||||
if(attachment.attachmentContentType == "text/html"
|
||||
&& linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
// Attachment is a snapshot with supporting files. Check if any of the
|
||||
// supporting files would cause a name conflict, and build a list of transfers
|
||||
// that should be performed
|
||||
// TODO: Change the below to use OS.File.DirectoryIterator?
|
||||
if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE
|
||||
&& await Zotero.Attachments.hasMultipleFiles(attachment)) {
|
||||
var copySrcs = [];
|
||||
var files = attachment.getFile().parent.directoryEntries;
|
||||
while (files.hasMoreElements()) {
|
||||
|
@ -1204,10 +1209,22 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
for(var i = 0; i < copySrcs.length; i++) {
|
||||
copySrcs[i].copyTo(directory, copySrcs[i].leafName);
|
||||
}
|
||||
} else {
|
||||
// Attachment is a single file
|
||||
// Copy the file to the specified location
|
||||
attachFile.copyTo(directory, targetFile.leafName);
|
||||
}
|
||||
// For single files, just copy to the specified location
|
||||
else {
|
||||
if (includeAnnotations) {
|
||||
// TODO: Make export async
|
||||
try {
|
||||
await Zotero.PDFWorker.export(attachment.id, targetFile.path);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
attachFile.copyTo(directory, targetFile.leafName);
|
||||
}
|
||||
}
|
||||
|
||||
attachmentArray.path = targetFile.path;
|
||||
|
|
|
@ -28,10 +28,11 @@ Zotero.Users = new function () {
|
|||
var _libraryID;
|
||||
var _username;
|
||||
var _localUserKey;
|
||||
var _users = {};
|
||||
|
||||
this.init = Zotero.Promise.coroutine(function* () {
|
||||
this.init = async function () {
|
||||
let sql = "SELECT key, value FROM settings WHERE setting='account'";
|
||||
let rows = yield Zotero.DB.queryAsync(sql);
|
||||
let rows = await Zotero.DB.queryAsync(sql);
|
||||
|
||||
let settings = {};
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
|
@ -56,11 +57,16 @@ Zotero.Users = new function () {
|
|||
let key = Zotero.randomString(8);
|
||||
|
||||
sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
|
||||
yield Zotero.DB.queryAsync(sql, key);
|
||||
await Zotero.DB.queryAsync(sql, key);
|
||||
|
||||
_localUserKey = key;
|
||||
}
|
||||
});
|
||||
|
||||
rows = await Zotero.DB.queryAsync("SELECT userID, name FROM users");
|
||||
for (let row of rows) {
|
||||
_users[row.userID] = row.name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.getCurrentUserID = function() { return _userID };
|
||||
|
@ -87,4 +93,18 @@ Zotero.Users = new function () {
|
|||
this.getLocalUserKey = function () {
|
||||
return _localUserKey;
|
||||
};
|
||||
|
||||
|
||||
this.getName = function (userID) {
|
||||
return _users[userID] || '';
|
||||
};
|
||||
|
||||
|
||||
this.setName = async function (userID, name) {
|
||||
if (this.getName(userID) == name) {
|
||||
return;
|
||||
}
|
||||
await Zotero.DB.queryAsync("REPLACE INTO users VALUES (?, ?)", [userID, name]);
|
||||
_users[userID] = name;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -49,6 +49,62 @@ Zotero.Utilities = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and returns a new, throttled version of the
|
||||
* passed function, that, when invoked repeatedly,
|
||||
* will only actually call the original function at most
|
||||
* once per every wait milliseconds
|
||||
*
|
||||
* By default, throttle will execute the function as soon
|
||||
* as you call it for the first time, and, if you call it
|
||||
* again any number of times during the wait period, as soon
|
||||
* as that period is over. If you'd like to disable the
|
||||
* leading-edge call, pass {leading: false}, and if you'd
|
||||
* like to disable the execution on the trailing-edge,
|
||||
* pass {trailing: false}. See
|
||||
* https://underscorejs.org/#throttle
|
||||
* https://github.com/jashkenas/underscore/blob/master/underscore.js
|
||||
* (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
* Underscore may be freely distributed under the MIT license.
|
||||
*
|
||||
* @param {Function} func Function to throttle
|
||||
* @param {Integer} wait Wait period in milliseconds
|
||||
* @param {Boolean} [options.leading] Call at the beginning of the wait period
|
||||
* @param {Boolean} [options.trailing] Call at the end of the wait period
|
||||
*/
|
||||
throttle: function (func, wait, options) {
|
||||
var context, args, result;
|
||||
var timeout = null;
|
||||
var previous = 0;
|
||||
if (!options) options = {};
|
||||
var later = function () {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
return function () {
|
||||
var now = Date.now();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
var remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
}
|
||||
else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Fixes author name capitalization.
|
||||
* Currently for all uppercase names only
|
||||
|
|
|
@ -228,7 +228,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
var version = yield deferred.promise;
|
||||
}
|
||||
Zotero.version = version;
|
||||
Zotero.isDevBuild = Zotero.version.includes('beta') || Zotero.version.includes('SOURCE');
|
||||
Zotero.isDevBuild = Zotero.version.includes('beta')
|
||||
|| Zotero.version.includes('dev')
|
||||
|| Zotero.version.includes('SOURCE');
|
||||
|
||||
// OS platform
|
||||
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
||||
|
@ -257,6 +259,8 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
return;
|
||||
}
|
||||
|
||||
Zotero.isPDFBuild = Zotero.Prefs.get('beta.zotero6');
|
||||
|
||||
try {
|
||||
yield Zotero.DataDirectory.init();
|
||||
if (this.restarting) {
|
||||
|
@ -719,6 +723,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
|
|||
yield Zotero.Groups.init();
|
||||
yield Zotero.Relations.init();
|
||||
yield Zotero.Retractions.init();
|
||||
yield Zotero.NoteBackups.init();
|
||||
|
||||
// Migrate fields from Extra that can be moved to item fields after a schema update
|
||||
yield Zotero.Schema.migrateExtraFields();
|
||||
|
|
|
@ -69,6 +69,13 @@ var ZoteroPane = new function()
|
|||
this.init = function () {
|
||||
Zotero.debug("Initializing Zotero pane");
|
||||
|
||||
if (!Zotero.isPDFBuild) {
|
||||
let win = document.getElementById('main-window')
|
||||
win.setAttribute('legacytoolbar', 'true');
|
||||
document.getElementById('titlebar').hidden = true;
|
||||
document.getElementById('tab-bar-container').hidden = true;
|
||||
}
|
||||
|
||||
// Set key down handler
|
||||
document.getElementById('appcontent').addEventListener('keydown', ZoteroPane_Local.handleKeyDown, true);
|
||||
|
||||
|
@ -144,6 +151,7 @@ var ZoteroPane = new function()
|
|||
Zotero.hiDPI = window.devicePixelRatio > 1;
|
||||
Zotero.hiDPISuffix = Zotero.hiDPI ? "@2x" : "";
|
||||
|
||||
Zotero_Tabs.init();
|
||||
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
|
||||
|
||||
// Add a default progress window
|
||||
|
@ -525,6 +533,21 @@ var ZoteroPane = new function()
|
|||
* Trigger actions based on keyboard shortcuts
|
||||
*/
|
||||
function handleKeyDown(event, from) {
|
||||
// Close current tab
|
||||
if (event.key == 'w') {
|
||||
let close = Zotero.isMac
|
||||
? (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey)
|
||||
: (event.ctrlKey && !event.shiftKey && !event.altKey);
|
||||
if (close) {
|
||||
if (Zotero_Tabs.selectedIndex > 0) {
|
||||
Zotero_Tabs.close();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Ignore keystrokes outside of Zotero pane
|
||||
if (!(event.originalTarget.ownerDocument instanceof XULDocument)) {
|
||||
|
@ -541,6 +564,20 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
if (from == 'zotero-pane') {
|
||||
// Tab navigation
|
||||
// TODO: Select across tabs without selecting with Ctrl-Shift, as in Firefox?
|
||||
let ctrlOnly = event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
|
||||
if (ctrlOnly) {
|
||||
if (event.key == 'PageUp') {
|
||||
Zotero_Tabs.selectPrev();
|
||||
}
|
||||
else if (event.key == 'PageDown') {
|
||||
Zotero_Tabs.selectNext();
|
||||
}
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Highlight collections containing selected items
|
||||
//
|
||||
// We use Control (17) on Windows because Alt triggers the menubar;
|
||||
|
@ -1166,6 +1203,14 @@ var ZoteroPane = new function()
|
|||
return;
|
||||
}
|
||||
|
||||
// Rename tab
|
||||
if (Zotero.isPDFBuild) {
|
||||
Zotero_Tabs.rename('zotero-pane', collectionTreeRow.getName());
|
||||
}
|
||||
|
||||
let type = Zotero.Libraries.get(collectionTreeRow.ref.libraryID).libraryType;
|
||||
ZoteroItemPane.switchEditorEngine(type == 'group' || !Zotero.isPDFBuild);
|
||||
|
||||
// Clear quick search and tag selector when switching views
|
||||
document.getElementById('zotero-tb-search').value = "";
|
||||
if (ZoteroPane.tagSelector) {
|
||||
|
@ -2690,6 +2735,8 @@ var ZoteroPane = new function()
|
|||
'showInLibrary',
|
||||
'sep1',
|
||||
'addNote',
|
||||
'createNoteFromAnnotations',
|
||||
'createNoteFromAnnotationsMenu',
|
||||
'addAttachments',
|
||||
'sep2',
|
||||
'findPDF',
|
||||
|
@ -2858,6 +2905,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Single item selected
|
||||
|
@ -2875,6 +2923,33 @@ var ZoteroPane = new function()
|
|||
|
||||
if (item.isRegularItem() && !item.isFeedItem) {
|
||||
show.push(m.addNote, m.addAttachments, m.sep2);
|
||||
|
||||
// Create Note from Annotations
|
||||
let popup = document.getElementById('create-note-from-annotations-popup');
|
||||
popup.textContent = '';
|
||||
let eligibleAttachments = Zotero.Items.get(item.getAttachments())
|
||||
.filter(item => item.isPDFAttachment());
|
||||
let attachmentsWithAnnotations = eligibleAttachments.filter(x => x.numAnnotations());
|
||||
if (attachmentsWithAnnotations.length) {
|
||||
// Display submenu if there's more than one PDF attachment, even if
|
||||
// there's only attachment with annotations, so it's clear which one
|
||||
// the annotations are coming from
|
||||
if (eligibleAttachments.length > 1) {
|
||||
show.push(m.createNoteFromAnnotationsMenu);
|
||||
for (let attachment of attachmentsWithAnnotations) {
|
||||
let menuitem = document.createElement('menuitem');
|
||||
menuitem.setAttribute('label', attachment.getDisplayTitle());
|
||||
menuitem.onclick = () => {
|
||||
ZoteroPane.createNoteFromAnnotationsForAttachment(attachment);
|
||||
};
|
||||
popup.appendChild(menuitem);
|
||||
}
|
||||
}
|
||||
// Single attachment with annotations
|
||||
else {
|
||||
show.push(m.createNoteFromAnnotations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Zotero.Attachments.canFindPDFForItem(item)) {
|
||||
|
@ -3462,7 +3537,7 @@ var ZoteroPane = new function()
|
|||
var items = this.getSelectedItems();
|
||||
|
||||
if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) {
|
||||
var note = items[0].getNote()
|
||||
var note = items[0].note;
|
||||
|
||||
items[0].setNote(note + text);
|
||||
yield items[0].saveTx();
|
||||
|
@ -3514,13 +3589,13 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
this.onNoteWindowClosed = async function (itemID, noteText) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
item.setNote(noteText);
|
||||
await item.saveTx();
|
||||
|
||||
|
||||
// If note is still selected, show the editor again when the note window closes
|
||||
var selectedItems = this.getSelectedItems(true);
|
||||
if (selectedItems.length == 1 && itemID == selectedItems[0]) {
|
||||
|
@ -3529,6 +3604,46 @@ var ZoteroPane = new function()
|
|||
};
|
||||
|
||||
|
||||
this.openBackupNoteWindow = function (itemID) {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
var name = null;
|
||||
|
||||
if (itemID) {
|
||||
let w = this.findBackupNoteWindow(itemID);
|
||||
if (w) {
|
||||
w.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a name for this window so we can focus it later
|
||||
//
|
||||
// Collection is only used on new notes, so we don't need to
|
||||
// include it in the name
|
||||
name = 'zotero-backup-note-' + itemID;
|
||||
}
|
||||
|
||||
var io = { itemID: itemID };
|
||||
window.openDialog('chrome://zotero/content/noteBackup.xul', name, 'chrome,resizable,centerscreen,dialog=false', io);
|
||||
}
|
||||
|
||||
|
||||
this.findBackupNoteWindow = function (itemID) {
|
||||
var name = 'zotero-backup-note-' + itemID;
|
||||
var wm = Services.wm;
|
||||
var e = wm.getEnumerator('zotero:note');
|
||||
while (e.hasMoreElements()) {
|
||||
var w = e.getNext();
|
||||
if (w.name == name) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.addAttachmentFromURI = Zotero.Promise.method(function (link, itemID) {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
|
@ -4000,9 +4115,27 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
|
||||
var launchFile = async function (path, contentType) {
|
||||
var launchFile = async (path, contentType, itemID) => {
|
||||
// Fix blank PDF attachment MIME type
|
||||
if (!contentType) {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let path = await item.getFilePathAsync();
|
||||
let type = 'application/pdf';
|
||||
if (Zotero.MIME.sniffForMIMEType(await Zotero.File.getSample(path)) == type) {
|
||||
contentType = type;
|
||||
item.attachmentContentType = type;
|
||||
await item.saveTx();
|
||||
}
|
||||
}
|
||||
// Custom PDF handler
|
||||
if (contentType === 'application/pdf') {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
let library = Zotero.Libraries.get(item.libraryID);
|
||||
// TEMP
|
||||
if (Zotero.isPDFBuild && library.libraryType == 'user') {
|
||||
this.viewPDF(itemID, event && event.shiftKey);
|
||||
return;
|
||||
}
|
||||
let pdfHandler = Zotero.Prefs.get("fileHandler.pdf");
|
||||
if (pdfHandler) {
|
||||
if (await OS.File.exists(pdfHandler)) {
|
||||
|
@ -4031,7 +4164,7 @@ var ZoteroPane = new function()
|
|||
continue;
|
||||
}
|
||||
|
||||
let isLinkedFile = !item.isImportedAttachment();
|
||||
let isLinkedFile = !item.isStoredFileAttachment();
|
||||
let path = item.getFilePath();
|
||||
if (!path) {
|
||||
ZoteroPane_Local.showAttachmentNotFoundDialog(
|
||||
|
@ -4058,7 +4191,7 @@ var ZoteroPane = new function()
|
|||
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
|
||||
if (await OS.File.exists(iCloudPath)) {
|
||||
Zotero.debug("Triggering download of iCloud file");
|
||||
await launchFile(iCloudPath, item.attachmentContentType);
|
||||
await launchFile(iCloudPath, item.attachmentContentType, itemID);
|
||||
let time = new Date();
|
||||
let maxTime = 5000;
|
||||
let revealed = false;
|
||||
|
@ -4118,7 +4251,7 @@ var ZoteroPane = new function()
|
|||
if (fileExists && !redownload) {
|
||||
Zotero.debug("Opening " + path);
|
||||
Zotero.Notifier.trigger('open', 'file', item.id);
|
||||
launchFile(path, item.attachmentContentType);
|
||||
await launchFile(path, item.attachmentContentType, item.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -4164,10 +4297,14 @@ var ZoteroPane = new function()
|
|||
|
||||
Zotero.debug("Opening " + path);
|
||||
Zotero.Notifier.trigger('open', 'file', item.id);
|
||||
launchFile(path, item.attachmentContentType);
|
||||
await launchFile(path, item.attachmentContentType, item.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.viewPDF = function (itemID, openWindow) {
|
||||
Zotero.Reader.open(itemID, null, openWindow);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
|
@ -4203,7 +4340,7 @@ var ZoteroPane = new function()
|
|||
var fileExists = await OS.File.exists(path);
|
||||
|
||||
// If file doesn't exist but an evicted iCloud Drive file does, reveal that instead
|
||||
if (!fileExists && Zotero.isMac && !attachment.isImportedAttachment()) {
|
||||
if (!fileExists && Zotero.isMac && !attachment.isStoredFileAttachment()) {
|
||||
let iCloudPath = Zotero.File.getEvictedICloudPath(path);
|
||||
if (await OS.File.exists(iCloudPath)) {
|
||||
path = iCloudPath;
|
||||
|
@ -4563,8 +4700,40 @@ var ZoteroPane = new function()
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
this.createNoteFromAnnotationsForAttachment = async function (attachment) {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
var note = await Zotero.EditorInstance.createNoteFromAnnotations(
|
||||
attachment.getAnnotations(), attachment.parentID
|
||||
);
|
||||
await this.selectItem(note.id);
|
||||
};
|
||||
|
||||
|
||||
this.createNoteFromAnnotationsFromSelected = async function () {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
}
|
||||
var item = this.getSelectedItems()[0];
|
||||
var attachment;
|
||||
if (item.isRegularItem()) {
|
||||
attachment = Zotero.Items.get(item.getAttachments())
|
||||
.find(x => x.isPDFAttachment() && x.numAnnotations());
|
||||
}
|
||||
else if (item.isFileAttachment()) {
|
||||
attachment = item;
|
||||
}
|
||||
else {
|
||||
throw new Error("Not a regular item or file attachment");
|
||||
}
|
||||
return this.createNoteFromAnnotationsForAttachment(attachment);
|
||||
};
|
||||
|
||||
this.createEmptyParent = async function (item) {
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
// TODO: remove once there are no top-level web attachments
|
||||
|
@ -4585,7 +4754,27 @@ var ZoteroPane = new function()
|
|||
await item.save();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.exportPDF = async function (itemID) {
|
||||
let item = await Zotero.Items.getAsync(itemID);
|
||||
if (!item || !item.isAttachment()) {
|
||||
throw new Error('Item ' + itemID + ' is not attachment');
|
||||
}
|
||||
let filename = item.attachmentFilename;
|
||||
|
||||
var fp = new FilePicker();
|
||||
fp.init(window, Zotero.getString('styles.editor.save'), fp.modeSave);
|
||||
fp.appendFilter("PDF", "*.pdf");
|
||||
fp.defaultString = filename;
|
||||
|
||||
var rv = await fp.show();
|
||||
if (rv === fp.returnOK || rv === fp.returnReplace) {
|
||||
let outputFile = fp.file;
|
||||
await Zotero.PDFWorker.export(item.id, outputFile, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
|
||||
// TEMP: fix
|
||||
|
@ -4947,6 +5136,7 @@ var ZoteroPane = new function()
|
|||
|
||||
this.updateToolbarPosition();
|
||||
this.updateTagsBoxSize();
|
||||
ZoteroContextPane.update();
|
||||
}
|
||||
|
||||
|
||||
|
@ -5095,6 +5285,8 @@ var ZoteroPane = new function()
|
|||
* Revisit when we're all HTML.
|
||||
*/
|
||||
this.updateTagsBoxSize = function () {
|
||||
// TODO: We can probably remove this function
|
||||
return;
|
||||
var pane = document.querySelector('#zotero-item-pane');
|
||||
var header = document.querySelector('#zotero-item-pane .tags-box-header');
|
||||
var list = document.querySelector('#zotero-item-pane .tags-box-list');
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Show in Library">
|
||||
<!ENTITY zotero.items.menu.attach.note "Add Note">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Add Attachment">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Attach Link to URI…">
|
||||
<!ENTITY zotero.items.menu.attach.file "Attach Stored Copy of File...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Store Copy of File...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Save Link to Current Page">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Take Snapshot of Current Page">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Attach Link to URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Link:">
|
||||
<!ENTITY zotero.attachLink.label.title "Title:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Briefly describe the problem:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Item
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=A Zotero operation is currently in progress.
|
||||
general.operationInProgress.waitUntilFinished=Please wait until it has finished.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Switch to two fields
|
|||
pane.item.creator.moveToTop=Move to Top
|
||||
pane.item.creator.moveUp=Move Up
|
||||
pane.item.creator.moveDown=Move Down
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Untitled Note
|
||||
pane.item.notes.delete.confirm=Are you sure you want to delete this note?
|
||||
pane.item.notes.count.zero=%S notes:
|
||||
pane.item.notes.count.singular=%S note:
|
||||
pane.item.notes.count.plural=%S notes:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Editing in separate window
|
||||
pane.item.attachments.rename.title=New title:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Rename associated file
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S related:
|
|||
pane.item.related.count.plural=%S related:
|
||||
pane.item.parentItem=Parent Item:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Edit Note
|
||||
|
||||
itemTypes.note=Note
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Attachment
|
||||
itemTypes.book=Book
|
||||
itemTypes.bookSection=Book Section
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=No PDFs found
|
||||
findPDF.noPDFFound=No PDF found
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Full Text
|
||||
attachment.acceptedVersion=Accepted Version
|
||||
attachment.submittedVersion=Submitted Version
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
|
|||
searchConditions.fulltextContent=Attachment Content
|
||||
searchConditions.programmingLanguage=Programming Language
|
||||
searchConditions.fileTypeID=Attachment File Type
|
||||
searchConditions.annotation=Annotation
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indexed
|
||||
fulltext.indexState.unavailable=Unknown
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
|
|||
|
||||
exportOptions.exportNotes=Export Notes
|
||||
exportOptions.exportFileData=Export Files
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
|
||||
charset.autoDetect=(auto detect)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
|
|||
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
|
||||
integration.exportDocument.description2=You should make a backup of the document before proceeding.
|
||||
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Install Style
|
||||
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "عرض في المكتبة">
|
||||
<!ENTITY zotero.items.menu.attach.note "اضافة ملاحظة">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "اضافة مرفق">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "ربط الرابط بالمسار...">
|
||||
<!ENTITY zotero.items.menu.attach.file "إرفاق نسخة من ملف مخزن...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "تخزين نسخة من ملف...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "حفظ رابط للصفحة الحالية">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "اخذ لقطة من الصفحة الحالية">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "لاتوجد اوسمة لعرضها">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "تحميل الوسوم...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "ارفاق رابط لمحتوى على الانترنت">
|
||||
<!ENTITY zotero.attachLink.label.link "رابط:">
|
||||
<!ENTITY zotero.attachLink.label.title "عنوان:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=صف المشكلة باختصار:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=عنصر
|
||||
general.pdf=بي دي إف
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=عملية زوتيرو حالياً تحت الإجراء.
|
||||
general.operationInProgress.waitUntilFinished=يرجى الانتظار حتى انتهائها.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
|
|||
app.standalone=النسخة المستقلة من زوتيرو
|
||||
app.firefox=نسخة زوتيرو لمتصفح الفايرفوكس
|
||||
|
||||
startupError=هناك خطأ عند بدء تشغيل زوتيرو.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=قاعدة بيانات زوتيرو لديك قيد الاستخدام. يمكن فتح نسخة واحدة فقط من زوتيرو تستخدم نفس قاعدة بيانات في وقت واحد حاليا.
|
||||
startupError.closeStandalone=إذا كانت النسخة المستقلة من زوتيرو مفتوحة، من فضلك قم بإغلاقها وإعادة تشغيل متصفح فايرفوكس.
|
||||
startupError.closeFirefox=إذا كان متصفح فايرفوكس مع امتداد زوتيرو مفتوح، فمن فضلك قم بإغلاقه وإعادة تشغيل النسخة المستقلة من زوتيرو.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=تحويل لحقل ثنائي
|
|||
pane.item.creator.moveToTop=تحريك لأعلى
|
||||
pane.item.creator.moveUp=تحريك لأعلى
|
||||
pane.item.creator.moveDown=تحريك لأسفل
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=ملاحظة بدون عنوان
|
||||
pane.item.notes.delete.confirm=هل ترغب في حذف هذه الملاحظة؟
|
||||
pane.item.notes.count.zero=لا توجد ملاحظات:
|
||||
pane.item.notes.count.singular=%S ملاحظات:
|
||||
pane.item.notes.count.plural=%S ملاحظة:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Editing in separate window
|
||||
pane.item.attachments.rename.title=عنوان جديد:
|
||||
pane.item.attachments.rename.renameAssociatedFile=إعادة تسمية الملف المرتبط
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S ارتباطات:
|
|||
pane.item.related.count.plural=%S ارتباط:
|
||||
pane.item.parentItem=عنصر رئيسي:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=تحرير ملاحظة
|
||||
|
||||
itemTypes.note=ملاحظة
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=مرفق
|
||||
itemTypes.book=كتاب
|
||||
itemTypes.bookSection=قسم في كتاب
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=بي دي إف (%S)
|
|||
findPDF.noPDFsFound=لا يوجد ملفات بي دي إف
|
||||
findPDF.noPDFFound=لم يتم العثور على بي دي إف
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=نص كامل
|
||||
attachment.acceptedVersion=نسخة مقبولة
|
||||
attachment.submittedVersion=نسخة مقدمة
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=تاريخ التعديل
|
|||
searchConditions.fulltextContent=محتويات المرفق
|
||||
searchConditions.programmingLanguage=لغة البرمجة
|
||||
searchConditions.fileTypeID=نوع الملف المرفق
|
||||
searchConditions.annotation=تعليق
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=مكشف
|
||||
fulltext.indexState.unavailable=غير معروف
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=في الانتظار
|
|||
|
||||
exportOptions.exportNotes=تصدير الملاحظات
|
||||
exportOptions.exportFileData=تصدير الملفات
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=استخدم اختصار الدورية
|
||||
charset.UTF8withoutBOM=يونيكود (UTF-8 بدون BOM)
|
||||
charset.autoDetect=(التعرف التلقائي)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
|
|||
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
|
||||
integration.exportDocument.description2=You should make a backup of the document before proceeding.
|
||||
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=ثبت نمط
|
||||
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Показване в библиотеката">
|
||||
<!ENTITY zotero.items.menu.attach.note "Добавяне на бележка">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Добавяне на прикачен файл">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Добавяне на връзка към URI…">
|
||||
<!ENTITY zotero.items.menu.attach.file "Добавяне на съхранено копие на файл от твърдия диск...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Запазване на копие от файл">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Запазване на връзка към настоящата страница">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Направи "моментна снимка" на настоящата страница">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Няма маркери, които да бъдат показани">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Зареждане на маркери...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Прикачване на линк към URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Линк:">
|
||||
<!ENTITY zotero.attachLink.label.title "Заглавие:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Опишете накратко проблема /In Eng
|
|||
general.nMegabytes=%S МБ
|
||||
general.item=Елемент
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Операция на Зотеро е активна в момента.
|
||||
general.operationInProgress.waitUntilFinished=Моля изчакайте докато приключи.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Show Current Director
|
|||
app.standalone=Zotero Standalone
|
||||
app.firefox=Zotero for Firefox
|
||||
|
||||
startupError=Появи се грешка при стартирането на Зотеро.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=Your Zotero database is currently in use. Only one instance of Zotero using the same database may be opened simultaneously at this time.
|
||||
startupError.closeStandalone=If Zotero Standalone is open, please close it and restart Firefox.
|
||||
startupError.closeFirefox=If Firefox with the Zotero extension is open, please close it and restart Zotero Standalone.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Превключва към две полета
|
|||
pane.item.creator.moveToTop=Move to Top
|
||||
pane.item.creator.moveUp=Move Up
|
||||
pane.item.creator.moveDown=Move Down
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Бележка без име
|
||||
pane.item.notes.delete.confirm=Сигурни ли сте, че искате да изтриете тази бележка?
|
||||
pane.item.notes.count.zero=%S бележки:
|
||||
pane.item.notes.count.singular=%S бележка:
|
||||
pane.item.notes.count.plural=%S бележки:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Editing in separate window
|
||||
pane.item.attachments.rename.title=Ново заглавие:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Преименува приложеният файл
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S близък:
|
|||
pane.item.related.count.plural=%S близки:
|
||||
pane.item.parentItem=Parent Item:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Редактира бележка
|
||||
|
||||
itemTypes.note=Бележка
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Приложение
|
||||
itemTypes.book=Книга
|
||||
itemTypes.bookSection=Глава от книга
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=No PDFs found
|
||||
findPDF.noPDFFound=No PDF found
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Full Text
|
||||
attachment.acceptedVersion=Accepted Version
|
||||
attachment.submittedVersion=Submitted Version
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Променен на
|
|||
searchConditions.fulltextContent=Съдържание на приложението
|
||||
searchConditions.programmingLanguage=Програмен език
|
||||
searchConditions.fileTypeID=Вид приложен файл
|
||||
searchConditions.annotation=Анотация
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Индексиран
|
||||
fulltext.indexState.unavailable=Неизвестен
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
|
|||
|
||||
exportOptions.exportNotes=Износ на бележки
|
||||
exportOptions.exportFileData=Износ на Файлове
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
|
||||
charset.UTF8withoutBOM=Уникод (UTF-8 без BOM)
|
||||
charset.autoDetect=(автоматично откриване)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
|
|||
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
|
||||
integration.exportDocument.description2=You should make a backup of the document before proceeding.
|
||||
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Install Style
|
||||
styles.install.unexpectedError=An unexpected error occurred while installing "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Diskouez el levraoueg:">
|
||||
<!ENTITY zotero.items.menu.attach.note "Ouzhpennañ un notenn">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Ouzhpennañ ur pezh-stag">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Stagañ ul liamm betek un URI…">
|
||||
<!ENTITY zotero.items.menu.attach.file "Stagañ un eilenn enrollet eus ur restr...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Stokañ un eilenn eus ar restr...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Enrollañ al liamm d'ar bajenn a-vremañ">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Kemer un dapadenn-skramm eus ar bajenn a-vremañ">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Baliz ebet da ziskouez">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "O kargañ ar balizoù...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Stagañ ul liamm betek un URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Liamm:">
|
||||
<!ENTITY zotero.attachLink.label.title "Titl:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Deskrivit ar gudenn e berr gomzoù:
|
|||
general.nMegabytes=%S Mo
|
||||
general.item=Elfenn
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Un oberiadenn a-berzh Zotero a zo war ober er mare-mañ.
|
||||
general.operationInProgress.waitUntilFinished=Gortozit betek ma vo echuet.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Diskouez div vaezienn
|
|||
pane.item.creator.moveToTop=Fiñval d'al laez
|
||||
pane.item.creator.moveUp=War-grec'h
|
||||
pane.item.creator.moveDown=War-draoñ
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Notenn hep-titl
|
||||
pane.item.notes.delete.confirm=Ha fellout a ra deoc'h dilemel an notenn-mañ da vat?
|
||||
pane.item.notes.count.zero=%S a notennoù:
|
||||
pane.item.notes.count.singular=%S notenn:
|
||||
pane.item.notes.count.plural=%S a notennoù:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Oc'h aozañ en ur prenestr nevez
|
||||
pane.item.attachments.rename.title=Titl nevez:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Adenvel ar restr kevelet
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S kevreet:
|
|||
pane.item.related.count.plural=%S kevreet:
|
||||
pane.item.parentItem=Elfenn kar:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Aozañ an notenn
|
||||
|
||||
itemTypes.note=Notenn
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Pezh-stag
|
||||
itemTypes.book=Levr
|
||||
itemTypes.bookSection=Chabistr levr
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=PDF ebet kavet
|
||||
findPDF.noPDFFound=PDF ebet kavet
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Testenn a-bezh
|
||||
attachment.acceptedVersion=Stumm asantet
|
||||
attachment.submittedVersion=Stumm kaset
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Deiziad kemmañ
|
|||
searchConditions.fulltextContent=Endalc'had ar pezh-stag
|
||||
searchConditions.programmingLanguage=Langaj programiñ
|
||||
searchConditions.fileTypeID=Doare restr-stag
|
||||
searchConditions.annotation=Ennotadur
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indekset
|
||||
fulltext.indexState.unavailable=Dianav
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=War-c'hortoz
|
|||
|
||||
exportOptions.exportNotes=Ezporzhiañ notennoù
|
||||
exportOptions.exportFileData=Ezporzhiañ restroù
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Implijout berradurioù ar c'helaouennoù
|
||||
charset.UTF8withoutBOM=Unikod (UTF-8 hep BOM)
|
||||
charset.autoDetect=(em-ziguzhiñ)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prientiñ an arroudennoù evit an dreuzkasadenn
|
|||
integration.exportDocument.description1=Zotero a emdroio arroudennoù an teuliad en ur furmad hag a c'hall bezañ treuzkaset en un doare sur davet un treter testennoù all.
|
||||
integration.exportDocument.description2=Rankout a rafec'h ober un enrolladenn eus an teuliad a-raok kenderc'hel.
|
||||
integration.importInstructions=An arroudennoù Zotero en teuliad-mañ a zo bet emdroet en ur furmad a c'hall bezañ treuzkaset en un doare sur kenetre an tretourioù testennoù. Digorit an teuliad-mañ e-barzh an treter testennoù kemeret e kont ha pouezit war Freskaat e-barzh al lugant Zotero evit kenderc'hel da labourat gant an arroudennoù.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Staliañ ar stil
|
||||
styles.install.unexpectedError=Ur fazi dic'hortoz a zo c'hoarvezet en ur staliañ "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Mostra a la biblioteca">
|
||||
<!ENTITY zotero.items.menu.attach.note "Afegeix una nota">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Afegeix un adjunt">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Adjunta un enllaç a l'URI...">
|
||||
<!ENTITY zotero.items.menu.attach.file "Adjunta la còpia emmagatzemada del fitxer...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Emmagatzema una còpia del fitxer...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Desa l'enllaç a la pàgina actual">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Captura la pàgina actual">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "No hi ha etiquetes per mostrar">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "S'estan carregant les etiquetes…">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Adjunta l'enllaç a l'URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Enllaç:">
|
||||
<!ENTITY zotero.attachLink.label.title "Títol:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Descriviu breument el problema:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Element
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Una operació del Zotero està actualment en curs.
|
||||
general.operationInProgress.waitUntilFinished=Espereu fins que hagi acabat.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Mostra el directori a
|
|||
app.standalone=Zotero Independent
|
||||
app.firefox=Zotero per Firefox
|
||||
|
||||
startupError=Hi ha hagut un error en iniciar Zotero.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=La teva base de dades de Zotero està actualment en ús. En aquest moment i simultàniament només pot estar oberta una instància de Zotero amb la mateixa base de dades.
|
||||
startupError.closeStandalone=Si el Zotero Independent és obert, tanqueu-lo i reinicieu Firefox.
|
||||
startupError.closeFirefox=Si Firefox amb l'extensió Zotero és obert, tanqueu i reinicieu Zotero Independent.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Canvia a dos camps
|
|||
pane.item.creator.moveToTop=Mou a dalt
|
||||
pane.item.creator.moveUp=Mou amunt
|
||||
pane.item.creator.moveDown=Mou avall
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Nota sense títol
|
||||
pane.item.notes.delete.confirm=Segur que voleu eliminar aquesta nota?
|
||||
pane.item.notes.count.zero=Cap nota:
|
||||
pane.item.notes.count.singular=%S nota:
|
||||
pane.item.notes.count.plural=%S notes:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Edició en finestra separada
|
||||
pane.item.attachments.rename.title=Nou títol:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Canvia el nom del fitxer associat
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S element relacionat:
|
|||
pane.item.related.count.plural=%S elements relacionats:
|
||||
pane.item.parentItem=Element ascendent
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Edita la nota
|
||||
|
||||
itemTypes.note=Nota
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Fitxer adjunt
|
||||
itemTypes.book=Llibre
|
||||
itemTypes.bookSection=Capítol d'un llibre
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=No s'ha trobat cap PDF
|
||||
findPDF.noPDFFound=No s'ha trobat cap PDF
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Text complet
|
||||
attachment.acceptedVersion=Versió acceptada
|
||||
attachment.submittedVersion=Versió enviada
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Data de modificació
|
|||
searchConditions.fulltextContent=Contingut de l'arxiu adjunt
|
||||
searchConditions.programmingLanguage=Llengua de programació
|
||||
searchConditions.fileTypeID=Tipus de fitxer adjunt
|
||||
searchConditions.annotation=Anotació
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indexat
|
||||
fulltext.indexState.unavailable=Desconegut
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=En cua
|
|||
|
||||
exportOptions.exportNotes=Exporta les notes
|
||||
exportOptions.exportFileData=Exporta els fitxers adjunts
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Utilitza l'abreviació de la publicació
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 sense BOM)
|
||||
charset.autoDetect=(Detecció automàtica)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepara les cites per transferir
|
|||
integration.exportDocument.description1=El Zotero convertirà les cites del document a un format que pot transferir-se de forma segura a un altre processador de text acceptat.
|
||||
integration.exportDocument.description2=Caldria que féssiu una còpia de seguretat del document abans de procedir.
|
||||
integration.importInstructions=Les cites de Zotero d'aquest document s'han convertit a un format que pot transferir-se de forma segura entre diferents processadors de text. Obriu el document en un processador de text que ho accepti i premeu Refresca des del connector del Zotero per a continuar treballant amb les cites.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Instal·la l'estil
|
||||
styles.install.unexpectedError=S'ha produït un error inesperat durant la instal·lació de "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Ukázat v knihovně">
|
||||
<!ENTITY zotero.items.menu.attach.note "Přidat poznámku">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Přidat přílohu">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Připojit odkaz na URI..,">
|
||||
<!ENTITY zotero.items.menu.attach.file "Přiložit uloženou kopii souboru...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Uložit kopii souboru...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Uložit odkaz na aktuální stránku">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Vytvořit snímek aktuální stránky">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Žádné štítky k zobrazení">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Nahrávám štítky...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Připojit odkaz na URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Odkaz:">
|
||||
<!ENTITY zotero.attachLink.label.title "Název:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Stručně popište problém:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Položka
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Právě probíhá operace se Zoterem.
|
||||
general.operationInProgress.waitUntilFinished=Počkejte prosím, dokud neskončí.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Zobrazit současný a
|
|||
app.standalone=Samostatné Zotero
|
||||
app.firefox=Zotero pro Firefox
|
||||
|
||||
startupError=Při spouštění aplikace Zotero nastala chyba.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=Vaše databáze Zotera je právě používána. V jednom okamžiku je možné mít otevřenu pouze jednu instanci Zotera používající stejnou databázi.
|
||||
startupError.closeStandalone=Pokud je otevřené Samostatné Zotero, zavřete ho prosím a restartujte Firefox.
|
||||
startupError.closeFirefox=Pokud je otevřen Firefox s přídavkem Zotero, zavřete ho prosím a restartujte Firefox.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Přepnout na více polí
|
|||
pane.item.creator.moveToTop=Přesunout nahoru
|
||||
pane.item.creator.moveUp=Posunout nahoru
|
||||
pane.item.creator.moveDown=Posunout dolů
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Nepojmenovaná poznámka
|
||||
pane.item.notes.delete.confirm=Jste si jistý, že chcete smazat tuto poznámku?
|
||||
pane.item.notes.count.zero=%S poznámek:
|
||||
pane.item.notes.count.singular=%S poznámka:
|
||||
pane.item.notes.count.plural=%S poznámek:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Editace v samostatném okně
|
||||
pane.item.attachments.rename.title=Nový název:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Přejmenovat asociovaný soubor
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S související:
|
|||
pane.item.related.count.plural=%S souvisejících:
|
||||
pane.item.parentItem=Rodičovská položka:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Upravit poznámku
|
||||
|
||||
itemTypes.note=Poznámka
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Příloha
|
||||
itemTypes.book=Kniha
|
||||
itemTypes.bookSection=Kapitola knihy
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=Nenalezena žádná PDF
|
||||
findPDF.noPDFFound=Nenalezeno žádné PDF
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Plný text
|
||||
attachment.acceptedVersion=Přijatá verze
|
||||
attachment.submittedVersion=Odeslaná verze
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Datum změny
|
|||
searchConditions.fulltextContent=Obsah přílohy
|
||||
searchConditions.programmingLanguage=Programovací jazyk
|
||||
searchConditions.fileTypeID=Typ souboru přílohy
|
||||
searchConditions.annotation=Anotace
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indexováno
|
||||
fulltext.indexState.unavailable=Neznámé
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=Ve frontě
|
|||
|
||||
exportOptions.exportNotes=Exportovat poznámky
|
||||
exportOptions.exportFileData=Exportovat soubory
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Použít zkrácený název časopisu
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 bez BOM)
|
||||
charset.autoDetect=(automaticky detekovat)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Připravit citace na přesun
|
|||
integration.exportDocument.description1=Zotero převede citace v dokumentu do formátu, který může být bezpečně přenesen do jiného podporovaného textového editoru.
|
||||
integration.exportDocument.description2=Než budete pokračovat, je vhodné vytvořit zálohu dokumentu.
|
||||
integration.importInstructions=Zotero citace v tomto dokumentu byly převedeny do formátu, v němž mohou být bezpečně přeneseny mezi textovými editory. Abyste mohli pokračovat v práci s citacemi, otevřete tento dokument v podporovaném textovém procesoru a v doplňku Zotero stiskněte Obnovit.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=instalovat Styl
|
||||
styles.install.unexpectedError=Při instalaci "%1$S" došlo k neočekávané chybě
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Vis i bibliotek">
|
||||
<!ENTITY zotero.items.menu.attach.note "Tilføj note">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Vedhæft">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Vedhæft link til URI...">
|
||||
<!ENTITY zotero.items.menu.attach.file "Vedhæft en gemt kopi af ...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Gem kopi af filen...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Gem link til den aktuelle side">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Tag kopi af den aktuelle side">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Der er ingen mærker at vise">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Indlæser mærker...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Tilføj link til URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Link:">
|
||||
<!ENTITY zotero.attachLink.label.title "Titel:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Beskriv kort problemet:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Element
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=En handling i Zotero er ved at blive udført.
|
||||
general.operationInProgress.waitUntilFinished=Vent venligst til den er færdig.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Vis nuværende mappe
|
|||
app.standalone=Zotero Standalone
|
||||
app.firefox=Zotero til Firefox
|
||||
|
||||
startupError=Der opstod en fejl under opstarten af Zotero.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=Din Zotero-database er allerede i brug. Det er ikke muligt at lade flere kopier af Zotero bruge databasen samtidig.
|
||||
startupError.closeStandalone=Hvis Zotero Standalone er aktiv, så luk den ned og genstart Firefox.
|
||||
startupError.closeFirefox=Hvis Zotero er aktiv i Firefox, så luk Zotero ned der og genstart Zotero Standalone.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Skift til delt navnefelt
|
|||
pane.item.creator.moveToTop=Flyt øverst
|
||||
pane.item.creator.moveUp=Flyt op
|
||||
pane.item.creator.moveDown=Flyt ned
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Note uden titel
|
||||
pane.item.notes.delete.confirm=Er du sikker på, du vil slette denne note?
|
||||
pane.item.notes.count.zero=%S noter:
|
||||
pane.item.notes.count.singular=%S note:
|
||||
pane.item.notes.count.plural=%S noter:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Editing in separate window
|
||||
pane.item.attachments.rename.title=Ny titel:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Omdøb en tilhørende fil
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S relateret:
|
|||
pane.item.related.count.plural=%S relaterede:
|
||||
pane.item.parentItem=Overordnet element:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Redigér note
|
||||
|
||||
itemTypes.note=Note
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Vedhæftning
|
||||
itemTypes.book=Bog
|
||||
itemTypes.bookSection=Bidrag til bog
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=Fandt ingen PDF'er
|
||||
findPDF.noPDFFound=Fandt ingen PDF
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Fuldtekst
|
||||
attachment.acceptedVersion=Antaget version
|
||||
attachment.submittedVersion=Indsendt version
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Ændringsdato
|
|||
searchConditions.fulltextContent=Indhold i Vedhæftning
|
||||
searchConditions.programmingLanguage=Programmeringssprog
|
||||
searchConditions.fileTypeID=Filtype (vedhæftn.)
|
||||
searchConditions.annotation=Annotering
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indekseret
|
||||
fulltext.indexState.unavailable=Ukendt
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=Sat i kø
|
|||
|
||||
exportOptions.exportNotes=Eksportér noter
|
||||
exportOptions.exportFileData=Eksportér filer
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Anvend tidsskriftforkortelse
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 uden BOM)
|
||||
charset.autoDetect=(auto-genkend.)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Prepare Citations for Transfer
|
|||
integration.exportDocument.description1=Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
|
||||
integration.exportDocument.description2=You should make a backup of the document before proceeding.
|
||||
integration.importInstructions=The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Installér bibliografisk format
|
||||
styles.install.unexpectedError=Der opstod en uventet fejl under forsøget på at installere "%1$S"
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "In Bibliothek anzeigen">
|
||||
<!ENTITY zotero.items.menu.attach.note "Notiz hinzufügen">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Anhang hinzufügen">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Link zu URI hinzufügen...">
|
||||
<!ENTITY zotero.items.menu.attach.file "Gespeicherte Kopie der Datei anhängen...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Kopie einer Datei speichern...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Link zur aktuellen Seite speichern">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Schnappschuss von aktueller Seite machen">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Keine Tags vorhanden">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Lade Tags...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Link zu einer URI anhängen">
|
||||
<!ENTITY zotero.attachLink.label.link "Link:">
|
||||
<!ENTITY zotero.attachLink.label.title "Titel:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Beschreiben Sie kurz das Problem:
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Eintrag
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Zotero ist beschäftigt.
|
||||
general.operationInProgress.waitUntilFinished=Bitte warten Sie, bis der Vorgang abgeschlossen ist.
|
||||
|
@ -187,7 +188,7 @@ dataDir.migration.failure.full.showCurrentDirectoryAndQuit=Aktuelles Verzeichnis
|
|||
app.standalone=Zotero Standalone
|
||||
app.firefox=Zotero für Firefox
|
||||
|
||||
startupError=Es gab einen Fehler beim Start von Zotero.
|
||||
startupError=There was an error starting %S.
|
||||
startupError.databaseInUse=Ihre Zotero-Datenbank wird bereits benutzt. Sie können nur je eine Instanz von Zotero mit derselben Datenbank gleichzeitig geöffnet haben.
|
||||
startupError.closeStandalone=Wenn Sie Zotero Standalone geöffnet haben, bitte schließen Sie die Anwendung und starten Sie Firefox neu.
|
||||
startupError.closeFirefox=Wenn Firefox mit der Zotero-Erweiterung offen ist, schließen Sie bitte Firefox und starten Sie Zotero Standalone neu.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Zu zwei Feldern wechseln
|
|||
pane.item.creator.moveToTop=An den Anfang verschieben
|
||||
pane.item.creator.moveUp=Nach oben verschieben
|
||||
pane.item.creator.moveDown=Nach unten verschieben
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Notiz ohne Titel
|
||||
pane.item.notes.delete.confirm=Sind Sie sicher, dass Sie diese Notiz löschen möchten?
|
||||
pane.item.notes.count.zero=%S Notizen:
|
||||
pane.item.notes.count.singular=%S Notiz:
|
||||
pane.item.notes.count.plural=%S Notizen:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=In einem neuen Fenster bearbeiten
|
||||
pane.item.attachments.rename.title=Neuer Titel:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Zugehörige Datei umbenennen
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S verwandter Eintrag:
|
|||
pane.item.related.count.plural=%S verwandte Einträge:
|
||||
pane.item.parentItem=Übergeordneter Eintrag:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Notiz bearbeiten
|
||||
|
||||
itemTypes.note=Notiz
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Anhang
|
||||
itemTypes.book=Buch
|
||||
itemTypes.bookSection=Buchteil
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=Keine PDFs gefunden
|
||||
findPDF.noPDFFound=Kein PDF gefunden
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Volltext
|
||||
attachment.acceptedVersion=Akzeptierte Version
|
||||
attachment.submittedVersion=Eingereichte Version
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=verändert am
|
|||
searchConditions.fulltextContent=Inhalt des Anhangs
|
||||
searchConditions.programmingLanguage=Programmiersprache
|
||||
searchConditions.fileTypeID=Dateityp des Anhangs
|
||||
searchConditions.annotation=Anmerkung
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indiziert
|
||||
fulltext.indexState.unavailable=Unbekannt
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=in Warteschlange
|
|||
|
||||
exportOptions.exportNotes=Notizen exportieren
|
||||
exportOptions.exportFileData=Dateien exportieren
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Abgekürzte Zeitschriftentitel verwenden
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 ohne BOM)
|
||||
charset.autoDetect=(automatisch erkennen)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Zitationen zum Transfer vorbereiten
|
|||
integration.exportDocument.description1=Zotero wird die Zitationen in diesem Dokument in ein Format konvertieren, in dem Sie es in einer anderen von Zotero unterstützten Textverarbeitung öffnen können
|
||||
integration.exportDocument.description2=Sie sollten ein Backup des Dokuments erstellen, bevor Sie fortfahren.
|
||||
integration.importInstructions=Die Zotero Zitationen in diesem Dokument sind konvertiert und können in von Zotero unterstützten Textverarbeitungsprogrammen genutzt werden. Öffnen Sie das Dokument und drücken Sie Refresh im Zotero-Plugin, um es zu importieren.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Zitierstil installieren
|
||||
styles.install.unexpectedError=Bei der Installation von "%1$S" trat ein unerwarteter Fehler auf
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
<!ENTITY zotero.items.menu.showInLibrary "Εμφάνιση στην Βιβλιοθήκη">
|
||||
<!ENTITY zotero.items.menu.attach.note "Προσθήκη σημείωσης">
|
||||
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
|
||||
<!ENTITY zotero.items.menu.attach "Προσθήκη προσαρτήματος">
|
||||
<!ENTITY zotero.items.menu.attach.link.uri "Προσάρτηση Συνδέσμου προς το URI...">
|
||||
<!ENTITY zotero.items.menu.attach.file "Προσκόληση αποθηκευμένου αντιγράφου αρχείου...">
|
||||
|
@ -155,6 +156,8 @@
|
|||
<!ENTITY zotero.toolbar.attachment.add "Αποθήκευση αντιγράφου του αρχείου...">
|
||||
<!ENTITY zotero.toolbar.attachment.weblink "Αποθήκευση Συνδέσμου στην Τρέχουσα Σελίδα">
|
||||
<!ENTITY zotero.toolbar.attachment.snapshot "Λήψη Στιγμιότυπου της Τρέχουσας Σελίδας">
|
||||
<!ENTITY zotero.toolbar.context.item "Item">
|
||||
<!ENTITY zotero.toolbar.context.notes "Notes">
|
||||
|
||||
<!ENTITY zotero.tagSelector.noTagsToDisplay "Δεν υπάρχουν ετικέτες προς εμφάνιση">
|
||||
<!ENTITY zotero.tagSelector.loadingTags "Φόρτωση ετικετών...">
|
||||
|
@ -325,3 +328,8 @@
|
|||
<!ENTITY zotero.attachLink.title "Προσάρτηση συνδέσμου στο URI">
|
||||
<!ENTITY zotero.attachLink.label.link "Σύνδεσμος:">
|
||||
<!ENTITY zotero.attachLink.label.title "Τίτλος:">
|
||||
|
||||
<!ENTITY zotero.context.addChildNote "Add Item Note">
|
||||
<!ENTITY zotero.context.addChildNoteFromAnnotations "Add Item Note from Annotations">
|
||||
<!ENTITY zotero.context.addStandaloneNote "Add Standalone Note">
|
||||
<!ENTITY zotero.context.addStandaloneNoteFromAnnotations "Add Standalone Note from Annotations">
|
||||
|
|
|
@ -76,6 +76,7 @@ general.describeProblem=Περιγράψτε συνοπτικά το πρόβλ
|
|||
general.nMegabytes=%S MB
|
||||
general.item=Είδος
|
||||
general.pdf=PDF
|
||||
general.back=Back
|
||||
|
||||
general.operationInProgress=Αυτή τη στιγμή εκτελείται κάποια λειτουργία Zotero.
|
||||
general.operationInProgress.waitUntilFinished=Παρακαλώ περιμένετε έως ότου ολοκληρωθεί.
|
||||
|
@ -363,11 +364,10 @@ pane.item.switchFieldMode.two=Μετάβαση σε δύο πεδία
|
|||
pane.item.creator.moveToTop=Μετακίνηση προς τα επάνω
|
||||
pane.item.creator.moveUp=Μετακίνηση Επάνω
|
||||
pane.item.creator.moveDown=Μετακίνηση Κάτω
|
||||
pane.item.notes.allNotes=All Notes
|
||||
pane.item.notes.untitled=Σημείωση χωρίς τίτλο
|
||||
pane.item.notes.delete.confirm=Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη σημείωση;
|
||||
pane.item.notes.count.zero=%S σημειώσεις:
|
||||
pane.item.notes.count.singular=%S σημείωση:
|
||||
pane.item.notes.count.plural=%S σημειώσεις:
|
||||
pane.item.notes.count=%1$S note;%1$S notes
|
||||
pane.item.notes.editingInWindow=Επεξεργασία σε ξεχωριστό παράθυρο
|
||||
pane.item.attachments.rename.title=Νέος τίτλος:
|
||||
pane.item.attachments.rename.renameAssociatedFile=Μετονομασία συσχετισμένου αρχείου
|
||||
|
@ -398,9 +398,15 @@ pane.item.related.count.singular=%S σχετικό:
|
|||
pane.item.related.count.plural=%S σχετικό:
|
||||
pane.item.parentItem=Γονικό Στοιχείο:
|
||||
|
||||
pane.context.noParent=No parent item
|
||||
pane.context.itemNotes=Item Notes
|
||||
pane.context.allNotes=All Notes
|
||||
pane.context.noNotes=No notes
|
||||
|
||||
noteEditor.editNote=Επεξεργασία Σημείωσης
|
||||
|
||||
itemTypes.note=Σημείωση
|
||||
itemTypes.annotation=Annotation
|
||||
itemTypes.attachment=Συνημμένο
|
||||
itemTypes.book=Βιβλίο
|
||||
itemTypes.bookSection=Ενότητα Βιβλίου
|
||||
|
@ -615,6 +621,8 @@ findPDF.pdfWithMethod=PDF (%S)
|
|||
findPDF.noPDFsFound=Δεν βρέθηκαν αρχεία PDF
|
||||
findPDF.noPDFFound=Δεν βρέθηκε PDF
|
||||
|
||||
note.annotationsWithDate=Annotations (%S)
|
||||
|
||||
attachment.fullText=Πλήρες Κείμενο
|
||||
attachment.acceptedVersion=Αποδεκτή Έκδοση
|
||||
attachment.submittedVersion=Υποβληθείσα Έκδοση
|
||||
|
@ -788,7 +796,8 @@ searchConditions.dateModified=Date Modified
|
|||
searchConditions.fulltextContent=Attachment Content
|
||||
searchConditions.programmingLanguage=Programming Language
|
||||
searchConditions.fileTypeID=Attachment File Type
|
||||
searchConditions.annotation=Annotation
|
||||
searchConditions.annotationText=Annotation Text
|
||||
searchConditions.annotationComment=Annotation Comment
|
||||
|
||||
fulltext.indexState.indexed=Indexed
|
||||
fulltext.indexState.unavailable=Unknown
|
||||
|
@ -797,6 +806,7 @@ fulltext.indexState.queued=Queued
|
|||
|
||||
exportOptions.exportNotes=Export Notes
|
||||
exportOptions.exportFileData=Export Files
|
||||
exportOptions.includeAnnotations=Include Annotations
|
||||
exportOptions.useJournalAbbreviation=Use Journal Abbreviation
|
||||
charset.UTF8withoutBOM=Unicode (UTF-8 without BOM)
|
||||
charset.autoDetect=(auto detect)
|
||||
|
@ -930,6 +940,7 @@ integration.exportDocument.title=Προετοιμάστε τις παραπομ
|
|||
integration.exportDocument.description1=Το Zotero θα μετατρέψει τις παραπομπές στο έγγραφο σε μορφή που μπορεί να μεταφερθεί με ασφάλεια σε άλλο υποστηριζόμενο επεξεργαστή κειμένου.
|
||||
integration.exportDocument.description2=Πριν προχωρήσετε, θα πρέπει να δημιουργήσετε αντίγραφο ασφαλείας του εγγράφου.
|
||||
integration.importInstructions=Οι αναφορές Zotero σε αυτό το έγγραφο έχουν μετατραπεί σε μορφή που μπορεί να μεταφερθεί με ασφάλεια μεταξύ επεξεργαστών κειμένου. Ανοίξτε αυτό το έγγραφο σε υποστηριζόμενο επεξεργαστή κειμένου και πατήστε Ανανέωση -Refresh- στο πρόσθετο Zotero για να συνεχίσετε να εργάζεστε με τις παραπομπές.
|
||||
integration.upgradeTemplate=The %S plugin for %S is outdated. Reinstall the plugin from Preferences → Cite → Word Processors.
|
||||
|
||||
styles.install.title=Εγκατάσταση Στυλ
|
||||
styles.install.unexpectedError=Παρουσιάστηκε μη αναμενόμενο σφάλμα κατά την εγκατάσταση του "%1$S"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue