Merge branch '3.1'
This commit is contained in:
commit
0469441318
55 changed files with 3278 additions and 1383 deletions
136
chrome/content/zotero/bindings/customcolorpicker.xml
Normal file
136
chrome/content/zotero/bindings/customcolorpicker.xml
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
An extension of the Mozilla colorpicker that allows for a custom set of colors
|
||||
-->
|
||||
<bindings id="colorpickerBindings"
|
||||
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="custom-colorpicker" extends="chrome://global/content/bindings/colorpicker.xml#colorpicker">
|
||||
<resources>
|
||||
<stylesheet src="chrome://zotero/skin/bindings/customcolorpicker.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<vbox anonid="tiles" flex="1" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<hbox>
|
||||
<spacer class="colorpickertile" style="background-color: #000000" color="#000000"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor><![CDATA[
|
||||
this.initialize();
|
||||
|
||||
this._colors = this.getAttribute('colors');
|
||||
if (this._colors) {
|
||||
this._cols = this.getAttribute('cols');
|
||||
}
|
||||
this.redraw();
|
||||
]]></constructor>
|
||||
|
||||
<!-- Defaults from the Mozilla colorpicker -->
|
||||
<field name="_defaultColors">
|
||||
[
|
||||
'L#FFFFFF','L#FFCCCC','L#FFCC99','L#FFFF99','L#FFFFCC','L#99FF99','L#99FFFF','L#CCFFFF','L#CCCCFF','L#FFCCFF',
|
||||
'#CCCCCC','#FF6666','#FF9966','L#FFFF66','L#FFFF33','L#66FF99','L#33FFFF','L#66FFFF','#9999FF','#FF99FF',
|
||||
'#C0C0C0','#FF0000','#FF9900','#FFCC66','L#FFFF00','L#33FF33','#66CCCC','#33CCFF','#6666CC','#CC66CC',
|
||||
'#999999','#CC0000','#FF6600','#FFCC33','#FFCC00','#33CC00','#00CCCC','#3366FF','#6633FF','#CC33CC',
|
||||
'#666666','#990000','#CC6600','#CC9933','#999900','#009900','#339999','#3333FF','#6600CC','#993399',
|
||||
'#333333','#660000','#993300','#996633','#666600','#006600','#336666','#000099','#333399','#663366',
|
||||
'#000000','#330000','#663300','#663333','#333300','#003300','#003333','#000066','#330099','#330033'
|
||||
]
|
||||
</field>
|
||||
<field name="_defaultCols">10</field>
|
||||
|
||||
<property name="colors" onget="return this._colors ? this._colors : []">
|
||||
<setter><![CDATA[
|
||||
if (typeof val == 'string') {
|
||||
val = val ? val.split(',') : null;
|
||||
}
|
||||
this._colors = val;
|
||||
this.redraw();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="cols" onget="return this.getAttribute('cols')">
|
||||
<setter><![CDATA[
|
||||
this.setAttribute('cols', val);
|
||||
this.redraw();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<method name="redraw">
|
||||
<body><![CDATA[
|
||||
//Zotero.debug("Redrawing color picker");
|
||||
|
||||
var tiles = document.getAnonymousNodes(this)[0];
|
||||
|
||||
var cols = this.getAttribute('cols') || this._defaultCols;
|
||||
var colors = this._colors.concat() || this._defaultColors.concat();
|
||||
|
||||
while (tiles.hasChildNodes()) {
|
||||
tiles.removeChild(tiles.firstChild);
|
||||
}
|
||||
|
||||
var rows = Math.ceil(colors.length / cols);
|
||||
|
||||
var tileWidth = this.getAttribute('tileWidth');
|
||||
var tileHeight = this.getAttribute('tileHeight');
|
||||
|
||||
for (let i=0; i<rows; i++) {
|
||||
var hbox = document.createElement('hbox');
|
||||
for (let j=0; j<cols; j++) {
|
||||
let color = colors.shift();
|
||||
if (!color) {
|
||||
break;
|
||||
}
|
||||
let light = color.charAt(0) == 'L';
|
||||
color = light ? color.substr(1) : color;
|
||||
|
||||
var spacer = document.createElement('spacer');
|
||||
spacer.className = 'colorpickertile' + (light ? ' cp-light' : '');
|
||||
spacer.setAttribute('style', 'background-color: ' + color);
|
||||
spacer.setAttribute('color', color);
|
||||
if (tileWidth) {
|
||||
spacer.width = tileWidth;
|
||||
}
|
||||
if (tileHeight) {
|
||||
spacer.height = tileHeight;
|
||||
}
|
||||
hbox.appendChild(spacer);
|
||||
}
|
||||
tiles.appendChild(hbox);
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<!-- The content of the Mozilla colorpicker-button, but with a customcolorpicker
|
||||
with some extra inherited attributes instead -->
|
||||
<binding id="custom-colorpicker-button" display="xul:menu"
|
||||
extends="chrome://global/content/bindings/colorpicker.xml#colorpicker-button">
|
||||
<resources>
|
||||
<stylesheet src="chrome://zotero/skin/bindings/customcolorpicker.css"/>
|
||||
</resources>
|
||||
<content>
|
||||
<xul:hbox class="colorpicker-button-colorbox" anonid="colorbox" flex="1" xbl:inherits="disabled"/>
|
||||
|
||||
<xul:panel class="colorpicker-button-menupopup"
|
||||
anonid="colorpopup" noautofocus="true" level="top"
|
||||
onmousedown="event.stopPropagation()"
|
||||
onpopupshowing="this._colorPicker.onPopupShowing()"
|
||||
onpopuphiding="this._colorPicker.onPopupHiding()"
|
||||
onselect="this._colorPicker.pickerChange()">
|
||||
<xul:customcolorpicker xbl:inherits="palettename,disabled,cols,columns,tileWidth,tileHeight" allowevents="true" anonid="colorpicker"/>
|
||||
</xul:panel>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<property name="colors" onget="return this.mPicker.colors" onset="this.mPicker.colors = val"/>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -37,6 +37,7 @@
|
|||
<implementation>
|
||||
<field name="clickHandler"/>
|
||||
<field name="_tagColors"/>
|
||||
<field name="_notifierID"/>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
<field name="_mode">"view"</field>
|
||||
|
@ -109,38 +110,64 @@
|
|||
if (this.hasAttribute('mode')) {
|
||||
this.mode = this.getAttribute('mode');
|
||||
}
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['setting']);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
|
||||
<method name="notify">
|
||||
<parameter name="event"/>
|
||||
<parameter name="type"/>
|
||||
<parameter name="ids"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
||||
this.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="reload">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var self = this;
|
||||
Zotero.debug('Reloading tags');
|
||||
var addButton = self.id('addButton');
|
||||
addButton.hidden = !self.editable;
|
||||
|
||||
var addButton = this.id('addButton');
|
||||
addButton.hidden = !this.editable;
|
||||
|
||||
var rows = this.id('tagRows');
|
||||
while(rows.hasChildNodes())
|
||||
rows.removeChild(rows.firstChild);
|
||||
|
||||
var tags = this.item.getTags();
|
||||
this._tagColors = Zotero.Tags.getColors();
|
||||
|
||||
if(tags)
|
||||
{
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
this.addDynamicRow(tags[i], i+1);
|
||||
return Zotero.Tags.getColors(self.item.libraryIDInt)
|
||||
.then(function (colors) {
|
||||
self._tagColors = colors;
|
||||
|
||||
var rows = self.id('tagRows');
|
||||
while(rows.hasChildNodes()) {
|
||||
rows.removeChild(rows.firstChild);
|
||||
}
|
||||
var tags = self.item.getTags();
|
||||
if (tags) {
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
self.addDynamicRow(tags[i], i+1);
|
||||
}
|
||||
|
||||
//self.fixPopup();
|
||||
}
|
||||
this.updateCount(tags.length);
|
||||
|
||||
//this.fixPopup();
|
||||
|
||||
return tags.length;
|
||||
}
|
||||
|
||||
this.updateCount(0);
|
||||
return 0;
|
||||
self.updateCount(0);
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -264,8 +291,13 @@
|
|||
}
|
||||
|
||||
// Tag color
|
||||
if (color = this._tagColors[valueText]) {
|
||||
valueElement.setAttribute('style', 'color:' + this._tagColors[valueText]);
|
||||
let color = this._tagColors[valueText];
|
||||
if (color) {
|
||||
valueElement.setAttribute(
|
||||
'style',
|
||||
'color:' + this._tagColors[valueText].color + '; '
|
||||
+ 'font-weight: bold'
|
||||
);
|
||||
}
|
||||
|
||||
return valueElement;
|
||||
|
|
|
@ -27,8 +27,7 @@
|
|||
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<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">
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="tag-selector">
|
||||
<resources>
|
||||
|
@ -77,6 +76,9 @@
|
|||
<property name="libraryID" onget="return this._libraryID">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
// TEMP: libraryIDInt
|
||||
val = val ? parseInt(val) : 0;
|
||||
|
||||
if (this._libraryID != val) {
|
||||
this._dirty = true;
|
||||
}
|
||||
|
@ -169,7 +171,7 @@
|
|||
<![CDATA[
|
||||
this._initialized = true;
|
||||
this.selection = {};
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag']);
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting']);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -215,211 +217,211 @@
|
|||
var empty = true;
|
||||
var tagsToggleBox = this.id('tags-toggle');
|
||||
|
||||
|
||||
if (fetch || this._dirty) {
|
||||
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
|
||||
|
||||
// Remove children
|
||||
tagsToggleBox.textContent = "";
|
||||
|
||||
var me = this,
|
||||
onTagClick = function(event) { me.handleTagClick(event, this) },
|
||||
lastTag;
|
||||
for (var tagID in this._tags) {
|
||||
var tagInfo = this._tags[tagID],
|
||||
tagName = tagInfo.name;
|
||||
// If the last tag was the same, add this tagID and tagType to it
|
||||
if(lastTag && lastTag.value === tagName) {
|
||||
lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
|
||||
lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagName.type);
|
||||
continue;
|
||||
}
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(this.libraryID)
|
||||
.then(function (tagColors) {
|
||||
if (fetch || self._dirty) {
|
||||
self._tags = Zotero.Tags.getAll(self._types, self.libraryID);
|
||||
|
||||
lastTag = document.createElement('label');
|
||||
lastTag.addEventListener('click', onTagClick, false);
|
||||
lastTag.className = 'zotero-clicky';
|
||||
// Remove children
|
||||
tagsToggleBox.textContent = "";
|
||||
|
||||
|
||||
lastTag.setAttribute('value', tagName);
|
||||
lastTag.setAttribute('tagID', tagID);
|
||||
lastTag.setAttribute('tagType', tagInfo.type);
|
||||
if (this.editable) {
|
||||
lastTag.setAttribute('context', 'tag-menu');
|
||||
lastTag.addEventListener('dragover', this.dragObserver.onDragOver, false);
|
||||
lastTag.addEventListener('dragexit', this.dragObserver.onDragExit, false);
|
||||
lastTag.addEventListener('drop', this.dragObserver.onDrop, true);
|
||||
}
|
||||
tagsToggleBox.appendChild(lastTag);
|
||||
}
|
||||
this._dirty = false;
|
||||
}
|
||||
|
||||
// Set attributes
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
var tagColors = Zotero.Tags.getColors();
|
||||
for (var i=0; i<labels.length; i++){
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
// Restore selection
|
||||
if (this.selection[labels[i].value]){
|
||||
labels[i].setAttribute('selected', 'true');
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('selected', 'false');
|
||||
}
|
||||
|
||||
// Check tags against filter
|
||||
if (this._hasFilter) {
|
||||
var inFilter = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (this._filter[tagID]) {
|
||||
inFilter = true;
|
||||
break;
|
||||
var lastTag;
|
||||
for (let tagID in self._tags) {
|
||||
let tagButton = self._makeClickableTag(tagID, lastTag, self.editable);
|
||||
if (tagButton) {
|
||||
tagButton.addEventListener('click', function(event) {
|
||||
self.handleTagClick(event, this);
|
||||
});
|
||||
if (self.editable) {
|
||||
tagButton.addEventListener('dragover', self.dragObserver.onDragOver);
|
||||
tagButton.addEventListener('dragexit', self.dragObserver.onDragExit);
|
||||
tagButton.addEventListener('drop', self.dragObserver.onDrop, true);
|
||||
}
|
||||
lastTag = tagButton;
|
||||
tagsToggleBox.appendChild(tagButton);
|
||||
}
|
||||
}
|
||||
self._dirty = false;
|
||||
}
|
||||
|
||||
// Check tags against scope
|
||||
if (this._hasScope) {
|
||||
var inScope = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (this._scope[tagID]) {
|
||||
inScope = true;
|
||||
break;
|
||||
// Set attributes
|
||||
var colorTags = {};
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
for (let i=0; i<labels.length; i++) {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
// Restore selection
|
||||
if (self.selection[labels[i].value]){
|
||||
labels[i].setAttribute('selected', 'true');
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('selected', 'false');
|
||||
}
|
||||
|
||||
// Check tags against filter
|
||||
if (self._hasFilter) {
|
||||
var inFilter = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (self._filter[tagID]) {
|
||||
inFilter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not in filter, hide
|
||||
if (this._hasFilter && !inFilter) {
|
||||
//Zotero.debug(1);
|
||||
labels[i].setAttribute('hidden', true);
|
||||
}
|
||||
else if (this.filterToScope) {
|
||||
if (this._hasScope && inScope) {
|
||||
//Zotero.debug(2);
|
||||
labels[i].setAttribute('inScope', true);
|
||||
|
||||
// Check tags against scope
|
||||
if (self._hasScope) {
|
||||
var inScope = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (self._scope[tagID]) {
|
||||
inScope = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not in filter, hide
|
||||
if (self._hasFilter && !inFilter) {
|
||||
labels[i].setAttribute('hidden', true);
|
||||
}
|
||||
else if (self.filterToScope) {
|
||||
if (self._hasScope && inScope) {
|
||||
labels[i].setAttribute('inScope', true);
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('hidden', true);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
}
|
||||
}
|
||||
// Display all
|
||||
else {
|
||||
if (self._hasScope && inScope) {
|
||||
labels[i].setAttribute('inScope', true);
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('inScope', false);
|
||||
|
||||
// If out of scope, make sure it's not selected (otherwise a tag
|
||||
// stays selected after removing an item with that tag from the
|
||||
// current collection)
|
||||
if (self.selection[labels[i].value]) {
|
||||
labels[i].setAttribute('selected', false);
|
||||
delete self.selection[labels[i].value];
|
||||
var doCommand = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
|
||||
let colorData = tagColors[labels[i].value];
|
||||
if (colorData) {
|
||||
labels[i].setAttribute(
|
||||
'style', 'color:' + colorData.color + '; ' + 'font-weight: bold'
|
||||
);
|
||||
colorTags[colorData.position] = tagsToggleBox.removeChild(labels[i]);
|
||||
// The HTMLCollection returned by getElementsByTagName() is live,
|
||||
// so since we removed something we need to decrement the counter
|
||||
i--;
|
||||
}
|
||||
else {
|
||||
//Zotero.debug(3);
|
||||
labels[i].setAttribute('hidden', true);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
labels[i].removeAttribute('style');
|
||||
}
|
||||
}
|
||||
// Display all
|
||||
else {
|
||||
if (this._hasScope && inScope) {
|
||||
//Zotero.debug(4);
|
||||
labels[i].setAttribute('inScope', true);
|
||||
|
||||
// Add color tags to beginning in order
|
||||
var positions = Object.keys(colorTags);
|
||||
positions.sort();
|
||||
for (var i=positions.length-1; i>=0; i--) {
|
||||
tagsToggleBox.insertBefore(colorTags[positions[i]], tagsToggleBox.firstChild);
|
||||
}
|
||||
|
||||
//start tag cloud code
|
||||
|
||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||
|
||||
if(tagCloud) {
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
|
||||
//loop through displayed labels and find number of linked items
|
||||
var numlinked= [];
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = self._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlinked.push(parseInt(linked.length));
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Zotero.debug(5);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
|
||||
// If out of scope, make sure it's not selected (otherwise a tag
|
||||
// stays selected after removing an item with that tag from the
|
||||
// current collection)
|
||||
if (this.selection[labels[i].value]) {
|
||||
labels[i].setAttribute('selected', false);
|
||||
delete this.selection[labels[i].value];
|
||||
var doCommand = true;
|
||||
}
|
||||
|
||||
//
|
||||
numlinked.sort();
|
||||
|
||||
//Get number of attached items from tag with fewest items
|
||||
var min = numlinked[0];
|
||||
|
||||
//Get number of attached items from tag with most items
|
||||
var max = numlinked.pop();
|
||||
numlinked.push(max);
|
||||
|
||||
//Create array of possible tag text sizes
|
||||
var sizes = ["11", "12", "13", "14", "15", "16", "17", "18", "19","20","21","22"];
|
||||
|
||||
//Number of possible tag sizes
|
||||
var categories = sizes.length;
|
||||
|
||||
//inc is the size of each size category of tags, in terms of the number of attached items
|
||||
var inc = Math.ceil((max-min)/categories);
|
||||
if(inc<1) {
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
|
||||
if (color = tagColors[labels[i].value]) {
|
||||
labels[i].setAttribute('style', 'color:' + color);
|
||||
}
|
||||
else {
|
||||
labels[i].removeAttribute('style');
|
||||
}
|
||||
}
|
||||
|
||||
//start tag cloud code
|
||||
|
||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||
|
||||
if(tagCloud) {
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
|
||||
//loop through displayed labels and find number of linked items
|
||||
var numlinked= [];
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = this._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlinked.push(parseInt(linked.length));
|
||||
}
|
||||
}
|
||||
//
|
||||
numlinked.sort();
|
||||
|
||||
//Get number of attached items from tag with fewest items
|
||||
var min = numlinked[0];
|
||||
|
||||
//Get number of attached items from tag with most items
|
||||
var max = numlinked.pop();
|
||||
numlinked.push(max);
|
||||
|
||||
//Create array of possible tag text sizes
|
||||
var sizes = ["11", "12", "13", "14", "15", "16", "17", "18", "19","20","21","22"];
|
||||
|
||||
//Number of possible tag sizes
|
||||
var categories = sizes.length;
|
||||
|
||||
//inc is the size of each size category of tags, in terms of the number of attached items
|
||||
var inc = Math.ceil((max-min)/categories);
|
||||
if(inc<1) {
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = this._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlink = linked.length;
|
||||
|
||||
//range is the difference between how many items this tag has and how many items the smallest tag has
|
||||
var range=(numlink-min);
|
||||
|
||||
//Divide the range by the size of the categories
|
||||
s=range/inc;
|
||||
|
||||
if(s==categories) {
|
||||
s=categories-1;
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = self._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlink = linked.length;
|
||||
|
||||
//range is the difference between how many items this tag has and how many items the smallest tag has
|
||||
var range=(numlink-min);
|
||||
|
||||
//Divide the range by the size of the categories
|
||||
s=range/inc;
|
||||
|
||||
if(s==categories) {
|
||||
s=categories-1;
|
||||
}
|
||||
var stylestr = 'font-size:'+sizes[s]+'px;';
|
||||
labels[i].setAttribute('style',stylestr);
|
||||
}
|
||||
var stylestr = 'font-size:'+sizes[s]+'px;';
|
||||
labels[i].setAttribute('style',stylestr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//end tag cloud code
|
||||
|
||||
|
||||
this.updateNumSelected();
|
||||
this._empty = empty;
|
||||
this.id('tags-toggle').setAttribute('collapsed', empty);
|
||||
this.id('no-tags-box').setAttribute('collapsed', !empty);
|
||||
|
||||
if (doCommand) {
|
||||
Zotero.debug('A selected tag went out of scope -- deselecting');
|
||||
this.doCommand();
|
||||
}
|
||||
|
||||
//end tag cloud code
|
||||
|
||||
|
||||
self.updateNumSelected();
|
||||
self._empty = empty;
|
||||
self.id('tags-toggle').setAttribute('collapsed', empty);
|
||||
self.id('no-tags-box').setAttribute('collapsed', !empty);
|
||||
|
||||
if (doCommand) {
|
||||
Zotero.debug('A selected tag went out of scope -- deselecting');
|
||||
self.doCommand();
|
||||
}
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -437,6 +439,7 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="updateNumSelected">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -466,6 +469,13 @@
|
|||
<parameter name="ids"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
|
||||
this.refresh(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var itemGroup = ZoteroPane_Local.getItemGroup();
|
||||
|
||||
// Ignore anything other than deletes in duplicates view
|
||||
|
@ -663,16 +673,20 @@
|
|||
delete this.selection[oldName];
|
||||
}
|
||||
|
||||
// TODO: redo transaction for async DB
|
||||
var promises = [];
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
for (var i=0; i<tagIDs.length; i++) {
|
||||
Zotero.Tags.rename(tagIDs[i], newName.value);
|
||||
promises.push(Zotero.Tags.rename(tagIDs[i], newName.value));
|
||||
}
|
||||
|
||||
if (wasSelected) {
|
||||
this.selection[newName.value] = true;
|
||||
}
|
||||
Zotero.DB.commitTransaction();
|
||||
Q.all(promises)
|
||||
.done();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
|
@ -719,43 +733,84 @@
|
|||
<![CDATA[
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = Zotero.Tags.getName(tagIDs[0]);
|
||||
return Zotero.Tags.getColor(name);
|
||||
return Zotero.Tags.getColor(this.libraryID, name)
|
||||
.then(function (colorData) {
|
||||
return colorData ? colorData.color : '#000000';
|
||||
});
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="setColor">
|
||||
<method name="_makeClickableTag">
|
||||
<parameter name="tagID"/>
|
||||
<parameter name="lastTag"/>
|
||||
<parameter name="editable"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var tagInfo = this._tags[tagID], tagName = tagInfo.name;
|
||||
// If the last tag was the same, add this tagID and tagType to it
|
||||
if(lastTag && lastTag.value === tagName) {
|
||||
lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
|
||||
lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagInfo.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.className = 'zotero-clicky';
|
||||
|
||||
label.setAttribute('value', tagName);
|
||||
label.setAttribute('tagID', tagID);
|
||||
label.setAttribute('tagType', tagInfo.type);
|
||||
if (editable) {
|
||||
label.setAttribute('context', 'tag-menu');
|
||||
}
|
||||
return label;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="_openColorPickerWindow">
|
||||
<parameter name="tagIDs"/>
|
||||
<parameter name="color"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = Zotero.Tags.getName(tagIDs[0]);
|
||||
Zotero.Tags.setColor(name, color);
|
||||
|
||||
// Iterate through all windows, updating tag selector and tags box
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var enumerator = wm.getEnumerator("navigator:browser");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
if (!win.ZoteroPane) continue;
|
||||
|
||||
var tagSelector = win.ZoteroPane.document.getElementById('zotero-tag-selector');
|
||||
var itemPane = win.ZoteroPane.document.getElementById('zotero-view-item');
|
||||
|
||||
tagSelector.refresh();
|
||||
|
||||
if (itemPane.selectedPanel.firstChild.getAttribute('id') == 'zotero-editpane-tags') {
|
||||
var tagsBox = win.ZoteroPane.document.getElementById('zotero-editpane-tags');
|
||||
tagsBox.reload();
|
||||
var io = {
|
||||
libraryID: this.libraryID,
|
||||
name: Zotero.Tags.getName(tagIDs[0])
|
||||
};
|
||||
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(this.libraryID)
|
||||
.then(function (tagColors) {
|
||||
if (Object.keys(tagColors).length >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors[io.name]) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null, "", Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.openDialog(
|
||||
'chrome://zotero/content/tagColorChooser.xul',
|
||||
"zotero-tagSelector-colorChooser",
|
||||
"chrome,modal,centerscreen", io
|
||||
);
|
||||
|
||||
// Dialog cancel
|
||||
if (typeof io.color == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
return Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="focusTextbox">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -854,48 +909,41 @@
|
|||
</implementation>
|
||||
|
||||
<content>
|
||||
<xul:groupbox flex="1">
|
||||
<xul:menupopup id="tag-menu">
|
||||
<xul:menuitem label="&zotero.tagSelector.renameTag;" oncommand="document.getBindingParent(this).rename(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<xul:menuitem label="&zotero.tagSelector.deleteTag;" oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<!-- TODO: localized -->
|
||||
<!--
|
||||
<xul:menu label="Assign Color">
|
||||
<xul:menupopup onpopupshowing="var color = document.getBindingParent(this).getColor(document.popupNode.getAttribute('tagID')); this.children[0].color = color">
|
||||
<xul:colorpicker onclick="document.getBindingParent(this).setColor(document.popupNode.getAttribute('tagID'), event.originalTarget.getAttribute('color')); document.getBindingParent(this).id('tag-menu').hidePopup(); event.stopPropagation()"/>
|
||||
</xul:menupopup>
|
||||
</xul:menu>
|
||||
-->
|
||||
</xul:menupopup>
|
||||
<groupbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1">
|
||||
<menupopup id="tag-menu">
|
||||
<menuitem label="&zotero.tagSelector.assignColor;" oncommand="_openColorPickerWindow(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<menuitem label="&zotero.tagSelector.renameTag;" oncommand="document.getBindingParent(this).rename(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<menuitem label="&zotero.tagSelector.deleteTag;" oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
</menupopup>
|
||||
|
||||
<xul:vbox id="no-tags-box" align="center" pack="center" flex="1">
|
||||
<xul:label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||
</xul:vbox>
|
||||
<vbox id="no-tags-box" align="center" pack="center" flex="1">
|
||||
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||
</vbox>
|
||||
|
||||
<xul:vbox id="tags-toggle" flex="1"/>
|
||||
<vbox id="tags-toggle" flex="1"/>
|
||||
|
||||
<xul:vbox id="tag-controls">
|
||||
<xul:hbox>
|
||||
<vbox id="tag-controls">
|
||||
<hbox>
|
||||
<!-- TODO: &zotero.tagSelector.filter; is now unused -->
|
||||
<xul:textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||
<textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||
oncommand="document.getBindingParent(this).handleKeyPress(); event.stopPropagation()"
|
||||
onkeypress="if (event.keyCode == event.DOM_VK_ESCAPE) { document.getBindingParent(this).handleKeyPress(true); }"/>
|
||||
<xul:toolbarbutton id="view-settings-menu" tooltiptext="&zotero.toolbar.actions.label;"
|
||||
<toolbarbutton id="view-settings-menu" tooltiptext="&zotero.toolbar.actions.label;"
|
||||
image="chrome://zotero/skin/tag-selector-menu.png" type="menu">
|
||||
<xul:menupopup id="view-settings-popup">
|
||||
<xul:menuitem id="num-selected" disabled="true"/>
|
||||
<xul:menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||
<menupopup id="view-settings-popup">
|
||||
<menuitem id="num-selected" disabled="true"/>
|
||||
<menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||
oncommand="document.getBindingParent(this).clearAll(); event.stopPropagation();"/>
|
||||
<xul:menuseparator/>
|
||||
<xul:menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" autocheck="true" type="checkbox"
|
||||
<menuseparator/>
|
||||
<menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" autocheck="true" type="checkbox"
|
||||
oncommand="var ts = document.getBindingParent(this); ts._dirty = true; ts.setAttribute('showAutomatic', this.getAttribute('checked') == 'true')"/>
|
||||
<xul:menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" autocheck="true" type="checkbox"
|
||||
<menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" autocheck="true" type="checkbox"
|
||||
oncommand="document.getBindingParent(this).filterToScope = !(this.getAttribute('checked') == 'true'); event.stopPropagation();"/>
|
||||
</xul:menupopup>
|
||||
</xul:toolbarbutton>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</xul:groupbox>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
|
@ -46,6 +46,7 @@ var Zotero_Browser = new function() {
|
|||
this.toggleCollapsed = toggleCollapsed;
|
||||
this.chromeLoad = chromeLoad;
|
||||
this.contentLoad = contentLoad;
|
||||
this.itemUpdated = itemUpdated;
|
||||
this.contentHide = contentHide;
|
||||
this.tabClose = tabClose;
|
||||
this.resize = resize;
|
||||
|
@ -306,7 +307,7 @@ var Zotero_Browser = new function() {
|
|||
if(isHTML) {
|
||||
var contentWin = doc.defaultView;
|
||||
if(!contentWin.haveZoteroEventListener) {
|
||||
contentWin.addEventListener("ZoteroItemUpdated", itemUpdated, false);
|
||||
contentWin.addEventListener("ZoteroItemUpdated", function(event) { itemUpdated(event.originalTarget) }, false);
|
||||
contentWin.haveZoteroEventListener = true;
|
||||
}
|
||||
}
|
||||
|
@ -342,14 +343,13 @@ var Zotero_Browser = new function() {
|
|||
/**
|
||||
* Called when item should be updated due to a DOM event
|
||||
*/
|
||||
function itemUpdated(event) {
|
||||
function itemUpdated(doc) {
|
||||
try {
|
||||
var doc = event.originalTarget;
|
||||
var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc);
|
||||
var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc);
|
||||
var tab = _getTabObject(browser);
|
||||
if(doc == tab.page.document || doc == rootDoc) tab.clear();
|
||||
tab.detectTranslators(rootDoc, doc);
|
||||
var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc);
|
||||
var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc);
|
||||
var tab = _getTabObject(browser);
|
||||
if(doc == tab.page.document || doc == rootDoc) tab.clear();
|
||||
tab.detectTranslators(rootDoc, doc);
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
}
|
||||
|
@ -694,6 +694,7 @@ Zotero_Browser.Tab.prototype.detectTranslators = function(rootDoc, doc) {
|
|||
var translate = new Zotero.Translate.Web();
|
||||
translate.setDocument(doc);
|
||||
translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
|
||||
translate.setHandler("pageModified", function(translate, doc) { Zotero_Browser.itemUpdated(doc) });
|
||||
translate.getTranslators(true);
|
||||
} else if(doc.documentURI.substr(0, 7) == "file://") {
|
||||
this._attemptLocalFileImport(doc);
|
||||
|
|
|
@ -371,38 +371,84 @@ function verifyStorageServer() {
|
|||
abortButton.hidden = false;
|
||||
progressMeter.hidden = false;
|
||||
|
||||
var requestHolder = Zotero.Sync.Storage.WebDAV.checkServer(function (uri, status, callback) {
|
||||
var request = null;
|
||||
var onDone = false;
|
||||
|
||||
Zotero.Sync.Storage.WebDAV.checkServer()
|
||||
// Get the XMLHttpRequest for possible cancelling
|
||||
.progress(function (obj) {
|
||||
request = obj.xmlhttp;
|
||||
})
|
||||
.finally(function () {
|
||||
verifyButton.hidden = false;
|
||||
abortButton.hidden = true;
|
||||
progressMeter.hidden = true;
|
||||
|
||||
})
|
||||
.spread(function (uri, status) {
|
||||
switch (status) {
|
||||
case Zotero.Sync.Storage.ERROR_NO_URL:
|
||||
setTimeout(function () {
|
||||
onDone = function () {
|
||||
urlField.focus();
|
||||
}, 1);
|
||||
};
|
||||
break;
|
||||
|
||||
case Zotero.Sync.Storage.ERROR_NO_USERNAME:
|
||||
setTimeout(function () {
|
||||
onDone = function () {
|
||||
usernameField.focus();
|
||||
}, 1);
|
||||
};
|
||||
break;
|
||||
|
||||
case Zotero.Sync.Storage.ERROR_NO_PASSWORD:
|
||||
setTimeout(function () {
|
||||
case Zotero.Sync.Storage.ERROR_AUTH_FAILED:
|
||||
onDone = function () {
|
||||
passwordField.focus();
|
||||
}, 1);
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.WebDAV.checkServerCallback(uri, status, window);
|
||||
});
|
||||
return Zotero.Sync.Storage.WebDAV.checkServerCallback(uri, status, window);
|
||||
})
|
||||
.then(function (success) {
|
||||
if (success) {
|
||||
Zotero.debug("WebDAV verification succeeded");
|
||||
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
promptService.alert(
|
||||
window,
|
||||
Zotero.getString('sync.storage.serverConfigurationVerified'),
|
||||
Zotero.getString('sync.storage.fileSyncSetUp')
|
||||
);
|
||||
Zotero.Prefs.set("sync.storage.verified", true);
|
||||
}
|
||||
else {
|
||||
Zotero.debug("WebDAV verification failed");
|
||||
if (onDone) {
|
||||
setTimeout(function () {
|
||||
onDone();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function (e) {
|
||||
Zotero.debug("WebDAV verification failed");
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
Zotero.Utilities.Internal.errorPrompt(Zotero.getString('general.error'), e);
|
||||
|
||||
if (onDone) {
|
||||
setTimeout(function () {
|
||||
onDone();
|
||||
}, 1);
|
||||
}
|
||||
})
|
||||
.done();
|
||||
|
||||
abortButton.onclick = function () {
|
||||
if (requestHolder.request) {
|
||||
requestHolder.request.onreadystatechange = undefined;
|
||||
requestHolder.request.abort();
|
||||
if (request) {
|
||||
Zotero.debug("Cancelling verification request");
|
||||
request.onreadystatechange = undefined;
|
||||
request.abort();
|
||||
verifyButton.hidden = false;
|
||||
abortButton.hidden = true;
|
||||
progressMeter.hidden = true;
|
||||
|
@ -1971,4 +2017,257 @@ function openInViewer(uri, newTab) {
|
|||
var win = ww.openWindow(null, uri, null, features + ",width=775,height=575", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Zotero_Preferences.Attachment_Base_Directory = {
|
||||
choosePath: function () {
|
||||
// Get existing base directory
|
||||
var oldBasePath = Zotero.Prefs.get('baseAttachmentPath');
|
||||
if (oldBasePath) {
|
||||
var oldBasePathFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
oldBasePathFile.persistentDescriptor = oldBasePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
oldBasePathFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
//Prompt user to choose new base path
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
if (oldBasePathFile) {
|
||||
fp.displayDirectory = oldBasePathFile;
|
||||
}
|
||||
fp.init(window, Zotero.getString('attachmentBasePath.selectDir'), nsIFilePicker.modeGetFolder);
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() != nsIFilePicker.returnOK) {
|
||||
return false;
|
||||
}
|
||||
var newBasePathFile = fp.file;
|
||||
|
||||
if (oldBasePathFile && oldBasePathFile.equals(newBasePathFile)) {
|
||||
Zotero.debug("Base directory hasn't changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find all current attachments with relative attachment paths
|
||||
var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=? AND path LIKE '"
|
||||
+ Zotero.Attachments.BASE_PATH_PLACEHOLDER + "%'";
|
||||
var params = [Zotero.Attachments.LINK_MODE_LINKED_FILE];
|
||||
var oldRelativeAttachmentIDs = Zotero.DB.columnQuery(sql, params) || [];
|
||||
|
||||
//Find all attachments on the new base path
|
||||
var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=?";
|
||||
var params = [Zotero.Attachments.LINK_MODE_LINKED_FILE];
|
||||
var allAttachments = Zotero.DB.columnQuery(sql,params);
|
||||
var newAttachmentPaths = {};
|
||||
var numNewAttachments = 0;
|
||||
var numOldAttachments = 0;
|
||||
var attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
for (let i=0; i<allAttachments.length; i++) {
|
||||
let attachmentID = allAttachments[i];
|
||||
|
||||
try {
|
||||
let attachment = Zotero.Items.get(attachmentID);
|
||||
attachmentFile.persistentDescriptor = attachment.attachmentPath;
|
||||
}
|
||||
catch (e) {
|
||||
// Don't deal with bad attachment paths. Just skip them.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a file with the same relative path exists within the new base directory,
|
||||
// don't touch the attachment, since it will continue to work
|
||||
let isExistingRelativeAttachment = oldRelativeAttachmentIDs.indexOf(attachmentID) != -1;
|
||||
if (isExistingRelativeAttachment && oldBasePathFile) {
|
||||
let relFile = attachmentFile.clone();
|
||||
let relPath = attachmentFile.getRelativeDescriptor(oldBasePathFile);
|
||||
relFile.setRelativeDescriptor(newBasePathFile, relPath);
|
||||
if (relFile.exists()) {
|
||||
numNewAttachments++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Files within the new base directory need to be updated to use
|
||||
// relative paths (or, if the new base directory is an ancestor or
|
||||
// descendant of the old one, new relative paths)
|
||||
if (Zotero.File.directoryContains(newBasePathFile, attachmentFile)) {
|
||||
newAttachmentPaths[attachmentID] = isExistingRelativeAttachment
|
||||
? attachmentFile.persistentDescriptor : null;
|
||||
numNewAttachments++;
|
||||
}
|
||||
// Existing relative attachments not within the new base directory
|
||||
// will be converted to absolute paths
|
||||
else if (isExistingRelativeAttachment && oldBasePathFile) {
|
||||
newAttachmentPaths[attachmentID] = attachmentFile.persistentDescriptor;
|
||||
numOldAttachments++;
|
||||
}
|
||||
}
|
||||
|
||||
//Confirm change of the base path
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
var chooseStrPrefix = 'attachmentBasePath.chooseNewPath.';
|
||||
var clearStrPrefix = 'attachmentBasePath.clearBasePath.';
|
||||
var title = Zotero.getString(chooseStrPrefix + 'title');
|
||||
var msg1 = Zotero.getString(chooseStrPrefix + 'message') + "\n\n", msg2 = msg3 = "";
|
||||
switch (numNewAttachments) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
msg2 += Zotero.getString(chooseStrPrefix + 'existingAttachments.singular') + " ";
|
||||
break;
|
||||
|
||||
default:
|
||||
msg2 += Zotero.getString(chooseStrPrefix + 'existingAttachments.plural', numNewAttachments) + " ";
|
||||
}
|
||||
|
||||
switch (numOldAttachments) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
msg3 += Zotero.getString(clearStrPrefix + 'existingAttachments.singular');
|
||||
break;
|
||||
|
||||
default:
|
||||
msg3 += Zotero.getString(clearStrPrefix + 'existingAttachments.plural', numOldAttachments);
|
||||
}
|
||||
|
||||
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
|
||||
var index = ps.confirmEx(
|
||||
null,
|
||||
title,
|
||||
(msg1 + msg2 + msg3).trim(),
|
||||
buttonFlags,
|
||||
Zotero.getString(chooseStrPrefix + 'button'),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{}
|
||||
);
|
||||
|
||||
if (index == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set new data directory
|
||||
Zotero.debug("Setting new base directory");
|
||||
Zotero.Prefs.set('baseAttachmentPath', newBasePathFile.persistentDescriptor);
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', true);
|
||||
// Resave all attachments on base path (so that their paths become relative)
|
||||
// and all other relative attachments (so that their paths become absolute)
|
||||
for (let id in newAttachmentPaths) {
|
||||
let attachment = Zotero.Items.get(id);
|
||||
if (newAttachmentPaths[id]) {
|
||||
attachment.attachmentPath = newAttachmentPaths[id];
|
||||
attachment.save();
|
||||
}
|
||||
else {
|
||||
attachment.updateAttachmentPath();
|
||||
}
|
||||
}
|
||||
return newBasePathFile.persistentDescriptor;
|
||||
},
|
||||
|
||||
|
||||
getPath: function (asFile) {
|
||||
var desc = Zotero.Prefs.get('baseAttachmentPath');
|
||||
if (desc == '') {
|
||||
return asFile ? null : '';
|
||||
}
|
||||
|
||||
var file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
file.persistentDescriptor = desc;
|
||||
}
|
||||
catch (e) {
|
||||
return asFile ? null : '';
|
||||
}
|
||||
return asFile ? file : file.path;
|
||||
},
|
||||
|
||||
|
||||
clearPath: function () {
|
||||
// Find all current attachments with relative paths
|
||||
var sql = "SELECT itemID FROM itemAttachments WHERE linkMode=? AND path LIKE '"
|
||||
+ Zotero.Attachments.BASE_PATH_PLACEHOLDER + "%'";
|
||||
var params = [Zotero.Attachments.LINK_MODE_LINKED_FILE];
|
||||
var relativeAttachmentIDs = Zotero.DB.columnQuery(sql, params) || [];
|
||||
|
||||
// Prompt for confirmation
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
var strPrefix = 'attachmentBasePath.clearBasePath.';
|
||||
var title = Zotero.getString(strPrefix + 'title');
|
||||
var msg = Zotero.getString(strPrefix + 'message');
|
||||
switch (relativeAttachmentIDs.length) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
msg += "\n\n" + Zotero.getString(strPrefix + 'existingAttachments.singular');
|
||||
break;
|
||||
|
||||
default:
|
||||
msg += "\n\n" + Zotero.getString(strPrefix + 'existingAttachments.plural',
|
||||
relativeAttachmentIDs.length);
|
||||
}
|
||||
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
|
||||
var index = ps.confirmEx(
|
||||
window,
|
||||
title,
|
||||
msg,
|
||||
buttonFlags,
|
||||
Zotero.getString(strPrefix + 'button'),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{}
|
||||
);
|
||||
|
||||
if (index == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable relative path saving and then resave all relative
|
||||
// attachments so that their absolute paths are stored
|
||||
Zotero.debug('Clearing base directory');
|
||||
Zotero.Prefs.set('saveRelativeAttachmentPath', false);
|
||||
for (var i=0; i<relativeAttachmentIDs.length; i++) {
|
||||
Zotero.Items.get(relativeAttachmentIDs[i]).updateAttachmentPath();
|
||||
}
|
||||
Zotero.Prefs.set('baseAttachmentPath', '');
|
||||
},
|
||||
|
||||
|
||||
updateUI: function () {
|
||||
var filefield = document.getElementById('baseAttachmentPath');
|
||||
var file = this.getPath(true);
|
||||
filefield.file = file;
|
||||
if (file) {
|
||||
filefield.label = file.path;
|
||||
}
|
||||
else {
|
||||
filefield.label = '';
|
||||
}
|
||||
|
||||
document.getElementById('resetBasePath').disabled = !Zotero.Prefs.get('baseAttachmentPath');
|
||||
}
|
||||
}
|
|
@ -242,7 +242,7 @@ To add a new preference:
|
|||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<stack id="storage-webdav-settings" style="margin-top: .5em; margin-bottom: .8em; border: 1px gray solid; -moz-border-radius: 3px">
|
||||
<stack id="storage-webdav-settings" style="margin-top: .5em; margin-bottom: .8em; border: 1px gray solid; border-radius: 3px">
|
||||
<!-- Background shading -->
|
||||
<box style="background: black; opacity:.03"/>
|
||||
|
||||
|
@ -564,8 +564,9 @@ To add a new preference:
|
|||
helpTopic="cite">
|
||||
|
||||
<preferences id="zotero-preferences-cite">
|
||||
<preference id="pref-cite-citePaperJournalArticleURL" name="extensions.zotero.export.citePaperJournalArticleURL" type="bool"/>
|
||||
<preference id="pref-cite-useClassicAddCitationDialog" name="extensions.zotero.integration.useClassicAddCitationDialog" type="bool"/>
|
||||
<preference id="pref-cite-citePaperJournalArticleURL" name="extensions.zotero.export.citePaperJournalArticleURL" type="bool"/>
|
||||
<preference id="pref-cite-automaticTitleAbbreviation" name="extensions.zotero.cite.automaticTitleAbbreviation" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<tabbox>
|
||||
|
@ -611,6 +612,8 @@ To add a new preference:
|
|||
<label id="export-citePaperJournalArticleURL" width="45em">
|
||||
&zotero.preferences.export.citePaperJournalArticleURL.description;
|
||||
</label>
|
||||
|
||||
<checkbox label="&zotero.preferences.cite.styles.automaticTitleAbbreviation;" preference="pref-cite-automaticTitleAbbreviation"/>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
|
@ -716,6 +719,7 @@ To add a new preference:
|
|||
image="chrome://zotero/skin/prefs-advanced.png"
|
||||
helpTopic="advanced">
|
||||
<preferences>
|
||||
<preference id="pref-baseAttachmentPath" name="extensions.zotero.baseAttachmentPath" type="string"/>
|
||||
<preference id="pref-useDataDir" name="extensions.zotero.useDataDir" type="bool"/>
|
||||
<preference id="pref-dataDir" name="extensions.zotero.dataDir" type="string"/>
|
||||
<preference id="pref-export-displayCharsetOption" name="extensions.zotero.export.displayCharsetOption" type="bool"/>
|
||||
|
@ -725,98 +729,139 @@ To add a new preference:
|
|||
<preference id="pref-openURL-version" name="extensions.zotero.openURL.version" type="string"/>
|
||||
</preferences>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.dataDir;"/>
|
||||
<tabbox id="zotero-prefpane-advanced-tabs">
|
||||
<tabs>
|
||||
<!-- TODO: localize -->
|
||||
<tab label="General"/>
|
||||
<tab label="Files and Folders"/>
|
||||
</tabs>
|
||||
|
||||
<radiogroup id="dataDir" preference="pref-useDataDir" onsyncfrompreference="onDataDirLoad();" onsynctopreference="return onDataDirUpdate(event);">
|
||||
<radio label="&zotero.preferences.dataDir.useProfile;" value="false"/>
|
||||
<hbox>
|
||||
<radio label="&zotero.preferences.dataDir.custom;" value="true"/>
|
||||
<textbox id="dataDirPath" preference="pref-dataDir" onsyncfrompreference="return getDataDirPath();" readonly="true" flex="1"/>
|
||||
<button label="&zotero.preferences.dataDir.choose;" oncommand="var file = Zotero.chooseZoteroDirectory(true); if (!file) { event.stopPropagation(); }"/>
|
||||
</hbox>
|
||||
</radiogroup>
|
||||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dataDir.reveal;" oncommand="revealDataDirectory()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.dbMaintenance;"/>
|
||||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dbMaintenance.integrityCheck;" oncommand="runIntegrityCheck()"/>
|
||||
<button label="&zotero.preferences.dbMaintenance.resetTranslators;" oncommand="resetTranslators()"/>
|
||||
<button label="&zotero.preferences.dbMaintenance.resetStyles;" oncommand="resetStyles()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.debugOutputLogging;"/>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width -->
|
||||
<vbox>
|
||||
<description width="45em">&zotero.preferences.debugOutputLogging.message;</description>
|
||||
</vbox>
|
||||
|
||||
<hbox align="center">
|
||||
<button id="debug-output-enable" oncommand="Zotero_Preferences.Debug_Output.toggleStore()"/>
|
||||
<label id="debug-output-lines" style="margin-right: 0"/>
|
||||
<label value="&zotero.preferences.debugOutputLogging.linesLogged;"/>
|
||||
<checkbox preference="pref-debug-output-enableAfterRestart" label="&zotero.preferences.debugOutputLogging.enableAfterRestart;" style="margin-left: 1.5em"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<button id="debug-output-view" label="&zotero.preferences.debugOutputLogging.viewOutput;" oncommand="Zotero_Preferences.Debug_Output.view()"/>
|
||||
<button id="debug-output-clear" label="&zotero.preferences.debugOutputLogging.clearOutput;" oncommand="Zotero_Preferences.Debug_Output.clear()"/>
|
||||
<button id="debug-output-submit" label="&zotero.preferences.debugOutputLogging.submitToServer;" oncommand="Zotero_Preferences.Debug_Output.submit()"/>
|
||||
<progressmeter id="debug-output-submit-progress" mode="undetermined" hidden="true"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.openurl.caption;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<!-- vbox prevents some weird vertical stretching of the menulist -->
|
||||
<vbox flex="1">
|
||||
<menulist id="openURLMenu" oncommand="onOpenURLSelected();">
|
||||
<menupopup>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.preferences.openurl.custom;" value="custom" selected="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</vbox>
|
||||
<button id="openURLSearchButton" label="&zotero.preferences.openurl.search;" oncommand="populateOpenURLResolvers()"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.server;"/>
|
||||
<textbox id="openURLServerField" flex="1" oninput="onOpenURLCustomized();" preference="pref-openURL-resolver"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.version;" control="openURLVersionMenu"/>
|
||||
<menulist id="openURLVersionMenu" oncommand="onOpenURLCustomized();" preference="pref-openURL-version">
|
||||
<menupopup>
|
||||
<menuitem label="0.1" value="0.1"/>
|
||||
<menuitem label="1.0" value="1.0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.miscellaneous;"/>
|
||||
<hbox align="center">
|
||||
<button id="openAboutConfig" label="&zotero.preferences.openAboutConfig;" oncommand="openInViewer('about:config')"/>
|
||||
<button id="openCSLEdit" label="&zotero.preferences.openCSLEdit;" oncommand="openInViewer('chrome://zotero/content/tools/csledit.xul', true)"/>
|
||||
<button id="openCSLPreview" label="&zotero.preferences.openCSLPreview;" oncommand="openInViewer('chrome://zotero/content/tools/cslpreview.xul', true)"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<separator/>
|
||||
<tabpanels id="zotero-prefpane-advanced-tabpanels">
|
||||
<tabpanel id="zotero-prefpane-advanced-general-tab" orient="vertical">
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.debugOutputLogging;"/>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width -->
|
||||
<vbox>
|
||||
<description width="45em">&zotero.preferences.debugOutputLogging.message;</description>
|
||||
</vbox>
|
||||
|
||||
<hbox align="center">
|
||||
<button id="debug-output-enable" oncommand="Zotero_Preferences.Debug_Output.toggleStore()"/>
|
||||
<label id="debug-output-lines" style="margin-right: 0"/>
|
||||
<label value="&zotero.preferences.debugOutputLogging.linesLogged;"/>
|
||||
<checkbox preference="pref-debug-output-enableAfterRestart" label="&zotero.preferences.debugOutputLogging.enableAfterRestart;" style="margin-left: 1.5em"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<button id="debug-output-view" label="&zotero.preferences.debugOutputLogging.viewOutput;" oncommand="Zotero_Preferences.Debug_Output.view()"/>
|
||||
<button id="debug-output-clear" label="&zotero.preferences.debugOutputLogging.clearOutput;" oncommand="Zotero_Preferences.Debug_Output.clear()"/>
|
||||
<button id="debug-output-submit" label="&zotero.preferences.debugOutputLogging.submitToServer;" oncommand="Zotero_Preferences.Debug_Output.submit()"/>
|
||||
<progressmeter id="debug-output-submit-progress" mode="undetermined" hidden="true"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.openurl.caption;"/>
|
||||
|
||||
<hbox align="center">
|
||||
<!-- vbox prevents some weird vertical stretching of the menulist -->
|
||||
<vbox flex="1">
|
||||
<menulist id="openURLMenu" oncommand="onOpenURLSelected();">
|
||||
<menupopup>
|
||||
<menuseparator/>
|
||||
<menuitem label="&zotero.preferences.openurl.custom;" value="custom" selected="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</vbox>
|
||||
<button id="openURLSearchButton" label="&zotero.preferences.openurl.search;" oncommand="populateOpenURLResolvers()"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.server;"/>
|
||||
<textbox id="openURLServerField" flex="1" oninput="onOpenURLCustomized();" preference="pref-openURL-resolver"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.openurl.version;" control="openURLVersionMenu"/>
|
||||
<menulist id="openURLVersionMenu" oncommand="onOpenURLCustomized();" preference="pref-openURL-version">
|
||||
<menupopup>
|
||||
<menuitem label="0.1" value="0.1"/>
|
||||
<menuitem label="1.0" value="1.0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="zotero-prefpane-advanced-miscellaneous">
|
||||
<caption label="&zotero.preferences.miscellaneous;"/>
|
||||
<hbox id="zotero-prefpane-advanced-openbuttons" align="center">
|
||||
<button id="openAboutConfig" label="&zotero.preferences.openAboutConfig;" oncommand="openInViewer('about:config')"/>
|
||||
<button id="openCSLEdit" label="&zotero.preferences.openCSLEdit;" oncommand="openInViewer('chrome://zotero/content/tools/csledit.xul', true)"/>
|
||||
<button id="openCSLPreview" label="&zotero.preferences.openCSLPreview;" oncommand="openInViewer('chrome://zotero/content/tools/cslpreview.xul', true)"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel orient="vertical">
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.attachmentBaseDir.caption;"/>
|
||||
|
||||
<!-- This doesn't wrap without an explicit width -->
|
||||
<vbox>
|
||||
<description width="45em">&zotero.preferences.attachmentBaseDir.message;</description>
|
||||
</vbox>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&zotero.preferences.attachmentBaseDir.basePath;"/>
|
||||
<filefield id="baseAttachmentPath"
|
||||
preference="pref-baseAttachmentPath"
|
||||
onsyncfrompreference="Zotero_Preferences.Attachment_Base_Directory.updateUI()"
|
||||
preference-editable="true"
|
||||
readonly="true"
|
||||
flex="1"
|
||||
tabindex="-1"/>
|
||||
<button label="&zotero.preferences.attachmentBaseDir.selectBasePath;"
|
||||
oncommand="Zotero_Preferences.Attachment_Base_Directory.choosePath()"/>
|
||||
</hbox>
|
||||
|
||||
<hbox>
|
||||
<button id="resetBasePath"
|
||||
label="&zotero.preferences.attachmentBaseDir.resetBasePath;"
|
||||
oncommand="Zotero_Preferences.Attachment_Base_Directory.clearPath()"/>
|
||||
</hbox>
|
||||
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.dataDir;"/>
|
||||
|
||||
<radiogroup id="dataDir" preference="pref-useDataDir" onsyncfrompreference="onDataDirLoad();" onsynctopreference="return onDataDirUpdate(event);">
|
||||
<radio label="&zotero.preferences.dataDir.useProfile;" value="false"/>
|
||||
<hbox>
|
||||
<radio label="&zotero.preferences.dataDir.custom;" value="true"/>
|
||||
<textbox id="dataDirPath" preference="pref-dataDir" onsyncfrompreference="return getDataDirPath();" readonly="true" flex="1"/>
|
||||
<button label="&zotero.preferences.dataDir.choose;" oncommand="var file = Zotero.chooseZoteroDirectory(true); if (!file) { event.stopPropagation(); }"/>
|
||||
</hbox>
|
||||
</radiogroup>
|
||||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dataDir.reveal;" oncommand="revealDataDirectory()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.dbMaintenance;"/>
|
||||
|
||||
<hbox>
|
||||
<button label="&zotero.preferences.dbMaintenance.integrityCheck;" oncommand="runIntegrityCheck()"/>
|
||||
<button label="&zotero.preferences.dbMaintenance.resetTranslators;" oncommand="resetTranslators()"/>
|
||||
<button label="&zotero.preferences.dbMaintenance.resetStyles;" oncommand="resetStyles()"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</prefpane>
|
||||
|
||||
<!-- These mess up the prefwindow (more) if they come before the prefpanes
|
||||
|
@ -829,5 +874,5 @@ To add a new preference:
|
|||
observerService.notifyObservers(null, "charsetmenu-selected", "other");
|
||||
]]>
|
||||
</script>
|
||||
<script src="preferences.js"/>
|
||||
<script src="preferences.js" type="application/javascript;version=1.8"/>
|
||||
</prefwindow>
|
||||
|
|
|
@ -49,9 +49,6 @@ To add a new preference:
|
|||
<preference id="pref-showIn" name="extensions.zotero.showIn" type="int"/>
|
||||
<preference id="pref-statusBarIcon" name="extensions.zotero.statusBarIcon" type="int"/>
|
||||
<preference id="pref-launchNonNativeFiles" name="extensions.zotero.launchNonNativeFiles" type="bool"/>
|
||||
<preference id="pref-zoteroDotOrgVersionHeader"
|
||||
name="extensions.zotero.zoteroDotOrgVersionHeader"
|
||||
type="bool"/>
|
||||
<preference id="pref-parseEndNoteMIMETypes"
|
||||
name="extensions.zotero.parseEndNoteMIMETypes"
|
||||
type="bool" onchange="Zotero.MIMETypeHandler.init()"/>
|
||||
|
@ -83,10 +80,6 @@ To add a new preference:
|
|||
</grid>
|
||||
</groupbox>
|
||||
<groupbox id="zotero-prefpane-miscellaneous-groupbox">
|
||||
<checkbox label="&zotero.preferences.zoteroDotOrgVersionHeader;"
|
||||
tooltiptext="&zotero.preferences.zoteroDotOrgVersionHeader.tooltip;"
|
||||
preference="pref-zoteroDotOrgVersionHeader"
|
||||
insertbefore="automaticSnapshots-checkbox"/>
|
||||
<checkbox id="launchNonNativeFiles-checkbox"
|
||||
preference="pref-launchNonNativeFiles"
|
||||
onsyncfrompreference="return !document.getElementById(this.getAttribute('preference')).value"
|
||||
|
@ -171,5 +164,28 @@ To add a new preference:
|
|||
|
||||
<separator/>
|
||||
</prefpane>
|
||||
|
||||
<prefpane id="zotero-prefpane-advanced">
|
||||
<preferences id="zotero-prefpane-general-preferences">
|
||||
<preference id="pref-zoteroDotOrgVersionHeader"
|
||||
name="extensions.zotero.zoteroDotOrgVersionHeader"
|
||||
type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<tabbox id="zotero-prefpane-advanced-tabs">
|
||||
<tabpanels id="zotero-prefpane-advanced-tabpanels">
|
||||
<tabpanel id="zotero-prefpane-advanced-general-tab">
|
||||
<groupbox id="zotero-prefpane-advanced-miscellaneous">
|
||||
<checkbox label="&zotero.preferences.zoteroDotOrgVersionHeader;"
|
||||
tooltiptext="&zotero.preferences.zoteroDotOrgVersionHeader.tooltip;"
|
||||
preference="pref-zoteroDotOrgVersionHeader"
|
||||
insertbefore="zotero-prefpane-advanced-openbuttons"/>
|
||||
<separator class="thin"
|
||||
insertbefore="zotero-prefpane-advanced-openbuttons"/>
|
||||
</groupbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</prefpane>
|
||||
</prefwindow>
|
||||
</overlay>
|
||||
|
|
|
@ -26,13 +26,13 @@
|
|||
/**
|
||||
* @fileOverview Tools for automatically retrieving a citation for the given PDF
|
||||
*/
|
||||
Components.utils.import("resource://zotero/q.js");
|
||||
|
||||
/**
|
||||
* Front end for recognizing PDFs
|
||||
* @namespace
|
||||
*/
|
||||
var Zotero_RecognizePDF = new function() {
|
||||
Components.utils.import("resource://zotero/q.js");
|
||||
var _progressWindow, _progressIndicator;
|
||||
|
||||
/**
|
||||
|
@ -184,15 +184,16 @@ var Zotero_RecognizePDF = new function() {
|
|||
return Zotero.HTTP.promise("GET", url, {"responseType":"document"})
|
||||
})
|
||||
.then(function(xmlhttp) {
|
||||
var doc = xmlhttp.response,
|
||||
deferred = Q.defer(),
|
||||
translate = new Zotero.Translate.Web();
|
||||
|
||||
if(Zotero.Utilities.xpath(doc, "//form[@action='Captcha']").length) {
|
||||
// Hit CAPTCHA
|
||||
limited = true;
|
||||
throw new Zotero.Exception.Alert("recognizePDF.limit");
|
||||
}
|
||||
|
||||
var doc = xmlhttp.response,
|
||||
deferred = Q.defer(),
|
||||
translate = new Zotero.Translate.Web();
|
||||
|
||||
translate.setTranslator("57a00950-f0d1-4b41-b6ba-44ff0fc30289");
|
||||
translate.setDocument(Zotero.HTTP.wrapDocument(doc, url));
|
||||
translate.setHandler("translators", function(translate, detected) {
|
||||
|
@ -363,148 +364,157 @@ var Zotero_RecognizePDF = new function() {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Handles UI, etc. for recognizing multiple items
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer = function () {
|
||||
this._stopped = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives metadata for the PDF items passed, displaying a progress dialog during conversion
|
||||
* and placing the PDFs as a children of the new items
|
||||
* @param {Zotero.Item[]} items
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer.prototype.recognizeItems = function(items) {
|
||||
var me = this;
|
||||
this._items = items.slice();
|
||||
this._itemTotal = items.length;
|
||||
|
||||
this._progressWindow = window.openDialog("chrome://zotero/content/pdfProgress.xul", "", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen");
|
||||
this._progressWindow.addEventListener("pageshow", function() { me._onWindowLoaded() }, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Halts recognition of PDFs
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer.prototype.stop = function() {
|
||||
this._stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the progress window has been opened; adds items to the tree and begins recognizing
|
||||
* @param
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer.prototype._onWindowLoaded = function() {
|
||||
// populate progress window
|
||||
var treechildren = this._progressWindow.document.getElementById("treechildren");
|
||||
for(var i in this._items) {
|
||||
var treeitem = this._progressWindow.document.createElement('treeitem');
|
||||
var treerow = this._progressWindow.document.createElement('treerow');
|
||||
|
||||
var treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-icon");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("label", this._items[i].getField("title"));
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-title");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treeitem.appendChild(treerow);
|
||||
treechildren.appendChild(treeitem);
|
||||
/**
|
||||
* @class Handles UI, etc. for recognizing multiple items
|
||||
*/
|
||||
this.ItemRecognizer = function () {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
var me = this;
|
||||
this._progressIndicator = this._progressWindow.document.getElementById("progress-indicator");
|
||||
this._progressWindow.document.getElementById("cancel-button").addEventListener("command", function() {
|
||||
me.stop();
|
||||
me._progressWindow.close();
|
||||
}, false);
|
||||
this._progressWindow.addEventListener("close", function() { me.stop() }, false);
|
||||
this._recognizeItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts an item off of this._items and recognizes it, then calls itself again if there are more
|
||||
* @private
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer.prototype._recognizeItem = function() {
|
||||
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
|
||||
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
|
||||
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
|
||||
this.ItemRecognizer.prototype = {
|
||||
"_stopped": false,
|
||||
"_itemsTotal": 0,
|
||||
"_progressWindow": null,
|
||||
"_progressIndicator": null,
|
||||
|
||||
if(!this._items.length) {
|
||||
this._done();
|
||||
return;
|
||||
/**
|
||||
* Retreives metadata for the PDF items passed, displaying a progress dialog during conversion
|
||||
* and placing the PDFs as a children of the new items
|
||||
* @param {Zotero.Item[]} items
|
||||
*/
|
||||
"recognizeItems": function(items) {
|
||||
var me = this;
|
||||
this._items = items.slice();
|
||||
this._itemTotal = items.length;
|
||||
|
||||
this._progressWindow = window.openDialog("chrome://zotero/content/pdfProgress.xul", "", "chrome,close=yes,resizable=yes,dependent,dialog,centerscreen");
|
||||
this._progressWindow.addEventListener("pageshow", function() { me._onWindowLoaded() }, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Halts recognition of PDFs
|
||||
*/
|
||||
"stop": function() {
|
||||
this._stopped = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the progress window has been opened; adds items to the tree and begins recognizing
|
||||
* @param
|
||||
*/
|
||||
"_onWindowLoaded": function() {
|
||||
// populate progress window
|
||||
var treechildren = this._progressWindow.document.getElementById("treechildren");
|
||||
for(var i in this._items) {
|
||||
var treeitem = this._progressWindow.document.createElement('treeitem');
|
||||
var treerow = this._progressWindow.document.createElement('treerow');
|
||||
|
||||
var treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-icon");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("label", this._items[i].getField("title"));
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treecell = this._progressWindow.document.createElement('treecell');
|
||||
treecell.setAttribute("id", "item-"+this._items[i].id+"-title");
|
||||
treerow.appendChild(treecell);
|
||||
|
||||
treeitem.appendChild(treerow);
|
||||
treechildren.appendChild(treeitem);
|
||||
}
|
||||
|
||||
var me = this;
|
||||
this._progressIndicator = this._progressWindow.document.getElementById("progress-indicator");
|
||||
this._progressWindow.document.getElementById("cancel-button").addEventListener("command", function() {
|
||||
me.stop();
|
||||
me._progressWindow.close();
|
||||
}, false);
|
||||
this._progressWindow.addEventListener("close", function() { me.stop() }, false);
|
||||
this._recognizeItem();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shifts an item off of this._items and recognizes it, then calls itself again if there are more
|
||||
* @private
|
||||
*/
|
||||
"_recognizeItem": function() {
|
||||
Components.utils.import("resource://zotero/q.js");
|
||||
|
||||
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
|
||||
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
|
||||
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
|
||||
|
||||
if(!this._items.length) {
|
||||
this._done();
|
||||
return;
|
||||
}
|
||||
|
||||
this._progressIndicator.value = (this._itemTotal-this._items.length)/this._itemTotal*100;
|
||||
|
||||
var item = this._items.shift(),
|
||||
itemIcon = this._progressWindow.document.getElementById("item-"+item.id+"-icon"),
|
||||
itemTitle = this._progressWindow.document.getElementById("item-"+item.id+"-title");
|
||||
itemIcon.setAttribute("src", LOADING_IMAGE);
|
||||
|
||||
var file = item.getFile(), me = this;
|
||||
|
||||
(file
|
||||
? Zotero_RecognizePDF.recognize(file, item.libraryID)
|
||||
: Q.reject(new Zotero.Exception.Alert("recognizePDF.fileNotFound")))
|
||||
.then(function(newItem) {
|
||||
// If already stopped, delete
|
||||
if(me._stopped) {
|
||||
Zotero.Items.erase(item.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// put new item in same collections as the old one
|
||||
var itemCollections = item.getCollections();
|
||||
for(var j=0; j<itemCollections.length; j++) {
|
||||
var collection = Zotero.Collections.get(itemCollections[j]);
|
||||
collection.addItem(newItem.id);
|
||||
}
|
||||
|
||||
// put old item as a child of the new item
|
||||
item.setSource(newItem.id);
|
||||
item.save();
|
||||
|
||||
itemTitle.setAttribute("label", newItem.getField("title"));
|
||||
itemIcon.setAttribute("src", SUCCESS_IMAGE);
|
||||
|
||||
me._recognizeItem();
|
||||
}, function(error) {
|
||||
Zotero.debug(error);
|
||||
Zotero.logError(error);
|
||||
|
||||
itemTitle.setAttribute("label", error instanceof Zotero.Exception.Alert ? error.message : Zotero.getString("recognizePDF.error"));
|
||||
itemIcon.setAttribute("src", FAILURE_IMAGE);
|
||||
|
||||
if(error instanceof Zotero.Exception.Alert && error.name === "recognizePDF.limit") {
|
||||
me._done();
|
||||
} else {
|
||||
me._recognizeItem();
|
||||
}
|
||||
}).fin(function() {
|
||||
// scroll to this item
|
||||
me._progressWindow.document.getElementById("tree").treeBoxObject.scrollToRow(Math.max(0, me._itemTotal-me._items.length-5));
|
||||
}).done();
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleans up after items are recognized, disabling the cancel button and making the progress window
|
||||
* close on blur
|
||||
*/
|
||||
"_done": function() {
|
||||
this._progressIndicator.value = 100;
|
||||
this._progressWindow.document.getElementById("cancel-button").label = Zotero.getString("recognizePDF.close.label");
|
||||
var me = this;
|
||||
this._progressWindow.addEventListener("blur",
|
||||
function() { me._progressWindow.setTimeout(function() { me._progressWindow.close() }, 2000) }, false);
|
||||
this._progressWindow.document.getElementById("label").value = Zotero.getString("recognizePDF.complete.label");
|
||||
}
|
||||
}
|
||||
|
||||
this._progressIndicator.value = (this._itemTotal-this._items.length)/this._itemTotal*100;
|
||||
|
||||
var item = this._items.shift(),
|
||||
itemIcon = this._progressWindow.document.getElementById("item-"+item.id+"-icon"),
|
||||
itemTitle = this._progressWindow.document.getElementById("item-"+item.id+"-title");
|
||||
itemIcon.setAttribute("src", LOADING_IMAGE);
|
||||
|
||||
var file = item.getFile(), me = this;
|
||||
|
||||
(file
|
||||
? Zotero_RecognizePDF.recognize(file, item.libraryID)
|
||||
: Q.reject(new Zotero.Exception.Alert("recognizePDF.fileNotFound")))
|
||||
.then(function(newItem) {
|
||||
// If already stopped, delete
|
||||
if(me._stopped) {
|
||||
Zotero.Items.erase(item.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// put new item in same collections as the old one
|
||||
var itemCollections = item.getCollections();
|
||||
for(var j=0; j<itemCollections.length; j++) {
|
||||
var collection = Zotero.Collections.get(itemCollections[j]);
|
||||
collection.addItem(newItem.id);
|
||||
}
|
||||
|
||||
// put old item as a child of the new item
|
||||
item.setSource(newItem.id);
|
||||
item.save();
|
||||
|
||||
itemTitle.setAttribute("label", newItem.getField("title"));
|
||||
itemIcon.setAttribute("src", SUCCESS_IMAGE);
|
||||
|
||||
me._recognizeItem();
|
||||
}, function(error) {
|
||||
Zotero.debug(error);
|
||||
Zotero.logError(error);
|
||||
|
||||
itemTitle.setAttribute("label", error instanceof Zotero.Exception.Alert ? error.message : Zotero.getString("recognizePDF.error"));
|
||||
itemIcon.setAttribute("src", FAILURE_IMAGE);
|
||||
|
||||
if(error instanceof Zotero.Exception.Alert && error.name === "recognizePDF.limit") {
|
||||
me._done();
|
||||
} else {
|
||||
me._recognizeItem();
|
||||
}
|
||||
}).fin(function() {
|
||||
// scroll to this item
|
||||
me._progressWindow.document.getElementById("tree").treeBoxObject.scrollToRow(Math.max(0, me._itemTotal-me._items.length-5));
|
||||
}).done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up after items are recognized, disabling the cancel button and making the progress window
|
||||
* close on blur
|
||||
*/
|
||||
Zotero_RecognizePDF.ItemRecognizer.prototype._done = function() {
|
||||
this._progressIndicator.value = 100;
|
||||
this._progressWindow.document.getElementById("cancel-button").label = Zotero.getString("recognizePDF.close.label");
|
||||
var me = this;
|
||||
this._progressWindow.addEventListener("blur",
|
||||
function() { me._progressWindow.setTimeout(function() { me._progressWindow.close() }, 2000) }, false);
|
||||
this._progressWindow.document.getElementById("label").value = Zotero.getString("recognizePDF.complete.label");
|
||||
}
|
|
@ -232,14 +232,14 @@
|
|||
flex="1" zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-hasAttachment"
|
||||
id="zotero-items-column-hasAttachment" hidden="true"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.attachments.label;"
|
||||
src="chrome://zotero/skin/attach-small.png"
|
||||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-hasNote"
|
||||
id="zotero-items-column-numNotes" hidden="true"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.notes.label;"
|
||||
src="chrome://zotero/skin/treeitem-note-small.png"
|
||||
|
|
154
chrome/content/zotero/tagColorChooser.js
Normal file
154
chrome/content/zotero/tagColorChooser.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 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 *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var _io;
|
||||
|
||||
var Zotero_Tag_Color_Chooser = new function() {
|
||||
this.init = function () {
|
||||
// Set font size from pref
|
||||
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
|
||||
|
||||
if (window.arguments && window.arguments.length) {
|
||||
_io = window.arguments[0];
|
||||
if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
|
||||
}
|
||||
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
|
||||
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
|
||||
|
||||
window.sizeToContent();
|
||||
|
||||
var dialog = document.getElementById('tag-color-chooser');
|
||||
var colorPicker = document.getElementById('color-picker');
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
|
||||
colorPicker.setAttribute('cols', 3);
|
||||
colorPicker.setAttribute('tileWidth', 24);
|
||||
colorPicker.setAttribute('tileHeight', 24);
|
||||
colorPicker.colors = [
|
||||
'#990000', '#CC9933', '#FF9900',
|
||||
'#FFCC00', '#007439', '#1049A9',
|
||||
'#9999FF', '#CC66CC', '#993399'
|
||||
];
|
||||
|
||||
var maxTags = document.getElementById('max-tags');
|
||||
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
|
||||
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(_io.libraryID)
|
||||
.then(function (tagColors) {
|
||||
var colorData = tagColors[_io.name];
|
||||
|
||||
// Color
|
||||
if (colorData) {
|
||||
colorPicker.color = colorData.color;
|
||||
dialog.buttons = "extra1,cancel,accept";
|
||||
}
|
||||
else {
|
||||
// Get unused color at random
|
||||
var usedColors = [];
|
||||
for (var i in tagColors) {
|
||||
usedColors.push(tagColors[i].color);
|
||||
}
|
||||
var unusedColors = Zotero.Utilities.arrayDiff(
|
||||
colorPicker.colors, usedColors
|
||||
);
|
||||
var color = unusedColors[Zotero.Utilities.rand(0, unusedColors.length - 1)];
|
||||
colorPicker.color = color;
|
||||
dialog.buttons = "cancel,accept";
|
||||
}
|
||||
colorPicker.setAttribute('disabled', 'false');
|
||||
|
||||
var numColors = Object.keys(tagColors).length;
|
||||
var max = colorData ? numColors : numColors + 1;
|
||||
|
||||
// Position
|
||||
for (let i=1; i<=max; i++) {
|
||||
tagPosition.appendItem(i, i-1);
|
||||
}
|
||||
if (numColors) {
|
||||
tagPosition.setAttribute('disabled', 'false');
|
||||
if (colorData) {
|
||||
tagPosition.selectedIndex = colorData.position;
|
||||
}
|
||||
// If no color currently, default to end
|
||||
else {
|
||||
tagPosition.selectedIndex = numColors;
|
||||
}
|
||||
}
|
||||
// If no colors currently, only position "1" is available
|
||||
else {
|
||||
tagPosition.selectedIndex = 0;
|
||||
}
|
||||
|
||||
self.onPositionChange();
|
||||
window.sizeToContent();
|
||||
})
|
||||
.catch(function (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
dialog.cancelDialog();
|
||||
})
|
||||
.done();
|
||||
};
|
||||
|
||||
|
||||
this.onPositionChange = function () {
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
var instructions = document.getElementById('number-key-instructions');
|
||||
|
||||
while (instructions.hasChildNodes()) {
|
||||
instructions.removeChild(instructions.firstChild);
|
||||
}
|
||||
|
||||
var msg = Zotero.getString('tagColorChooser.numberKeyInstructions');
|
||||
var matches = msg.match(/(.+)\$NUMBER(.+)/);
|
||||
|
||||
var num = document.createElement('label');
|
||||
num.id = 'number-key';
|
||||
num.setAttribute('value', parseInt(tagPosition.value) + 1);
|
||||
|
||||
instructions.appendChild(document.createTextNode(matches[1]));
|
||||
instructions.appendChild(num);
|
||||
instructions.appendChild(document.createTextNode(matches[2]));
|
||||
};
|
||||
|
||||
|
||||
this.onDialogAccept = function () {
|
||||
var colorPicker = document.getElementById('color-picker');
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
_io.color = colorPicker.color;
|
||||
_io.position = tagPosition.value;
|
||||
};
|
||||
|
||||
|
||||
this.onDialogCancel = function () {};
|
||||
|
||||
|
||||
this.onDialogRemoveColor = function () {
|
||||
_io.color = false;
|
||||
window.close();
|
||||
};
|
||||
};
|
63
chrome/content/zotero/tagColorChooser.xul
Normal file
63
chrome/content/zotero/tagColorChooser.xul
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 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://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/tagColorChooser.css" type="text/css"?>
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&zotero.tagColorChooser.title;" buttons="cancel,accept"
|
||||
id="tag-color-chooser"
|
||||
buttonlabelaccept="&zotero.tagColorChooser.setColor;"
|
||||
buttonlabelextra1="&zotero.tagColorChooser.removeColor;"
|
||||
ondialogaccept="Zotero_Tag_Color_Chooser.onDialogAccept()"
|
||||
ondialogcancel="Zotero_Tag_Color_Chooser.onDialogCancel()"
|
||||
ondialogextra1="Zotero_Tag_Color_Chooser.onDialogRemoveColor()"
|
||||
onload="Zotero_Tag_Color_Chooser.init()"
|
||||
width="300"
|
||||
height="140">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="tagColorChooser.js" type="text/javascript;version=1.8"/>
|
||||
|
||||
<vbox id="tag-color-chooser-container">
|
||||
<hbox align="center">
|
||||
<label value="&zotero.tagColorChooser.color;" control="color-picker"/>
|
||||
<customcolorpicker id="color-picker" type="button" disabled="true"/>
|
||||
<separator width="20"/>
|
||||
<label value="&zotero.tagColorChooser.position;" control="tag-position" disabled="true"/>
|
||||
<menulist id="tag-position" disabled="true" sizetopopup="always"
|
||||
oncommand="Zotero_Tag_Color_Chooser.onPositionChange()">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<separator/>
|
||||
<description id="number-key-instructions"/>
|
||||
<separator class="thin"/>
|
||||
<description id="max-tags"/>
|
||||
<separator class="thin"/>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -28,6 +28,9 @@ Zotero.Attachments = new function(){
|
|||
this.LINK_MODE_IMPORTED_URL = 1;
|
||||
this.LINK_MODE_LINKED_FILE = 2;
|
||||
this.LINK_MODE_LINKED_URL = 3;
|
||||
this.BASE_PATH_PLACEHOLDER = 'attachments:';
|
||||
|
||||
this.SNAPSHOT_MIMETYPES = ["text/html", "application/xhtml+xml"];
|
||||
|
||||
this.importFromFile = importFromFile;
|
||||
this.linkFromFile = linkFromFile;
|
||||
|
@ -573,7 +576,7 @@ Zotero.Attachments = new function(){
|
|||
};
|
||||
}
|
||||
|
||||
if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') {
|
||||
if (this.SNAPSHOT_MIMETYPES.indexOf(mimeType) != -1) {
|
||||
var sync = true;
|
||||
|
||||
// Load WebPageDump code
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Utility functions for dealing with citations
|
||||
* @namespace
|
||||
|
@ -471,3 +473,145 @@ Zotero.Cite.System = {
|
|||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions for creating and manipulating field abbreviations
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.Cite.Abbreviations = new function() {
|
||||
var abbreviations,
|
||||
abbreviationCategories;
|
||||
|
||||
/**
|
||||
* Initialize abbreviations database.
|
||||
*/
|
||||
function init() {
|
||||
if(!abbreviations) Zotero.Cite.Abbreviations.loadAbbreviations();
|
||||
}
|
||||
|
||||
this.loadAbbreviations = function() {
|
||||
var file = Zotero.getZoteroDirectory();
|
||||
file.append("abbreviations.json");
|
||||
|
||||
var json, origin;
|
||||
if(file.exists()) {
|
||||
json = Zotero.File.getContents(file);
|
||||
origin = file.path;
|
||||
} else {
|
||||
json = Zotero.File.getContentsFromURL("resource://zotero/schema/abbreviations.json");
|
||||
origin = "resource://zotero/schema/abbreviations.json";
|
||||
}
|
||||
|
||||
try {
|
||||
abbreviations = JSON.parse(json);
|
||||
} catch(e) {
|
||||
throw new Zotero.Exception.Alert("styles.abbreviations.parseError", origin,
|
||||
"styles.abbreviations.title", e);
|
||||
}
|
||||
|
||||
if(!abbreviations.info || !abbreviations.info.name || !abbreviations.info.URI) {
|
||||
throw new Zotero.Exception.Alert("styles.abbreviations.missingInfo", origin,
|
||||
"styles.abbreviations.title");
|
||||
}
|
||||
|
||||
abbreviationCategories = {};
|
||||
for(var jurisdiction in abbreviations) {
|
||||
for(var category in abbreviations[jurisdiction]) {
|
||||
abbreviationCategories[category] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a key
|
||||
*/
|
||||
function normalizeKey(key) {
|
||||
// Strip periods, normalize spacing, and convert to lowercase
|
||||
return key.toString().toLowerCase().
|
||||
replace(/(?:\b|^)(?:and|et|y|und|l[ae]|the|[ld]')(?:\b|$)|[\x21-\x2C.\/\x3A-\x40\x5B-\x60\\\x7B-\x7E]/g, "").
|
||||
replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function lookupKey(key) {
|
||||
return key.toLowerCase().replace(/\s*\./g, "." );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace getAbbreviation on citeproc-js with our own handler.
|
||||
*/
|
||||
Zotero.CiteProc.CSL.getAbbreviation = function getAbbreviation(listname, obj, jurisdiction, category, key) {
|
||||
if(!Zotero.Prefs.get("cite.automaticTitleAbbreviation")) return;
|
||||
|
||||
init();
|
||||
|
||||
// Short circuit if we know we don't handle this kind of abbreviation
|
||||
if(!abbreviationCategories[category] && !abbreviationCategories[category+"-word"]) return;
|
||||
|
||||
var normalizedKey = normalizeKey(key),
|
||||
lcNormalizedKey = lookupKey(normalizedKey),
|
||||
abbreviation;
|
||||
if(!normalizedKey) return;
|
||||
|
||||
var jurisdictions = ["default"];
|
||||
if(jurisdiction !== "default" && abbreviations[jurisdiction]) {
|
||||
jurisdictions.unshift(jurisdiction);
|
||||
}
|
||||
|
||||
// Look for full abbreviation
|
||||
var jur, cat;
|
||||
for(var i=0; i<jurisdictions.length && !abbreviation; i++) {
|
||||
if((jur = abbreviations[jurisdictions[i]]) && (cat = jur[category])) {
|
||||
abbreviation = cat[lcNormalizedKey];
|
||||
}
|
||||
}
|
||||
|
||||
if(!abbreviation) {
|
||||
// Abbreviate words individually
|
||||
var words = normalizedKey.split(/([ \-])/);
|
||||
|
||||
if(words.length > 1) {
|
||||
for(var j=0; j<words.length; j+=2) {
|
||||
var word = words[j],
|
||||
lcWord = lookupKey(word),
|
||||
newWord = undefined;
|
||||
|
||||
for(var i=0; i<jurisdictions.length && newWord === undefined; i++) {
|
||||
if(!(jur = abbreviations[jurisdictions[i]])) continue;
|
||||
if(!(cat = jur[category+"-word"])) continue;
|
||||
|
||||
// Complete match
|
||||
if(cat.hasOwnProperty(lcWord)) {
|
||||
newWord = cat[lcWord];
|
||||
} else {
|
||||
// Partial match
|
||||
for(var k=1; k<=word.length && newWord === undefined; k++) {
|
||||
newWord = cat[lcWord.substr(0, k)+"-"];
|
||||
if(newWord && word.length - newWord.length < 1) {
|
||||
newWord = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to full word
|
||||
if(newWord === undefined ) newWord = word;
|
||||
|
||||
words[j] = newWord.substr(0, 1).toUpperCase() + newWord.substr(1);
|
||||
}
|
||||
}
|
||||
abbreviation = words.join("").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
if(!abbreviation || abbreviation === key) {
|
||||
Zotero.debug("No abbreviation found for "+key);
|
||||
return;
|
||||
}
|
||||
Zotero.debug("Abbreviated "+key+" as "+abbreviation);
|
||||
|
||||
// Add to jurisdiction object
|
||||
if(!obj[jurisdiction]) {
|
||||
obj[jurisdiction] = new Zotero.CiteProc.CSL.AbbreviationSegments();
|
||||
}
|
||||
obj[jurisdiction][category][key] = abbreviation;
|
||||
}
|
||||
};
|
|
@ -355,6 +355,10 @@ Zotero.Group.prototype.erase = function() {
|
|||
var prefix = "groups/" + this.id;
|
||||
Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
|
||||
|
||||
// Delete settings
|
||||
sql = "DELETE FROM syncedSettings WHERE libraryID=?";
|
||||
Zotero.DB.query(sql, this.libraryID ? parseInt(this.libraryID) : 0);
|
||||
|
||||
// Delete group
|
||||
sql = "DELETE FROM groups WHERE groupID=?";
|
||||
Zotero.DB.query(sql, this.id)
|
||||
|
|
|
@ -113,6 +113,8 @@ Zotero.Item.prototype.__defineGetter__('itemID', function () {
|
|||
});
|
||||
Zotero.Item.prototype.__defineSetter__('id', function (val) { this.setField('id', val); });
|
||||
Zotero.Item.prototype.__defineGetter__('libraryID', function () { return this.getField('libraryID'); });
|
||||
// Temporary until everything expects an integer
|
||||
Zotero.Item.prototype.__defineGetter__('libraryIDInt', function () { var libraryID = this.getField('libraryID'); return libraryID ? parseInt(libraryID) : 0; });
|
||||
Zotero.Item.prototype.__defineSetter__('libraryID', function (val) { this.setField('libraryID', val); });
|
||||
Zotero.Item.prototype.__defineGetter__('key', function () { return this.getField('key'); });
|
||||
Zotero.Item.prototype.__defineSetter__('key', function (val) { this.setField('key', val) });
|
||||
|
@ -1522,6 +1524,41 @@ Zotero.Item.prototype.save = function() {
|
|||
var path = this.attachmentPath;
|
||||
var syncState = this.attachmentSyncState;
|
||||
|
||||
// Save attachment within attachment base directory as relative path
|
||||
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE && path) {
|
||||
let basePath = Zotero.Prefs.get('baseAttachmentPath');
|
||||
if (basePath != '' && Zotero.Prefs.get('saveRelativeAttachmentPath')) {
|
||||
let baseDir = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
baseDir.persistentDescriptor = basePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
baseDir = null;
|
||||
}
|
||||
|
||||
if (baseDir) {
|
||||
let attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
attachmentFile.initWithPath(path);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
attachmentFile = null;
|
||||
}
|
||||
|
||||
if (attachmentFile && Zotero.File.directoryContains(baseDir, attachmentFile)) {
|
||||
path = Zotero.Attachments.BASE_PATH_PLACEHOLDER
|
||||
+ attachmentFile.getRelativeDescriptor(baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bindParams = [
|
||||
itemID,
|
||||
parent ? parent : null,
|
||||
|
@ -1916,6 +1953,41 @@ Zotero.Item.prototype.save = function() {
|
|||
var path = this.attachmentPath;
|
||||
var syncState = this.attachmentSyncState;
|
||||
|
||||
// Save attachment within attachment base directory as relative path
|
||||
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE && path) {
|
||||
let basePath = Zotero.Prefs.get('baseAttachmentPath');
|
||||
if (basePath != '' && Zotero.Prefs.get('saveRelativeAttachmentPath')) {
|
||||
let baseDir = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
baseDir.persistentDescriptor = basePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
baseDir = null;
|
||||
}
|
||||
|
||||
if (baseDir) {
|
||||
let attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
attachmentFile.initWithPath(path);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
attachmentFile = null;
|
||||
}
|
||||
|
||||
if (attachmentFile && Zotero.File.directoryContains(baseDir, attachmentFile)) {
|
||||
path = Zotero.Attachments.BASE_PATH_PLACEHOLDER
|
||||
+ attachmentFile.getRelativeDescriptor(baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bindParams = [
|
||||
parent ? parent : null,
|
||||
{ int: linkMode },
|
||||
|
@ -3172,20 +3244,58 @@ Zotero.Item.prototype.__defineGetter__('attachmentPath', function () {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (this._attachmentPath !== null) {
|
||||
return this._attachmentPath;
|
||||
}
|
||||
var pathIsRelative = false;
|
||||
|
||||
if (!this.id) {
|
||||
if (this._attachmentPath !== null) {
|
||||
pathIsRelative = this._attachmentPath.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0
|
||||
&& this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE;
|
||||
if (!pathIsRelative) {
|
||||
return this._attachmentPath;
|
||||
}
|
||||
}
|
||||
else if (!this.id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var sql = "SELECT path FROM itemAttachments WHERE itemID=?";
|
||||
var path = Zotero.DB.valueQuery(sql, this.id);
|
||||
if (!path) {
|
||||
path = '';
|
||||
else {
|
||||
var sql = "SELECT path FROM itemAttachments WHERE itemID=?";
|
||||
var path = Zotero.DB.valueQuery(sql, this.id);
|
||||
if (!path) {
|
||||
this._attachmentPath = '';
|
||||
return this._attachmentPath;
|
||||
}
|
||||
|
||||
this._attachmentPath = path;
|
||||
|
||||
pathIsRelative = path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0
|
||||
&& this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE;
|
||||
}
|
||||
this._attachmentPath = path;
|
||||
|
||||
if (pathIsRelative) {
|
||||
var basePath = Zotero.Prefs.get('baseAttachmentPath');
|
||||
// If the base path has been cleared, don't try to recreate the full attachment path
|
||||
if (basePath == '') {
|
||||
return '';
|
||||
}
|
||||
var baseDir = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
try {
|
||||
baseDir.persistentDescriptor = basePath;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
return '';
|
||||
}
|
||||
|
||||
var relativePath = this._attachmentPath.substr(
|
||||
Zotero.Attachments.BASE_PATH_PLACEHOLDER.length
|
||||
);
|
||||
var attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
attachmentFile.setRelativeDescriptor(baseDir,relativePath);
|
||||
return attachmentFile.persistentDescriptor;
|
||||
}
|
||||
|
||||
return path;
|
||||
});
|
||||
|
||||
|
@ -3215,6 +3325,21 @@ Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Force an update of the attachment path and resave the item
|
||||
*
|
||||
* This is used when changing the attachment base directory, since relative
|
||||
* path handling is done on item save.
|
||||
*/
|
||||
Zotero.Item.prototype.updateAttachmentPath = function () {
|
||||
if (!this._changedAttachmentData) {
|
||||
this._changedAttachmentData = {};
|
||||
}
|
||||
this._changedAttachmentData.path = true;
|
||||
this.save();
|
||||
};
|
||||
|
||||
|
||||
Zotero.Item.prototype.__defineGetter__('attachmentSyncState', function () {
|
||||
if (!this.isAttachment()) {
|
||||
return undefined;
|
||||
|
|
|
@ -453,7 +453,7 @@ Zotero.Tag.prototype.save = function (full) {
|
|||
pairs.push(itemID + "-" + tagID);
|
||||
}
|
||||
|
||||
var tempRemoved = removed;
|
||||
let tempRemoved = removed.concat();
|
||||
|
||||
do {
|
||||
var chunk = tempRemoved.splice(0, maxItems);
|
||||
|
|
|
@ -31,8 +31,14 @@ Zotero.Tags = new function() {
|
|||
Zotero.DataObjects.apply(this, ['tag']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
|
||||
this.MAX_COLORED_TAGS = 6;
|
||||
|
||||
var _tags = {}; // indexed by tag text
|
||||
var _colorsByItem = {};
|
||||
|
||||
var _libraryColors = [];
|
||||
var _libraryColorsByName = {};
|
||||
var _itemsListImagePromises = {};
|
||||
var _itemsListExtraImagePromises = {};
|
||||
|
||||
this.get = get;
|
||||
this.getName = getName;
|
||||
|
@ -258,7 +264,7 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
function getTagItems(tagID) {
|
||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||
return Zotero.DB.columnQuery(sql, tagID);
|
||||
return Zotero.DB.columnQuery(sql, tagID) || [];
|
||||
}
|
||||
|
||||
|
||||
|
@ -287,179 +293,545 @@ Zotero.Tags = new function() {
|
|||
}
|
||||
|
||||
|
||||
function rename(tagID, name) {
|
||||
Zotero.debug('Renaming tag', 4);
|
||||
|
||||
name = Zotero.Utilities.trim(name);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var tagObj = this.get(tagID);
|
||||
var libraryID = tagObj.libraryID;
|
||||
var oldName = tagObj.name;
|
||||
var oldType = tagObj.type;
|
||||
var notifierData = {};
|
||||
notifierData[tagID] = { old: tagObj.serialize() };
|
||||
|
||||
if (oldName == name) {
|
||||
Zotero.debug("Tag name hasn't changed", 2);
|
||||
Zotero.DB.commitTransaction();
|
||||
return;
|
||||
}
|
||||
|
||||
var sql = "SELECT tagID, name FROM tags WHERE name=? AND type=0 AND libraryID=?";
|
||||
var row = Zotero.DB.rowQuery(sql, [name, libraryID]);
|
||||
if (row) {
|
||||
var existingTagID = row.tagID;
|
||||
var existingName = row.name;
|
||||
}
|
||||
// New tag already exists as manual tag
|
||||
if (existingTagID
|
||||
// Tag check is case-insensitive, so make sure we have a different tag
|
||||
&& existingTagID != tagID) {
|
||||
/**
|
||||
* Rename a tag and update the tag colors setting accordingly if necessary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
function rename(tagID, newName) {
|
||||
var tagObj, libraryID, oldName, oldType, notifierData, self = this;
|
||||
return Q.fcall(function () {
|
||||
Zotero.debug('Renaming tag', 4);
|
||||
|
||||
var changed = false;
|
||||
var itemsAdded = false;
|
||||
newName = newName.trim();
|
||||
|
||||
// Change case of existing manual tag before switching automatic
|
||||
if (oldName.toLowerCase() == name.toLowerCase() || existingName != name) {
|
||||
var sql = "UPDATE tags SET name=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [name, existingTagID]);
|
||||
changed = true;
|
||||
tagObj = self.get(tagID);
|
||||
libraryID = tagObj.libraryID;
|
||||
oldName = tagObj.name;
|
||||
oldType = tagObj.type;
|
||||
notifierData = {};
|
||||
notifierData[tagID] = { old: tagObj.serialize() };
|
||||
|
||||
if (oldName == newName) {
|
||||
Zotero.debug("Tag name hasn't changed", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
var itemIDs = this.getTagItems(tagID);
|
||||
var existingItemIDs = this.getTagItems(existingTagID);
|
||||
// We need to know if the old tag has a color assigned so that
|
||||
// we can assign it to the new name
|
||||
return self.getColor(libraryID ? parseInt(libraryID) : 0, oldName);
|
||||
})
|
||||
.then(function (oldColorData) {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
// Would be easier to just call removeTag(tagID) and addTag(existingID)
|
||||
// here, but this is considerably more efficient
|
||||
var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [existingTagID, tagID]);
|
||||
|
||||
// Manual purge of old tag
|
||||
sql = "DELETE FROM tags WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
if (_tags[libraryID] && _tags[libraryID][oldType]) {
|
||||
delete _tags[libraryID][oldType]['_' + oldName];
|
||||
}
|
||||
delete this._objectCache[tagID];
|
||||
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
|
||||
|
||||
// Simulate tag removal on items that used old tag
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
itemTags.push(itemIDs[i] + '-' + tagID);
|
||||
}
|
||||
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
|
||||
|
||||
// And send tag add for new tag (except for those that already had it)
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
if (!existingItemIDs || existingItemIDs.indexOf(itemIDs[i]) == -1) {
|
||||
itemTags.push(itemIDs[i] + '-' + existingTagID);
|
||||
itemsAdded = true;
|
||||
}
|
||||
var sql = "SELECT tagID, name FROM tags WHERE name=? AND type=0 AND libraryID=?";
|
||||
var row = Zotero.DB.rowQuery(sql, [newName, libraryID]);
|
||||
if (row) {
|
||||
var existingTagID = row.tagID;
|
||||
var existingName = row.name;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (itemsAdded) {
|
||||
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
||||
// New tag already exists as manual tag
|
||||
if (existingTagID
|
||||
// Tag check is case-insensitive, so make sure we have a different tag
|
||||
&& existingTagID != tagID) {
|
||||
|
||||
var changed = false;
|
||||
var itemsAdded = false;
|
||||
|
||||
// Change case of existing manual tag before switching automatic
|
||||
if (oldName.toLowerCase() == newName.toLowerCase() || existingName != newName) {
|
||||
var sql = "UPDATE tags SET name=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [newName, existingTagID]);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Mark existing tag as updated
|
||||
sql = "UPDATE tags SET dateModified=CURRENT_TIMESTAMP, "
|
||||
+ "clientDateModified=CURRENT_TIMESTAMP WHERE tagID=?";
|
||||
Zotero.DB.query(sql, existingTagID);
|
||||
Zotero.Notifier.trigger('modify', 'tag', existingTagID);
|
||||
Zotero.Tags.reload(existingTagID);
|
||||
var itemIDs = self.getTagItems(tagID);
|
||||
var existingItemIDs = self.getTagItems(existingTagID);
|
||||
|
||||
// Would be easier to just call removeTag(tagID) and addTag(existingID)
|
||||
// here, but this is considerably more efficient
|
||||
var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [existingTagID, tagID]);
|
||||
|
||||
// Manual purge of old tag
|
||||
sql = "DELETE FROM tags WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
if (_tags[libraryID] && _tags[libraryID][oldType]) {
|
||||
delete _tags[libraryID][oldType]['_' + oldName];
|
||||
}
|
||||
delete self._objectCache[tagID];
|
||||
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
|
||||
|
||||
// Simulate tag removal on items that used old tag
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
itemTags.push(itemIDs[i] + '-' + tagID);
|
||||
}
|
||||
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
|
||||
|
||||
// And send tag add for new tag (except for those that already had it)
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
if (existingItemIDs.indexOf(itemIDs[i]) == -1) {
|
||||
itemTags.push(itemIDs[i] + '-' + existingTagID);
|
||||
itemsAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (itemsAdded) {
|
||||
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
||||
}
|
||||
|
||||
// Mark existing tag as updated
|
||||
sql = "UPDATE tags SET dateModified=CURRENT_TIMESTAMP, "
|
||||
+ "clientDateModified=CURRENT_TIMESTAMP WHERE tagID=?";
|
||||
Zotero.DB.query(sql, existingTagID);
|
||||
Zotero.Notifier.trigger('modify', 'tag', existingTagID);
|
||||
Zotero.Tags.reload(existingTagID);
|
||||
}
|
||||
|
||||
// TODO: notify linked items?
|
||||
//Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||
}
|
||||
else {
|
||||
tagObj.name = newName;
|
||||
// Set all renamed tags to manual
|
||||
tagObj.type = 0;
|
||||
tagObj.save();
|
||||
}
|
||||
|
||||
// TODO: notify linked items?
|
||||
//Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
if (oldColorData) {
|
||||
var libraryIDInt = libraryID ? parseInt(libraryID) : 0
|
||||
|
||||
// Remove color from old tag
|
||||
return self.setColor(libraryIDInt, oldName)
|
||||
// Add color to new tag
|
||||
.then(function () {
|
||||
return self.setColor(
|
||||
libraryIDInt,
|
||||
newName,
|
||||
oldColorData.color,
|
||||
oldColorData.position
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Integer} libraryID
|
||||
* @param {String} name Tag name
|
||||
* @return {Promise} A Q promise for the tag color as a hex string (e.g., '#990000')
|
||||
*/
|
||||
this.getColor = function (libraryID, name) {
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
return _libraryColorsByName[libraryID][name]
|
||||
? _libraryColorsByName[libraryID][name] : false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get color data by position (number key - 1)
|
||||
*
|
||||
* @param {Integer} libraryID
|
||||
* @param {Integer} position The position of the tag, starting at 0
|
||||
* @return {Promise} A Q promise for an object containing 'name' and 'color'
|
||||
*/
|
||||
this.getColorByPosition = function (libraryID, position) {
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
return _libraryColors[libraryID][position]
|
||||
? _libraryColors[libraryID][position] : false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Integer} libraryID
|
||||
* @return {Promise} A Q promise for an object with tag names as keys and
|
||||
* objects containing 'color' and 'position' as values
|
||||
*/
|
||||
this.getColors = function (libraryID) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
if (_libraryColorsByName[libraryID]) {
|
||||
return _libraryColorsByName[libraryID];
|
||||
}
|
||||
|
||||
return Zotero.SyncedSettings.get(libraryID, 'tagColors')
|
||||
.then(function (tagColors) {
|
||||
// If the colors became available from another run
|
||||
if (_libraryColorsByName[libraryID]) {
|
||||
return _libraryColorsByName[libraryID];
|
||||
}
|
||||
|
||||
tagColors = tagColors || [];
|
||||
|
||||
// Remove colors for tags that don't exist
|
||||
tagColors = tagColors.filter(function (val) {
|
||||
var tagIDs = self.getIDs(val.name, libraryID);
|
||||
// TEMP: handle future getIDs return format change
|
||||
return tagIDs && tagIDs.length;
|
||||
});
|
||||
|
||||
_libraryColors[libraryID] = tagColors;
|
||||
_libraryColorsByName[libraryID] = {};
|
||||
|
||||
// Also create object keyed by name for quick checking for individual tag colors
|
||||
for (var i=0; i<tagColors.length; i++) {
|
||||
_libraryColorsByName[libraryID][tagColors[i].name] = {
|
||||
color: tagColors[i].color,
|
||||
position: i
|
||||
};
|
||||
}
|
||||
|
||||
return _libraryColorsByName[libraryID];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assign a color to a tag
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
this.setColor = function (libraryID, name, color, position) {
|
||||
if (libraryID === null) {
|
||||
throw new Error("libraryID must be an integer");
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
var tagColors = _libraryColors[libraryID];
|
||||
var tagIDs = self.getIDs(name, libraryID);
|
||||
|
||||
// Just to be safe, remove colors for tags that don't exist
|
||||
tagColors = tagColors.filter(function (val) {
|
||||
// TEMP: handle future getIDs return format change
|
||||
return tagIDs && tagIDs.length;
|
||||
});
|
||||
|
||||
// Unset
|
||||
if (!color) {
|
||||
// Trying to clear color on tag that doesn't have one
|
||||
if (!_libraryColorsByName[libraryID][name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
tagColors = tagColors.filter(function (val) val.name != name);
|
||||
}
|
||||
else {
|
||||
// Get current position if present
|
||||
var currentPosition = -1;
|
||||
for (var i=0; i<tagColors.length; i++) {
|
||||
if (tagColors[i].name == name) {
|
||||
currentPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove if present
|
||||
if (currentPosition != -1) {
|
||||
// If no position was specified, we'll reinsert into the same place
|
||||
if (typeof position == 'undefined') {
|
||||
position = currentPosition;
|
||||
}
|
||||
tagColors.splice(currentPosition, 1);
|
||||
}
|
||||
var newObj = {
|
||||
name: name,
|
||||
color: color
|
||||
};
|
||||
// If no position or after end, add at end
|
||||
if (typeof position == 'undefined' || position >= tagColors.length) {
|
||||
tagColors.push(newObj);
|
||||
}
|
||||
// Otherwise insert into new position
|
||||
else {
|
||||
tagColors.splice(position, 0, newObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagColors.length) {
|
||||
return Zotero.SyncedSettings.set(libraryID, 'tagColors', tagColors);
|
||||
}
|
||||
else {
|
||||
return Zotero.SyncedSettings.set(libraryID, 'tagColors');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update caches and trigger redrawing of items in the items list
|
||||
* when a 'tagColors' setting is modified
|
||||
*/
|
||||
this.notify = function (event, type, ids, extraData) {
|
||||
if (type != 'setting') {
|
||||
return;
|
||||
}
|
||||
|
||||
tagObj.name = name;
|
||||
// Set all renamed tags to manual
|
||||
tagObj.type = 0;
|
||||
tagObj.save();
|
||||
var self = this;
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.getColor = function (name) {
|
||||
var tagColors = this.getColors();
|
||||
return tagColors[name] ? tagColors[name] : '#000000';
|
||||
}
|
||||
|
||||
|
||||
this.getColors = function (name) {
|
||||
var tagColors = Zotero.Prefs.get('tagColors');
|
||||
return tagColors ? JSON.parse(tagColors) : {};
|
||||
}
|
||||
|
||||
|
||||
this.getItemColor = function (itemID) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init library tag colors if not yet done
|
||||
var libraryID = item.libraryID ? item.libraryID : 0;
|
||||
if (!_colorsByItem[libraryID]) {
|
||||
_colorsByItem[libraryID] = {};
|
||||
var tagColors = this.getColors();
|
||||
for (var name in tagColors) {
|
||||
var color = tagColors[name];
|
||||
var tagIDs = Zotero.Tags.getIDs(name, libraryID);
|
||||
if (!tagIDs) {
|
||||
continue;
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
let libraryID, setting;
|
||||
[libraryID, setting] = ids[i].split("/");
|
||||
libraryID = parseInt(libraryID);
|
||||
|
||||
if (setting != 'tagColors') {
|
||||
continue;
|
||||
}
|
||||
|
||||
delete _libraryColors[libraryID];
|
||||
delete _libraryColorsByName[libraryID];
|
||||
|
||||
// Get the tag colors for each library in which they were modified
|
||||
Zotero.SyncedSettings.get(libraryID, 'tagColors')
|
||||
.then(function (tagColors) {
|
||||
if (!tagColors) {
|
||||
tagColors = [];
|
||||
}
|
||||
for each(var tagID in tagIDs) {
|
||||
var tag = Zotero.Tags.get(tagID);
|
||||
var itemIDs = tag.getLinkedItems(true);
|
||||
if (!itemIDs) {
|
||||
continue;
|
||||
|
||||
let id = libraryID + "/" + setting;
|
||||
if (event == 'modify' && extraData[id].changed) {
|
||||
var previousTagColors = extraData[id].changed.value;
|
||||
}
|
||||
else {
|
||||
var previousTagColors = [];
|
||||
}
|
||||
|
||||
var affectedItems = [];
|
||||
|
||||
// Get all items linked to previous or current tag colors
|
||||
var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
|
||||
tagNames = Zotero.Utilities.arrayUnique(tagNames);
|
||||
for (let i=0; i<tagNames.length; i++) {
|
||||
let tagIDs = self.getIDs(tagNames[i], libraryID) || [];
|
||||
for (let i=0; i<tagIDs.length; i++) {
|
||||
affectedItems = affectedItems.concat(self.getTagItems(tagIDs[i]));
|
||||
}
|
||||
for each(var id in itemIDs) {
|
||||
_colorsByItem[libraryID][id] = color;
|
||||
};
|
||||
|
||||
if (affectedItems.length) {
|
||||
Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' });
|
||||
}
|
||||
})
|
||||
.done();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.toggleItemsListTags = function (libraryID, items, name) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
var tagIDs = self.getIDs(name, libraryID);
|
||||
var tags = tagIDs.map(function (tagID) {
|
||||
return Zotero.Tags.get(tagID, true);
|
||||
});
|
||||
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
// Base our action on the first item. If it has the tag,
|
||||
// remove the tag from all items. If it doesn't, add it to all.
|
||||
var firstItem = items[0];
|
||||
// Remove from all items
|
||||
if (firstItem.hasTags(tagIDs)) {
|
||||
for (var i=0; i<items.length; i++) {
|
||||
for (var j=0; j<tags.length; j++) {
|
||||
tags[j].removeItem(items[i].id);
|
||||
}
|
||||
}
|
||||
tags.forEach(function (tag) tag.save());
|
||||
Zotero.Prefs.set('purge.tags', true);
|
||||
}
|
||||
// Add to all items
|
||||
else {
|
||||
for (var i=0; i<items.length; i++) {
|
||||
items[i].addTag(name);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A tree cell can show only one image, and (as of Fx19) it can't be an SVG,
|
||||
* so we need to generate a composite image containing the existing item type
|
||||
* icon and one or more tag color swatches.
|
||||
*
|
||||
* @params {Array} colors Array of swatch colors
|
||||
* @params {String} extraImage Chrome URL of image to add to final image
|
||||
* @return {Q Promise} A Q promise for a data: URL for a PNG
|
||||
*/
|
||||
this.generateItemsListImage = function (colors, extraImage) {
|
||||
var swatchWidth = 8;
|
||||
var separator = 3;
|
||||
var extraImageSeparator = 1;
|
||||
var extraImageWidth = 16;
|
||||
var canvasHeight = 16;
|
||||
var swatchHeight = 8;
|
||||
var prependExtraImage = true;
|
||||
|
||||
var hash = colors.join("") + (extraImage ? extraImage : "");
|
||||
|
||||
if (_itemsListImagePromises[hash]) {
|
||||
return _itemsListImagePromises[hash];
|
||||
}
|
||||
|
||||
return _colorsByItem[libraryID][itemID] ? _colorsByItem[libraryID][itemID] : false;
|
||||
}
|
||||
|
||||
|
||||
this.setColor = function (name, color) {
|
||||
var tagColors = this.getColors();
|
||||
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Components.interfaces.nsIAppShellService)
|
||||
.hiddenDOMWindow;
|
||||
var doc = win.document;
|
||||
var canvas = doc.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
|
||||
|
||||
// Unset
|
||||
if (!color || color == '#000000') {
|
||||
delete tagColors[name];
|
||||
var width = colors.length * (swatchWidth + separator);
|
||||
if (extraImage) {
|
||||
width += (colors.length ? extraImageSeparator : 0) + extraImageWidth;
|
||||
}
|
||||
else if (colors.length) {
|
||||
width -= separator;
|
||||
}
|
||||
canvas.width = width;
|
||||
canvas.height = canvasHeight;
|
||||
var swatchTop = Math.floor((canvasHeight - swatchHeight) / 2);
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var x = prependExtraImage ? extraImageWidth + separator + extraImageSeparator : 0;
|
||||
for (let i=0, len=colors.length; i<len; i++) {
|
||||
ctx.fillStyle = colors[i];
|
||||
_canvasRoundRect(ctx, x, swatchTop + 1, swatchWidth, swatchHeight, 2, true, false)
|
||||
x += swatchWidth + separator;
|
||||
}
|
||||
|
||||
// If there's no extra iamge, resolve a promise now
|
||||
if (!extraImage) {
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
return dataURIPromise;
|
||||
}
|
||||
|
||||
// Add an extra image to the beginning or end of the swatches
|
||||
if (prependExtraImage) {
|
||||
x = 0;
|
||||
}
|
||||
else {
|
||||
tagColors[name] = color;
|
||||
x += extraImageSeparator;
|
||||
}
|
||||
|
||||
tagColors = JSON.stringify(tagColors);
|
||||
Zotero.Prefs.set('tagColors', tagColors);
|
||||
// If extra image hasn't started loading, start now
|
||||
if (typeof _itemsListExtraImagePromises[extraImage] == 'undefined') {
|
||||
let ios = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces["nsIIOService"]);
|
||||
let uri = ios.newURI(extraImage, null, null);
|
||||
uri = Components.classes['@mozilla.org/chrome/chrome-registry;1']
|
||||
.getService(Components.interfaces["nsIChromeRegistry"])
|
||||
.convertChromeURL(uri);
|
||||
|
||||
let file = uri.QueryInterface(Components.interfaces.nsIFileURL).file;
|
||||
var img = new win.Image();
|
||||
img.src = Zotero.File.generateDataURI(file, "image/png");
|
||||
|
||||
// Mark that we've started loading
|
||||
var deferred = Q.defer();
|
||||
var extraImageDeferred = Q.defer();
|
||||
_itemsListExtraImagePromises[extraImage] = extraImageDeferred.promise;
|
||||
|
||||
// When extra image has loaded, draw it
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
|
||||
// Fulfill the promise for this call
|
||||
deferred.resolve(dataURI);
|
||||
|
||||
// And resolve the extra image's promise to fulfill
|
||||
// other promises waiting on it
|
||||
extraImageDeferred.resolve(img);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
_reloadTagColors();
|
||||
Zotero.Notifier.trigger('redraw', 'item', []);
|
||||
// If extra image has already started loading, return a promise
|
||||
// for the composite image once it's ready
|
||||
return _itemsListExtraImagePromises[extraImage]
|
||||
.then(function (img) {
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
return dataURIPromise;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _reloadTagColors() {
|
||||
_colorsByItem = {};
|
||||
/**
|
||||
* From http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
|
||||
*
|
||||
* Draws a rounded rectangle using the current state of the canvas.
|
||||
* If you omit the last three params, it will draw a rectangle
|
||||
* outline with a 5 pixel border radius
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Number} x The top left x coordinate
|
||||
* @param {Number} y The top left y coordinate
|
||||
* @param {Number} width The width of the rectangle
|
||||
* @param {Number} height The height of the rectangle
|
||||
* @param {Number} radius The corner radius. Defaults to 5;
|
||||
* @param {Boolean} fill Whether to fill the rectangle. Defaults to false.
|
||||
* @param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
|
||||
*/
|
||||
function _canvasRoundRect(ctx, x, y, width, height, radius, fill, stroke) {
|
||||
if (typeof stroke == "undefined" ) {
|
||||
stroke = true;
|
||||
}
|
||||
if (typeof radius === "undefined") {
|
||||
radius = 5;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
ctx.closePath();
|
||||
if (stroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
if (fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
function erase(ids) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
|
@ -563,7 +935,6 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
this._reload = function (ids) {
|
||||
_tags = {};
|
||||
_reloadTagColors();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -117,8 +117,8 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
}
|
||||
|
||||
str = Zotero.Utilities.removeDiacritics(str)
|
||||
.replace(/[!-/:-@[-`{-~]/g, ' ') // Convert (ASCII) punctuation to spaces
|
||||
.replace(/ +/, ' ') // Normalize spaces
|
||||
.replace(/[ !-/:-@[-`{-~]+/g, ' ') // Convert (ASCII) punctuation to spaces
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
return str;
|
||||
|
@ -194,7 +194,7 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
var isbnCache = {};
|
||||
if (rows) {
|
||||
for each(var row in rows) {
|
||||
isbnCache[row.itemID] = row.value;
|
||||
isbnCache[row.itemID] = (row.value+'').replace(/[^\dX]+/ig, '').toUpperCase(); //ignore formatting
|
||||
}
|
||||
}
|
||||
processRows();
|
||||
|
@ -209,7 +209,7 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
var doiCache = {};
|
||||
if (rows) {
|
||||
for each(var row in rows) {
|
||||
doiCache[row.itemID] = row.value;
|
||||
doiCache[row.itemID] = row.value.trim();
|
||||
}
|
||||
}
|
||||
processRows();
|
||||
|
@ -241,12 +241,22 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND fieldID BETWEEN 110 AND 113 "
|
||||
+ "AND itemTypeID NOT IN (1, 14) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ "ORDER BY value COLLATE locale";
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems)";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID]);
|
||||
//normalize all values ahead of time
|
||||
rows = rows.map(function(row) {
|
||||
row.value = normalizeString(row.value);
|
||||
return row;
|
||||
});
|
||||
//sort rows by normalized values
|
||||
rows = rows.sort(function(a, b) {
|
||||
if(a.value === b.value) return 0;
|
||||
if(a.value < b.value) return -1;
|
||||
return 1;
|
||||
});
|
||||
processRows(function (a, b) {
|
||||
var aTitle = normalizeString(a.value);
|
||||
var bTitle = normalizeString(b.value);
|
||||
var aTitle = a.value;
|
||||
var bTitle = b.value;
|
||||
|
||||
// If we stripped one of the strings completely, we can't compare them
|
||||
if (aTitle.length == 0 || bTitle.length == 0) {
|
||||
|
@ -254,31 +264,32 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
}
|
||||
|
||||
if (aTitle !== bTitle) {
|
||||
return -1;
|
||||
return -1; //everything is sorted by title, so if this mismatches, everything following will too
|
||||
}
|
||||
|
||||
// If both items have a DOI and they don't match, it's not a dupe
|
||||
if (typeof doiCache[a.itemID] != 'undefined'
|
||||
&& typeof doiCache[b.itemID] != 'undefined'
|
||||
&& doiCache[a.itemID] != doiCache[b.itemID]) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If both items have an ISBN and they don't match, it's not a dupe
|
||||
if (typeof isbnCache[a.itemID] != 'undefined'
|
||||
&& typeof isbnCache[b.itemID] != 'undefined'
|
||||
&& isbnCache[a.itemID] != isbnCache[b.itemID]) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If both items have a year and they're off by more than one, it's not a dupe
|
||||
if (typeof yearCache[a.itemID] != 'undefined'
|
||||
&& typeof yearCache[b.itemID] != 'undefined'
|
||||
&& Math.abs(yearCache[a.itemID] - yearCache[b.itemID]) > 1) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for at least one match on last name + first initial of first name
|
||||
var aCreatorRows, bCreatorRows;
|
||||
if (typeof creatorRowsCache[a.itemID] != 'undefined') {
|
||||
aCreatorRows = creatorRowsCache[a.itemID];
|
||||
}
|
||||
|
@ -287,7 +298,7 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "JOIN creators USING (creatorID) "
|
||||
+ "JOIN creatorData USING (creatorDataID) "
|
||||
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
|
||||
var aCreatorRows = Zotero.DB.query(sql, a.itemID);
|
||||
aCreatorRows = Zotero.DB.query(sql, a.itemID);
|
||||
creatorRowsCache[a.itemID] = aCreatorRows;
|
||||
}
|
||||
|
||||
|
@ -300,12 +311,12 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "JOIN creators USING (creatorID) "
|
||||
+ "JOIN creatorData USING (creatorDataID) "
|
||||
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
|
||||
var bCreatorRows = Zotero.DB.query(sql, b.itemID);
|
||||
bCreatorRows = Zotero.DB.query(sql, b.itemID);
|
||||
creatorRowsCache[b.itemID] = bCreatorRows;
|
||||
}
|
||||
|
||||
// Match if no creators
|
||||
if (!aCreatorRows && !bCreatorRows.length) {
|
||||
if (!aCreatorRows && !bCreatorRows) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -315,11 +326,11 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
|
||||
for each(var aCreatorRow in aCreatorRows) {
|
||||
var aLastName = normalizeString(aCreatorRow.lastName);
|
||||
var aFirstInitial = aCreatorRow.fieldMode == 0 ? normalizeString(aCreatorRow.firstName.substr(1)) : false;
|
||||
var aFirstInitial = aCreatorRow.fieldMode == 0 ? normalizeString(aCreatorRow.firstName).charAt(0) : false;
|
||||
|
||||
for each(var bCreatorRow in bCreatorRows) {
|
||||
var bLastName = normalizeString(bCreatorRow.lastName);
|
||||
var bFirstInitial = bCreatorRow.fieldMode == 0 ? normalizeString(bCreatorRow.firstName.substr(1)) : false;
|
||||
var bFirstInitial = bCreatorRow.fieldMode == 0 ? normalizeString(bCreatorRow.firstName).charAt(0) : false;
|
||||
|
||||
if (aLastName === bLastName && aFirstInitial === bFirstInitial) {
|
||||
return 1;
|
||||
|
|
|
@ -231,6 +231,27 @@ Zotero.File = new function(){
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generate a data: URI from an nsIFile
|
||||
*
|
||||
* From https://developer.mozilla.org/en-US/docs/data_URIs
|
||||
*/
|
||||
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, 0600, 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;
|
||||
}
|
||||
|
||||
|
||||
function copyToUnique(file, newFile) {
|
||||
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
||||
var newName = newFile.leafName;
|
||||
|
@ -268,6 +289,32 @@ Zotero.File = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a directory is an ancestor directory of another directory/file
|
||||
*/
|
||||
this.directoryContains = function (dir, file) {
|
||||
if (!dir.isDirectory()) {
|
||||
throw new Error("dir must be a directory");
|
||||
}
|
||||
|
||||
if (dir.exists()) {
|
||||
dir.normalize();
|
||||
}
|
||||
if (file.exists()) {
|
||||
file.normalize();
|
||||
}
|
||||
|
||||
if (!dir.path) {
|
||||
throw new Error("dir.path is empty");
|
||||
}
|
||||
if (!file.path) {
|
||||
throw new Error("file.path is empty");
|
||||
}
|
||||
|
||||
return file.path.indexOf(dir.path) == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strip potentially invalid characters
|
||||
*
|
||||
|
|
|
@ -2155,6 +2155,9 @@ Zotero.Integration.Session.prototype.getCitationField = function(citation) {
|
|||
serializeCitationItem.id = citationItem.itemData.id;
|
||||
serializeCitationItem.uris = citationItem.uris;
|
||||
|
||||
// XXX For compatibility with older versions of Zotero; to be removed at a later date
|
||||
serializeCitationItem.uri = serializeCitationItem.uris;
|
||||
|
||||
// always store itemData, since we have no way to get it back otherwise
|
||||
serializeCitationItem.itemData = citationItem.itemData;
|
||||
addSchema = true;
|
||||
|
|
|
@ -37,8 +37,10 @@
|
|||
Zotero.ItemTreeView = function(itemGroup, sourcesOnly)
|
||||
{
|
||||
this.wrappedJSObject = this;
|
||||
this.rowCount = 0;
|
||||
|
||||
this._initialized = false;
|
||||
this._skipKeypress = false;
|
||||
|
||||
this._itemGroup = itemGroup;
|
||||
this._sourcesOnly = sourcesOnly;
|
||||
|
@ -50,7 +52,7 @@ Zotero.ItemTreeView = function(itemGroup, sourcesOnly)
|
|||
this._needsSort = false;
|
||||
|
||||
this._dataItems = [];
|
||||
this.rowCount = 0;
|
||||
this._itemImages = {};
|
||||
|
||||
this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item', 'share-items', 'bucket']);
|
||||
}
|
||||
|
@ -136,44 +138,118 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
|
|||
|
||||
// Add a keypress listener for expand/collapse
|
||||
var tree = this._treebox.treeBody.parentNode;
|
||||
var me = this;
|
||||
var self = this;
|
||||
var coloredTagsRE = new RegExp("^[1-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1}$");
|
||||
var listener = function(event) {
|
||||
if (self._skipKeyPress) {
|
||||
self._skipKeyPress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle arrow keys specially on multiple selection, since
|
||||
// otherwise the tree just applies it to the last-selected row
|
||||
if (event.keyCode == 39 || event.keyCode == 37) {
|
||||
if (me._treebox.view.selection.count > 1) {
|
||||
if (self._treebox.view.selection.count > 1) {
|
||||
switch (event.keyCode) {
|
||||
case 39:
|
||||
me.expandSelectedRows();
|
||||
self.expandSelectedRows();
|
||||
break;
|
||||
|
||||
case 37:
|
||||
me.collapseSelectedRows();
|
||||
self.collapseSelectedRows();
|
||||
break;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var key = String.fromCharCode(event.which);
|
||||
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
me.expandAllRows();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
me.collapseAllRows();
|
||||
event.preventDefault();
|
||||
// Ignore other non-character keypresses
|
||||
if (!event.charCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
Q.fcall(function () {
|
||||
var key = String.fromCharCode(event.which);
|
||||
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
self.expandAllRows();
|
||||
return false;
|
||||
}
|
||||
else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) {
|
||||
self.collapseAllRows();
|
||||
return false;
|
||||
}
|
||||
else if (coloredTagsRE.test(key)) {
|
||||
let libraryID = self._itemGroup.libraryID;
|
||||
libraryID = libraryID ? parseInt(libraryID) : 0;
|
||||
let position = parseInt(key) - 1;
|
||||
return Zotero.Tags.getColorByPosition(libraryID, position)
|
||||
.then(function (colorData) {
|
||||
// If a color isn't assigned to this number, allow key navigation,
|
||||
// though I'm not sure this is a good idea.
|
||||
if (!colorData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var items = self.getSelectedItems();
|
||||
return Zotero.Tags.toggleItemsListTags(libraryID, items, colorData.name)
|
||||
.then(function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return true;
|
||||
})
|
||||
// We have to disable key navigation on the tree in order to
|
||||
// keep it from acting on the 1-6 keys used for colored tags.
|
||||
// To allow navigation with other keys, we temporarily enable
|
||||
// key navigation and recreate the keyboard event. Since
|
||||
// that will trigger this listener again, we set a flag to
|
||||
// ignore the event, and then clear the flag above when the
|
||||
// event comes in. I see no way this could go wrong...
|
||||
.then(function (resend) {
|
||||
if (!resend) {
|
||||
return;
|
||||
}
|
||||
|
||||
tree.disableKeyNavigation = false;
|
||||
self._skipKeyPress = true;
|
||||
var nsIDWU = Components.interfaces.nsIDOMWindowUtils
|
||||
var domWindowUtils = event.originalTarget.ownerDocument.defaultView
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(nsIDWU);
|
||||
var modifiers = 0;
|
||||
if (event.ctrlKey) {
|
||||
modifiers |= nsIDWU.MODIFIER_CTRL;
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
modifiers |= nsIDWU.MODIFIER_SHIFT;
|
||||
}
|
||||
if (event.metaKey) {
|
||||
modifiers |= nsIDWU.MODIFIER_META;
|
||||
}
|
||||
domWindowUtils.sendKeyEvent(
|
||||
'keypress',
|
||||
event.keyCode,
|
||||
event.charCode,
|
||||
modifiers
|
||||
);
|
||||
tree.disableKeyNavigation = true;
|
||||
})
|
||||
.catch(function (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
})
|
||||
.done();
|
||||
};
|
||||
// Store listener so we can call removeEventListener()
|
||||
// in overlay.js::onCollectionSelected()
|
||||
this.listener = listener;
|
||||
tree.addEventListener('keypress', listener, false);
|
||||
tree.addEventListener('keypress', listener);
|
||||
|
||||
// This seems to be the only way to prevent Enter/Return
|
||||
// from toggle row open/close. The event is handled by
|
||||
// handleKeyPress() in zoteroPane.js.
|
||||
|
@ -259,7 +335,7 @@ Zotero.ItemTreeView.prototype._refreshGenerator = function()
|
|||
var field = visibleFields[i];
|
||||
switch (field) {
|
||||
case 'hasAttachment':
|
||||
case 'hasNote':
|
||||
case 'numNotes':
|
||||
continue;
|
||||
|
||||
case 'year':
|
||||
|
@ -371,17 +447,22 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
'zotero-items-column-' + extraData.column
|
||||
);
|
||||
for each(var id in ids) {
|
||||
if (extraData.column == 'title') {
|
||||
delete this._itemImages[id];
|
||||
}
|
||||
this._treebox.invalidateCell(this._itemRowMap[id], col);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for each(var id in ids) {
|
||||
delete this._itemImages[id];
|
||||
this._treebox.invalidateRow(this._itemRowMap[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Redraw the whole tree
|
||||
else {
|
||||
this._itemImages = {};
|
||||
this._treebox.invalidate();
|
||||
}
|
||||
return;
|
||||
|
@ -507,7 +588,14 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
for each(var item in items) {
|
||||
var id = item.id;
|
||||
|
||||
// Make sure row map is up to date
|
||||
// if we made changes in a previous loop
|
||||
if (madeChanges) {
|
||||
this._refreshHashMap();
|
||||
}
|
||||
var row = this._itemRowMap[id];
|
||||
// Clear item type icon and tag colors
|
||||
delete this._itemImages[id];
|
||||
|
||||
// Deleted items get a modify that we have to ignore when
|
||||
// not viewing the trash
|
||||
|
@ -809,7 +897,7 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
|
|||
var val;
|
||||
|
||||
// Image only
|
||||
if (column.id === "zotero-items-column-hasAttachment" || column.id === "zotero-items-column-hasNote") {
|
||||
if (column.id === "zotero-items-column-hasAttachment") {
|
||||
return;
|
||||
}
|
||||
else if(column.id == "zotero-items-column-type")
|
||||
|
@ -820,6 +908,9 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
|
|||
else if (column.id == "zotero-items-column-year") {
|
||||
val = obj.getField('date', true).substr(0, 4)
|
||||
}
|
||||
else if (column.id === "zotero-items-column-numNotes") {
|
||||
val = obj.numNotes();
|
||||
}
|
||||
else {
|
||||
var col = column.id.substring(20);
|
||||
|
||||
|
@ -893,7 +984,51 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
|
|||
{
|
||||
if(col.id == 'zotero-items-column-title')
|
||||
{
|
||||
return this._getItemAtRow(row).ref.getImageSrc();
|
||||
// Get item type icon and tag swatches
|
||||
var item = this._getItemAtRow(row).ref;
|
||||
var itemID = item.id;
|
||||
if (this._itemImages[itemID]) {
|
||||
return this._itemImages[itemID];
|
||||
}
|
||||
var uri = item.getImageSrc();
|
||||
var tags = item.getTags();
|
||||
if (!tags.length) {
|
||||
this._itemImages[itemID] = uri;
|
||||
return uri;
|
||||
}
|
||||
|
||||
//Zotero.debug("Generating tree image for item " + itemID);
|
||||
|
||||
var colorData = [];
|
||||
for (let i=0, len=tags.length; i<len; i++) {
|
||||
let libraryIDInt = item.libraryIDInt; // TEMP
|
||||
colorData.push(Zotero.Tags.getColor(libraryIDInt, tags[i].name));
|
||||
}
|
||||
var self = this;
|
||||
Q.all(colorData)
|
||||
.then(function (colorData) {
|
||||
colorData = colorData.filter(function (val) val !== false);
|
||||
if (!colorData.length) {
|
||||
return false;
|
||||
}
|
||||
colorData.sort(function (a, b) {
|
||||
return a.position - b.position;
|
||||
});
|
||||
var colors = colorData.map(function (val) val.color);
|
||||
return Zotero.Tags.generateItemsListImage(colors, uri);
|
||||
})
|
||||
// When the promise is fulfilled, the data URL is ready, so invalidate
|
||||
// the cell to force requesting it again
|
||||
.then(function (dataURL) {
|
||||
self._itemImages[itemID] = dataURL ? dataURL : uri;
|
||||
if (dataURL) {
|
||||
self._treebox.invalidateCell(row, col);
|
||||
}
|
||||
})
|
||||
.done();
|
||||
|
||||
this._itemImages[itemID] = uri;
|
||||
return uri;
|
||||
}
|
||||
else if (col.id == 'zotero-items-column-hasAttachment') {
|
||||
if (this._itemGroup.isTrash()) return false;
|
||||
|
@ -929,19 +1064,6 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (col.id == 'zotero-items-column-hasNote') {
|
||||
if (this._itemGroup.isTrash()) return false;
|
||||
|
||||
var treerow = this._getItemAtRow(row);
|
||||
if (treerow.level === 0 && treerow.ref.isRegularItem() && treerow.ref.numNotes(false, true)) {
|
||||
return "chrome://zotero/skin/bullet_yellow.png";
|
||||
}
|
||||
else if (treerow.ref.isAttachment()) {
|
||||
if (treerow.ref.hasNote()) {
|
||||
return "chrome://zotero/skin/bullet_yellow.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.prototype.isContainer = function(row)
|
||||
|
@ -1186,10 +1308,15 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
|
|||
|
||||
case 'hasAttachment':
|
||||
getField = function (row) {
|
||||
if (!row.ref.isRegularItem()) {
|
||||
if (row.ref.isAttachment()) {
|
||||
var state = row.ref.fileExists ? 1 : -1;
|
||||
}
|
||||
else if (row.ref.isRegularItem()) {
|
||||
var state = row.ref.getBestAttachmentState();
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
var state = row.ref.getBestAttachmentState();
|
||||
// Make sort order present, missing, empty when ascending
|
||||
if (state === -1) {
|
||||
state = 2;
|
||||
|
@ -1198,12 +1325,11 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
|
|||
};
|
||||
break;
|
||||
|
||||
case 'hasNote':
|
||||
case 'numNotes':
|
||||
getField = function (row) {
|
||||
if (!row.ref.isRegularItem()) {
|
||||
return 0;
|
||||
}
|
||||
return row.ref.numNotes(false, true) ? 1 : 0;
|
||||
// Sort descending by default
|
||||
order = !order;
|
||||
return row.numNotes(false, true) || 0;
|
||||
};
|
||||
break;
|
||||
|
||||
|
@ -2825,37 +2951,12 @@ Zotero.ItemTreeView.prototype.onDragExit = function (event) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Zotero.ItemTreeView.prototype.isSeparator = function(row) { return false; }
|
||||
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {
|
||||
var treeRow = this._getItemAtRow(row);
|
||||
var itemID = treeRow.ref.id;
|
||||
|
||||
// Set background color for selected items with colored tags
|
||||
if (this.selection.isSelected(row)) {
|
||||
if (color = Zotero.Tags.getItemColor(itemID)) {
|
||||
var aServ = Components.classes["@mozilla.org/atom-service;1"].
|
||||
getService(Components.interfaces.nsIAtomService);
|
||||
prop.AppendElement(aServ.getAtom("color" + color.substr(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) { }
|
||||
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {}
|
||||
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) {}
|
||||
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
|
||||
var treeRow = this._getItemAtRow(row);
|
||||
var itemID = treeRow.ref.id;
|
||||
|
||||
// Set tag colors
|
||||
//
|
||||
// Don't set the text color if the row is selected, in which case the background
|
||||
// color is set in getRowProperties() instead, unless the tree isn't focused,
|
||||
// in which case it's not
|
||||
if (!this.selection.isSelected(row) || !this._treebox.focused) {
|
||||
if (color = Zotero.Tags.getItemColor(itemID)) {
|
||||
var aServ = Components.classes["@mozilla.org/atom-service;1"].
|
||||
getService(Components.interfaces.nsIAtomService);
|
||||
prop.AppendElement(aServ.getAtom("color" + color.substr(1)));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark items not matching search as context rows, displayed in gray
|
||||
if (this._searchMode && !this._searchItemIDs[itemID]) {
|
||||
var aServ = Components.classes["@mozilla.org/atom-service;1"].
|
||||
|
@ -2900,3 +3001,10 @@ Zotero.ItemTreeView.TreeRow.prototype.getField = function(field, unformatted)
|
|||
{
|
||||
return this.ref.getField(field, unformatted, true);
|
||||
}
|
||||
|
||||
Zotero.ItemTreeView.TreeRow.prototype.numNotes = function() {
|
||||
if (!this.ref.isRegularItem()) {
|
||||
return '';
|
||||
}
|
||||
return this.ref.numNotes(false, true) || '';
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ Zotero.Notifier = new function(){
|
|||
var _disabled = false;
|
||||
var _types = [
|
||||
'collection', 'creator', 'search', 'share', 'share-items', 'item',
|
||||
'collection-item', 'item-tag', 'tag', 'group', 'trash', 'bucket', 'relation'
|
||||
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
|
||||
];
|
||||
var _inTransaction;
|
||||
var _locked = false;
|
||||
|
|
|
@ -115,6 +115,7 @@ Zotero.Schema = new function(){
|
|||
*/
|
||||
this.updateSchema = function () {
|
||||
var dbVersion = this.getDBVersion('userdata');
|
||||
var dbVersion2 = this.getDBVersion('userdata2');
|
||||
|
||||
// 'schema' check is for old (<= 1.0b1) schema system,
|
||||
// 'user' is for pre-1.0b2 'user' table
|
||||
|
@ -133,7 +134,7 @@ Zotero.Schema = new function(){
|
|||
if (dbVersion < schemaVersion){
|
||||
Zotero.DB.backupDatabase(dbVersion);
|
||||
Zotero.wait(1000);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
|
@ -154,19 +155,20 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
var up2 = _updateSchema('system');
|
||||
var up1 = _updateSchema('system');
|
||||
// Update custom tables if they exist so that changes are in place before user data migration
|
||||
if (Zotero.DB.tableExists('customItemTypes')) {
|
||||
this.updateCustomTables(up2);
|
||||
}
|
||||
if(up2) Zotero.wait();
|
||||
var up1 = _migrateUserDataSchema(dbVersion);
|
||||
var up3 = _updateSchema('triggers');
|
||||
// Update custom tables again in case custom fields were changed during user data migration
|
||||
if (up1) {
|
||||
this.updateCustomTables();
|
||||
this.updateCustomTables(up1);
|
||||
}
|
||||
if(up1) Zotero.wait();
|
||||
var up2 = _migrateUserDataSchema(dbVersion);
|
||||
var up3 = _migrateUserDataSchemaSilent(dbVersion2);
|
||||
var up4 = _updateSchema('triggers');
|
||||
if (up2) {
|
||||
// Update custom tables again in case custom fields were changed during user data migration
|
||||
this.updateCustomTables();
|
||||
Zotero.wait()
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
@ -176,7 +178,7 @@ Zotero.Schema = new function(){
|
|||
throw(e);
|
||||
}
|
||||
|
||||
if (up1) {
|
||||
if (up2) {
|
||||
// Upgrade seems to have been a success -- delete any previous backups
|
||||
var maxPrevious = dbVersion - 1;
|
||||
var file = Zotero.getZoteroDirectory();
|
||||
|
@ -1353,10 +1355,21 @@ Zotero.Schema = new function(){
|
|||
* Retrieve the version from the top line of the schema SQL file
|
||||
*/
|
||||
function _getSchemaSQLVersion(schema){
|
||||
// TEMP
|
||||
if (schema == 'userdata2') {
|
||||
schema = 'userdata';
|
||||
var newUserdata = true;
|
||||
}
|
||||
var sql = _getSchemaSQL(schema);
|
||||
|
||||
// Fetch the schema version from the first line of the file
|
||||
var schemaVersion = sql.match(/^-- ([0-9]+)/)[1];
|
||||
var schemaVersion = parseInt(sql.match(/^-- ([0-9]+)/)[1]);
|
||||
|
||||
// TEMP: For 'userdata', cap the version at 76
|
||||
// For 'userdata2', versions > 76 are allowed.
|
||||
if (schema == 'userdata' && !newUserdata) {
|
||||
schemaVersion = Math.min(76, schemaVersion);
|
||||
}
|
||||
|
||||
_schemaVersions[schema] = schemaVersion;
|
||||
return schemaVersion;
|
||||
|
@ -1416,6 +1429,7 @@ Zotero.Schema = new function(){
|
|||
|
||||
_updateDBVersion('system', _getSchemaSQLVersion('system'));
|
||||
_updateDBVersion('userdata', _getSchemaSQLVersion('userdata'));
|
||||
_updateDBVersion('userdata2', _getSchemaSQLVersion('userdata2'));
|
||||
_updateDBVersion('triggers', _getSchemaSQLVersion('triggers'));
|
||||
|
||||
if (!Zotero.Schema.skipDefaultData) {
|
||||
|
@ -1762,6 +1776,9 @@ Zotero.Schema = new function(){
|
|||
function _migrateUserDataSchema(fromVersion){
|
||||
var toVersion = _getSchemaSQLVersion('userdata');
|
||||
|
||||
// Only upgrades through version 76 are handled here
|
||||
toVersion = Math.min(76, toVersion);
|
||||
|
||||
if (fromVersion==toVersion){
|
||||
return false;
|
||||
}
|
||||
|
@ -3286,4 +3303,47 @@ Zotero.Schema = new function(){
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// TEMP
|
||||
//
|
||||
// TODO: Make this asynchronous, and make it block other SQLite
|
||||
function _migrateUserDataSchemaSilent(fromVersion) {
|
||||
var toVersion = _getSchemaSQLVersion('userdata2');
|
||||
|
||||
if (!fromVersion) {
|
||||
fromVersion = 76;
|
||||
}
|
||||
|
||||
if (fromVersion == toVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
try {
|
||||
// Step through version changes until we reach the current version
|
||||
//
|
||||
// Each block performs the changes necessary to move from the
|
||||
// previous revision to that one.
|
||||
for (var i=fromVersion + 1; i<=toVersion; i++) {
|
||||
if (i == 77) {
|
||||
Zotero.DB.query("CREATE TABLE syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID)\n)");
|
||||
Zotero.DB.query("INSERT OR IGNORE INTO syncObjectTypes VALUES (7, 'setting')");
|
||||
}
|
||||
}
|
||||
|
||||
_updateDBVersion('userdata2', toVersion);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1197,8 +1197,20 @@ Zotero.Search.prototype._buildQuery = function(){
|
|||
break;
|
||||
|
||||
case 'year':
|
||||
condSQL += 'fieldID IN (?) AND ';
|
||||
condSQLParams.push(Zotero.ItemFields.getID('date'));
|
||||
//Add base field
|
||||
var dateFields = Zotero.ItemFields.getTypeFieldsFromBase('date');
|
||||
if (dateFields) {
|
||||
condSQL += 'fieldID IN (?,';
|
||||
// Add type-specific date fields (dateEnacted, dateDecided, issueDate)
|
||||
for each(var fieldID in dateFields) {
|
||||
condSQL += '?,';
|
||||
condSQLParams.push(fieldID);
|
||||
}
|
||||
condSQL = condSQL.substr(0, condSQL.length - 1);
|
||||
condSQL += ') AND ';
|
||||
}
|
||||
|
||||
condSQL += "valueID IN (SELECT valueID FROM "
|
||||
+ "itemDataValues WHERE ";
|
||||
|
||||
|
|
|
@ -114,10 +114,10 @@ Zotero.Sync.Storage = new function () {
|
|||
Zotero.debug("WebDAV file sync is not active");
|
||||
|
||||
// Try to verify server now if it hasn't been
|
||||
return mode.checkServerPromise()
|
||||
.then(function () {
|
||||
libraryModes[0] = Zotero.Sync.Storage.WebDAV;
|
||||
});
|
||||
return Zotero.Sync.Storage.checkServerPromise(Zotero.Sync.Storage.WebDAV)
|
||||
.then(function () {
|
||||
libraryModes[0] = Zotero.Sync.Storage.WebDAV;
|
||||
});
|
||||
}
|
||||
|
||||
libraryModes[0] = Zotero.Sync.Storage.WebDAV;
|
||||
|
@ -147,6 +147,7 @@ Zotero.Sync.Storage = new function () {
|
|||
}
|
||||
}
|
||||
return Q.allResolved(promises)
|
||||
// Get library last-sync times
|
||||
.then(function () {
|
||||
var promises = [];
|
||||
for (var libraryID in libraryModes) {
|
||||
|
@ -175,22 +176,29 @@ Zotero.Sync.Storage = new function () {
|
|||
return [];
|
||||
}
|
||||
|
||||
var libraryQueues = [];
|
||||
|
||||
// Get the libraries we have sync times for
|
||||
promises.forEach(function (p) {
|
||||
p = p.valueOf();
|
||||
var libraryID = p[0].valueOf();
|
||||
Zotero.debug(libraryID);
|
||||
let libraryID = p[0].valueOf();
|
||||
let lastSyncTime = p[1].valueOf();
|
||||
if (p[1].isFulfilled()) {
|
||||
librarySyncTimes[libraryID] = p[1].valueOf();
|
||||
librarySyncTimes[libraryID] = lastSyncTime;
|
||||
}
|
||||
else {
|
||||
// TODO: error log of some sort
|
||||
//librarySyncTimes[libraryID] = p[1].valueOf().exception;
|
||||
Components.utils.reportError(p[1].valueOf().exception);
|
||||
let e = lastSyncTime.exception;
|
||||
Zotero.debug(e);
|
||||
Components.utils.reportError(e);
|
||||
// Pass rejected promise through
|
||||
libraryQueues.push(Q.allResolved(
|
||||
[libraryID, lastSyncTime]
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// Queue files to download and upload from each library
|
||||
for (var libraryID in librarySyncTimes) {
|
||||
for (let libraryID in librarySyncTimes) {
|
||||
var lastSyncTime = librarySyncTimes[libraryID];
|
||||
libraryID = parseInt(libraryID);
|
||||
|
||||
|
@ -215,7 +223,7 @@ Zotero.Sync.Storage = new function () {
|
|||
if (downloadAll && !downloadForced && lastSyncTime) {
|
||||
var version = self.getStoredLastSyncTime(
|
||||
libraryModes[libraryID], libraryID
|
||||
);
|
||||
);
|
||||
if (version == lastSyncTime) {
|
||||
Zotero.debug("Last " + libraryModes[libraryID].name
|
||||
+ " sync time hasn't changed for library "
|
||||
|
@ -239,41 +247,58 @@ Zotero.Sync.Storage = new function () {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: change to start() for each library, with allResolved()
|
||||
// for the whole set and all() for each library
|
||||
return Zotero.Sync.Storage.QueueManager.start();
|
||||
// Start queues for each library
|
||||
for (let libraryID in librarySyncTimes) {
|
||||
libraryID = parseInt(libraryID);
|
||||
libraryQueues.push(Q.allResolved(
|
||||
[libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
|
||||
));
|
||||
}
|
||||
|
||||
// The promise is done when all libraries are done
|
||||
return Q.allResolved(libraryQueues);
|
||||
})
|
||||
.then(function (promises) {
|
||||
Zotero.debug('Queue manager is finished');
|
||||
|
||||
var changedLibraries = [];
|
||||
|
||||
var finalPromises = [];
|
||||
|
||||
promises.forEach(function (promise) {
|
||||
var result = promise.valueOf();
|
||||
if (promise.isFulfilled()) {
|
||||
Zotero.debug("File " + result.type + " sync finished "
|
||||
+ "for library " + result.libraryID);
|
||||
Zotero.debug(result);
|
||||
if (result.localChanges) {
|
||||
changedLibraries.push(result.libraryID);
|
||||
}
|
||||
finalPromises.push(Q.allResolved([
|
||||
result.libraryID,
|
||||
libraryModes[result.libraryID].setLastSyncTime(
|
||||
result.libraryID,
|
||||
result.remoteChanges
|
||||
? false : librarySyncTimes[result.libraryID]
|
||||
)
|
||||
]));
|
||||
// Discard first allResolved() promise
|
||||
p = promise.valueOf();
|
||||
|
||||
var libraryID = p[0].valueOf();
|
||||
var libraryQueues = p[1].valueOf();
|
||||
|
||||
if (p[1].isFulfilled()) {
|
||||
libraryQueues.forEach(function (queuePromise) {
|
||||
let result = queuePromise.valueOf();
|
||||
if (queuePromise.isFulfilled()) {
|
||||
Zotero.debug("File " + result.type + " sync finished "
|
||||
+ "for library " + libraryID);
|
||||
if (result.localChanges) {
|
||||
changedLibraries.push(libraryID);
|
||||
}
|
||||
finalPromises.push(Q.allResolved([
|
||||
libraryID,
|
||||
libraryModes[libraryID].setLastSyncTime(
|
||||
libraryID,
|
||||
result.remoteChanges ? false : librarySyncTimes[libraryID]
|
||||
)
|
||||
]));
|
||||
}
|
||||
else {
|
||||
result = result.exception;
|
||||
Zotero.debug("File " + result.type + " sync failed "
|
||||
+ "for library " + libraryID);
|
||||
finalPromises.push([libraryID, queuePromise]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
result = result.exception;
|
||||
Zotero.debug("File " + result.type + " sync failed "
|
||||
+ "for library " + result.libraryID);
|
||||
|
||||
finalPromises.push([result.libraryID, promise]);
|
||||
Zotero.debug("File sync failed for library " + libraryID);
|
||||
finalPromises.push([libraryID, libraryQueues]);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -354,7 +379,7 @@ Zotero.Sync.Storage = new function () {
|
|||
var request = new Zotero.Sync.Storage.Request(
|
||||
(item.libraryID ? item.libraryID : 0) + '/' + item.key, callbacks
|
||||
);
|
||||
if (queue == 'upload') {
|
||||
if (queue.type == 'upload') {
|
||||
request.setMaxSize(Zotero.Attachments.getTotalFileSize(item));
|
||||
}
|
||||
queue.addRequest(request, highPriority);
|
||||
|
@ -953,18 +978,14 @@ Zotero.Sync.Storage = new function () {
|
|||
|
||||
|
||||
this.checkServerPromise = function (mode) {
|
||||
var deferred = Q.defer();
|
||||
mode.checkServer(function (uri, status) {
|
||||
return mode.checkServer()
|
||||
.spread(function (uri, status) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
var success = mode.checkServerCallback(uri, status, lastWin, true);
|
||||
if (success) {
|
||||
Zotero.debug(mode.name + " file sync is successfully set up");
|
||||
Q.resolve();
|
||||
}
|
||||
else {
|
||||
if (!success) {
|
||||
Zotero.debug(mode.name + " verification failed");
|
||||
|
||||
var e = new Zotero.Error(
|
||||
|
@ -980,11 +1001,13 @@ Zotero.Sync.Storage = new function () {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
Q.reject(e);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
Zotero.debug(mode.name + " file sync is successfully set up");
|
||||
Zotero.Prefs.set("sync.storage.verified", true);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1638,7 +1661,6 @@ Zotero.Sync.Storage = new function () {
|
|||
|
||||
//Zotero.debug("Adding file " + fileName);
|
||||
|
||||
fileName = Zotero.Utilities.Internal.Base64.encode(fileName) + "%ZB64";
|
||||
zipWriter.addEntryFile(
|
||||
fileName,
|
||||
Components.interfaces.nsIZipWriter.COMPRESSION_DEFAULT,
|
||||
|
|
|
@ -49,6 +49,8 @@ Zotero.Sync.Storage.Queue = function (type, libraryID) {
|
|||
this._localChanges = false;
|
||||
this._remoteChanges = false;
|
||||
this._conflicts = [];
|
||||
this._cachedPercentage;
|
||||
this._cachedPercentageTime;
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.Queue.prototype.__defineGetter__('name', function () {
|
||||
|
@ -158,11 +160,18 @@ Zotero.Sync.Storage.Queue.prototype.__defineGetter__('percentage', function () {
|
|||
return 100;
|
||||
}
|
||||
|
||||
// Cache percentage for a second
|
||||
if (this._cachedPercentage && (new Date() - this._cachedPercentageTime) < 1000) {
|
||||
return this._cachedPercentage;
|
||||
}
|
||||
|
||||
var completedRequests = 0;
|
||||
for each(var request in this._requests) {
|
||||
completedRequests += request.percentage / 100;
|
||||
}
|
||||
return Math.round((completedRequests / this.totalRequests) * 100);
|
||||
this._cachedPercentage = Math.round((completedRequests / this.totalRequests) * 100);
|
||||
this._cachedPercentageTime = new Date();
|
||||
return this._cachedPercentage;
|
||||
});
|
||||
|
||||
|
||||
|
@ -288,7 +297,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
|
|||
);
|
||||
}
|
||||
})
|
||||
.fail(function (e) {
|
||||
.catch(function (e) {
|
||||
self.error(e);
|
||||
});
|
||||
|
||||
|
@ -330,7 +339,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
|
|||
);
|
||||
}
|
||||
})
|
||||
.fail(function (e) {
|
||||
.catch(function (e) {
|
||||
self.error(e);
|
||||
});
|
||||
|
||||
|
@ -362,7 +371,6 @@ Zotero.Sync.Storage.Queue.prototype.error = function (e) {
|
|||
this._error = e;
|
||||
}
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e.message ? e.message : e);
|
||||
this.stop();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,12 +31,14 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
|||
this.start = function (libraryID) {
|
||||
if (libraryID === 0 || libraryID) {
|
||||
var queues = this.getAll(libraryID);
|
||||
var suffix = " for library " + libraryID;
|
||||
}
|
||||
else {
|
||||
var queues = this.getAll();
|
||||
var suffix = "";
|
||||
}
|
||||
|
||||
Zotero.debug("Starting file sync queues");
|
||||
Zotero.debug("Starting file sync queues" + suffix);
|
||||
|
||||
var promises = [];
|
||||
for each(var queue in queues) {
|
||||
|
@ -48,13 +50,15 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
|||
}
|
||||
|
||||
if (!promises.length) {
|
||||
Zotero.debug("No files to sync");
|
||||
Zotero.debug("No files to sync" + suffix);
|
||||
}
|
||||
|
||||
return Q.allResolved(promises)
|
||||
.then(function (promises) {
|
||||
Zotero.debug("All storage queues are finished");
|
||||
Zotero.debug("All storage queues are finished" + suffix);
|
||||
|
||||
promises.forEach(function (promise) {
|
||||
// Check for conflicts to resolve
|
||||
if (promise.isFulfilled()) {
|
||||
var result = promise.valueOf();
|
||||
if (result.conflicts.length) {
|
||||
|
@ -67,7 +71,6 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
return promises;
|
||||
});
|
||||
};
|
||||
|
@ -125,6 +128,10 @@ Zotero.Sync.Storage.QueueManager = new function () {
|
|||
|
||||
|
||||
this.getAll = function (libraryID) {
|
||||
if (typeof libraryID == 'string') {
|
||||
throw new Error("libraryID must be a number or undefined");
|
||||
}
|
||||
|
||||
var queues = [];
|
||||
for each(var queue in _queues) {
|
||||
if (typeof libraryID == 'undefined' || queue.libraryID === libraryID) {
|
||||
|
|
|
@ -208,7 +208,6 @@ Zotero.Sync.Storage.Request.prototype.start = function () {
|
|||
};
|
||||
})
|
||||
.then(function (results) {
|
||||
Zotero.debug('!!!!');
|
||||
Zotero.debug(results);
|
||||
|
||||
if (results.localChanges) {
|
||||
|
@ -263,7 +262,7 @@ Zotero.Sync.Storage.Request.prototype.isFinished = function () {
|
|||
* (usually total bytes)
|
||||
*/
|
||||
Zotero.Sync.Storage.Request.prototype.onProgress = function (channel, progress, progressMax) {
|
||||
Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
|
||||
//Zotero.debug(progress + "/" + progressMax + " for request " + this.name);
|
||||
|
||||
if (!this._running) {
|
||||
Zotero.debug("Trying to update finished request " + this.name + " in "
|
||||
|
|
|
@ -68,20 +68,22 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (req.responseXML) {
|
||||
// TODO: other stuff, but this makes us forward-compatible
|
||||
try {
|
||||
var mtime = req.responseXML.getElementsByTagName('mtime')[0]
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
var mtime = "";
|
||||
}
|
||||
var seconds = false;
|
||||
var seconds = false;
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
try {
|
||||
var xml = parser.parseFromString(req.responseText, "text/xml");
|
||||
var mtime = xml.getElementsByTagName('mtime')[0].textContent;
|
||||
}
|
||||
else {
|
||||
catch (e) {
|
||||
Zotero.debug(e);
|
||||
var mtime = false;
|
||||
}
|
||||
|
||||
// TEMP
|
||||
if (!mtime) {
|
||||
mtime = req.responseText;
|
||||
var seconds = true;
|
||||
seconds = true;
|
||||
}
|
||||
|
||||
var invalid = false;
|
||||
|
@ -106,13 +108,15 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
+ "' for item " + Zotero.Items.getLibraryKeyHash(item);
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
deleteStorageFiles([item.key + ".prop"]);
|
||||
throw _defaultError;
|
||||
return deleteStorageFiles([item.key + ".prop"])
|
||||
.then(function (results) {
|
||||
throw new Error(_defaultError);
|
||||
});
|
||||
}
|
||||
|
||||
return new Date(parseInt(mtime));
|
||||
})
|
||||
.fail(function (e) {
|
||||
.catch(function (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
Zotero.debug(req.responseText);
|
||||
throw new Error("Unexpected status code " + e.status + " in " + funcName);
|
||||
|
@ -138,8 +142,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
+ '<hash>' + hash + '</hash>'
|
||||
+ '</properties>';
|
||||
|
||||
return Zotero.HTTP.promise("PUT", uri, prop,
|
||||
{ debug: true, successCodes: [200, 201, 204] })
|
||||
return Zotero.HTTP.promise("PUT", uri,
|
||||
{ body: prop, debug: true, successCodes: [200, 201, 204] })
|
||||
.then(function (req) {
|
||||
return { mtime: mtime, hash: hash };
|
||||
})
|
||||
|
@ -385,28 +389,22 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
|
||||
switch (req.status) {
|
||||
case 201:
|
||||
callback(uri, Zotero.Sync.Storage.SUCCESS);
|
||||
break;
|
||||
return [uri, Zotero.Sync.Storage.SUCCESS];
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return [uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED];
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return [uri, Zotero.Sync.Storage.ERROR_FORBIDDEN];
|
||||
|
||||
case 405:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_NOT_ALLOWED);
|
||||
return;
|
||||
return [uri, Zotero.Sync.Storage.ERROR_NOT_ALLOWED];
|
||||
|
||||
case 500:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR);
|
||||
return;
|
||||
return [uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR];
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return [uri, Zotero.Sync.Storage.ERROR_UNKNOWN];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -465,7 +463,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
* 'deleted', 'missing', and 'error',
|
||||
* each containing filenames
|
||||
*/
|
||||
function deleteStorageFiles(files, callback) {
|
||||
function deleteStorageFiles(files) {
|
||||
var results = {
|
||||
deleted: [],
|
||||
missing: [],
|
||||
|
@ -473,119 +471,95 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
};
|
||||
|
||||
if (files.length == 0) {
|
||||
if (callback) {
|
||||
callback(results);
|
||||
}
|
||||
return;
|
||||
return Q.resolve(results);
|
||||
}
|
||||
|
||||
for (var i=0; i<files.length; i++) {
|
||||
let last = (i == files.length - 1);
|
||||
let fileName = files[i];
|
||||
|
||||
let deleteURI = rootURI;
|
||||
// This should never happen, but let's be safe
|
||||
if (!deleteURI.spec.match(/\/$/)) {
|
||||
if (callback) {
|
||||
callback(deleted);
|
||||
}
|
||||
Zotero.Sync.Storage.EventManager.error(
|
||||
"Root URI does not end in slash in "
|
||||
+ "Zotero.Sync.Storage.WebDAV.deleteStorageFiles()"
|
||||
);
|
||||
}
|
||||
deleteURI.QueryInterface(Components.interfaces.nsIURL);
|
||||
deleteURI.fileName = files[i];
|
||||
deleteURI.QueryInterface(Components.interfaces.nsIURI);
|
||||
Zotero.HTTP.WebDAV.doDelete(deleteURI, function (req) {
|
||||
switch (req.status) {
|
||||
case 204:
|
||||
// IIS 5.1 and Sakai return 200
|
||||
case 200:
|
||||
var fileDeleted = true;
|
||||
break;
|
||||
|
||||
case 404:
|
||||
var fileDeleted = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (last && callback) {
|
||||
callback(results);
|
||||
}
|
||||
|
||||
results.error.push(fileName);
|
||||
var msg = "An error occurred attempting to delete "
|
||||
+ "'" + fileName
|
||||
+ "' (" + req.status + " " + req.statusText + ").";
|
||||
Zotero.Sync.Storage.EventManager.error(msg);
|
||||
}
|
||||
|
||||
// If an item file URI, get the property URI
|
||||
var deletePropURI = getPropertyURIFromItemURI(deleteURI);
|
||||
if (!deletePropURI) {
|
||||
if (fileDeleted) {
|
||||
results.deleted.push(fileName);
|
||||
}
|
||||
else {
|
||||
results.missing.push(fileName);
|
||||
}
|
||||
if (last && callback) {
|
||||
callback(results);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If property file appears separately in delete queue,
|
||||
// remove it, since we're taking care of it here
|
||||
var propIndex = files.indexOf(deletePropURI.fileName);
|
||||
if (propIndex > i) {
|
||||
delete files[propIndex];
|
||||
i--;
|
||||
last = (i == files.length - 1);
|
||||
}
|
||||
|
||||
// Delete property file
|
||||
Zotero.HTTP.WebDAV.doDelete(deletePropURI, function (req) {
|
||||
let deleteURI = _rootURI.clone();
|
||||
// This should never happen, but let's be safe
|
||||
if (!deleteURI.spec.match(/\/$/)) {
|
||||
throw new Error(
|
||||
"Root URI does not end in slash in "
|
||||
+ "Zotero.Sync.Storage.WebDAV.deleteStorageFiles()"
|
||||
);
|
||||
}
|
||||
|
||||
results = Q.resolve(results);
|
||||
files.forEach(function (fileName) {
|
||||
results = results.then(function (results) {
|
||||
let deleteURI = _rootURI.clone();
|
||||
deleteURI.QueryInterface(Components.interfaces.nsIURL);
|
||||
deleteURI.fileName = fileName;
|
||||
deleteURI.QueryInterface(Components.interfaces.nsIURI);
|
||||
return Zotero.HTTP.promise("DELETE", deleteURI, { successCodes: [200, 204, 404] })
|
||||
.then(function (req) {
|
||||
switch (req.status) {
|
||||
case 204:
|
||||
// IIS 5.1 and Sakai return 200
|
||||
case 200:
|
||||
results.deleted.push(fileName);
|
||||
var fileDeleted = true;
|
||||
break;
|
||||
|
||||
case 404:
|
||||
if (fileDeleted) {
|
||||
results.deleted.push(fileName);
|
||||
}
|
||||
else {
|
||||
results.missing.push(fileName);
|
||||
}
|
||||
var fileDeleted = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
var error = true;
|
||||
}
|
||||
|
||||
if (last && callback) {
|
||||
callback(results);
|
||||
// If an item file URI, get the property URI
|
||||
var deletePropURI = getPropertyURIFromItemURI(deleteURI);
|
||||
if (!deletePropURI) {
|
||||
if (fileDeleted) {
|
||||
results.deleted.push(fileName);
|
||||
}
|
||||
else {
|
||||
results.missing.push(fileName);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
results.error.push(fileName);
|
||||
var msg = "An error occurred attempting to delete "
|
||||
+ "'" + fileName
|
||||
+ "' (" + req.status + " " + req.statusText + ").";
|
||||
Zotero.Sync.Storage.EventManager.error(msg);
|
||||
// If property file appears separately in delete queue,
|
||||
// remove it, since we're taking care of it here
|
||||
var propIndex = files.indexOf(deletePropURI.fileName);
|
||||
if (propIndex > i) {
|
||||
delete files[propIndex];
|
||||
i--;
|
||||
last = (i == files.length - 1);
|
||||
}
|
||||
|
||||
// Delete property file
|
||||
return Zotero.HTTP.promise("DELETE", deletePropURI, { successCodes: [200, 204, 404] })
|
||||
.then(function (req) {
|
||||
switch (req.status) {
|
||||
case 204:
|
||||
// IIS 5.1 and Sakai return 200
|
||||
case 200:
|
||||
results.deleted.push(fileName);
|
||||
break;
|
||||
|
||||
case 404:
|
||||
if (fileDeleted) {
|
||||
results.deleted.push(fileName);
|
||||
}
|
||||
else {
|
||||
results.missing.push(fileName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function (e) {
|
||||
results.error.push(fileName);
|
||||
var msg = "An error occurred attempting to delete "
|
||||
+ "'" + fileName
|
||||
+ "' (" + e.status + " " + e.xmlhttp.statusText + ").";
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks for an invalid SSL certificate and displays a nice error
|
||||
* Checks for an invalid SSL certificate and throws a nice error
|
||||
*/
|
||||
function checkResponse(req) {
|
||||
var channel = req.channel;
|
||||
|
@ -844,7 +818,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
|
||||
if (!mdate) {
|
||||
Zotero.debug("Remote file not found for item " + Zotero.Items.getLibraryKeyHash(item));
|
||||
request.finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -895,9 +868,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
Components.utils.reportError(msg);
|
||||
|
||||
// Delete the orphaned prop file
|
||||
deleteStorageFiles([item.key + ".prop"]);
|
||||
|
||||
deferred.resolve(false);
|
||||
deleteStorageFiles([item.key + ".prop"])
|
||||
.finally(function (results) {
|
||||
deferred.resolve(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
else if (status != 200) {
|
||||
|
@ -981,41 +955,41 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
return Q.fcall(function () {
|
||||
return self._cacheCredentials();
|
||||
})
|
||||
.then(function () {
|
||||
var lastSyncURI = this.rootURI;
|
||||
lastSyncURI.spec += "lastsync";
|
||||
return Zotero.HTTP.promise("GET", lastSyncURI,
|
||||
{ debug: true, successCodes: [200, 404] });
|
||||
})
|
||||
.then(function (req) {
|
||||
if (req.status == 404) {
|
||||
Zotero.debug("No last WebDAV sync time");
|
||||
return null;
|
||||
}
|
||||
|
||||
var lastModified = req.getResponseHeader("Last-Modified");
|
||||
var date = new Date(lastModified);
|
||||
Zotero.debug("Last successful WebDAV sync was " + date);
|
||||
return Zotero.Date.toUnixTimestamp(date);
|
||||
})
|
||||
.fail(function (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
if (e.status == 403) {
|
||||
Zotero.debug("Clearing WebDAV authentication credentials", 2);
|
||||
_cachedCredentials = false;
|
||||
}
|
||||
else {
|
||||
throw("Unexpected status code " + e.status + " getting "
|
||||
+ "WebDAV last sync time");
|
||||
.then(function () {
|
||||
var lastSyncURI = self.rootURI;
|
||||
lastSyncURI.spec += "lastsync";
|
||||
return Zotero.HTTP.promise("GET", lastSyncURI,
|
||||
{ debug: true, successCodes: [200, 404] });
|
||||
})
|
||||
.then(function (req) {
|
||||
if (req.status == 404) {
|
||||
Zotero.debug("No last WebDAV sync time");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Q.reject(e);
|
||||
}
|
||||
// TODO: handle browser offline exception
|
||||
else {
|
||||
throw (e);
|
||||
}
|
||||
});
|
||||
var lastModified = req.getResponseHeader("Last-Modified");
|
||||
var date = new Date(lastModified);
|
||||
Zotero.debug("Last successful WebDAV sync was " + date);
|
||||
return Zotero.Date.toUnixTimestamp(date);
|
||||
})
|
||||
.fail(function (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
if (e.status == 403) {
|
||||
Zotero.debug("Clearing WebDAV authentication credentials", 2);
|
||||
_cachedCredentials = false;
|
||||
}
|
||||
else {
|
||||
throw("Unexpected status code " + e.status + " getting "
|
||||
+ "WebDAV last sync time");
|
||||
}
|
||||
|
||||
return Q.reject(e);
|
||||
}
|
||||
// TODO: handle browser offline exception
|
||||
else {
|
||||
throw (e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -1068,31 +1042,28 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
}
|
||||
|
||||
return Zotero.HTTP.promise("OPTIONS", this.rootURI)
|
||||
.then(function (req) {
|
||||
// TODO: promisify
|
||||
checkResponse(req);
|
||||
|
||||
Zotero.debug("Credentials are cached");
|
||||
_cachedCredentials = true;
|
||||
})
|
||||
.fail(function (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
var msg = "Unexpected status code " + e.status + " "
|
||||
+ "for OPTIONS request caching WebDAV credentials";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
throw new Error(_defaultErrorRestart);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
.then(function (req) {
|
||||
checkResponse(req);
|
||||
|
||||
Zotero.debug("Credentials are cached");
|
||||
_cachedCredentials = true;
|
||||
})
|
||||
.fail(function (e) {
|
||||
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
|
||||
var msg = "Unexpected status code " + e.status + " "
|
||||
+ "for OPTIONS request caching WebDAV credentials";
|
||||
Zotero.debug(msg, 1);
|
||||
Components.utils.reportError(msg);
|
||||
throw new Error(_defaultErrorRestart);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Function} callback Function to pass URI and result value to
|
||||
* @param {Object} errorCallbacks
|
||||
*/
|
||||
obj._checkServer = function (callback) {
|
||||
obj._checkServer = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
try {
|
||||
// Clear URIs
|
||||
this.init();
|
||||
|
@ -1103,27 +1074,23 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
catch (e) {
|
||||
switch (e.name) {
|
||||
case 'Z_ERROR_NO_URL':
|
||||
callback(null, Zotero.Sync.Storage.ERROR_NO_URL);
|
||||
return;
|
||||
deferred.resolve([null, Zotero.Sync.Storage.ERROR_NO_URL]);
|
||||
|
||||
case 'Z_ERROR_NO_USERNAME':
|
||||
callback(null, Zotero.Sync.Storage.ERROR_NO_USERNAME);
|
||||
return;
|
||||
deferred.resolve([null, Zotero.Sync.Storage.ERROR_NO_USERNAME]);
|
||||
|
||||
case 'Z_ERROR_NO_PASSWORD':
|
||||
callback(null, Zotero.Sync.Storage.ERROR_NO_PASSWORD);
|
||||
return;
|
||||
deferred.resolve([null, Zotero.Sync.Storage.ERROR_NO_PASSWORD]);
|
||||
|
||||
default:
|
||||
Zotero.debug(e);
|
||||
Components.utils.reportError(e);
|
||||
callback(null, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
deferred.resolve([null, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
var requestHolder = { request: null };
|
||||
|
||||
var xmlstr = "<propfind xmlns='DAV:'><prop>"
|
||||
// IIS 5.1 requires at least one property in PROPFIND
|
||||
+ "<getcontentlength/>"
|
||||
|
@ -1133,10 +1100,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
var request = Zotero.HTTP.doOptions(uri, function (req) {
|
||||
// Timeout
|
||||
if (req.status == 0) {
|
||||
checkResponse(req);
|
||||
try {
|
||||
checkResponse(req);
|
||||
}
|
||||
catch (e) {
|
||||
deferred.reject(e);
|
||||
}
|
||||
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNREACHABLE);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNREACHABLE]);
|
||||
}
|
||||
|
||||
Zotero.debug(req.getAllResponseHeaders());
|
||||
|
@ -1145,26 +1116,21 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
|
||||
switch (req.status) {
|
||||
case 400:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST]);
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FORBIDDEN]);
|
||||
|
||||
case 500:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR]);
|
||||
}
|
||||
|
||||
var dav = req.getResponseHeader("DAV");
|
||||
if (dav == null) {
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_NOT_DAV);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_NOT_DAV]);
|
||||
}
|
||||
|
||||
// Get the Authorization header used in case we need to do a request
|
||||
|
@ -1209,32 +1175,26 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
switch (req.status) {
|
||||
case 200: // IIS 5.1 and Sakai return 200
|
||||
case 204:
|
||||
callback(uri, Zotero.Sync.Storage.SUCCESS);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.SUCCESS]);
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FORBIDDEN]);
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FORBIDDEN]);
|
||||
|
||||
// This can happen with cloud storage services
|
||||
// backed by S3 or other eventually consistent
|
||||
|
@ -1244,51 +1204,41 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
// not to serve extensionless files or .prop files
|
||||
// http://support.microsoft.com/kb/326965
|
||||
case 404:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FILE_MISSING_AFTER_UPLOAD]);
|
||||
|
||||
case 500:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR]);
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FORBIDDEN]);
|
||||
|
||||
case 500:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR]);
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
case 400:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST]);
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
case 403:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_FORBIDDEN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_FORBIDDEN]);
|
||||
|
||||
case 404:
|
||||
// Include Authorization header from /zotero request,
|
||||
|
@ -1309,49 +1259,53 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
switch (req.status) {
|
||||
// Parent directory existed
|
||||
case 207:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_ZOTERO_DIR_NOT_FOUND]);
|
||||
|
||||
case 400:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_BAD_REQUEST]);
|
||||
|
||||
case 401:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_AUTH_FAILED]);
|
||||
|
||||
// Parent directory wasn't found either
|
||||
case 404:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_PARENT_DIR_NOT_FOUND]);
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
}, newHeaders);
|
||||
return;
|
||||
|
||||
case 500:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_SERVER_ERROR]);
|
||||
|
||||
default:
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_UNKNOWN);
|
||||
return;
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_UNKNOWN]);
|
||||
}
|
||||
}, headers);
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
callback(uri, Zotero.Sync.Storage.ERROR_OFFLINE);
|
||||
return deferred.resolve([uri, Zotero.Sync.Storage.ERROR_OFFLINE]);
|
||||
}
|
||||
|
||||
requestHolder.request = request;
|
||||
return requestHolder;
|
||||
// Pass XMLHttpRequest to progress handler
|
||||
setTimeout(function () {
|
||||
var obj = {};
|
||||
obj.xmlhttp = request;
|
||||
deferred.notify(obj)
|
||||
}, 0);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the result of WebDAV verification, displaying an alert if necessary.
|
||||
*
|
||||
* @return bool True if the verification succeeded, false otherwise
|
||||
*/
|
||||
obj._checkServerCallback = function (uri, status, window, skipSuccessMessage) {
|
||||
var promptService =
|
||||
Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
|
@ -1362,14 +1316,6 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
|
||||
switch (status) {
|
||||
case Zotero.Sync.Storage.SUCCESS:
|
||||
if (!skipSuccessMessage) {
|
||||
promptService.alert(
|
||||
window,
|
||||
Zotero.getString('sync.storage.serverConfigurationVerified'),
|
||||
Zotero.getString('sync.storage.fileSyncSetUp')
|
||||
);
|
||||
}
|
||||
Zotero.Prefs.set("sync.storage.verified", true);
|
||||
return true;
|
||||
|
||||
case Zotero.Sync.Storage.ERROR_NO_URL:
|
||||
|
@ -1510,27 +1456,23 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
*
|
||||
* @param {Function} callback Passed number of files deleted
|
||||
*/
|
||||
obj._purgeDeletedStorageFiles = function (callback) {
|
||||
obj._purgeDeletedStorageFiles = function () {
|
||||
if (!this._active) {
|
||||
return false;
|
||||
return Q(false);
|
||||
}
|
||||
|
||||
Zotero.debug("Purging deleted storage files");
|
||||
var files = Zotero.Sync.Storage.getDeletedFiles();
|
||||
if (!files) {
|
||||
Zotero.debug("No files to delete remotely");
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return false;
|
||||
return Q(false);
|
||||
}
|
||||
|
||||
// TODO: promisify
|
||||
|
||||
// Add .zip extension
|
||||
var files = files.map(function (file) file + ".zip");
|
||||
|
||||
deleteStorageFiles(files, function (results) {
|
||||
return deleteStorageFiles(files)
|
||||
.then(function (results) {
|
||||
// Remove deleted and nonexistent files from storage delete log
|
||||
var toPurge = results.deleted.concat(results.missing);
|
||||
if (toPurge.length > 0) {
|
||||
|
@ -1552,11 +1494,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(results.deleted.length);
|
||||
}
|
||||
|
||||
Zotero.Sync.Storage.EventManager.success();
|
||||
return results.deleted.length;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1683,12 +1621,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
|
|||
}
|
||||
}
|
||||
|
||||
deleteStorageFiles(deleteFiles, function (results) {
|
||||
deleteStorageFiles(deleteFiles)
|
||||
.then(function (results) {
|
||||
Zotero.Prefs.set("lastWebDAVOrphanPurge", Math.round(new Date().getTime() / 1000))
|
||||
if (callback) {
|
||||
callback(results);
|
||||
}
|
||||
Zotero.Sync.Storage.EventManager.success();
|
||||
Zotero.debug(results);
|
||||
});
|
||||
}, { Depth: 1 });
|
||||
};
|
||||
|
|
|
@ -52,6 +52,10 @@ Zotero.Sync = new function() {
|
|||
singular: 'Relation',
|
||||
plural: 'Relations'
|
||||
},
|
||||
setting: {
|
||||
singular: 'Setting',
|
||||
plural: 'Settings'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -128,6 +132,10 @@ Zotero.Sync = new function() {
|
|||
}
|
||||
|
||||
for (var type in this.syncObjects) {
|
||||
if (type == 'setting') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var Types = this.syncObjects[type].plural; // 'Items'
|
||||
var types = Types.toLowerCase(); // 'items'
|
||||
|
||||
|
@ -439,9 +447,15 @@ Zotero.Sync.EventListener = new function () {
|
|||
continue;
|
||||
}
|
||||
|
||||
var oldItem = extraData[ids[i]].old;
|
||||
var libraryID = oldItem.primary.libraryID;
|
||||
var key = oldItem.primary.key;
|
||||
var libraryID, key;
|
||||
if (type == 'setting') {
|
||||
[libraryID, key] = ids[i].split("/");
|
||||
}
|
||||
else {
|
||||
var oldItem = extraData[ids[i]].old;
|
||||
libraryID = oldItem.primary.libraryID;
|
||||
key = oldItem.primary.key;
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
throw("Key not provided in notifier object in "
|
||||
|
@ -553,8 +567,11 @@ Zotero.Sync.Runner = new function () {
|
|||
Zotero.debug("File sync is finished");
|
||||
|
||||
if (results.errors.length) {
|
||||
Zotero.debug(results.errors, 1);
|
||||
for each(var e in results.errors) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
Zotero.Sync.Runner.setErrors(results.errors);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -567,10 +584,11 @@ Zotero.Sync.Runner = new function () {
|
|||
Zotero.Sync.Runner.stop();
|
||||
}
|
||||
})
|
||||
.fail(function (e) {
|
||||
.catch(function (e) {
|
||||
Zotero.debug("File sync failed", 1);
|
||||
Zotero.Sync.Runner.error(e);
|
||||
});
|
||||
})
|
||||
.done();
|
||||
};
|
||||
|
||||
Zotero.Sync.Server.sync({
|
||||
|
@ -620,7 +638,6 @@ Zotero.Sync.Runner = new function () {
|
|||
e = new Error(e);
|
||||
e.status = 'error';
|
||||
}
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug(e, 1);
|
||||
Zotero.Sync.Runner.setSyncIcon(e);
|
||||
throw (e);
|
||||
|
@ -716,9 +733,7 @@ Zotero.Sync.Runner = new function () {
|
|||
* library-specific sync error icons across all windows
|
||||
*/
|
||||
this.setErrors = function (errors) {
|
||||
Zotero.debug(errors);
|
||||
errors = [this.parseSyncError(e) for each(e in errors)];
|
||||
Zotero.debug(errors);
|
||||
_errorsByLibrary = {};
|
||||
|
||||
var primaryError = this.getPrimaryError(errors);
|
||||
|
@ -883,6 +898,7 @@ Zotero.Sync.Runner = new function () {
|
|||
}
|
||||
}
|
||||
if (!parsed.message) {
|
||||
// TODO: include file name and line?
|
||||
parsed.message = e.message ? e.message : e;
|
||||
}
|
||||
|
||||
|
@ -2746,6 +2762,20 @@ Zotero.Sync.Server.Data = new function() {
|
|||
typeloop:
|
||||
for each(var objectNode in updatedNode.xpath(types + "/" + type)) {
|
||||
var libraryID = _libID(objectNode.getAttribute('libraryID'));
|
||||
|
||||
// Process remote settings
|
||||
if (type == 'setting') {
|
||||
var name = objectNode.getAttribute('name');
|
||||
if (!libraryID) {
|
||||
libraryID = 0;
|
||||
}
|
||||
Zotero.debug("Processing remote setting " + libraryID + "/" + name);
|
||||
var version = objectNode.getAttribute('version');
|
||||
var value = JSON.parse(objectNode.textContent);
|
||||
Zotero.SyncedSettings.setSynchronous(libraryID, name, value, version, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = objectNode.getAttribute('key');
|
||||
var objLibraryKeyHash = Zotero[Types].makeLibraryKeyHash(libraryID, key);
|
||||
|
||||
|
@ -3115,6 +3145,18 @@ Zotero.Sync.Server.Data = new function() {
|
|||
for each(var delNode in deletedObjectNodes) {
|
||||
var libraryID = _libID(delNode.getAttribute('libraryID'));
|
||||
var key = delNode.getAttribute('key');
|
||||
|
||||
// Process remote settings deletions
|
||||
if (type == 'setting') {
|
||||
if (!libraryID) {
|
||||
libraryID = 0;
|
||||
}
|
||||
Zotero.debug("Processing remote setting " + libraryID + "/" + key);
|
||||
Zotero.Sync.EventListener.ignoreDeletions('setting', [libraryID + "/" + key]);
|
||||
Zotero.SyncedSettings.setSynchronous(libraryID, key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var obj = Zotero[Types].getByLibraryAndKey(libraryID, key);
|
||||
// Object can't be found
|
||||
if (!obj) {
|
||||
|
@ -3389,6 +3431,10 @@ Zotero.Sync.Server.Data = new function() {
|
|||
var types = Types.toLowerCase(); // 'items'
|
||||
var objectsNode = false;
|
||||
|
||||
if (type == 'setting') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Zotero.debug("Processing locally changed " + types);
|
||||
|
||||
var libraryID, key;
|
||||
|
@ -3425,6 +3471,21 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Add unsynced settings
|
||||
var sql = "SELECT libraryID, setting, value FROM syncedSettings WHERE synced=0";
|
||||
var rows = Zotero.DB.query(sql);
|
||||
if (rows) {
|
||||
var settingsNode = doc.createElement('settings');
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
var settingNode = doc.createElement('setting');
|
||||
settingNode.setAttribute('libraryID', rows[i].libraryID ? rows[i].libraryID : Zotero.libraryID);
|
||||
settingNode.setAttribute('name', rows[i].setting);
|
||||
settingNode.appendChild(doc.createTextNode(_xmlize(rows[i].value)));
|
||||
settingsNode.appendChild(settingNode);
|
||||
}
|
||||
docElem.appendChild(settingsNode);
|
||||
}
|
||||
|
||||
// Deletions
|
||||
var deletedNode = doc.createElement('deleted');
|
||||
var inserted = false;
|
||||
|
|
115
chrome/content/zotero/xpcom/syncedSettings.js
Normal file
115
chrome/content/zotero/xpcom/syncedSettings.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 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 *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.SyncedSettings = (function () {
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
var module = {
|
||||
get: function (libraryID, setting) {
|
||||
return Q.fcall(function () {
|
||||
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
return JSON.parse(Zotero.DB.valueQuery(sql, [setting, libraryID]));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
set: function (libraryID, setting, value, version, synced) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
return self.setSynchronous(libraryID, setting, value, version, synced);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
setSynchronous: function (libraryID, setting, value, version, synced) {
|
||||
// TODO: get rid of this once we have proper affected rows handling
|
||||
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
var currentValue = Zotero.DB.valueQuery(sql, [setting, libraryID]);
|
||||
|
||||
// Make sure we can tell the difference between a
|
||||
// missing setting (FALSE as returned by valueQuery())
|
||||
// and a FALSE setting (FALSE as returned by JSON.parse())
|
||||
var hasCurrentValue = currentValue !== false;
|
||||
var hasValue = typeof value != 'undefined';
|
||||
|
||||
currentValue = JSON.parse(currentValue);
|
||||
|
||||
if ((!hasCurrentValue && !hasValue) || value === currentValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var id = libraryID + '/' + setting;
|
||||
|
||||
if (hasCurrentValue) {
|
||||
var extraData = {};
|
||||
extraData[id] = {
|
||||
changed: {}
|
||||
};
|
||||
extraData[id].changed = {
|
||||
value: currentValue
|
||||
};
|
||||
}
|
||||
|
||||
// Clear
|
||||
if (typeof value == 'undefined') {
|
||||
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
Zotero.DB.query(sql, [setting, libraryID]);
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set/update
|
||||
|
||||
if (currentValue === false) {
|
||||
var event = 'add';
|
||||
var extraData = {};
|
||||
}
|
||||
else {
|
||||
var event = 'modify';
|
||||
}
|
||||
|
||||
synced = synced ? 1 : 0;
|
||||
|
||||
if (hasCurrentValue) {
|
||||
var sql = "UPDATE syncedSettings SET value=?, synced=? WHERE setting=? AND libraryID=?";
|
||||
Zotero.DB.query(sql, [JSON.stringify(value), synced, setting, libraryID]);
|
||||
}
|
||||
else {
|
||||
var sql = "INSERT INTO syncedSettings "
|
||||
+ "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)";
|
||||
Zotero.DB.query(sql, [setting, libraryID, JSON.stringify(value), synced]);
|
||||
}
|
||||
Zotero.Notifier.trigger(event, 'setting', [id], extraData);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return module;
|
||||
}());
|
|
@ -636,6 +636,52 @@ Zotero.Translate.Sandbox = {
|
|||
|
||||
// call super
|
||||
Zotero.Translate.Sandbox.Base._itemDone(translate, item);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells Zotero to monitor changes to the DOM and re-trigger detectWeb
|
||||
* Can only be set during the detectWeb call
|
||||
* @param {DOMNode} target Document node to monitor for changes
|
||||
* @param {MutationObserverInit} [config] specifies which DOM mutations should be reported
|
||||
*/
|
||||
"monitorDOMChanges":function(translate, target, config) {
|
||||
if(translate._currentState != "detect") {
|
||||
Zotero.debug("Translate: monitorDOMChanges can only be called during the 'detect' stage");
|
||||
return;
|
||||
}
|
||||
|
||||
var window = translate.document.defaultView
|
||||
var mutationObserver = window && ( window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver );
|
||||
if(!mutationObserver) {
|
||||
Zotero.debug("Translate: This browser does not support mutation observers.");
|
||||
return;
|
||||
}
|
||||
|
||||
var translator = translate._potentialTranslators[0];
|
||||
if(!translate._registeredDOMObservers[translator.translatorID])
|
||||
translate._registeredDOMObservers[translator.translatorID] = [];
|
||||
var obs = translate._registeredDOMObservers[translator.translatorID];
|
||||
|
||||
//do not re-register observer by the same translator for the same node
|
||||
if(obs.indexOf(target) != -1) {
|
||||
Zotero.debug("Translate: Already monitoring this node");
|
||||
return;
|
||||
}
|
||||
|
||||
obs.push(target);
|
||||
|
||||
var observer = new mutationObserver(function(mutations, observer) {
|
||||
obs.splice(obs.indexOf(target),1);
|
||||
observer.disconnect();
|
||||
|
||||
Zotero.debug("Translate: Page modified.");
|
||||
//we don't really care what got updated
|
||||
var doc = mutations[0].target.ownerDocument;
|
||||
translate._runHandler("pageModified", doc);
|
||||
});
|
||||
|
||||
observer.observe(target, config || {childList: true, subtree: true});
|
||||
Zotero.debug("Translate: Mutation observer registered on <" + target.nodeName + "> node");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -844,6 +890,11 @@ Zotero.Translate.Base.prototype = {
|
|||
* complete
|
||||
* passed: an array of appropriate translators
|
||||
* returns: N/A
|
||||
* pageModified
|
||||
* valid: web
|
||||
* called: when a web page has been modified
|
||||
* passed: the document object for the modified page
|
||||
* returns: N/A
|
||||
* @param {Function} handler Callback function. All handlers will be passed the current
|
||||
* translate instance as the first argument. The second argument is dependent on the handler.
|
||||
*/
|
||||
|
@ -910,7 +961,7 @@ Zotero.Translate.Base.prototype = {
|
|||
if(this._handlers[type]) {
|
||||
// compile list of arguments
|
||||
if(this._parentTranslator) {
|
||||
// if there is a parent translator, make sure we don't the Zotero.Translate
|
||||
// if there is a parent translator, make sure we don't pass the Zotero.Translate
|
||||
// object, since it could open a security hole
|
||||
var args = [null];
|
||||
} else {
|
||||
|
@ -1366,6 +1417,7 @@ Zotero.Translate.Base.prototype = {
|
|||
this._generateSandbox();
|
||||
}
|
||||
|
||||
this._currentTranslator = translator;
|
||||
this._runningAsyncProcesses = 0;
|
||||
this._returnValue = undefined;
|
||||
this._aborted = false;
|
||||
|
@ -1445,8 +1497,8 @@ Zotero.Translate.Base.prototype = {
|
|||
this._sandboxManager.sandbox.Zotero.isConnector = Zotero.isConnector || false;
|
||||
this._sandboxManager.sandbox.Zotero.isServer = Zotero.isServer || false;
|
||||
this._sandboxManager.sandbox.Zotero.parentTranslator = this._parentTranslator
|
||||
&& this._parentTranslator.translator && this._parentTranslator.translator[0] ?
|
||||
this._parentTranslator.translator[0].translatorID : null;
|
||||
&& this._parentTranslator._currentTranslator ?
|
||||
this._parentTranslator._currentTranslator.translatorID : null;
|
||||
|
||||
// create shortcuts
|
||||
this._sandboxManager.sandbox.Z = this._sandboxManager.sandbox.Zotero;
|
||||
|
@ -1533,6 +1585,7 @@ Zotero.Translate.Base.prototype = {
|
|||
* this Translate instance.
|
||||
*/
|
||||
Zotero.Translate.Web = function() {
|
||||
this._registeredDOMObservers = {}
|
||||
this.init();
|
||||
}
|
||||
Zotero.Translate.Web.prototype = new Zotero.Translate.Base();
|
||||
|
|
|
@ -336,15 +336,27 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
// Determine whether to save an attachment
|
||||
if(attachment.snapshot !== false) {
|
||||
if(attachment.document
|
||||
|| (attachment.mimeType && attachment.mimeType == "text/html")) {
|
||||
if(!Zotero.Prefs.get("automaticSnapshots")) return;
|
||||
|| (attachment.mimeType && Zotero.Attachments.SNAPSHOT_MIMETYPES.indexOf(attachment.mimeType) != -1)) {
|
||||
if(!Zotero.Prefs.get("automaticSnapshots")) {
|
||||
Zotero.debug("Translate: Automatic snapshots are disabled. Skipping.", 4);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if(!Zotero.Prefs.get("downloadAssociatedFiles")) return;
|
||||
if(!Zotero.Prefs.get("downloadAssociatedFiles")) {
|
||||
Zotero.debug("Translate: File attachments are disabled. Skipping.", 4);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(attachment.document) {
|
||||
attachment.document = Zotero.Translate.DOMWrapper.unwrap(attachment.document);
|
||||
if(!attachment.title) attachment.title = attachment.document.title;
|
||||
}
|
||||
var title = attachment.title || null;
|
||||
if(!title) {
|
||||
// If no title provided, use "Attachment" as title for progress UI (but not for item)
|
||||
attachment.title = Zotero.getString("itemTypes.attachment");
|
||||
}
|
||||
|
||||
if(attachment.snapshot === false || !this._saveFiles) {
|
||||
|
@ -354,7 +366,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
try {
|
||||
Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
|
||||
(attachment.title ? attachment.title : attachment.document.title));
|
||||
title);
|
||||
attachmentCallback(attachment, 100);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
|
@ -362,14 +374,14 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
}
|
||||
return true;
|
||||
} else {
|
||||
if(!attachment.mimeType || !attachment.title) {
|
||||
if(!attachment.mimeType || !title) {
|
||||
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
|
||||
}
|
||||
|
||||
try {
|
||||
Zotero.Attachments.linkFromURL(attachment.url, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : undefined),
|
||||
(attachment.title ? attachment.title : undefined));
|
||||
title);
|
||||
attachmentCallback(attachment, 100);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
|
@ -383,7 +395,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
try {
|
||||
attachment.linkMode = "imported_url";
|
||||
Zotero.Attachments.importFromDocument(attachment.document,
|
||||
parentID, attachment.title, null, function(status, err) {
|
||||
parentID, title, null, function(status, err) {
|
||||
if(status) {
|
||||
attachmentCallback(attachment, 100);
|
||||
} else {
|
||||
|
@ -399,12 +411,24 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
// Save attachment if snapshot pref enabled or not HTML
|
||||
// (in which case downloadAssociatedFiles applies)
|
||||
} else {
|
||||
if(!attachment.mimeType && attachment.mimeType !== '') {
|
||||
Zotero.debug("Translate: No mimeType specified for a possible snapshot. Trying to determine mimeType.", 4);
|
||||
var me = this;
|
||||
try {
|
||||
Zotero.MIME.getMIMETypeFromURL(attachment.url, function (mimeType, hasNativeHandler) {
|
||||
attachment.mimeType = mimeType || '';
|
||||
me._saveAttachmentDownload(attachment, parentID, attachmentCallback);
|
||||
}, this._cookieSandbox);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var mimeType = (attachment.mimeType ? attachment.mimeType : null);
|
||||
var title = (attachment.title ? attachment.title : null);
|
||||
|
||||
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
|
||||
try {
|
||||
Zotero.debug('Importing attachment from URL');
|
||||
Zotero.debug('Translate: Importing attachment from URL', 4);
|
||||
attachment.linkMode = "imported_url";
|
||||
Zotero.Attachments.importFromURL(attachment.url, parentID, title,
|
||||
fileBaseName, null, mimeType, this._libraryID, function(status, err) {
|
||||
|
|
|
@ -642,7 +642,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
Zotero.startupError = msg;
|
||||
}
|
||||
else {
|
||||
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
||||
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError') + "\n\n" + e;
|
||||
}
|
||||
Zotero.skipLoading = true;
|
||||
Components.utils.reportError(e);
|
||||
|
@ -664,6 +664,8 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
Zotero.Server.init();
|
||||
}
|
||||
|
||||
Zotero.Notifier.registerObserver(Zotero.Tags, 'setting');
|
||||
|
||||
Zotero.Sync.init();
|
||||
Zotero.Sync.Runner.init();
|
||||
|
||||
|
|
|
@ -2380,11 +2380,11 @@ var ZoteroPane = new function()
|
|||
return;
|
||||
}
|
||||
|
||||
var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
|
||||
|
||||
// Prevent the tree's select event from being called for a click
|
||||
// on a library sync error icon
|
||||
if (tree.id == 'zotero-collections-tree') {
|
||||
let itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
|
||||
|
||||
// Prevent the tree's select event from being called for a click
|
||||
// on a library sync error icon
|
||||
if (itemGroup.isLibrary(true)) {
|
||||
if (col.value.id == 'zotero-collections-sync-status-column') {
|
||||
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
|
||||
|
@ -2399,37 +2399,41 @@ var ZoteroPane = new function()
|
|||
|
||||
// Automatically select all equivalent items when clicking on an item
|
||||
// in duplicates view
|
||||
else if (tree.id == 'zotero-items-tree' && itemGroup.isDuplicates()) {
|
||||
// Trigger only on primary-button single clicks with modifiers
|
||||
// (so that items can still be selected and deselected manually)
|
||||
if (!event || event.detail != 1 || event.button != 0 || event.metaKey || event.shiftKey) {
|
||||
return;
|
||||
else if (tree.id == 'zotero-items-tree') {
|
||||
let itemGroup = ZoteroPane_Local.getItemGroup();
|
||||
|
||||
if (itemGroup.isDuplicates()) {
|
||||
// Trigger only on primary-button single clicks with modifiers
|
||||
// (so that items can still be selected and deselected manually)
|
||||
if (!event || event.detail != 1 || event.button != 0 || event.metaKey || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
var t = event.originalTarget;
|
||||
|
||||
if (t.localName != 'treechildren') {
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = t.parentNode;
|
||||
|
||||
var row = {}, col = {}, obj = {};
|
||||
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
|
||||
|
||||
// obj.value == 'cell'/'text'/'image'/'twisty'
|
||||
if (!obj.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplicated in itemTreeView.js::notify()
|
||||
var itemID = ZoteroPane_Local.itemsView._getItemAtRow(row.value).ref.id;
|
||||
var setItemIDs = itemGroup.ref.getSetItemsByItemID(itemID);
|
||||
ZoteroPane_Local.itemsView.selectItems(setItemIDs);
|
||||
|
||||
// Prevent the tree's select event from being called here,
|
||||
// since it's triggered by the multi-select
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
var t = event.originalTarget;
|
||||
|
||||
if (t.localName != 'treechildren') {
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = t.parentNode;
|
||||
|
||||
var row = {}, col = {}, obj = {};
|
||||
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
|
||||
|
||||
// obj.value == 'cell'/'text'/'image'/'twisty'
|
||||
if (!obj.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplicated in itemTreeView.js::notify()
|
||||
var itemID = ZoteroPane_Local.itemsView._getItemAtRow(row.value).ref.id;
|
||||
var setItemIDs = itemGroup.ref.getSetItemsByItemID(itemID);
|
||||
ZoteroPane_Local.itemsView.selectItems(setItemIDs);
|
||||
|
||||
// Prevent the tree's select event from being called here,
|
||||
// since it's triggered by the multi-select
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2452,32 +2456,34 @@ var ZoteroPane = new function()
|
|||
if (row.value == -1) {
|
||||
return;
|
||||
}
|
||||
var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
|
||||
|
||||
// Show the error panel when clicking a library-specific
|
||||
// sync error icon
|
||||
if (itemGroup.isLibrary(true)) {
|
||||
if (col.value.id == 'zotero-collections-sync-status-column') {
|
||||
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
|
||||
var errors = Zotero.Sync.Runner.getErrors(libraryID);
|
||||
if (!errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = Zotero.Sync.Runner.updateErrorPanel(window.document, errors);
|
||||
|
||||
var anchor = document.getElementById('zotero-collections-tree-shim');
|
||||
|
||||
var x = {}, y = {}, width = {}, height = {};
|
||||
tree.treeBoxObject.getCoordsForCellItem(row.value, col.value, 'image', x, y, width, height);
|
||||
|
||||
x = x.value + Math.round(width.value / 2);
|
||||
y = y.value + height.value + 3;
|
||||
|
||||
panel.openPopup(anchor, "after_start", x, y, false, false);
|
||||
}
|
||||
if (tree.id == 'zotero-collections-tree') {
|
||||
let itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
|
||||
|
||||
return;
|
||||
// Show the error panel when clicking a library-specific
|
||||
// sync error icon
|
||||
if (itemGroup.isLibrary(true)) {
|
||||
if (col.value.id == 'zotero-collections-sync-status-column') {
|
||||
var libraryID = itemGroup.isLibrary() ? 0 : itemGroup.ref.libraryID;
|
||||
var errors = Zotero.Sync.Runner.getErrors(libraryID);
|
||||
if (!errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = Zotero.Sync.Runner.updateErrorPanel(window.document, errors);
|
||||
|
||||
var anchor = document.getElementById('zotero-collections-tree-shim');
|
||||
|
||||
var x = {}, y = {}, width = {}, height = {};
|
||||
tree.treeBoxObject.getCoordsForCellItem(row.value, col.value, 'image', x, y, width, height);
|
||||
|
||||
x = x.value + Math.round(width.value / 2);
|
||||
y = y.value + height.value + 3;
|
||||
|
||||
panel.openPopup(anchor, "after_start", x, y, false, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The Mozilla tree binding fires select() in mousedown(),
|
||||
|
@ -2485,17 +2491,20 @@ var ZoteroPane = new function()
|
|||
// what it expects (say, because multiple items had been
|
||||
// selected during mousedown(), as is the case in duplicates mode),
|
||||
// it fires select() again. We prevent that here.
|
||||
else if (itemGroup.isDuplicates() && tree.id == 'zotero-items-tree') {
|
||||
if (event.metaKey || event.shiftKey) {
|
||||
return;
|
||||
else if (tree.id == 'zotero-items-tree') {
|
||||
let itemGroup = ZoteroPane_Local.getItemGroup();
|
||||
if (itemGroup.isDuplicates()) {
|
||||
if (event.metaKey || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.value == 'twisty') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (obj.value == 'twisty') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -2596,12 +2605,30 @@ var ZoteroPane = new function()
|
|||
pane: paneID,
|
||||
action: action
|
||||
};
|
||||
window.openDialog('chrome://zotero/content/preferences/preferences.xul',
|
||||
'zotero-prefs',
|
||||
'chrome,titlebar,toolbar,centerscreen,'
|
||||
+ Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal',
|
||||
io
|
||||
);
|
||||
|
||||
var win = null;
|
||||
// If window is already open, just focus it
|
||||
if (!action) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var enumerator = wm.getEnumerator("zotero:pref");
|
||||
if (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
win.focus();
|
||||
if (paneID) {
|
||||
var pane = win.document.getElementsByAttribute('id', paneID)[0];
|
||||
pane.parentElement.showPane(pane);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!win) {
|
||||
window.openDialog('chrome://zotero/content/preferences/preferences.xul',
|
||||
'zotero-prefs',
|
||||
'chrome,titlebar,toolbar,centerscreen,'
|
||||
+ Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal',
|
||||
io
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -3378,6 +3405,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
var file = item.getFile();
|
||||
Zotero.debug("Opening " + file.path);
|
||||
if (file) {
|
||||
if(forceExternalViewer !== undefined) {
|
||||
var externalViewer = forceExternalViewer;
|
||||
|
|
|
@ -86,7 +86,6 @@
|
|||
</popup>
|
||||
|
||||
<stack id="zotero-pane-stack">
|
||||
|
||||
<!-- Barrier to prevent tabbing into Zotero pane when busy -->
|
||||
<box id="zotero-pane-tab-catcher-top" hidden="true" align="center" pack="center" style="opacity: 0">
|
||||
<checkbox/>
|
||||
|
@ -324,9 +323,11 @@
|
|||
|
||||
<vbox id="zotero-items-pane" zotero-persist="width" flex="1">
|
||||
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
|
||||
<!-- Key navigation is handled by listener in itemTreeView.js -->
|
||||
<tree
|
||||
id="zotero-items-tree" context="zotero-itemmenu"
|
||||
enableColumnDrag="true"
|
||||
disableKeyNavigation="true"
|
||||
onfocus="if (ZoteroPane_Local.itemsView.rowCount && !ZoteroPane_Local.itemsView.selection.count) { ZoteroPane_Local.itemsView.selection.select(0); }"
|
||||
onkeydown="ZoteroPane_Local.handleKeyDown(event, this.id)"
|
||||
onkeypress="ZoteroPane_Local.handleKeyPress(event, this.id)"
|
||||
|
@ -506,7 +507,7 @@
|
|||
zotero-persist="width ordinal hidden sortActive sortDirection"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol
|
||||
id="zotero-items-column-hasNote"
|
||||
id="zotero-items-column-numNotes" hidden="true"
|
||||
class="treecol-image"
|
||||
label="&zotero.tabs.notes.label;"
|
||||
src="chrome://zotero/skin/treeitem-note-small.png"
|
||||
|
@ -540,7 +541,7 @@
|
|||
|
||||
<deck id="zotero-pane-overlay-deck" flex="1">
|
||||
<box id="zotero-pane-progress" flex="1" align="center" pack="center">
|
||||
<box style="background: white; -moz-border-radius: 1px; -moz-box-shadow: gray 4px 6px 4px;" width="300" height="30">
|
||||
<box style="background: white; border-radius: 1px; box-shadow: gray 4px 6px 4px;" width="300" height="30">
|
||||
<vbox style="padding:10px" flex="1">
|
||||
<label id="zotero-pane-progress-label"/>
|
||||
<progressmeter id="zotero-pane-progressmeter" mode="undetermined"/>
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
<!ENTITY zotero.preferences.cite.styles.styleManager.title "Title">
|
||||
<!ENTITY zotero.preferences.cite.styles.styleManager.updated "Updated">
|
||||
<!ENTITY zotero.preferences.cite.styles.styleManager.csl "CSL">
|
||||
<!ENTITY zotero.preferences.cite.styles.automaticTitleAbbreviation "Automatically abbreviate journal titles">
|
||||
<!ENTITY zotero.preferences.export.getAdditionalStyles "Get additional styles...">
|
||||
|
||||
<!ENTITY zotero.preferences.prefpane.keys "Shortcuts">
|
||||
|
@ -177,6 +178,12 @@
|
|||
<!ENTITY zotero.preferences.dataDir.choose "Choose...">
|
||||
<!ENTITY zotero.preferences.dataDir.reveal "Show Data Directory">
|
||||
|
||||
<!ENTITY zotero.preferences.attachmentBaseDir.caption "Linked Attachment Base Directory">
|
||||
<!ENTITY zotero.preferences.attachmentBaseDir.message "Zotero will use relative paths for linked file attachments within the base directory, allowing you to access files on different computers as long as the file structure within the base directory remains the same.">
|
||||
<!ENTITY zotero.preferences.attachmentBaseDir.basePath "Base directory:">
|
||||
<!ENTITY zotero.preferences.attachmentBaseDir.selectBasePath "Choose…">
|
||||
<!ENTITY zotero.preferences.attachmentBaseDir.resetBasePath "Revert to Absolute Paths…">
|
||||
|
||||
<!ENTITY zotero.preferences.dbMaintenance "Database Maintenance">
|
||||
<!ENTITY zotero.preferences.dbMaintenance.integrityCheck "Check Database Integrity">
|
||||
<!ENTITY zotero.preferences.dbMaintenance.resetTranslatorsAndStyles "Reset Translators and Styles...">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<!ENTITY pageSetupCmd.accesskey "U">
|
||||
<!ENTITY printCmd.label "Print…">
|
||||
<!ENTITY printCmd.key "P">
|
||||
<!ENTITY printCmd.accesskey "U">
|
||||
<!ENTITY printCmd.accesskey "P">
|
||||
<!ENTITY closeCmd.label "Close">
|
||||
<!ENTITY closeCmd.key "W">
|
||||
<!ENTITY closeCmd.accesskey "C">
|
||||
|
@ -91,7 +91,7 @@
|
|||
<!ENTITY aboutProduct.label "About &brandShortName;">
|
||||
<!ENTITY aboutProduct.accesskey "A">
|
||||
<!ENTITY productHelp.label "Support and Documentation">
|
||||
<!ENTITY productHelp.accesskey "H">
|
||||
<!ENTITY productHelp.accesskey "D">
|
||||
<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
|
||||
<!ENTITY helpTroubleshootingInfo.accesskey "T">
|
||||
<!ENTITY helpFeedbackPage.label "Submit Feedback…">
|
||||
|
|
|
@ -136,9 +136,16 @@
|
|||
<!ENTITY zotero.tagSelector.selectVisible "Select Visible">
|
||||
<!ENTITY zotero.tagSelector.clearVisible "Deselect Visible">
|
||||
<!ENTITY zotero.tagSelector.clearAll "Deselect All">
|
||||
<!ENTITY zotero.tagSelector.assignColor "Assign Color…">
|
||||
<!ENTITY zotero.tagSelector.renameTag "Rename Tag…">
|
||||
<!ENTITY zotero.tagSelector.deleteTag "Delete Tag…">
|
||||
|
||||
<!ENTITY zotero.tagColorChooser.title "Choose a Tag Color and Position">
|
||||
<!ENTITY zotero.tagColorChooser.color "Color:">
|
||||
<!ENTITY zotero.tagColorChooser.position "Position:">
|
||||
<!ENTITY zotero.tagColorChooser.setColor "Set Color">
|
||||
<!ENTITY zotero.tagColorChooser.removeColor "Remove Color">
|
||||
|
||||
<!ENTITY zotero.lookup.description "Enter the ISBN, DOI, or PMID to look up in the box below.">
|
||||
|
||||
<!ENTITY zotero.selectitems.title "Select Items">
|
||||
|
|
|
@ -69,6 +69,18 @@ errorReport.stepsToReproduce = Steps to Reproduce:
|
|||
errorReport.expectedResult = Expected result:
|
||||
errorReport.actualResult = Actual result:
|
||||
|
||||
attachmentBasePath.selectDir = Choose Base Directory
|
||||
attachmentBasePath.chooseNewPath.title = Confirm New Base Directory
|
||||
attachmentBasePath.chooseNewPath.message = Linked file attachments below this directory will be saved using relative paths.
|
||||
attachmentBasePath.chooseNewPath.existingAttachments.singular = One existing attachment was found within the new base directory.
|
||||
attachmentBasePath.chooseNewPath.existingAttachments.plural = %S existing attachments were found within the new base directory.
|
||||
attachmentBasePath.chooseNewPath.button = Change Base Directory Setting
|
||||
attachmentBasePath.clearBasePath.title = Revert to Absolute Paths
|
||||
attachmentBasePath.clearBasePath.message = New linked file attachments will be saved using absolute paths.
|
||||
attachmentBasePath.clearBasePath.existingAttachments.singular = One existing attachment within the old base directory will be converted to use an absolute path.
|
||||
attachmentBasePath.clearBasePath.existingAttachments.plural = %S existing attachments within the old base directory will be converted to use absolute paths.
|
||||
attachmentBasePath.clearBasePath.button = Clear Base Directory Setting
|
||||
|
||||
dataDir.notFound = The Zotero data directory could not be found.
|
||||
dataDir.previousDir = Previous directory:
|
||||
dataDir.useProfileDir = Use %S profile directory
|
||||
|
@ -138,6 +150,10 @@ pane.tagSelector.delete.message = Are you sure you want to delete this tag?\
|
|||
pane.tagSelector.numSelected.none = 0 tags selected
|
||||
pane.tagSelector.numSelected.singular = %S tag selected
|
||||
pane.tagSelector.numSelected.plural = %S tags selected
|
||||
pane.tagSelector.maxColoredTags = Only %S tags in each library can have colors assigned.
|
||||
|
||||
tagColorChooser.numberKeyInstructions = You can add this tag to selected items by pressing the $NUMBER key on the keyboard.
|
||||
tagColorChooser.maxTags = Up to %S tags in each library can have colors assigned.
|
||||
|
||||
pane.items.loading = Loading items list…
|
||||
pane.items.trash.title = Move to Trash
|
||||
|
@ -657,6 +673,10 @@ styles.installSourceError = %1$S references an invalid or non-existent CSL fil
|
|||
styles.deleteStyle = Are you sure you want to delete the style "%1$S"?
|
||||
styles.deleteStyles = Are you sure you want to delete the selected styles?
|
||||
|
||||
styles.abbreviations.title = Load Abbreviations
|
||||
styles.abbreviations.parseError = The abbreviations file "%1$S" is not valid JSON.
|
||||
styles.abbreviations.missingInfo = The abbreviations file "%1$S" does not specify a complete info block.
|
||||
|
||||
sync.cancel = Cancel Sync
|
||||
sync.openSyncPreferences = Open Sync Preferences
|
||||
sync.resetGroupAndSync = Reset Group and Sync
|
||||
|
@ -794,4 +814,4 @@ connector.standaloneOpen = Your database cannot be accessed because Zotero Sta
|
|||
firstRunGuidance.saveIcon = Zotero has found a reference on this page. Click this icon in the address bar to save the reference to your Zotero library.
|
||||
firstRunGuidance.authorMenu = Zotero lets you specify editors and translators, too. You can turn an author into an editor or translator by selecting from this menu.
|
||||
firstRunGuidance.quickFormat = Type a title or author to search for a reference.\n\nAfter you've made your selection, click the bubble or press Ctrl-\u2193 to add page numbers, prefixes, or suffixes. You can also include a page number along with your search terms to add it directly.\n\nYou can edit citations directly in the word processor document.
|
||||
firstRunGuidance.quickFormatMac = Type a title or author to search for a reference.\n\nAfter you've made your selection, click the bubble or press Cmd-\u2193 to add page numbers, prefixes, or suffixes. You can also include a page number along with your search terms to add it directly.\n\nYou can edit citations directly in the word processor document.
|
||||
firstRunGuidance.quickFormatMac = Type a title or author to search for a reference.\n\nAfter you've made your selection, click the bubble or press Cmd-\u2193 to add page numbers, prefixes, or suffixes. You can also include a page number along with your search terms to add it directly.\n\nYou can edit citations directly in the word processor document.
|
||||
|
|
|
@ -58,7 +58,6 @@ row > label, row > hbox
|
|||
-moz-margin-start: 1px !important;
|
||||
-moz-margin-end: 5px !important;
|
||||
padding: 0 2px 0 2px !important;
|
||||
-moz-border-radius: 6px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
|
24
chrome/skin/default/zotero/bindings/customcolorpicker.css
Normal file
24
chrome/skin/default/zotero/bindings/customcolorpicker.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
customcolorpicker[type="button"] {
|
||||
width: 38px;
|
||||
height: 24px;
|
||||
border: 1px solid #a7a7a7;
|
||||
background-color: ThreeDFace;
|
||||
padding: 3px;
|
||||
-moz-appearance: button-bevel;
|
||||
}
|
||||
|
||||
.colorpickertile[hover="true"], .cp-light[hover="true"] {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.colorpickertile[selected="true"] {
|
||||
border : 1px outset #C0C0C0;
|
||||
}
|
||||
|
||||
.colorpickertile[hover="true"]:not([selected="true"]) {
|
||||
border : 1px dotted #A7A7A7;
|
||||
}
|
||||
|
||||
.cp-light[hover="true"]:not([selected="true"]) {
|
||||
border : 1px dotted #000000;
|
||||
}
|
|
@ -146,7 +146,6 @@ row > vbox > description
|
|||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
border-width: 0 !important;
|
||||
-moz-border-radius: 4px !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ row > label, row > hbox
|
|||
-moz-margin-start: 1px !important;
|
||||
-moz-margin-end: 5px !important;
|
||||
padding: 0 2px 0 2px !important;
|
||||
-moz-border-radius: 6px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
|
|
@ -52,11 +52,14 @@
|
|||
background-image: none;
|
||||
}
|
||||
|
||||
#zotero-items-column-hasAttachment, #zotero-items-column-hasNote
|
||||
{
|
||||
#zotero-items-column-hasAttachment, #zotero-items-column-numNotes {
|
||||
min-width: 21px;
|
||||
}
|
||||
|
||||
#zotero-items-column-numNotes {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#zotero-items-tree treechildren::-moz-tree-image
|
||||
{
|
||||
margin-right: 5px;
|
||||
|
@ -70,7 +73,7 @@
|
|||
-moz-image-region: rect(0px, 32px, 32px, 0px);
|
||||
}
|
||||
|
||||
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie1) { -moz-image-region: rect(0px, 32px, 32px, 0x); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie1) { -moz-image-region: rect(0px, 32px, 32px, 0px); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie2) { -moz-image-region: rect(0px, 64px, 32px, 32px); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie3) { -moz-image-region: rect(0px, 96px, 32px, 64px); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(hasAttachment, pie4) { -moz-image-region: rect(0px, 128px, 32px, 96px); }
|
||||
|
@ -200,149 +203,6 @@
|
|||
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie63) { -moz-image-region: rect(32px, 2016px, 64px, 1984px); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie64) { -moz-image-region: rect(32px, 2048px, 64px, 2016px); }
|
||||
|
||||
|
||||
/* Set tag colors */
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC99) { color:#FFCC99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF99) { color:#FFFF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFCC) { color:#FFFFCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color99FF99) { color:#99FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color99FFFF) { color:#99FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCFFFF) { color:#CCFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCCCFF) { color:#CCCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCFF) { color:#FFCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCCCCC) { color:#CCCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF6666) { color:#FF6666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF9966) { color:#FF9966 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF66) { color:#FFFF66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF33) { color:#FFFF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66FF99) { color:#66FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33FFFF) { color:#33FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66FFFF) { color:#66FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color9999FF) { color:#9999FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF99FF) { color:#FF99FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorC0C0C0) { color:#C0C0C0 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF0000) { color:#FF0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF9900) { color:#FF9900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC66) { color:#FFCC66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF00) { color:#FFFF00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33FF33) { color:#33FF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66CCCC) { color:#66CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33CCFF) { color:#33CCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6666CC) { color:#6666CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC66CC) { color:#CC66CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color999999) { color:#999999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC0000) { color:#CC0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF6600) { color:#FF6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC33) { color:#FFCC33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC00) { color:#FFCC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33CC00) { color:#33CC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color00CCCC) { color:#00CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color3366FF) { color:#3366FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6633FF) { color:#6633FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC33CC) { color:#CC33CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color666666) { color:#666666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color990000) { color:#990000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC6600) { color:#CC6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC9933) { color:#CC9933 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color999900) { color:#999900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color009900) { color:#009900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color339999) { color:#339999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color3333FF) { color:#3333FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6600CC) { color:#6600CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color993399) { color:#993399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333333) { color:#333333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color660000) { color:#660000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color993300) { color:#993300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color996633) { color:#996633 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color666600) { color:#666600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color006600) { color:#006600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color336666) { color:#336666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color000099) { color:#000099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333399) { color:#333399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663366) { color:#663366 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330000) { color:#330000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663300) { color:#663300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663333) { color:#663333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333300) { color:#333300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color003300) { color:#003300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color003333) { color:#003333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color000066) { color:#000066 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330099) { color:#330099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330033) { color:#330033 }
|
||||
|
||||
/* For selected item with colored tags, set the background color instead */
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFFFF,focus) { background:#FFFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCCCC,focus) { background:#FFCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC99,focus) { background:#FFCC99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF99,focus) { background:#FFFF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFFCC,focus) { background:#FFFFCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color99FF99,focus) { background:#99FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color99FFFF,focus) { background:#99FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCFFFF,focus) { background:#CCFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCCCFF,focus) { background:#CCCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCCFF,focus) { background:#FFCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCCCCC,focus) { background:#CCCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF6666,focus) { background:#FF6666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF9966,focus) { background:#FF9966 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF66,focus) { background:#FFFF66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF33,focus) { background:#FFFF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66FF99,focus) { background:#66FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33FFFF,focus) { background:#33FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66FFFF,focus) { background:#66FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color9999FF,focus) { background:#9999FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF99FF,focus) { background:#FF99FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorC0C0C0,focus) { background:#C0C0C0 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF0000,focus) { background:#FF0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF9900,focus) { background:#FF9900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC66,focus) { background:#FFCC66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF00,focus) { background:#FFFF00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33FF33,focus) { background:#33FF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66CCCC,focus) { background:#66CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33CCFF,focus) { background:#33CCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6666CC,focus) { background:#6666CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC66CC,focus) { background:#CC66CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color999999,focus) { background:#999999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC0000,focus) { background:#CC0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF6600,focus) { background:#FF6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC33,focus) { background:#FFCC33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC00,focus) { background:#FFCC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33CC00,focus) { background:#33CC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color00CCCC,focus) { background:#00CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color3366FF,focus) { background:#3366FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6633FF,focus) { background:#6633FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC33CC,focus) { background:#CC33CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color666666,focus) { background:#666666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color990000,focus) { background:#990000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC6600,focus) { background:#CC6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC9933,focus) { background:#CC9933 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color999900,focus) { background:#999900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color009900,focus) { background:#009900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color339999,focus) { background:#339999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color3333FF,focus) { background:#3333FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6600CC,focus) { background:#6600CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color993399,focus) { background:#993399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333333,focus) { background:#333333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color660000,focus) { background:#660000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color993300,focus) { background:#993300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color996633,focus) { background:#996633 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color666600,focus) { background:#666600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color006600,focus) { background:#006600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color336666,focus) { background:#336666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color000099,focus) { background:#000099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333399,focus) { background:#333399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663366,focus) { background:#663366 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330000,focus) { background:#330000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663300,focus) { background:#663300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663333,focus) { background:#663333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333300,focus) { background:#333300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color003300,focus) { background:#003300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color003333,focus) { background:#003333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color000066,focus) { background:#000066 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330099,focus) { background:#330099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330033,focus) { background:#330033 }
|
||||
|
||||
/* Style search results, display non-matches in gray */
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow) {
|
||||
color: gray;
|
||||
|
|
8
chrome/skin/default/zotero/tagColorChooser.css
Normal file
8
chrome/skin/default/zotero/tagColorChooser.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
menulist {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#number-key {
|
||||
margin: 0 1px;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -96,6 +96,14 @@ tagsbox row
|
|||
-moz-box-align:center;
|
||||
}
|
||||
|
||||
customcolorpicker {
|
||||
-moz-binding: url(chrome://zotero/content/bindings/customcolorpicker.xml#custom-colorpicker);
|
||||
}
|
||||
|
||||
customcolorpicker[type=button] {
|
||||
-moz-binding: url(chrome://zotero/content/bindings/customcolorpicker.xml#custom-colorpicker-button);
|
||||
}
|
||||
|
||||
seealsobox
|
||||
{
|
||||
-moz-binding: url('chrome://zotero/content/bindings/relatedbox.xml#seealso-box');
|
||||
|
@ -160,7 +168,6 @@ zoterofilesyncstatus {
|
|||
-moz-binding: url('chrome://zotero/content/bindings/filesyncstatus.xml#file-sync-status');
|
||||
}
|
||||
|
||||
|
||||
label.zotero-text-link {
|
||||
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
|
||||
-moz-user-focus: normal;
|
||||
|
|
|
@ -101,6 +101,7 @@ const xpcomFilesLocal = [
|
|||
'storage/mode',
|
||||
'storage/zfs',
|
||||
'storage/webdav',
|
||||
'syncedSettings',
|
||||
'timeline',
|
||||
'uri',
|
||||
'translation/translate_item',
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
pref("extensions.zotero.firstRun2", true);
|
||||
pref("extensions.zotero@chnm.gmu.edu.description", "chrome://zotero/locale/zotero.properties");
|
||||
|
||||
pref("extensions.zotero.saveRelativeAttachmentPath", false);
|
||||
pref("extensions.zotero.baseAttachmentPath", '');
|
||||
pref("extensions.zotero.useDataDir", false);
|
||||
pref("extensions.zotero.dataDir", '');
|
||||
pref("extensions.zotero.lastDataDir", '');
|
||||
|
@ -91,8 +93,9 @@ pref("extensions.zotero.export.translatorSettings", 'true,false');
|
|||
pref("extensions.zotero.export.lastStyle", 'http://www.zotero.org/styles/chicago-note-bibliography');
|
||||
pref("extensions.zotero.export.bibliographySettings", 'save-as-rtf');
|
||||
pref("extensions.zotero.export.bibliographyLocale", '');
|
||||
pref("extensions.zotero.export.citePaperJournalArticleURL", false);
|
||||
pref("extensions.zotero.export.displayCharsetOption", false);
|
||||
pref("extensions.zotero.export.citePaperJournalArticleURL", false);
|
||||
pref("extensions.zotero.cite.automaticTitleAbbreviation", false);
|
||||
pref("extensions.zotero.import.charset", "auto");
|
||||
pref("extensions.zotero.import.createNewCollection.fromFileOpenHandler", true);
|
||||
pref("extensions.zotero.rtfScan.lastInputFile", "");
|
||||
|
|
1
resource/schema/abbreviations.json
Normal file
1
resource/schema/abbreviations.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
2013-02-08 07:00:00
|
||||
2013-02-19 00:25:00
|
||||
|
|
|
@ -1360,3 +1360,4 @@ INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
|||
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
||||
INSERT INTO "syncObjectTypes" VALUES(5, 'tag');
|
||||
INSERT INTO "syncObjectTypes" VALUES(6, 'relation');
|
||||
INSERT INTO "syncObjectTypes" VALUES(7, 'setting');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- 76
|
||||
-- 77
|
||||
|
||||
-- Copyright (c) 2009 Center for History and New Media
|
||||
-- George Mason University, Fairfax, Virginia, USA
|
||||
|
@ -30,6 +30,7 @@ CREATE TABLE version (
|
|||
);
|
||||
CREATE INDEX schema ON version(schema);
|
||||
|
||||
-- Settings that have to be tied to the local database rather than the profile directory
|
||||
CREATE TABLE settings (
|
||||
setting TEXT,
|
||||
key TEXT,
|
||||
|
@ -37,6 +38,16 @@ CREATE TABLE settings (
|
|||
PRIMARY KEY (setting, key)
|
||||
);
|
||||
|
||||
-- Settings that get synced between Zotero installations
|
||||
CREATE TABLE syncedSettings (
|
||||
setting TEXT NOT NULL,
|
||||
libraryID INT NOT NULL,
|
||||
value NOT NULL,
|
||||
version INT NOT NULL DEFAULT 0,
|
||||
synced INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (setting, libraryID)
|
||||
);
|
||||
|
||||
-- The foundational table; every item collected has a unique record here
|
||||
CREATE TABLE items (
|
||||
itemID INTEGER PRIMARY KEY,
|
||||
|
|
2
styles
2
styles
|
@ -1 +1 @@
|
|||
Subproject commit a482d1ea39213aaebaabfa372879f41f627d8a49
|
||||
Subproject commit 47c49468f4d829a43bee6c4105f83b37c91a2fb7
|
|
@ -1 +1 @@
|
|||
Subproject commit 23c54a2766497e2a5cf9103cfe44ff83b04c41b4
|
||||
Subproject commit 15385de597ac53a3b5397371aa01e578e136fe62
|
Loading…
Add table
Reference in a new issue