Tag colors, synced settings, and (stopgap) silent DB upgrades
- New tag colors support, with the ability to assign colors to up to 6 tags per library. Tags with colors assigned will show up at the top of the tag selector and can be added to (and removed from) selected items by pressing the 1-6 keys on the keyboard. The tags will show up as color swatches before an item's title in the items list. - Synced settings, with Notifier triggers when they change and accessible via the API (currently restricted on the server to 'tagColors', but available for other things upon request) - Silent DB upgrades for backwards-compatible changes. We'll do something fancier with async DB queries in 4.0, but this will work for changes that can be made without breaking compatibility with older clients, like the creation of new tables. The 'userdata' value is capped at 76, while further increments go to 'userdata2'. TODO: - Try to avoid jitter when redrawing swatches - Optimize tag color images for retina displays - Redo attachment dots in flat style? - Clear all colors from an item with 0 (as in Thunderbird), but I don't think we can do this without undo
This commit is contained in:
parent
01c7c7f9e1
commit
d2f028d797
26 changed files with 1706 additions and 616 deletions
136
chrome/content/zotero/bindings/customcolorpicker.xml
Normal file
136
chrome/content/zotero/bindings/customcolorpicker.xml
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
An extension of the Mozilla colorpicker that allows for a custom set of colors
|
||||
-->
|
||||
<bindings id="colorpickerBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="custom-colorpicker" extends="chrome://global/content/bindings/colorpicker.xml#colorpicker">
|
||||
<resources>
|
||||
<stylesheet src="chrome://zotero/skin/bindings/customcolorpicker.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<vbox anonid="tiles" flex="1" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<hbox>
|
||||
<spacer class="colorpickertile" style="background-color: #000000" color="#000000"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDOMEventListener">
|
||||
<constructor><![CDATA[
|
||||
this.initialize();
|
||||
|
||||
this._colors = this.getAttribute('colors');
|
||||
if (this._colors) {
|
||||
this._cols = this.getAttribute('cols');
|
||||
}
|
||||
this.redraw();
|
||||
]]></constructor>
|
||||
|
||||
<!-- Defaults from the Mozilla colorpicker -->
|
||||
<field name="_defaultColors">
|
||||
[
|
||||
'L#FFFFFF','L#FFCCCC','L#FFCC99','L#FFFF99','L#FFFFCC','L#99FF99','L#99FFFF','L#CCFFFF','L#CCCCFF','L#FFCCFF',
|
||||
'#CCCCCC','#FF6666','#FF9966','L#FFFF66','L#FFFF33','L#66FF99','L#33FFFF','L#66FFFF','#9999FF','#FF99FF',
|
||||
'#C0C0C0','#FF0000','#FF9900','#FFCC66','L#FFFF00','L#33FF33','#66CCCC','#33CCFF','#6666CC','#CC66CC',
|
||||
'#999999','#CC0000','#FF6600','#FFCC33','#FFCC00','#33CC00','#00CCCC','#3366FF','#6633FF','#CC33CC',
|
||||
'#666666','#990000','#CC6600','#CC9933','#999900','#009900','#339999','#3333FF','#6600CC','#993399',
|
||||
'#333333','#660000','#993300','#996633','#666600','#006600','#336666','#000099','#333399','#663366',
|
||||
'#000000','#330000','#663300','#663333','#333300','#003300','#003333','#000066','#330099','#330033'
|
||||
]
|
||||
</field>
|
||||
<field name="_defaultCols">10</field>
|
||||
|
||||
<property name="colors" onget="return this._colors ? this._colors : []">
|
||||
<setter><![CDATA[
|
||||
if (typeof val == 'string') {
|
||||
val = val ? val.split(',') : null;
|
||||
}
|
||||
this._colors = val;
|
||||
this.redraw();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="cols" onget="return this.getAttribute('cols')">
|
||||
<setter><![CDATA[
|
||||
this.setAttribute('cols', val);
|
||||
this.redraw();
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<method name="redraw">
|
||||
<body><![CDATA[
|
||||
//Zotero.debug("Redrawing color picker");
|
||||
|
||||
var tiles = document.getAnonymousNodes(this)[0];
|
||||
|
||||
var cols = this.getAttribute('cols') || this._defaultCols;
|
||||
var colors = this._colors.concat() || this._defaultColors.concat();
|
||||
|
||||
while (tiles.hasChildNodes()) {
|
||||
tiles.removeChild(tiles.firstChild);
|
||||
}
|
||||
|
||||
var rows = Math.ceil(colors.length / cols);
|
||||
|
||||
var tileWidth = this.getAttribute('tileWidth');
|
||||
var tileHeight = this.getAttribute('tileHeight');
|
||||
|
||||
for (let i=0; i<rows; i++) {
|
||||
var hbox = document.createElement('hbox');
|
||||
for (let j=0; j<cols; j++) {
|
||||
let color = colors.shift();
|
||||
if (!color) {
|
||||
break;
|
||||
}
|
||||
let light = color.charAt(0) == 'L';
|
||||
color = light ? color.substr(1) : color;
|
||||
|
||||
var spacer = document.createElement('spacer');
|
||||
spacer.className = 'colorpickertile' + (light ? ' cp-light' : '');
|
||||
spacer.setAttribute('style', 'background-color: ' + color);
|
||||
spacer.setAttribute('color', color);
|
||||
if (tileWidth) {
|
||||
spacer.width = tileWidth;
|
||||
}
|
||||
if (tileHeight) {
|
||||
spacer.height = tileHeight;
|
||||
}
|
||||
hbox.appendChild(spacer);
|
||||
}
|
||||
tiles.appendChild(hbox);
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<!-- The content of the Mozilla colorpicker-button, but with a customcolorpicker
|
||||
with some extra inherited attributes instead -->
|
||||
<binding id="custom-colorpicker-button" display="xul:menu"
|
||||
extends="chrome://global/content/bindings/colorpicker.xml#colorpicker-button">
|
||||
<resources>
|
||||
<stylesheet src="chrome://zotero/skin/bindings/customcolorpicker.css"/>
|
||||
</resources>
|
||||
<content>
|
||||
<xul:hbox class="colorpicker-button-colorbox" anonid="colorbox" flex="1" xbl:inherits="disabled"/>
|
||||
|
||||
<xul:panel class="colorpicker-button-menupopup"
|
||||
anonid="colorpopup" noautofocus="true" level="top"
|
||||
onmousedown="event.stopPropagation()"
|
||||
onpopupshowing="this._colorPicker.onPopupShowing()"
|
||||
onpopuphiding="this._colorPicker.onPopupHiding()"
|
||||
onselect="this._colorPicker.pickerChange()">
|
||||
<xul:customcolorpicker xbl:inherits="palettename,disabled,cols,columns,tileWidth,tileHeight" allowevents="true" anonid="colorpicker"/>
|
||||
</xul:panel>
|
||||
</content>
|
||||
|
||||
<implementation>
|
||||
<property name="colors" onget="return this.mPicker.colors" onset="this.mPicker.colors = val"/>
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
|
@ -37,6 +37,7 @@
|
|||
<implementation>
|
||||
<field name="clickHandler"/>
|
||||
<field name="_tagColors"/>
|
||||
<field name="_notifierID"/>
|
||||
|
||||
<!-- Modes are predefined settings groups for particular tasks -->
|
||||
<field name="_mode">"view"</field>
|
||||
|
@ -109,38 +110,64 @@
|
|||
if (this.hasAttribute('mode')) {
|
||||
this.mode = this.getAttribute('mode');
|
||||
}
|
||||
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['setting']);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
|
||||
<method name="notify">
|
||||
<parameter name="event"/>
|
||||
<parameter name="type"/>
|
||||
<parameter name="ids"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
|
||||
this.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="reload">
|
||||
<body>
|
||||
<![CDATA[
|
||||
var self = this;
|
||||
Zotero.debug('Reloading tags');
|
||||
var addButton = self.id('addButton');
|
||||
addButton.hidden = !self.editable;
|
||||
|
||||
var addButton = this.id('addButton');
|
||||
addButton.hidden = !this.editable;
|
||||
|
||||
var rows = this.id('tagRows');
|
||||
while(rows.hasChildNodes())
|
||||
rows.removeChild(rows.firstChild);
|
||||
|
||||
var tags = this.item.getTags();
|
||||
this._tagColors = Zotero.Tags.getColors();
|
||||
|
||||
if(tags)
|
||||
{
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
this.addDynamicRow(tags[i], i+1);
|
||||
return Zotero.Tags.getColors(self.item.libraryIDInt)
|
||||
.then(function (colors) {
|
||||
self._tagColors = colors;
|
||||
|
||||
var rows = self.id('tagRows');
|
||||
while(rows.hasChildNodes()) {
|
||||
rows.removeChild(rows.firstChild);
|
||||
}
|
||||
var tags = self.item.getTags();
|
||||
if (tags) {
|
||||
for (var i=0; i<tags.length; i++) {
|
||||
self.addDynamicRow(tags[i], i+1);
|
||||
}
|
||||
|
||||
//self.fixPopup();
|
||||
}
|
||||
this.updateCount(tags.length);
|
||||
|
||||
//this.fixPopup();
|
||||
|
||||
return tags.length;
|
||||
}
|
||||
|
||||
this.updateCount(0);
|
||||
return 0;
|
||||
self.updateCount(0);
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -264,8 +291,13 @@
|
|||
}
|
||||
|
||||
// Tag color
|
||||
if (color = this._tagColors[valueText]) {
|
||||
valueElement.setAttribute('style', 'color:' + this._tagColors[valueText]);
|
||||
let color = this._tagColors[valueText];
|
||||
if (color) {
|
||||
valueElement.setAttribute(
|
||||
'style',
|
||||
'color:' + this._tagColors[valueText].color + '; '
|
||||
+ 'font-weight: bold'
|
||||
);
|
||||
}
|
||||
|
||||
return valueElement;
|
||||
|
|
|
@ -27,8 +27,7 @@
|
|||
<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="tag-selector">
|
||||
<resources>
|
||||
|
@ -77,6 +76,9 @@
|
|||
<property name="libraryID" onget="return this._libraryID">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
// TEMP: libraryIDInt
|
||||
val = val ? parseInt(val) : 0;
|
||||
|
||||
if (this._libraryID != val) {
|
||||
this._dirty = true;
|
||||
}
|
||||
|
@ -169,7 +171,7 @@
|
|||
<![CDATA[
|
||||
this._initialized = true;
|
||||
this.selection = {};
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag']);
|
||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting']);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -215,211 +217,211 @@
|
|||
var empty = true;
|
||||
var tagsToggleBox = this.id('tags-toggle');
|
||||
|
||||
|
||||
if (fetch || this._dirty) {
|
||||
this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
|
||||
|
||||
// Remove children
|
||||
tagsToggleBox.textContent = "";
|
||||
|
||||
var me = this,
|
||||
onTagClick = function(event) { me.handleTagClick(event, this) },
|
||||
lastTag;
|
||||
for (var tagID in this._tags) {
|
||||
var tagInfo = this._tags[tagID],
|
||||
tagName = tagInfo.name;
|
||||
// If the last tag was the same, add this tagID and tagType to it
|
||||
if(lastTag && lastTag.value === tagName) {
|
||||
lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
|
||||
lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagName.type);
|
||||
continue;
|
||||
}
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(this.libraryID)
|
||||
.then(function (tagColors) {
|
||||
if (fetch || self._dirty) {
|
||||
self._tags = Zotero.Tags.getAll(self._types, self.libraryID);
|
||||
|
||||
lastTag = document.createElement('label');
|
||||
lastTag.addEventListener('click', onTagClick, false);
|
||||
lastTag.className = 'zotero-clicky';
|
||||
// Remove children
|
||||
tagsToggleBox.textContent = "";
|
||||
|
||||
|
||||
lastTag.setAttribute('value', tagName);
|
||||
lastTag.setAttribute('tagID', tagID);
|
||||
lastTag.setAttribute('tagType', tagInfo.type);
|
||||
if (this.editable) {
|
||||
lastTag.setAttribute('context', 'tag-menu');
|
||||
lastTag.addEventListener('dragover', this.dragObserver.onDragOver, false);
|
||||
lastTag.addEventListener('dragexit', this.dragObserver.onDragExit, false);
|
||||
lastTag.addEventListener('drop', this.dragObserver.onDrop, true);
|
||||
}
|
||||
tagsToggleBox.appendChild(lastTag);
|
||||
}
|
||||
this._dirty = false;
|
||||
}
|
||||
|
||||
// Set attributes
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
var tagColors = Zotero.Tags.getColors();
|
||||
for (var i=0; i<labels.length; i++){
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
// Restore selection
|
||||
if (this.selection[labels[i].value]){
|
||||
labels[i].setAttribute('selected', 'true');
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('selected', 'false');
|
||||
}
|
||||
|
||||
// Check tags against filter
|
||||
if (this._hasFilter) {
|
||||
var inFilter = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (this._filter[tagID]) {
|
||||
inFilter = true;
|
||||
break;
|
||||
var lastTag;
|
||||
for (let tagID in self._tags) {
|
||||
let tagButton = self._makeClickableTag(tagID, lastTag, self.editable);
|
||||
if (tagButton) {
|
||||
tagButton.addEventListener('click', function(event) {
|
||||
self.handleTagClick(event, this);
|
||||
});
|
||||
if (self.editable) {
|
||||
tagButton.addEventListener('dragover', self.dragObserver.onDragOver);
|
||||
tagButton.addEventListener('dragexit', self.dragObserver.onDragExit);
|
||||
tagButton.addEventListener('drop', self.dragObserver.onDrop, true);
|
||||
}
|
||||
lastTag = tagButton;
|
||||
tagsToggleBox.appendChild(tagButton);
|
||||
}
|
||||
}
|
||||
self._dirty = false;
|
||||
}
|
||||
|
||||
// Check tags against scope
|
||||
if (this._hasScope) {
|
||||
var inScope = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (this._scope[tagID]) {
|
||||
inScope = true;
|
||||
break;
|
||||
// Set attributes
|
||||
var colorTags = {};
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
for (let i=0; i<labels.length; i++) {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
// Restore selection
|
||||
if (self.selection[labels[i].value]){
|
||||
labels[i].setAttribute('selected', 'true');
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('selected', 'false');
|
||||
}
|
||||
|
||||
// Check tags against filter
|
||||
if (self._hasFilter) {
|
||||
var inFilter = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (self._filter[tagID]) {
|
||||
inFilter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not in filter, hide
|
||||
if (this._hasFilter && !inFilter) {
|
||||
//Zotero.debug(1);
|
||||
labels[i].setAttribute('hidden', true);
|
||||
}
|
||||
else if (this.filterToScope) {
|
||||
if (this._hasScope && inScope) {
|
||||
//Zotero.debug(2);
|
||||
labels[i].setAttribute('inScope', true);
|
||||
|
||||
// Check tags against scope
|
||||
if (self._hasScope) {
|
||||
var inScope = false;
|
||||
for each(var tagID in tagIDs) {
|
||||
if (self._scope[tagID]) {
|
||||
inScope = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not in filter, hide
|
||||
if (self._hasFilter && !inFilter) {
|
||||
labels[i].setAttribute('hidden', true);
|
||||
}
|
||||
else if (self.filterToScope) {
|
||||
if (self._hasScope && inScope) {
|
||||
labels[i].setAttribute('inScope', true);
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('hidden', true);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
}
|
||||
}
|
||||
// Display all
|
||||
else {
|
||||
if (self._hasScope && inScope) {
|
||||
labels[i].setAttribute('inScope', true);
|
||||
}
|
||||
else {
|
||||
labels[i].setAttribute('inScope', false);
|
||||
|
||||
// If out of scope, make sure it's not selected (otherwise a tag
|
||||
// stays selected after removing an item with that tag from the
|
||||
// current collection)
|
||||
if (self.selection[labels[i].value]) {
|
||||
labels[i].setAttribute('selected', false);
|
||||
delete self.selection[labels[i].value];
|
||||
var doCommand = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
|
||||
let colorData = tagColors[labels[i].value];
|
||||
if (colorData) {
|
||||
labels[i].setAttribute(
|
||||
'style', 'color:' + colorData.color + '; ' + 'font-weight: bold'
|
||||
);
|
||||
colorTags[colorData.position] = tagsToggleBox.removeChild(labels[i]);
|
||||
// The HTMLCollection returned by getElementsByTagName() is live,
|
||||
// so since we removed something we need to decrement the counter
|
||||
i--;
|
||||
}
|
||||
else {
|
||||
//Zotero.debug(3);
|
||||
labels[i].setAttribute('hidden', true);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
labels[i].removeAttribute('style');
|
||||
}
|
||||
}
|
||||
// Display all
|
||||
else {
|
||||
if (this._hasScope && inScope) {
|
||||
//Zotero.debug(4);
|
||||
labels[i].setAttribute('inScope', true);
|
||||
|
||||
// Add color tags to beginning in order
|
||||
var positions = Object.keys(colorTags);
|
||||
positions.sort();
|
||||
for (var i=positions.length-1; i>=0; i--) {
|
||||
tagsToggleBox.insertBefore(colorTags[positions[i]], tagsToggleBox.firstChild);
|
||||
}
|
||||
|
||||
//start tag cloud code
|
||||
|
||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||
|
||||
if(tagCloud) {
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
|
||||
//loop through displayed labels and find number of linked items
|
||||
var numlinked= [];
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = self._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlinked.push(parseInt(linked.length));
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Zotero.debug(5);
|
||||
labels[i].setAttribute('inScope', false);
|
||||
|
||||
// If out of scope, make sure it's not selected (otherwise a tag
|
||||
// stays selected after removing an item with that tag from the
|
||||
// current collection)
|
||||
if (this.selection[labels[i].value]) {
|
||||
labels[i].setAttribute('selected', false);
|
||||
delete this.selection[labels[i].value];
|
||||
var doCommand = true;
|
||||
}
|
||||
|
||||
//
|
||||
numlinked.sort();
|
||||
|
||||
//Get number of attached items from tag with fewest items
|
||||
var min = numlinked[0];
|
||||
|
||||
//Get number of attached items from tag with most items
|
||||
var max = numlinked.pop();
|
||||
numlinked.push(max);
|
||||
|
||||
//Create array of possible tag text sizes
|
||||
var sizes = ["11", "12", "13", "14", "15", "16", "17", "18", "19","20","21","22"];
|
||||
|
||||
//Number of possible tag sizes
|
||||
var categories = sizes.length;
|
||||
|
||||
//inc is the size of each size category of tags, in terms of the number of attached items
|
||||
var inc = Math.ceil((max-min)/categories);
|
||||
if(inc<1) {
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
labels[i].setAttribute('hidden', false);
|
||||
empty = false;
|
||||
}
|
||||
|
||||
if (color = tagColors[labels[i].value]) {
|
||||
labels[i].setAttribute('style', 'color:' + color);
|
||||
}
|
||||
else {
|
||||
labels[i].removeAttribute('style');
|
||||
}
|
||||
}
|
||||
|
||||
//start tag cloud code
|
||||
|
||||
var tagCloud = Zotero.Prefs.get('tagCloud');
|
||||
|
||||
if(tagCloud) {
|
||||
var labels = tagsToggleBox.getElementsByTagName('label');
|
||||
|
||||
//loop through displayed labels and find number of linked items
|
||||
var numlinked= [];
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = this._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlinked.push(parseInt(linked.length));
|
||||
}
|
||||
}
|
||||
//
|
||||
numlinked.sort();
|
||||
|
||||
//Get number of attached items from tag with fewest items
|
||||
var min = numlinked[0];
|
||||
|
||||
//Get number of attached items from tag with most items
|
||||
var max = numlinked.pop();
|
||||
numlinked.push(max);
|
||||
|
||||
//Create array of possible tag text sizes
|
||||
var sizes = ["11", "12", "13", "14", "15", "16", "17", "18", "19","20","21","22"];
|
||||
|
||||
//Number of possible tag sizes
|
||||
var categories = sizes.length;
|
||||
|
||||
//inc is the size of each size category of tags, in terms of the number of attached items
|
||||
var inc = Math.ceil((max-min)/categories);
|
||||
if(inc<1) {
|
||||
inc = 1;
|
||||
}
|
||||
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = this._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlink = linked.length;
|
||||
|
||||
//range is the difference between how many items this tag has and how many items the smallest tag has
|
||||
var range=(numlink-min);
|
||||
|
||||
//Divide the range by the size of the categories
|
||||
s=range/inc;
|
||||
|
||||
if(s==categories) {
|
||||
s=categories-1;
|
||||
for (var i=0; i<labels.length; i++){
|
||||
if(labels[i].getAttribute("hidden") != 'true') {
|
||||
var tagIDs = labels[i].getAttribute('tagID').split('-');
|
||||
|
||||
|
||||
//replace getLinkedItems() with function that gets linked items within the current collection
|
||||
var linked = self._tags[tagIDs[0]].getLinkedItems();
|
||||
|
||||
numlink = linked.length;
|
||||
|
||||
//range is the difference between how many items this tag has and how many items the smallest tag has
|
||||
var range=(numlink-min);
|
||||
|
||||
//Divide the range by the size of the categories
|
||||
s=range/inc;
|
||||
|
||||
if(s==categories) {
|
||||
s=categories-1;
|
||||
}
|
||||
var stylestr = 'font-size:'+sizes[s]+'px;';
|
||||
labels[i].setAttribute('style',stylestr);
|
||||
}
|
||||
var stylestr = 'font-size:'+sizes[s]+'px;';
|
||||
labels[i].setAttribute('style',stylestr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//end tag cloud code
|
||||
|
||||
|
||||
this.updateNumSelected();
|
||||
this._empty = empty;
|
||||
this.id('tags-toggle').setAttribute('collapsed', empty);
|
||||
this.id('no-tags-box').setAttribute('collapsed', !empty);
|
||||
|
||||
if (doCommand) {
|
||||
Zotero.debug('A selected tag went out of scope -- deselecting');
|
||||
this.doCommand();
|
||||
}
|
||||
|
||||
//end tag cloud code
|
||||
|
||||
|
||||
self.updateNumSelected();
|
||||
self._empty = empty;
|
||||
self.id('tags-toggle').setAttribute('collapsed', empty);
|
||||
self.id('no-tags-box').setAttribute('collapsed', !empty);
|
||||
|
||||
if (doCommand) {
|
||||
Zotero.debug('A selected tag went out of scope -- deselecting');
|
||||
self.doCommand();
|
||||
}
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -437,6 +439,7 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="updateNumSelected">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -466,6 +469,13 @@
|
|||
<parameter name="ids"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (type == 'setting') {
|
||||
if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
|
||||
this.refresh(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var itemGroup = ZoteroPane_Local.getItemGroup();
|
||||
|
||||
// Ignore anything other than deletes in duplicates view
|
||||
|
@ -663,16 +673,20 @@
|
|||
delete this.selection[oldName];
|
||||
}
|
||||
|
||||
// TODO: redo transaction for async DB
|
||||
var promises = [];
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
for (var i=0; i<tagIDs.length; i++) {
|
||||
Zotero.Tags.rename(tagIDs[i], newName.value);
|
||||
promises.push(Zotero.Tags.rename(tagIDs[i], newName.value));
|
||||
}
|
||||
|
||||
if (wasSelected) {
|
||||
this.selection[newName.value] = true;
|
||||
}
|
||||
Zotero.DB.commitTransaction();
|
||||
Q.all(promises)
|
||||
.done();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
|
@ -719,43 +733,84 @@
|
|||
<![CDATA[
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = Zotero.Tags.getName(tagIDs[0]);
|
||||
return Zotero.Tags.getColor(name);
|
||||
return Zotero.Tags.getColor(this.libraryID, name)
|
||||
.then(function (colorData) {
|
||||
return colorData ? colorData.color : '#000000';
|
||||
});
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="setColor">
|
||||
<method name="_makeClickableTag">
|
||||
<parameter name="tagID"/>
|
||||
<parameter name="lastTag"/>
|
||||
<parameter name="editable"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var tagInfo = this._tags[tagID], tagName = tagInfo.name;
|
||||
// If the last tag was the same, add this tagID and tagType to it
|
||||
if(lastTag && lastTag.value === tagName) {
|
||||
lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
|
||||
lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagInfo.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.className = 'zotero-clicky';
|
||||
|
||||
label.setAttribute('value', tagName);
|
||||
label.setAttribute('tagID', tagID);
|
||||
label.setAttribute('tagType', tagInfo.type);
|
||||
if (editable) {
|
||||
label.setAttribute('context', 'tag-menu');
|
||||
}
|
||||
return label;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="_openColorPickerWindow">
|
||||
<parameter name="tagIDs"/>
|
||||
<parameter name="color"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
tagIDs = tagIDs.split('-');
|
||||
var name = Zotero.Tags.getName(tagIDs[0]);
|
||||
Zotero.Tags.setColor(name, color);
|
||||
|
||||
// Iterate through all windows, updating tag selector and tags box
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var enumerator = wm.getEnumerator("navigator:browser");
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
if (!win.ZoteroPane) continue;
|
||||
|
||||
var tagSelector = win.ZoteroPane.document.getElementById('zotero-tag-selector');
|
||||
var itemPane = win.ZoteroPane.document.getElementById('zotero-view-item');
|
||||
|
||||
tagSelector.refresh();
|
||||
|
||||
if (itemPane.selectedPanel.firstChild.getAttribute('id') == 'zotero-editpane-tags') {
|
||||
var tagsBox = win.ZoteroPane.document.getElementById('zotero-editpane-tags');
|
||||
tagsBox.reload();
|
||||
var io = {
|
||||
libraryID: this.libraryID,
|
||||
name: Zotero.Tags.getName(tagIDs[0])
|
||||
};
|
||||
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(this.libraryID)
|
||||
.then(function (tagColors) {
|
||||
if (Object.keys(tagColors).length >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors[io.name]) {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
ps.alert(null, "", Zotero.getString('pane.tagSelector.maxColoredTags', Zotero.Tags.MAX_COLORED_TAGS));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.openDialog(
|
||||
'chrome://zotero/content/tagColorChooser.xul',
|
||||
"zotero-tagSelector-colorChooser",
|
||||
"chrome,modal,centerscreen", io
|
||||
);
|
||||
|
||||
// Dialog cancel
|
||||
if (typeof io.color == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
return Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
|
||||
})
|
||||
.done();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
|
||||
<method name="focusTextbox">
|
||||
<body>
|
||||
<![CDATA[
|
||||
|
@ -854,48 +909,41 @@
|
|||
</implementation>
|
||||
|
||||
<content>
|
||||
<xul:groupbox flex="1">
|
||||
<xul:menupopup id="tag-menu">
|
||||
<xul:menuitem label="&zotero.tagSelector.renameTag;" oncommand="document.getBindingParent(this).rename(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<xul:menuitem label="&zotero.tagSelector.deleteTag;" oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<!-- TODO: localized -->
|
||||
<!--
|
||||
<xul:menu label="Assign Color">
|
||||
<xul:menupopup onpopupshowing="var color = document.getBindingParent(this).getColor(document.popupNode.getAttribute('tagID')); this.children[0].color = color">
|
||||
<xul:colorpicker onclick="document.getBindingParent(this).setColor(document.popupNode.getAttribute('tagID'), event.originalTarget.getAttribute('color')); document.getBindingParent(this).id('tag-menu').hidePopup(); event.stopPropagation()"/>
|
||||
</xul:menupopup>
|
||||
</xul:menu>
|
||||
-->
|
||||
</xul:menupopup>
|
||||
<groupbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" flex="1">
|
||||
<menupopup id="tag-menu">
|
||||
<menuitem label="&zotero.tagSelector.assignColor;" oncommand="_openColorPickerWindow(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<menuitem label="&zotero.tagSelector.renameTag;" oncommand="document.getBindingParent(this).rename(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
<menuitem label="&zotero.tagSelector.deleteTag;" oncommand="document.getBindingParent(this).delete(document.popupNode.getAttribute('tagID')); event.stopPropagation()"/>
|
||||
</menupopup>
|
||||
|
||||
<xul:vbox id="no-tags-box" align="center" pack="center" flex="1">
|
||||
<xul:label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||
</xul:vbox>
|
||||
<vbox id="no-tags-box" align="center" pack="center" flex="1">
|
||||
<label value="&zotero.tagSelector.noTagsToDisplay;"/>
|
||||
</vbox>
|
||||
|
||||
<xul:vbox id="tags-toggle" flex="1"/>
|
||||
<vbox id="tags-toggle" flex="1"/>
|
||||
|
||||
<xul:vbox id="tag-controls">
|
||||
<xul:hbox>
|
||||
<vbox id="tag-controls">
|
||||
<hbox>
|
||||
<!-- TODO: &zotero.tagSelector.filter; is now unused -->
|
||||
<xul:textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||
<textbox id="tags-search" flex="1" type="search" timeout="250" dir="reverse"
|
||||
oncommand="document.getBindingParent(this).handleKeyPress(); event.stopPropagation()"
|
||||
onkeypress="if (event.keyCode == event.DOM_VK_ESCAPE) { document.getBindingParent(this).handleKeyPress(true); }"/>
|
||||
<xul:toolbarbutton id="view-settings-menu" tooltiptext="&zotero.toolbar.actions.label;"
|
||||
<toolbarbutton id="view-settings-menu" tooltiptext="&zotero.toolbar.actions.label;"
|
||||
image="chrome://zotero/skin/tag-selector-menu.png" type="menu">
|
||||
<xul:menupopup id="view-settings-popup">
|
||||
<xul:menuitem id="num-selected" disabled="true"/>
|
||||
<xul:menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||
<menupopup id="view-settings-popup">
|
||||
<menuitem id="num-selected" disabled="true"/>
|
||||
<menuitem id="deselect-all" label="&zotero.tagSelector.clearAll;"
|
||||
oncommand="document.getBindingParent(this).clearAll(); event.stopPropagation();"/>
|
||||
<xul:menuseparator/>
|
||||
<xul:menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" autocheck="true" type="checkbox"
|
||||
<menuseparator/>
|
||||
<menuitem id="show-automatic" label="&zotero.tagSelector.showAutomatic;" autocheck="true" type="checkbox"
|
||||
oncommand="var ts = document.getBindingParent(this); ts._dirty = true; ts.setAttribute('showAutomatic', this.getAttribute('checked') == 'true')"/>
|
||||
<xul:menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" autocheck="true" type="checkbox"
|
||||
<menuitem id="display-all-tags" label="&zotero.tagSelector.displayAllInLibrary;" autocheck="true" type="checkbox"
|
||||
oncommand="document.getBindingParent(this).filterToScope = !(this.getAttribute('checked') == 'true'); event.stopPropagation();"/>
|
||||
</xul:menupopup>
|
||||
</xul:toolbarbutton>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</xul:groupbox>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
154
chrome/content/zotero/tagColorChooser.js
Normal file
154
chrome/content/zotero/tagColorChooser.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var _io;
|
||||
|
||||
var Zotero_Tag_Color_Chooser = new function() {
|
||||
this.init = function () {
|
||||
// Set font size from pref
|
||||
Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
|
||||
|
||||
if (window.arguments && window.arguments.length) {
|
||||
_io = window.arguments[0];
|
||||
if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
|
||||
}
|
||||
if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
|
||||
if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
|
||||
|
||||
window.sizeToContent();
|
||||
|
||||
var dialog = document.getElementById('tag-color-chooser');
|
||||
var colorPicker = document.getElementById('color-picker');
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
|
||||
colorPicker.setAttribute('cols', 3);
|
||||
colorPicker.setAttribute('tileWidth', 24);
|
||||
colorPicker.setAttribute('tileHeight', 24);
|
||||
colorPicker.colors = [
|
||||
'#990000', '#CC9933', '#FF9900',
|
||||
'#FFCC00', '#007439', '#1049A9',
|
||||
'#9999FF', '#CC66CC', '#993399'
|
||||
];
|
||||
|
||||
var maxTags = document.getElementById('max-tags');
|
||||
maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
|
||||
|
||||
var self = this;
|
||||
Zotero.Tags.getColors(_io.libraryID)
|
||||
.then(function (tagColors) {
|
||||
var colorData = tagColors[_io.name];
|
||||
|
||||
// Color
|
||||
if (colorData) {
|
||||
colorPicker.color = colorData.color;
|
||||
dialog.buttons = "extra1,cancel,accept";
|
||||
}
|
||||
else {
|
||||
// Get unused color at random
|
||||
var usedColors = [];
|
||||
for (var i in tagColors) {
|
||||
usedColors.push(tagColors[i].color);
|
||||
}
|
||||
var unusedColors = Zotero.Utilities.arrayDiff(
|
||||
colorPicker.colors, usedColors
|
||||
);
|
||||
var color = unusedColors[Zotero.Utilities.rand(0, unusedColors.length - 1)];
|
||||
colorPicker.color = color;
|
||||
dialog.buttons = "cancel,accept";
|
||||
}
|
||||
colorPicker.setAttribute('disabled', 'false');
|
||||
|
||||
var numColors = Object.keys(tagColors).length;
|
||||
var max = colorData ? numColors : numColors + 1;
|
||||
|
||||
// Position
|
||||
for (let i=1; i<=max; i++) {
|
||||
tagPosition.appendItem(i, i-1);
|
||||
}
|
||||
if (numColors) {
|
||||
tagPosition.setAttribute('disabled', 'false');
|
||||
if (colorData) {
|
||||
tagPosition.selectedIndex = colorData.position;
|
||||
}
|
||||
// If no color currently, default to end
|
||||
else {
|
||||
tagPosition.selectedIndex = numColors;
|
||||
}
|
||||
}
|
||||
// If no colors currently, only position "1" is available
|
||||
else {
|
||||
tagPosition.selectedIndex = 0;
|
||||
}
|
||||
|
||||
self.onPositionChange();
|
||||
window.sizeToContent();
|
||||
})
|
||||
.catch(function (e) {
|
||||
Zotero.debug(e, 1);
|
||||
Components.utils.reportError(e);
|
||||
dialog.cancelDialog();
|
||||
})
|
||||
.done();
|
||||
};
|
||||
|
||||
|
||||
this.onPositionChange = function () {
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
var instructions = document.getElementById('number-key-instructions');
|
||||
|
||||
while (instructions.hasChildNodes()) {
|
||||
instructions.removeChild(instructions.firstChild);
|
||||
}
|
||||
|
||||
var msg = Zotero.getString('tagColorChooser.numberKeyInstructions');
|
||||
var matches = msg.match(/(.+)\$NUMBER(.+)/);
|
||||
|
||||
var num = document.createElement('label');
|
||||
num.id = 'number-key';
|
||||
num.setAttribute('value', parseInt(tagPosition.value) + 1);
|
||||
|
||||
instructions.appendChild(document.createTextNode(matches[1]));
|
||||
instructions.appendChild(num);
|
||||
instructions.appendChild(document.createTextNode(matches[2]));
|
||||
};
|
||||
|
||||
|
||||
this.onDialogAccept = function () {
|
||||
var colorPicker = document.getElementById('color-picker');
|
||||
var tagPosition = document.getElementById('tag-position');
|
||||
_io.color = colorPicker.color;
|
||||
_io.position = tagPosition.value;
|
||||
};
|
||||
|
||||
|
||||
this.onDialogCancel = function () {};
|
||||
|
||||
|
||||
this.onDialogRemoveColor = function () {
|
||||
_io.color = false;
|
||||
window.close();
|
||||
};
|
||||
};
|
63
chrome/content/zotero/tagColorChooser.xul
Normal file
63
chrome/content/zotero/tagColorChooser.xul
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/tagColorChooser.css" type="text/css"?>
|
||||
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
|
||||
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&zotero.tagColorChooser.title;" buttons="cancel,accept"
|
||||
id="tag-color-chooser"
|
||||
buttonlabelaccept="&zotero.tagColorChooser.setColor;"
|
||||
buttonlabelextra1="&zotero.tagColorChooser.removeColor;"
|
||||
ondialogaccept="Zotero_Tag_Color_Chooser.onDialogAccept()"
|
||||
ondialogcancel="Zotero_Tag_Color_Chooser.onDialogCancel()"
|
||||
ondialogextra1="Zotero_Tag_Color_Chooser.onDialogRemoveColor()"
|
||||
onload="Zotero_Tag_Color_Chooser.init()"
|
||||
width="300"
|
||||
height="140">
|
||||
|
||||
<script src="include.js"/>
|
||||
<script src="tagColorChooser.js" type="text/javascript;version=1.8"/>
|
||||
|
||||
<vbox id="tag-color-chooser-container">
|
||||
<hbox align="center">
|
||||
<label value="&zotero.tagColorChooser.color;" control="color-picker"/>
|
||||
<customcolorpicker id="color-picker" type="button" disabled="true"/>
|
||||
<separator width="20"/>
|
||||
<label value="&zotero.tagColorChooser.position;" control="tag-position" disabled="true"/>
|
||||
<menulist id="tag-position" disabled="true" sizetopopup="always"
|
||||
oncommand="Zotero_Tag_Color_Chooser.onPositionChange()">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<separator/>
|
||||
<description id="number-key-instructions"/>
|
||||
<separator class="thin"/>
|
||||
<description id="max-tags"/>
|
||||
<separator class="thin"/>
|
||||
</vbox>
|
||||
</dialog>
|
|
@ -355,6 +355,10 @@ Zotero.Group.prototype.erase = function() {
|
|||
var prefix = "groups/" + this.id;
|
||||
Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
|
||||
|
||||
// Delete settings
|
||||
sql = "DELETE FROM syncedSettings WHERE libraryID=?";
|
||||
Zotero.DB.query(sql, this.libraryID ? parseInt(this.libraryID) : 0);
|
||||
|
||||
// Delete group
|
||||
sql = "DELETE FROM groups WHERE groupID=?";
|
||||
Zotero.DB.query(sql, this.id)
|
||||
|
|
|
@ -113,6 +113,8 @@ Zotero.Item.prototype.__defineGetter__('itemID', function () {
|
|||
});
|
||||
Zotero.Item.prototype.__defineSetter__('id', function (val) { this.setField('id', val); });
|
||||
Zotero.Item.prototype.__defineGetter__('libraryID', function () { return this.getField('libraryID'); });
|
||||
// Temporary until everything expects an integer
|
||||
Zotero.Item.prototype.__defineGetter__('libraryIDInt', function () { var libraryID = this.getField('libraryID'); return libraryID ? parseInt(libraryID) : 0; });
|
||||
Zotero.Item.prototype.__defineSetter__('libraryID', function (val) { this.setField('libraryID', val); });
|
||||
Zotero.Item.prototype.__defineGetter__('key', function () { return this.getField('key'); });
|
||||
Zotero.Item.prototype.__defineSetter__('key', function (val) { this.setField('key', val) });
|
||||
|
|
|
@ -453,7 +453,7 @@ Zotero.Tag.prototype.save = function (full) {
|
|||
pairs.push(itemID + "-" + tagID);
|
||||
}
|
||||
|
||||
var tempRemoved = removed;
|
||||
let tempRemoved = removed.concat();
|
||||
|
||||
do {
|
||||
var chunk = tempRemoved.splice(0, maxItems);
|
||||
|
|
|
@ -31,8 +31,14 @@ Zotero.Tags = new function() {
|
|||
Zotero.DataObjects.apply(this, ['tag']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
|
||||
this.MAX_COLORED_TAGS = 6;
|
||||
|
||||
var _tags = {}; // indexed by tag text
|
||||
var _colorsByItem = {};
|
||||
|
||||
var _libraryColors = [];
|
||||
var _libraryColorsByName = {};
|
||||
var _itemsListImagePromises = {};
|
||||
var _itemsListExtraImagePromises = {};
|
||||
|
||||
this.get = get;
|
||||
this.getName = getName;
|
||||
|
@ -258,7 +264,7 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
function getTagItems(tagID) {
|
||||
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||
return Zotero.DB.columnQuery(sql, tagID);
|
||||
return Zotero.DB.columnQuery(sql, tagID) || [];
|
||||
}
|
||||
|
||||
|
||||
|
@ -287,179 +293,545 @@ Zotero.Tags = new function() {
|
|||
}
|
||||
|
||||
|
||||
function rename(tagID, name) {
|
||||
Zotero.debug('Renaming tag', 4);
|
||||
|
||||
name = Zotero.Utilities.trim(name);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var tagObj = this.get(tagID);
|
||||
var libraryID = tagObj.libraryID;
|
||||
var oldName = tagObj.name;
|
||||
var oldType = tagObj.type;
|
||||
var notifierData = {};
|
||||
notifierData[tagID] = { old: tagObj.serialize() };
|
||||
|
||||
if (oldName == name) {
|
||||
Zotero.debug("Tag name hasn't changed", 2);
|
||||
Zotero.DB.commitTransaction();
|
||||
return;
|
||||
}
|
||||
|
||||
var sql = "SELECT tagID, name FROM tags WHERE name=? AND type=0 AND libraryID=?";
|
||||
var row = Zotero.DB.rowQuery(sql, [name, libraryID]);
|
||||
if (row) {
|
||||
var existingTagID = row.tagID;
|
||||
var existingName = row.name;
|
||||
}
|
||||
// New tag already exists as manual tag
|
||||
if (existingTagID
|
||||
// Tag check is case-insensitive, so make sure we have a different tag
|
||||
&& existingTagID != tagID) {
|
||||
/**
|
||||
* Rename a tag and update the tag colors setting accordingly if necessary
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
function rename(tagID, newName) {
|
||||
var tagObj, libraryID, oldName, oldType, notifierData, self = this;
|
||||
return Q.fcall(function () {
|
||||
Zotero.debug('Renaming tag', 4);
|
||||
|
||||
var changed = false;
|
||||
var itemsAdded = false;
|
||||
newName = newName.trim();
|
||||
|
||||
// Change case of existing manual tag before switching automatic
|
||||
if (oldName.toLowerCase() == name.toLowerCase() || existingName != name) {
|
||||
var sql = "UPDATE tags SET name=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [name, existingTagID]);
|
||||
changed = true;
|
||||
tagObj = self.get(tagID);
|
||||
libraryID = tagObj.libraryID;
|
||||
oldName = tagObj.name;
|
||||
oldType = tagObj.type;
|
||||
notifierData = {};
|
||||
notifierData[tagID] = { old: tagObj.serialize() };
|
||||
|
||||
if (oldName == newName) {
|
||||
Zotero.debug("Tag name hasn't changed", 2);
|
||||
return;
|
||||
}
|
||||
|
||||
var itemIDs = this.getTagItems(tagID);
|
||||
var existingItemIDs = this.getTagItems(existingTagID);
|
||||
// We need to know if the old tag has a color assigned so that
|
||||
// we can assign it to the new name
|
||||
return self.getColor(libraryID ? parseInt(libraryID) : 0, oldName);
|
||||
})
|
||||
.then(function (oldColorData) {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
// Would be easier to just call removeTag(tagID) and addTag(existingID)
|
||||
// here, but this is considerably more efficient
|
||||
var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [existingTagID, tagID]);
|
||||
|
||||
// Manual purge of old tag
|
||||
sql = "DELETE FROM tags WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
if (_tags[libraryID] && _tags[libraryID][oldType]) {
|
||||
delete _tags[libraryID][oldType]['_' + oldName];
|
||||
}
|
||||
delete this._objectCache[tagID];
|
||||
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
|
||||
|
||||
// Simulate tag removal on items that used old tag
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
itemTags.push(itemIDs[i] + '-' + tagID);
|
||||
}
|
||||
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
|
||||
|
||||
// And send tag add for new tag (except for those that already had it)
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
if (!existingItemIDs || existingItemIDs.indexOf(itemIDs[i]) == -1) {
|
||||
itemTags.push(itemIDs[i] + '-' + existingTagID);
|
||||
itemsAdded = true;
|
||||
}
|
||||
var sql = "SELECT tagID, name FROM tags WHERE name=? AND type=0 AND libraryID=?";
|
||||
var row = Zotero.DB.rowQuery(sql, [newName, libraryID]);
|
||||
if (row) {
|
||||
var existingTagID = row.tagID;
|
||||
var existingName = row.name;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (itemsAdded) {
|
||||
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
||||
// New tag already exists as manual tag
|
||||
if (existingTagID
|
||||
// Tag check is case-insensitive, so make sure we have a different tag
|
||||
&& existingTagID != tagID) {
|
||||
|
||||
var changed = false;
|
||||
var itemsAdded = false;
|
||||
|
||||
// Change case of existing manual tag before switching automatic
|
||||
if (oldName.toLowerCase() == newName.toLowerCase() || existingName != newName) {
|
||||
var sql = "UPDATE tags SET name=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [newName, existingTagID]);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Mark existing tag as updated
|
||||
sql = "UPDATE tags SET dateModified=CURRENT_TIMESTAMP, "
|
||||
+ "clientDateModified=CURRENT_TIMESTAMP WHERE tagID=?";
|
||||
Zotero.DB.query(sql, existingTagID);
|
||||
Zotero.Notifier.trigger('modify', 'tag', existingTagID);
|
||||
Zotero.Tags.reload(existingTagID);
|
||||
var itemIDs = self.getTagItems(tagID);
|
||||
var existingItemIDs = self.getTagItems(existingTagID);
|
||||
|
||||
// Would be easier to just call removeTag(tagID) and addTag(existingID)
|
||||
// here, but this is considerably more efficient
|
||||
var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
|
||||
Zotero.DB.query(sql, [existingTagID, tagID]);
|
||||
|
||||
// Manual purge of old tag
|
||||
sql = "DELETE FROM tags WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
if (_tags[libraryID] && _tags[libraryID][oldType]) {
|
||||
delete _tags[libraryID][oldType]['_' + oldName];
|
||||
}
|
||||
delete self._objectCache[tagID];
|
||||
Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
|
||||
|
||||
// Simulate tag removal on items that used old tag
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
itemTags.push(itemIDs[i] + '-' + tagID);
|
||||
}
|
||||
Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
|
||||
|
||||
// And send tag add for new tag (except for those that already had it)
|
||||
var itemTags = [];
|
||||
for (var i in itemIDs) {
|
||||
if (existingItemIDs.indexOf(itemIDs[i]) == -1) {
|
||||
itemTags.push(itemIDs[i] + '-' + existingTagID);
|
||||
itemsAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (itemsAdded) {
|
||||
Zotero.Notifier.trigger('add', 'item-tag', itemTags);
|
||||
}
|
||||
|
||||
// Mark existing tag as updated
|
||||
sql = "UPDATE tags SET dateModified=CURRENT_TIMESTAMP, "
|
||||
+ "clientDateModified=CURRENT_TIMESTAMP WHERE tagID=?";
|
||||
Zotero.DB.query(sql, existingTagID);
|
||||
Zotero.Notifier.trigger('modify', 'tag', existingTagID);
|
||||
Zotero.Tags.reload(existingTagID);
|
||||
}
|
||||
|
||||
// TODO: notify linked items?
|
||||
//Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||
}
|
||||
else {
|
||||
tagObj.name = newName;
|
||||
// Set all renamed tags to manual
|
||||
tagObj.type = 0;
|
||||
tagObj.save();
|
||||
}
|
||||
|
||||
// TODO: notify linked items?
|
||||
//Zotero.Notifier.trigger('modify', 'item', itemIDs);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
if (oldColorData) {
|
||||
var libraryIDInt = libraryID ? parseInt(libraryID) : 0
|
||||
|
||||
// Remove color from old tag
|
||||
return self.setColor(libraryIDInt, oldName)
|
||||
// Add color to new tag
|
||||
.then(function () {
|
||||
return self.setColor(
|
||||
libraryIDInt,
|
||||
newName,
|
||||
oldColorData.color,
|
||||
oldColorData.position
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Integer} libraryID
|
||||
* @param {String} name Tag name
|
||||
* @return {Promise} A Q promise for the tag color as a hex string (e.g., '#990000')
|
||||
*/
|
||||
this.getColor = function (libraryID, name) {
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
return _libraryColorsByName[libraryID][name]
|
||||
? _libraryColorsByName[libraryID][name] : false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get color data by position (number key - 1)
|
||||
*
|
||||
* @param {Integer} libraryID
|
||||
* @param {Integer} position The position of the tag, starting at 0
|
||||
* @return {Promise} A Q promise for an object containing 'name' and 'color'
|
||||
*/
|
||||
this.getColorByPosition = function (libraryID, position) {
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
return _libraryColors[libraryID][position]
|
||||
? _libraryColors[libraryID][position] : false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Integer} libraryID
|
||||
* @return {Promise} A Q promise for an object with tag names as keys and
|
||||
* objects containing 'color' and 'position' as values
|
||||
*/
|
||||
this.getColors = function (libraryID) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
if (_libraryColorsByName[libraryID]) {
|
||||
return _libraryColorsByName[libraryID];
|
||||
}
|
||||
|
||||
return Zotero.SyncedSettings.get(libraryID, 'tagColors')
|
||||
.then(function (tagColors) {
|
||||
// If the colors became available from another run
|
||||
if (_libraryColorsByName[libraryID]) {
|
||||
return _libraryColorsByName[libraryID];
|
||||
}
|
||||
|
||||
tagColors = tagColors || [];
|
||||
|
||||
// Remove colors for tags that don't exist
|
||||
tagColors = tagColors.filter(function (val) {
|
||||
var tagIDs = self.getIDs(val.name, libraryID);
|
||||
// TEMP: handle future getIDs return format change
|
||||
return tagIDs && tagIDs.length;
|
||||
});
|
||||
|
||||
_libraryColors[libraryID] = tagColors;
|
||||
_libraryColorsByName[libraryID] = {};
|
||||
|
||||
// Also create object keyed by name for quick checking for individual tag colors
|
||||
for (var i=0; i<tagColors.length; i++) {
|
||||
_libraryColorsByName[libraryID][tagColors[i].name] = {
|
||||
color: tagColors[i].color,
|
||||
position: i
|
||||
};
|
||||
}
|
||||
|
||||
return _libraryColorsByName[libraryID];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assign a color to a tag
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
this.setColor = function (libraryID, name, color, position) {
|
||||
if (libraryID === null) {
|
||||
throw new Error("libraryID must be an integer");
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this.getColors(libraryID)
|
||||
.then(function () {
|
||||
var tagColors = _libraryColors[libraryID];
|
||||
var tagIDs = self.getIDs(name, libraryID);
|
||||
|
||||
// Just to be safe, remove colors for tags that don't exist
|
||||
tagColors = tagColors.filter(function (val) {
|
||||
// TEMP: handle future getIDs return format change
|
||||
return tagIDs && tagIDs.length;
|
||||
});
|
||||
|
||||
// Unset
|
||||
if (!color) {
|
||||
// Trying to clear color on tag that doesn't have one
|
||||
if (!_libraryColorsByName[libraryID][name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
tagColors = tagColors.filter(function (val) val.name != name);
|
||||
}
|
||||
else {
|
||||
// Get current position if present
|
||||
var currentPosition = -1;
|
||||
for (var i=0; i<tagColors.length; i++) {
|
||||
if (tagColors[i].name == name) {
|
||||
currentPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove if present
|
||||
if (currentPosition != -1) {
|
||||
// If no position was specified, we'll reinsert into the same place
|
||||
if (typeof position == 'undefined') {
|
||||
position = currentPosition;
|
||||
}
|
||||
tagColors.splice(currentPosition, 1);
|
||||
}
|
||||
var newObj = {
|
||||
name: name,
|
||||
color: color
|
||||
};
|
||||
// If no position or after end, add at end
|
||||
if (typeof position == 'undefined' || position >= tagColors.length) {
|
||||
tagColors.push(newObj);
|
||||
}
|
||||
// Otherwise insert into new position
|
||||
else {
|
||||
tagColors.splice(position, 0, newObj);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagColors.length) {
|
||||
return Zotero.SyncedSettings.set(libraryID, 'tagColors', tagColors);
|
||||
}
|
||||
else {
|
||||
return Zotero.SyncedSettings.set(libraryID, 'tagColors');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update caches and trigger redrawing of items in the items list
|
||||
* when a 'tagColors' setting is modified
|
||||
*/
|
||||
this.notify = function (event, type, ids, extraData) {
|
||||
if (type != 'setting') {
|
||||
return;
|
||||
}
|
||||
|
||||
tagObj.name = name;
|
||||
// Set all renamed tags to manual
|
||||
tagObj.type = 0;
|
||||
tagObj.save();
|
||||
var self = this;
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
|
||||
this.getColor = function (name) {
|
||||
var tagColors = this.getColors();
|
||||
return tagColors[name] ? tagColors[name] : '#000000';
|
||||
}
|
||||
|
||||
|
||||
this.getColors = function (name) {
|
||||
var tagColors = Zotero.Prefs.get('tagColors');
|
||||
return tagColors ? JSON.parse(tagColors) : {};
|
||||
}
|
||||
|
||||
|
||||
this.getItemColor = function (itemID) {
|
||||
var item = Zotero.Items.get(itemID);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init library tag colors if not yet done
|
||||
var libraryID = item.libraryID ? item.libraryID : 0;
|
||||
if (!_colorsByItem[libraryID]) {
|
||||
_colorsByItem[libraryID] = {};
|
||||
var tagColors = this.getColors();
|
||||
for (var name in tagColors) {
|
||||
var color = tagColors[name];
|
||||
var tagIDs = Zotero.Tags.getIDs(name, libraryID);
|
||||
if (!tagIDs) {
|
||||
continue;
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
let libraryID, setting;
|
||||
[libraryID, setting] = ids[i].split("/");
|
||||
libraryID = parseInt(libraryID);
|
||||
|
||||
if (setting != 'tagColors') {
|
||||
continue;
|
||||
}
|
||||
|
||||
delete _libraryColors[libraryID];
|
||||
delete _libraryColorsByName[libraryID];
|
||||
|
||||
// Get the tag colors for each library in which they were modified
|
||||
Zotero.SyncedSettings.get(libraryID, 'tagColors')
|
||||
.then(function (tagColors) {
|
||||
if (!tagColors) {
|
||||
tagColors = [];
|
||||
}
|
||||
for each(var tagID in tagIDs) {
|
||||
var tag = Zotero.Tags.get(tagID);
|
||||
var itemIDs = tag.getLinkedItems(true);
|
||||
if (!itemIDs) {
|
||||
continue;
|
||||
|
||||
let id = libraryID + "/" + setting;
|
||||
if (event == 'modify' && extraData[id].changed) {
|
||||
var previousTagColors = extraData[id].changed.value;
|
||||
}
|
||||
else {
|
||||
var previousTagColors = [];
|
||||
}
|
||||
|
||||
var affectedItems = [];
|
||||
|
||||
// Get all items linked to previous or current tag colors
|
||||
var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
|
||||
tagNames = Zotero.Utilities.arrayUnique(tagNames);
|
||||
for (let i=0; i<tagNames.length; i++) {
|
||||
let tagIDs = self.getIDs(tagNames[i], libraryID) || [];
|
||||
for (let i=0; i<tagIDs.length; i++) {
|
||||
affectedItems = affectedItems.concat(self.getTagItems(tagIDs[i]));
|
||||
}
|
||||
for each(var id in itemIDs) {
|
||||
_colorsByItem[libraryID][id] = color;
|
||||
};
|
||||
|
||||
if (affectedItems.length) {
|
||||
Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' });
|
||||
}
|
||||
})
|
||||
.done();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.toggleItemsListTags = function (libraryID, items, name) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
var tagIDs = self.getIDs(name, libraryID);
|
||||
var tags = tagIDs.map(function (tagID) {
|
||||
return Zotero.Tags.get(tagID, true);
|
||||
});
|
||||
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
// Base our action on the first item. If it has the tag,
|
||||
// remove the tag from all items. If it doesn't, add it to all.
|
||||
var firstItem = items[0];
|
||||
// Remove from all items
|
||||
if (firstItem.hasTags(tagIDs)) {
|
||||
for (var i=0; i<items.length; i++) {
|
||||
for (var j=0; j<tags.length; j++) {
|
||||
tags[j].removeItem(items[i].id);
|
||||
}
|
||||
}
|
||||
tags.forEach(function (tag) tag.save());
|
||||
Zotero.Prefs.set('purge.tags', true);
|
||||
}
|
||||
// Add to all items
|
||||
else {
|
||||
for (var i=0; i<items.length; i++) {
|
||||
items[i].addTag(name);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A tree cell can show only one image, and (as of Fx19) it can't be an SVG,
|
||||
* so we need to generate a composite image containing the existing item type
|
||||
* icon and one or more tag color swatches.
|
||||
*
|
||||
* @params {Array} colors Array of swatch colors
|
||||
* @params {String} extraImage Chrome URL of image to add to final image
|
||||
* @return {Q Promise} A Q promise for a data: URL for a PNG
|
||||
*/
|
||||
this.generateItemsListImage = function (colors, extraImage) {
|
||||
var swatchWidth = 8;
|
||||
var separator = 3;
|
||||
var extraImageSeparator = 1;
|
||||
var extraImageWidth = 16;
|
||||
var canvasHeight = 16;
|
||||
var swatchHeight = 8;
|
||||
var prependExtraImage = true;
|
||||
|
||||
var hash = colors.join("") + (extraImage ? extraImage : "");
|
||||
|
||||
if (_itemsListImagePromises[hash]) {
|
||||
return _itemsListImagePromises[hash];
|
||||
}
|
||||
|
||||
return _colorsByItem[libraryID][itemID] ? _colorsByItem[libraryID][itemID] : false;
|
||||
}
|
||||
|
||||
|
||||
this.setColor = function (name, color) {
|
||||
var tagColors = this.getColors();
|
||||
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Components.interfaces.nsIAppShellService)
|
||||
.hiddenDOMWindow;
|
||||
var doc = win.document;
|
||||
var canvas = doc.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
|
||||
|
||||
// Unset
|
||||
if (!color || color == '#000000') {
|
||||
delete tagColors[name];
|
||||
var width = colors.length * (swatchWidth + separator);
|
||||
if (extraImage) {
|
||||
width += (colors.length ? extraImageSeparator : 0) + extraImageWidth;
|
||||
}
|
||||
else if (colors.length) {
|
||||
width -= separator;
|
||||
}
|
||||
canvas.width = width;
|
||||
canvas.height = canvasHeight;
|
||||
var swatchTop = Math.floor((canvasHeight - swatchHeight) / 2);
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var x = prependExtraImage ? extraImageWidth + separator + extraImageSeparator : 0;
|
||||
for (let i=0, len=colors.length; i<len; i++) {
|
||||
ctx.fillStyle = colors[i];
|
||||
_canvasRoundRect(ctx, x, swatchTop + 1, swatchWidth, swatchHeight, 2, true, false)
|
||||
x += swatchWidth + separator;
|
||||
}
|
||||
|
||||
// If there's no extra iamge, resolve a promise now
|
||||
if (!extraImage) {
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
return dataURIPromise;
|
||||
}
|
||||
|
||||
// Add an extra image to the beginning or end of the swatches
|
||||
if (prependExtraImage) {
|
||||
x = 0;
|
||||
}
|
||||
else {
|
||||
tagColors[name] = color;
|
||||
x += extraImageSeparator;
|
||||
}
|
||||
|
||||
tagColors = JSON.stringify(tagColors);
|
||||
Zotero.Prefs.set('tagColors', tagColors);
|
||||
// If extra image hasn't started loading, start now
|
||||
if (typeof _itemsListExtraImagePromises[extraImage] == 'undefined') {
|
||||
let ios = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces["nsIIOService"]);
|
||||
let uri = ios.newURI(extraImage, null, null);
|
||||
uri = Components.classes['@mozilla.org/chrome/chrome-registry;1']
|
||||
.getService(Components.interfaces["nsIChromeRegistry"])
|
||||
.convertChromeURL(uri);
|
||||
|
||||
let file = uri.QueryInterface(Components.interfaces.nsIFileURL).file;
|
||||
var img = new win.Image();
|
||||
img.src = Zotero.File.generateDataURI(file, "image/png");
|
||||
|
||||
// Mark that we've started loading
|
||||
var deferred = Q.defer();
|
||||
var extraImageDeferred = Q.defer();
|
||||
_itemsListExtraImagePromises[extraImage] = extraImageDeferred.promise;
|
||||
|
||||
// When extra image has loaded, draw it
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
|
||||
// Fulfill the promise for this call
|
||||
deferred.resolve(dataURI);
|
||||
|
||||
// And resolve the extra image's promise to fulfill
|
||||
// other promises waiting on it
|
||||
extraImageDeferred.resolve(img);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
_reloadTagColors();
|
||||
Zotero.Notifier.trigger('redraw', 'item', []);
|
||||
// If extra image has already started loading, return a promise
|
||||
// for the composite image once it's ready
|
||||
return _itemsListExtraImagePromises[extraImage]
|
||||
.then(function (img) {
|
||||
ctx.drawImage(img, x, 0);
|
||||
|
||||
var dataURI = canvas.toDataURL("image/png");
|
||||
var dataURIPromise = Q(dataURI);
|
||||
_itemsListImagePromises[hash] = dataURIPromise;
|
||||
return dataURIPromise;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function _reloadTagColors() {
|
||||
_colorsByItem = {};
|
||||
/**
|
||||
* From http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
|
||||
*
|
||||
* Draws a rounded rectangle using the current state of the canvas.
|
||||
* If you omit the last three params, it will draw a rectangle
|
||||
* outline with a 5 pixel border radius
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {Number} x The top left x coordinate
|
||||
* @param {Number} y The top left y coordinate
|
||||
* @param {Number} width The width of the rectangle
|
||||
* @param {Number} height The height of the rectangle
|
||||
* @param {Number} radius The corner radius. Defaults to 5;
|
||||
* @param {Boolean} fill Whether to fill the rectangle. Defaults to false.
|
||||
* @param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
|
||||
*/
|
||||
function _canvasRoundRect(ctx, x, y, width, height, radius, fill, stroke) {
|
||||
if (typeof stroke == "undefined" ) {
|
||||
stroke = true;
|
||||
}
|
||||
if (typeof radius === "undefined") {
|
||||
radius = 5;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
ctx.closePath();
|
||||
if (stroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
if (fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
function erase(ids) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
|
@ -563,7 +935,6 @@ Zotero.Tags = new function() {
|
|||
*/
|
||||
this._reload = function (ids) {
|
||||
_tags = {};
|
||||
_reloadTagColors();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -513,6 +594,8 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
|
|||
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
|
||||
|
@ -898,7 +981,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;
|
||||
|
@ -2830,37 +2957,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"].
|
||||
|
|
|
@ -28,7 +28,7 @@ Zotero.Notifier = new function(){
|
|||
var _disabled = false;
|
||||
var _types = [
|
||||
'collection', 'creator', 'search', 'share', 'share-items', 'item',
|
||||
'collection-item', 'item-tag', 'tag', 'group', 'trash', 'bucket', 'relation'
|
||||
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
|
||||
];
|
||||
var _inTransaction;
|
||||
var _locked = false;
|
||||
|
|
|
@ -115,6 +115,7 @@ Zotero.Schema = new function(){
|
|||
*/
|
||||
this.updateSchema = function () {
|
||||
var dbVersion = this.getDBVersion('userdata');
|
||||
var dbVersion2 = this.getDBVersion('userdata2');
|
||||
|
||||
// 'schema' check is for old (<= 1.0b1) schema system,
|
||||
// 'user' is for pre-1.0b2 'user' table
|
||||
|
@ -133,7 +134,7 @@ Zotero.Schema = new function(){
|
|||
if (dbVersion < schemaVersion){
|
||||
Zotero.DB.backupDatabase(dbVersion);
|
||||
Zotero.wait(1000);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
|
@ -154,19 +155,20 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
}
|
||||
|
||||
var up2 = _updateSchema('system');
|
||||
var up1 = _updateSchema('system');
|
||||
// Update custom tables if they exist so that changes are in place before user data migration
|
||||
if (Zotero.DB.tableExists('customItemTypes')) {
|
||||
this.updateCustomTables(up2);
|
||||
}
|
||||
if(up2) Zotero.wait();
|
||||
var up1 = _migrateUserDataSchema(dbVersion);
|
||||
var up3 = _updateSchema('triggers');
|
||||
// Update custom tables again in case custom fields were changed during user data migration
|
||||
if (up1) {
|
||||
this.updateCustomTables();
|
||||
this.updateCustomTables(up1);
|
||||
}
|
||||
if(up1) Zotero.wait();
|
||||
var up2 = _migrateUserDataSchema(dbVersion);
|
||||
var up3 = _migrateUserDataSchemaSilent(dbVersion2);
|
||||
var up4 = _updateSchema('triggers');
|
||||
if (up2) {
|
||||
// Update custom tables again in case custom fields were changed during user data migration
|
||||
this.updateCustomTables();
|
||||
Zotero.wait()
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
@ -176,7 +178,7 @@ Zotero.Schema = new function(){
|
|||
throw(e);
|
||||
}
|
||||
|
||||
if (up1) {
|
||||
if (up2) {
|
||||
// Upgrade seems to have been a success -- delete any previous backups
|
||||
var maxPrevious = dbVersion - 1;
|
||||
var file = Zotero.getZoteroDirectory();
|
||||
|
@ -1353,10 +1355,21 @@ Zotero.Schema = new function(){
|
|||
* Retrieve the version from the top line of the schema SQL file
|
||||
*/
|
||||
function _getSchemaSQLVersion(schema){
|
||||
// TEMP
|
||||
if (schema == 'userdata2') {
|
||||
schema = 'userdata';
|
||||
var newUserdata = true;
|
||||
}
|
||||
var sql = _getSchemaSQL(schema);
|
||||
|
||||
// Fetch the schema version from the first line of the file
|
||||
var schemaVersion = sql.match(/^-- ([0-9]+)/)[1];
|
||||
var schemaVersion = parseInt(sql.match(/^-- ([0-9]+)/)[1]);
|
||||
|
||||
// TEMP: For 'userdata', cap the version at 76
|
||||
// For 'userdata2', versions > 76 are allowed.
|
||||
if (schema == 'userdata' && !newUserdata) {
|
||||
schemaVersion = Math.min(76, schemaVersion);
|
||||
}
|
||||
|
||||
_schemaVersions[schema] = schemaVersion;
|
||||
return schemaVersion;
|
||||
|
@ -1416,6 +1429,7 @@ Zotero.Schema = new function(){
|
|||
|
||||
_updateDBVersion('system', _getSchemaSQLVersion('system'));
|
||||
_updateDBVersion('userdata', _getSchemaSQLVersion('userdata'));
|
||||
_updateDBVersion('userdata2', _getSchemaSQLVersion('userdata2'));
|
||||
_updateDBVersion('triggers', _getSchemaSQLVersion('triggers'));
|
||||
|
||||
if (!Zotero.Schema.skipDefaultData) {
|
||||
|
@ -1762,6 +1776,9 @@ Zotero.Schema = new function(){
|
|||
function _migrateUserDataSchema(fromVersion){
|
||||
var toVersion = _getSchemaSQLVersion('userdata');
|
||||
|
||||
// Only upgrades through version 76 are handled here
|
||||
toVersion = Math.min(76, toVersion);
|
||||
|
||||
if (fromVersion==toVersion){
|
||||
return false;
|
||||
}
|
||||
|
@ -3286,4 +3303,47 @@ Zotero.Schema = new function(){
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// TEMP
|
||||
//
|
||||
// TODO: Make this asynchronous, and make it block other SQLite
|
||||
function _migrateUserDataSchemaSilent(fromVersion) {
|
||||
var toVersion = _getSchemaSQLVersion('userdata2');
|
||||
|
||||
if (!fromVersion) {
|
||||
fromVersion = 76;
|
||||
}
|
||||
|
||||
if (fromVersion == toVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
try {
|
||||
// Step through version changes until we reach the current version
|
||||
//
|
||||
// Each block performs the changes necessary to move from the
|
||||
// previous revision to that one.
|
||||
for (var i=fromVersion + 1; i<=toVersion; i++) {
|
||||
if (i == 77) {
|
||||
Zotero.DB.query("CREATE TABLE syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID)\n)");
|
||||
Zotero.DB.query("INSERT OR IGNORE INTO syncObjectTypes VALUES (7, 'setting')");
|
||||
}
|
||||
}
|
||||
|
||||
_updateDBVersion('userdata2', toVersion);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 "
|
||||
|
@ -2748,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);
|
||||
|
||||
|
@ -3117,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) {
|
||||
|
@ -3391,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;
|
||||
|
@ -3427,6 +3471,21 @@ Zotero.Sync.Server.Data = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
// Add unsynced settings
|
||||
var sql = "SELECT libraryID, setting, value FROM syncedSettings WHERE synced=0";
|
||||
var rows = Zotero.DB.query(sql);
|
||||
if (rows) {
|
||||
var settingsNode = doc.createElement('settings');
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
var settingNode = doc.createElement('setting');
|
||||
settingNode.setAttribute('libraryID', rows[i].libraryID ? rows[i].libraryID : Zotero.libraryID);
|
||||
settingNode.setAttribute('name', rows[i].setting);
|
||||
settingNode.appendChild(doc.createTextNode(_xmlize(rows[i].value)));
|
||||
settingsNode.appendChild(settingNode);
|
||||
}
|
||||
docElem.appendChild(settingsNode);
|
||||
}
|
||||
|
||||
// Deletions
|
||||
var deletedNode = doc.createElement('deleted');
|
||||
var inserted = false;
|
||||
|
|
115
chrome/content/zotero/xpcom/syncedSettings.js
Normal file
115
chrome/content/zotero/xpcom/syncedSettings.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2013 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
Zotero.SyncedSettings = (function () {
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
var module = {
|
||||
get: function (libraryID, setting) {
|
||||
return Q.fcall(function () {
|
||||
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
return JSON.parse(Zotero.DB.valueQuery(sql, [setting, libraryID]));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
set: function (libraryID, setting, value, version, synced) {
|
||||
var self = this;
|
||||
return Q.fcall(function () {
|
||||
return self.setSynchronous(libraryID, setting, value, version, synced);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
setSynchronous: function (libraryID, setting, value, version, synced) {
|
||||
// TODO: get rid of this once we have proper affected rows handling
|
||||
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
var currentValue = Zotero.DB.valueQuery(sql, [setting, libraryID]);
|
||||
|
||||
// Make sure we can tell the difference between a
|
||||
// missing setting (FALSE as returned by valueQuery())
|
||||
// and a FALSE setting (FALSE as returned by JSON.parse())
|
||||
var hasCurrentValue = currentValue !== false;
|
||||
var hasValue = typeof value != 'undefined';
|
||||
|
||||
currentValue = JSON.parse(currentValue);
|
||||
|
||||
if ((!hasCurrentValue && !hasValue) || value === currentValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var id = libraryID + '/' + setting;
|
||||
|
||||
if (hasCurrentValue) {
|
||||
var extraData = {};
|
||||
extraData[id] = {
|
||||
changed: {}
|
||||
};
|
||||
extraData[id].changed = {
|
||||
value: currentValue
|
||||
};
|
||||
}
|
||||
|
||||
// Clear
|
||||
if (typeof value == 'undefined') {
|
||||
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
|
||||
Zotero.DB.query(sql, [setting, libraryID]);
|
||||
|
||||
Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set/update
|
||||
|
||||
if (currentValue === false) {
|
||||
var event = 'add';
|
||||
var extraData = {};
|
||||
}
|
||||
else {
|
||||
var event = 'modify';
|
||||
}
|
||||
|
||||
synced = synced ? 1 : 0;
|
||||
|
||||
if (hasCurrentValue) {
|
||||
var sql = "UPDATE syncedSettings SET value=?, synced=? WHERE setting=? AND libraryID=?";
|
||||
Zotero.DB.query(sql, [JSON.stringify(value), synced, setting, libraryID]);
|
||||
}
|
||||
else {
|
||||
var sql = "INSERT INTO syncedSettings "
|
||||
+ "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)";
|
||||
Zotero.DB.query(sql, [setting, libraryID, JSON.stringify(value), synced]);
|
||||
}
|
||||
Zotero.Notifier.trigger(event, 'setting', [id], extraData);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return module;
|
||||
}());
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -86,7 +86,6 @@
|
|||
</popup>
|
||||
|
||||
<stack id="zotero-pane-stack">
|
||||
|
||||
<!-- Barrier to prevent tabbing into Zotero pane when busy -->
|
||||
<box id="zotero-pane-tab-catcher-top" hidden="true" align="center" pack="center" style="opacity: 0">
|
||||
<checkbox/>
|
||||
|
@ -324,9 +323,11 @@
|
|||
|
||||
<vbox id="zotero-items-pane" zotero-persist="width" flex="1">
|
||||
<deck id="zotero-items-pane-content" selectedIndex="0" flex="1">
|
||||
<!-- Key navigation is handled by listener in itemTreeView.js -->
|
||||
<tree
|
||||
id="zotero-items-tree" context="zotero-itemmenu"
|
||||
enableColumnDrag="true"
|
||||
disableKeyNavigation="true"
|
||||
onfocus="if (ZoteroPane_Local.itemsView.rowCount && !ZoteroPane_Local.itemsView.selection.count) { ZoteroPane_Local.itemsView.selection.select(0); }"
|
||||
onkeydown="ZoteroPane_Local.handleKeyDown(event, this.id)"
|
||||
onkeypress="ZoteroPane_Local.handleKeyPress(event, this.id)"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -150,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
|
||||
|
|
24
chrome/skin/default/zotero/bindings/customcolorpicker.css
Normal file
24
chrome/skin/default/zotero/bindings/customcolorpicker.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
customcolorpicker[type="button"] {
|
||||
width: 38px;
|
||||
height: 24px;
|
||||
border: 1px solid #a7a7a7;
|
||||
background-color: ThreeDFace;
|
||||
padding: 3px;
|
||||
-moz-appearance: button-bevel;
|
||||
}
|
||||
|
||||
.colorpickertile[hover="true"], .cp-light[hover="true"] {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.colorpickertile[selected="true"] {
|
||||
border : 1px outset #C0C0C0;
|
||||
}
|
||||
|
||||
.colorpickertile[hover="true"]:not([selected="true"]) {
|
||||
border : 1px dotted #A7A7A7;
|
||||
}
|
||||
|
||||
.cp-light[hover="true"]:not([selected="true"]) {
|
||||
border : 1px dotted #000000;
|
||||
}
|
|
@ -200,149 +200,6 @@
|
|||
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie63) { -moz-image-region: rect(32px, 2016px, 64px, 1984px); }
|
||||
#zotero-items-tree treechildren::-moz-tree-image(selected, hasAttachment, pie64) { -moz-image-region: rect(32px, 2048px, 64px, 2016px); }
|
||||
|
||||
|
||||
/* Set tag colors */
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFFF) { color:#FFFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCCC) { color:#FFCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC99) { color:#FFCC99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF99) { color:#FFFF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFFCC) { color:#FFFFCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color99FF99) { color:#99FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color99FFFF) { color:#99FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCFFFF) { color:#CCFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCCCFF) { color:#CCCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCCFF) { color:#FFCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCCCCCC) { color:#CCCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF6666) { color:#FF6666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF9966) { color:#FF9966 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF66) { color:#FFFF66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF33) { color:#FFFF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66FF99) { color:#66FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33FFFF) { color:#33FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66FFFF) { color:#66FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color9999FF) { color:#9999FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF99FF) { color:#FF99FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorC0C0C0) { color:#C0C0C0 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF0000) { color:#FF0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF9900) { color:#FF9900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC66) { color:#FFCC66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFFF00) { color:#FFFF00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33FF33) { color:#33FF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color66CCCC) { color:#66CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33CCFF) { color:#33CCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6666CC) { color:#6666CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC66CC) { color:#CC66CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color999999) { color:#999999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC0000) { color:#CC0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFF6600) { color:#FF6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC33) { color:#FFCC33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorFFCC00) { color:#FFCC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color33CC00) { color:#33CC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color00CCCC) { color:#00CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color3366FF) { color:#3366FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6633FF) { color:#6633FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC33CC) { color:#CC33CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color666666) { color:#666666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color990000) { color:#990000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC6600) { color:#CC6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(colorCC9933) { color:#CC9933 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color999900) { color:#999900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color009900) { color:#009900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color339999) { color:#339999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color3333FF) { color:#3333FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color6600CC) { color:#6600CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color993399) { color:#993399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333333) { color:#333333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color660000) { color:#660000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color993300) { color:#993300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color996633) { color:#996633 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color666600) { color:#666600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color006600) { color:#006600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color336666) { color:#336666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color000099) { color:#000099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333399) { color:#333399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663366) { color:#663366 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330000) { color:#330000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663300) { color:#663300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color663333) { color:#663333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color333300) { color:#333300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color003300) { color:#003300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color003333) { color:#003333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color000066) { color:#000066 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330099) { color:#330099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(color330033) { color:#330033 }
|
||||
|
||||
/* For selected item with colored tags, set the background color instead */
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFFFF,focus) { background:#FFFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCCCC,focus) { background:#FFCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC99,focus) { background:#FFCC99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF99,focus) { background:#FFFF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFFCC,focus) { background:#FFFFCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color99FF99,focus) { background:#99FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color99FFFF,focus) { background:#99FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCFFFF,focus) { background:#CCFFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCCCFF,focus) { background:#CCCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCCFF,focus) { background:#FFCCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCCCCCC,focus) { background:#CCCCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF6666,focus) { background:#FF6666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF9966,focus) { background:#FF9966 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF66,focus) { background:#FFFF66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF33,focus) { background:#FFFF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66FF99,focus) { background:#66FF99 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33FFFF,focus) { background:#33FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66FFFF,focus) { background:#66FFFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color9999FF,focus) { background:#9999FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF99FF,focus) { background:#FF99FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorC0C0C0,focus) { background:#C0C0C0 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF0000,focus) { background:#FF0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF9900,focus) { background:#FF9900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC66,focus) { background:#FFCC66 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFFF00,focus) { background:#FFFF00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33FF33,focus) { background:#33FF33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color66CCCC,focus) { background:#66CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33CCFF,focus) { background:#33CCFF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6666CC,focus) { background:#6666CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC66CC,focus) { background:#CC66CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color999999,focus) { background:#999999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC0000,focus) { background:#CC0000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFF6600,focus) { background:#FF6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC33,focus) { background:#FFCC33 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorFFCC00,focus) { background:#FFCC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color33CC00,focus) { background:#33CC00 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color00CCCC,focus) { background:#00CCCC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color3366FF,focus) { background:#3366FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6633FF,focus) { background:#6633FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC33CC,focus) { background:#CC33CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color666666,focus) { background:#666666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color990000,focus) { background:#990000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC6600,focus) { background:#CC6600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(colorCC9933,focus) { background:#CC9933 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color999900,focus) { background:#999900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color009900,focus) { background:#009900 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color339999,focus) { background:#339999 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color3333FF,focus) { background:#3333FF }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color6600CC,focus) { background:#6600CC }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color993399,focus) { background:#993399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333333,focus) { background:#333333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color660000,focus) { background:#660000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color993300,focus) { background:#993300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color996633,focus) { background:#996633 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color666600,focus) { background:#666600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color006600,focus) { background:#006600 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color336666,focus) { background:#336666 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color000099,focus) { background:#000099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333399,focus) { background:#333399 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663366,focus) { background:#663366 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330000,focus) { background:#330000 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663300,focus) { background:#663300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color663333,focus) { background:#663333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color333300,focus) { background:#333300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color003300,focus) { background:#003300 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color003333,focus) { background:#003333 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color000066,focus) { background:#000066 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330099,focus) { background:#330099 }
|
||||
#zotero-items-tree treechildren::-moz-tree-row(color330033,focus) { background:#330033 }
|
||||
|
||||
/* Style search results, display non-matches in gray */
|
||||
#zotero-items-tree treechildren::-moz-tree-cell-text(contextRow) {
|
||||
color: gray;
|
||||
|
|
8
chrome/skin/default/zotero/tagColorChooser.css
Normal file
8
chrome/skin/default/zotero/tagColorChooser.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
menulist {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#number-key {
|
||||
margin: 0 1px;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -96,6 +96,14 @@ tagsbox row
|
|||
-moz-box-align:center;
|
||||
}
|
||||
|
||||
customcolorpicker {
|
||||
-moz-binding: url(chrome://zotero/content/bindings/customcolorpicker.xml#custom-colorpicker);
|
||||
}
|
||||
|
||||
customcolorpicker[type=button] {
|
||||
-moz-binding: url(chrome://zotero/content/bindings/customcolorpicker.xml#custom-colorpicker-button);
|
||||
}
|
||||
|
||||
seealsobox
|
||||
{
|
||||
-moz-binding: url('chrome://zotero/content/bindings/relatedbox.xml#seealso-box');
|
||||
|
@ -160,7 +168,6 @@ zoterofilesyncstatus {
|
|||
-moz-binding: url('chrome://zotero/content/bindings/filesyncstatus.xml#file-sync-status');
|
||||
}
|
||||
|
||||
|
||||
label.zotero-text-link {
|
||||
-moz-binding: url('chrome://zotero/content/bindings/text-link.xml#text-link');
|
||||
-moz-user-focus: normal;
|
||||
|
|
|
@ -101,6 +101,7 @@ const xpcomFilesLocal = [
|
|||
'storage/mode',
|
||||
'storage/zfs',
|
||||
'storage/webdav',
|
||||
'syncedSettings',
|
||||
'timeline',
|
||||
'uri',
|
||||
'translation/translate_item',
|
||||
|
|
|
@ -1360,3 +1360,4 @@ INSERT INTO "syncObjectTypes" VALUES(3, 'item');
|
|||
INSERT INTO "syncObjectTypes" VALUES(4, 'search');
|
||||
INSERT INTO "syncObjectTypes" VALUES(5, 'tag');
|
||||
INSERT INTO "syncObjectTypes" VALUES(6, 'relation');
|
||||
INSERT INTO "syncObjectTypes" VALUES(7, 'setting');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- 76
|
||||
-- 77
|
||||
|
||||
-- Copyright (c) 2009 Center for History and New Media
|
||||
-- George Mason University, Fairfax, Virginia, USA
|
||||
|
@ -30,6 +30,7 @@ CREATE TABLE version (
|
|||
);
|
||||
CREATE INDEX schema ON version(schema);
|
||||
|
||||
-- Settings that have to be tied to the local database rather than the profile directory
|
||||
CREATE TABLE settings (
|
||||
setting TEXT,
|
||||
key TEXT,
|
||||
|
@ -37,6 +38,16 @@ CREATE TABLE settings (
|
|||
PRIMARY KEY (setting, key)
|
||||
);
|
||||
|
||||
-- Settings that get synced between Zotero installations
|
||||
CREATE TABLE syncedSettings (
|
||||
setting TEXT NOT NULL,
|
||||
libraryID INT NOT NULL,
|
||||
value NOT NULL,
|
||||
version INT NOT NULL DEFAULT 0,
|
||||
synced INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (setting, libraryID)
|
||||
);
|
||||
|
||||
-- The foundational table; every item collected has a unique record here
|
||||
CREATE TABLE items (
|
||||
itemID INTEGER PRIMARY KEY,
|
||||
|
|
Loading…
Reference in a new issue