Merge branch '3.1'

This commit is contained in:
Dan Stillman 2013-03-04 17:06:53 -05:00
commit 0469441318
55 changed files with 3278 additions and 1383 deletions

View 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>

View file

@ -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;

View file

@ -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>

View file

@ -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);

View file

@ -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');
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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");
}

View file

@ -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"

View 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();
};
};

View 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>

View file

@ -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

View file

@ -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;
}
};

View file

@ -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)

View file

@ -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;

View file

@ -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);

View file

@ -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();
}

View file

@ -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;

View file

@ -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
*

View file

@ -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;

View file

@ -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) || '';
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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 ";

View file

@ -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,

View file

@ -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();
}

View file

@ -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) {

View file

@ -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 "

View file

@ -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 });
};

View file

@ -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;

View 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;
}());

View file

@ -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();

View file

@ -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) {

View file

@ -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();

View file

@ -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;

View file

@ -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 &amp;&amp; !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"/>

View file

@ -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...">

View file

@ -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…">

View file

@ -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">

View file

@ -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.

View file

@ -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;
}

View 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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -0,0 +1,8 @@
menulist {
font-size: 14px;
}
#number-key {
margin: 0 1px;
font-weight: bold;
}

View file

@ -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;

View file

@ -101,6 +101,7 @@ const xpcomFilesLocal = [
'storage/mode',
'storage/zfs',
'storage/webdav',
'syncedSettings',
'timeline',
'uri',
'translation/translate_item',

View file

@ -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", "");

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
2013-02-08 07:00:00
2013-02-19 00:25:00

View file

@ -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');

View file

@ -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

@ -1 +1 @@
Subproject commit a482d1ea39213aaebaabfa372879f41f627d8a49
Subproject commit 47c49468f4d829a43bee6c4105f83b37c91a2fb7

@ -1 +1 @@
Subproject commit 23c54a2766497e2a5cf9103cfe44ff83b04c41b4
Subproject commit 15385de597ac53a3b5397371aa01e578e136fe62