Kaynağa Gözat

rework pref-feeds dialog; update other pref panes

Andrew Dolgov 13 yıl önce
ebeveyn
işleme
1985a5e0d7
7 değiştirilmiş dosya ile 636 ekleme ve 76 silme
  1. 19 3
      backend.php
  2. 46 38
      functions.php
  3. 464 0
      lib/CheckBoxTree.js
  4. 64 11
      modules/pref-feeds.php
  5. 10 10
      modules/pref-filters.php
  6. 9 8
      modules/pref-labels.php
  7. 24 6
      prefs.js

+ 19 - 3
backend.php

@@ -161,9 +161,8 @@
 		break; // rpc
 
 		case "feeds":
-			$print_exec_time = true;
-
 			$subop = $_REQUEST["subop"];
+			$root = (bool)$_REQUEST["root"];
 
 			switch($subop) {
 				case "catchupAll":
@@ -206,7 +205,24 @@
 
 			}
 
-			print json_encode(outputFeedList($link));
+			if (!$root) {
+				print json_encode(outputFeedList($link));
+			} else {
+
+				$feeds = outputFeedList($link, false);
+
+				$root = array();
+				$root['id'] = 'root';
+				$root['name'] = __('Feeds');
+				$root['items'] = $feeds['items'];
+
+				$fl = array();
+				$fl['identifier'] = 'id';
+				$fl['label'] = 'name';
+				$fl['items'] = array($root);
+
+				print json_encode($fl);
+			}
 
 		break; // feeds
 

+ 46 - 38
functions.php

@@ -4203,7 +4203,7 @@
 
 		}
 	
-	function outputFeedList($link) {
+	function outputFeedList($link, $special = true) {
 
 		$feedlist = array();
 
@@ -4217,47 +4217,51 @@
 
 		/* virtual feeds */
 
-		if ($enable_cats) {
-			$cat_hidden = get_pref($link, "_COLLAPSED_SPECIAL");
-			$cat = feedlist_init_cat($link, -1, $cat_hidden);
-		} else {
-			$cat['items'] = array();
-		}
-
-		foreach (array(-4, -3, -1, -2, 0) as $i) {
-			array_push($cat['items'], feedlist_init_feed($link, $i));
-		}
+		if ($special) {
 
-		if ($enable_cats) {
-			array_push($feedlist['items'], $cat);
-		} else {
-			$feedlist['items'] = array_merge($feedlist['items'], $cat['items']);
-		}
-
-		$result = db_query($link, "SELECT * FROM
-			ttrss_labels2 WHERE owner_uid = '$owner_uid' ORDER by caption");
+			if ($enable_cats) {
+				$cat_hidden = get_pref($link, "_COLLAPSED_SPECIAL");
+				$cat = feedlist_init_cat($link, -1, $cat_hidden);
+			} else {
+				$cat['items'] = array();
+			}
 	
-		if (get_pref($link, 'ENABLE_FEED_CATS')) {
-			$cat_hidden = get_pref($link, "_COLLAPSED_LABELS");
-			$cat = feedlist_init_cat($link, -2, $cat_hidden);
-		} else {
-			$cat['items'] = array();
-		}
-
-		while ($line = db_fetch_assoc($result)) {
-
-			$label_id = -$line['id'] - 11;
-			$count = getFeedUnread($link, $label_id);
+			foreach (array(-4, -3, -1, -2, 0) as $i) {
+				array_push($cat['items'], feedlist_init_feed($link, $i));
+			}
+	
+			if ($enable_cats) {
+				array_push($feedlist['items'], $cat);
+			} else {
+				$feedlist['items'] = array_merge($feedlist['items'], $cat['items']);
+			}
+	
+			$result = db_query($link, "SELECT * FROM
+				ttrss_labels2 WHERE owner_uid = '$owner_uid' ORDER by caption");
+		
+			if (get_pref($link, 'ENABLE_FEED_CATS')) {
+				$cat_hidden = get_pref($link, "_COLLAPSED_LABELS");
+				$cat = feedlist_init_cat($link, -2, $cat_hidden);
+			} else {
+				$cat['items'] = array();
+			}
 
-			array_push($cat['items'], feedlist_init_feed($link, $label_id, 
-				false, $count));
+			while ($line = db_fetch_assoc($result)) {
+	
+				$label_id = -$line['id'] - 11;
+				$count = getFeedUnread($link, $label_id);
+	
+				array_push($cat['items'], feedlist_init_feed($link, $label_id, 
+					false, $count));
+			}
+	
+			if ($enable_cats) {
+				array_push($feedlist['items'], $cat);
+			} else {
+				$feedlist['items'] = array_merge($feedlist['items'], $cat['items']);
+			}
 		}
 
-		if ($enable_cats) {
-			array_push($feedlist['items'], $cat);
-		} else {
-			$feedlist['items'] = array_merge($feedlist['items'], $cat['items']);
-		}
 	
 /*		if (get_pref($link, 'ENABLE_FEED_CATS')) {
 			if (get_pref($link, "FEEDS_SORT_BY_UNREAD")) {
@@ -6997,18 +7001,20 @@
 			$cat_unread = getCategoryUnread($link, $cat_id);
 		}
 
-		$obj['id'] = 'CAT:' . ((int)$cat_id);
+		$obj['id'] = 'CAT:' . $cat_id;
 		$obj['items'] = array();
 		$obj['name'] = getCategoryTitle($link, $cat_id);
 		$obj['type'] = 'feed';
 		$obj['unread'] = (int) $cat_unread;
 		$obj['hidden'] = $hidden;
+		$obj['bare_id'] = $cat_id;
 
 		return $obj;
 	}
 
 	function feedlist_init_feed($link, $feed_id, $title = false, $unread = false, $error = '', $updated = '') {
 		$obj = array();
+		$feed_id = (int) $feed_id;
 
 		if (!$title) 
 			$title = getFeedTitle($link, $feed_id, false);
@@ -7023,6 +7029,8 @@
 		$obj['error'] = $error;
 		$obj['updated'] = $updated;
 		$obj['icon'] = getFeedIcon($feed_id);
+		$obj['checkbox'] = false;
+		$obj['bare_id'] = $feed_id;
 
 		return $obj;
 	}

+ 464 - 0
lib/CheckBoxTree.js

@@ -0,0 +1,464 @@
+dojo.provide("lib.CheckBoxTree");
+dojo.provide("lib.CheckBoxStoreModel");
+
+// THIS WIDGET IS BASED ON DOJO/DIJIT 1.4.0 AND WILL NOT WORK WITH PREVIOUS VERSIONS
+//
+//	Release date: 02/05/2010
+//
+
+dojo.require("dijit.Tree");
+dojo.require("dijit.form.CheckBox");
+
+dojo.declare( "lib.CheckBoxStoreModel", dijit.tree.TreeStoreModel, 
+{
+	// checkboxAll: Boolean
+	//		If true, every node in the tree will receive a checkbox regardless if the 'checkbox' attribute 
+	//		is specified in the dojo.data.
+	checkboxAll: true,
+
+	// checkboxState: Boolean
+	// 		The default state applied to every checkbox unless otherwise specified in the dojo.data.
+	//		(see also: checkboxIdent)
+	checkboxState: false,
+
+	// checkboxRoot: Boolean
+	//		If true, the root node will receive a checkbox eventhough it's not a true entry in the store.
+	//		This attribute is independent of the showRoot attribute of the tree itself. If the tree
+	//		attribute 'showRoot' is set to false to checkbox for the root will not show either.  
+	checkboxRoot: false,
+
+	// checkboxStrict: Boolean
+	//		If true, a strict parent-child checkbox relation is maintained. For example, if all children 
+	//		are checked the parent will automatically be checked or if any of the children are unchecked
+	//		the parent will be unchecked. 
+	checkboxStrict: true,
+
+	// checkboxIdent: String
+	//		The attribute name (attribute of the dojo.data.item) that specifies that items checkbox initial
+	//		state. Example:	{ name:'Egypt', type:'country', checkbox: true }
+	//		If a dojo.data.item has no 'checkbox' attribute specified it will depend on the attribute
+	//		'checkboxAll' if one will be created automatically and if so what the initial state will be as
+	//		specified by 'checkboxState'. 
+	checkboxIdent: "checkbox",
+
+	updateCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
+		// summary:
+		//		Update the checkbox state (true/false) for the item and the associated parent and 
+		//		child checkboxes if any. 
+		// description:
+		//		Update a single checkbox state (true/false) for the item and the associated parent 
+		//		and child checkboxes if any. This function is called from the tree if a user checked
+		//		or unchecked a checkbox on the tree. The parent and child tree nodes are updated to 
+		//		maintain consistency if 'checkboxStrict' is set to true.
+		//	storeItem:
+		//		The item in the dojo.data.store whos checkbox state needs updating.
+		//	newState:
+		//		The new state of the checkbox: true or false
+		//	example:
+		//	| model.updateCheckboxState(item, true);
+		//
+		this._setCheckboxState( storeItem, newState );
+		if( this.checkboxStrict ) {
+			this._updateChildCheckbox( storeItem, newState );
+			this._updateParentCheckbox( storeItem, newState );
+		}
+	},
+	setAllChecked: function(checked) {
+		var items = this.store._arrayOfAllItems;
+		this.setCheckboxState(items, checked);
+	},
+	setCheckboxState: function(items, checked) {
+		for (var i = 0; i < items.length; i++) {
+			this._setCheckboxState(items[i], checked);
+		}
+	},
+	getCheckedItems: function() {
+		var items = this.store._arrayOfAllItems;
+		var result = [];
+
+		for (var i = 0; i < items.length; i++) {
+			if (this.store.getValue(items[i], 'checkbox')) 
+				result.push(items[i]);
+		}
+
+		return result;
+   },
+
+	getCheckboxState: function(/*dojo.data.Item*/ storeItem) {
+		// summary:
+		//		Get the current checkbox state from the dojo.data.store.
+		// description:
+		//		Get the current checkbox state from the dojo.data store. A checkbox can have three
+		//		different states: true, false or undefined. Undefined in this context means no
+		//		checkbox identifier (checkboxIdent) was found in the dojo.data store. Depending on
+		//		the checkbox attributes as specified above the following will take place:
+		//		a) 	If the current checkbox state is undefined and the checkbox attribute 'checkboxAll' or
+		//			'checkboxRoot' is true one will be created and the default state 'checkboxState' will
+		//			be applied.
+		//		b)	If the current state is undefined and 'checkboxAll' is false the state undefined remains
+		//			unchanged and is returned. This will prevent any tree node from creating a checkbox.
+		//
+		//	storeItem:
+		//		The item in the dojo.data.store whos checkbox state is returned.
+		//	example:
+		//	| var currState = model.getCheckboxState(item);
+		//		
+		var currState = undefined;
+		
+		// Special handling required for the 'fake' root entry (the root is NOT a dojo.data.item).
+		if ( storeItem == this.root ) {
+			if( typeof(storeItem.checkbox) == "undefined" ) {
+				this.root.checkbox = undefined;		// create a new checbox reference as undefined.
+				if( this.checkboxRoot ) {
+					currState = this.root.checkbox = this.checkboxState;
+				}
+			} else {
+				currState = this.root.checkbox;
+			}
+		} else {	// a valid dojo.store.item
+			currState = this.store.getValue(storeItem, this.checkboxIdent);
+			if( currState == undefined && this.checkboxAll) {
+				this._setCheckboxState( storeItem, this.checkboxState );
+				currState = this.checkboxState;
+			}
+		}
+		return currState  // the current state of the checkbox (true/false or undefined)
+	},
+
+	_setCheckboxState: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
+		// summary:
+		//		Set/update the checkbox state on the dojo.data store.
+		// description:
+		//		Set/update the checkbox state on the dojo.data.store. Retreive the current
+		//		state of the checkbox and validate if an update is required, this will keep 
+		//		update events to a minimum. On completion a 'onCheckboxChange' event is
+		//		triggered. 
+		//		If the current state is undefined (ie: no checkbox attribute specified for 
+		//		this dojo.data.item) the 'checkboxAll' attribute is checked	to see if one 
+		//		needs to be created. In case of the root the 'checkboxRoot' attribute is checked.
+		//		NOTE: the store.setValue function will create the 'checkbox' attribute for the 
+		//		item if none exists.   
+		//	storeItem:
+		//		The item in the dojo.data.store whos checkbox state is updated.
+		//	newState:
+		//		The new state of the checkbox: true or false
+		//	example:
+		//	| model.setCheckboxState(item, true);
+		//
+		var stateChanged = true;
+		
+		if( storeItem != this.root ) {
+			var currState = this.store.getValue(storeItem, this.checkboxIdent);
+			if( currState != newState && ( currState !== undefined || this.checkboxAll ) ) {
+				this.store.setValue(storeItem, this.checkboxIdent, newState);
+			} else {
+				stateChanged = false;	// No changes to the checkbox
+			}
+		} else {  // Tree root instance
+			if( this.root.checkbox != newState && ( this.root.checkbox !== undefined || this.checkboxRoot ) ) {
+				this.root.checkbox = newState;
+			} else {
+				stateChanged = false;
+			}
+		}
+		if( stateChanged ) {	// In case of any changes trigger the update event.
+			this.onCheckboxChange(storeItem);
+		}
+		return stateChanged;
+	},
+	
+	_updateChildCheckbox: function(/*dojo.data.Item*/ parentItem, /*Boolean*/ newState ) {
+		//	summary:
+		//		Set all child checkboxes to true/false depending on the parent checkbox state.
+		//	description:
+		//		If a parent checkbox changes state, all child and grandchild checkboxes will be 
+		//		updated to reflect the change. For example, if the parent state is set to true, 
+		//		all child and grandchild checkboxes will receive that same 'true' state.
+		//		If a child checkbox changes state and has multiple parent, all of its parents
+		//		need to be re-evaluated.
+		//	parentItem:
+		//		The parent dojo.data.item whos child/grandchild checkboxes require updating.
+		//	newState:
+		//		The new state of the checkbox: true or false
+		//
+		if( this.mayHaveChildren( parentItem )) {
+			this.getChildren( parentItem, dojo.hitch( this,
+				function( children ) {
+					dojo.forEach( children, function(child) {
+						if( this._setCheckboxState(child, newState) ) {					
+							var parents = this._getParentsItem(child);
+							if( parents.length > 1 ) {
+								this._updateParentCheckbox( child, newState );
+							}
+						}
+						if( this.mayHaveChildren( child )) {
+							this._updateChildCheckbox( child, newState );
+						}
+					}, this );
+				}),
+				function(err) {
+					console.error(this, ": updating child checkboxes: ", err);
+				} 
+			);
+		}
+	},
+
+	_updateParentCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
+		//	summary:
+		//		Update the parent checkbox state depending on the state of all child checkboxes.
+		//	description:
+		//		Update the parent checkbox state depending on the state of all child checkboxes.
+		//		The parent checkbox automatically changes state if ALL child checkboxes are true
+		//		or false. If, as a result, the parent checkbox changes state, we will check if 
+		//		its parent needs to be updated as well all the way upto the root.
+		//	storeItem:
+		//		The dojo.data.item whos parent checkboxes require updating.
+		//	newState:
+		//		The new state of the checkbox: true or false
+		//
+		var parents = this._getParentsItem(storeItem);
+		dojo.forEach( parents, function( parentItem ) {
+			if( newState ) { // new state = true (checked)
+				this.getChildren( parentItem, dojo.hitch( this,
+					function(siblings) {
+						var allChecked  = true;			
+						dojo.some( siblings, function(sibling) {
+							siblState = this.getCheckboxState(sibling);
+							if( siblState !== undefined && allChecked )
+								allChecked = siblState; 
+							return !(allChecked);
+						}, this );
+						if( allChecked ) {
+							this._setCheckboxState( parentItem, true );
+							this._updateParentCheckbox( parentItem, true );
+						}
+					}),
+					function(err) {
+						console.error(this, ": updating parent checkboxes: ", err);
+					}
+				);
+			} else { 	// new state = false (unchecked)
+				if( this._setCheckboxState( parentItem, false ) ) {
+					this._updateParentCheckbox( parentItem, false );
+				}
+			}
+		}, this );
+	},
+	
+	_getParentsItem: function(/*dojo.data.Item*/ storeItem ) {
+		// summary:
+		//		Get the parent(s) of a dojo.data item.  
+		// description:
+		//		Get the parent(s) of a dojo.data item. The '_reverseRefMap' entry of the item is
+		//		used to identify the parent(s). A child will have a parent reference if the parent 
+		//		specified the '_reference' attribute. 
+		//		For example: children:[{_reference:'Mexico'}, {_reference:'Canada'}, ...
+		//	storeItem:
+		//		The dojo.data.item whos parent(s) will be returned.
+		//
+		var parents = [];
+
+		if( storeItem != this.root ) {
+			var references = storeItem[this.store._reverseRefMap];
+			for(itemId in references ) {
+				parents.push(this.store._itemsByIdentity[itemId]);
+			}
+			if (!parents.length) {
+				parents.push(this.root);
+			}
+		}
+		return parents // parent(s) of a dojo.data.item (Array of dojo.data.items)
+	},
+
+	validateData: function(/*dojo.data.Item*/ storeItem, /*thisObject*/ scope ) {
+		// summary:
+		//		Validate/normalize the parent(s) checkbox data in the dojo.data store.
+		// description:
+		//		Validate/normalize the parent-child checkbox relationship if the attribute
+		//		'checkboxStrict' is set to true. This function is called as part of the post 
+		//		creation of the Tree instance. All parent checkboxes are set to the appropriate
+		//		state according to the actual state(s) of their children. 
+		//		This will potentionally overwrite whatever was specified for the parent in the
+		//		dojo.data store. This will garantee the tree is in a consistent state after startup.
+		//	storeItem:
+		//		The element to start traversing the dojo.data.store, typically model.root
+		//	scope:
+		//		The scope to use when this method executes.
+		//	example:
+		//	| this.model.validateData(this.model.root, this.model);
+		//
+		if( !scope.checkboxStrict ) {
+			return;
+		}
+		scope.getChildren( storeItem, dojo.hitch( scope,
+			function(children) {
+				var allChecked  = true;
+				var childState;
+				dojo.forEach( children, function( child ) {
+					if( this.mayHaveChildren( child )) {
+						this.validateData( child, this );
+					}
+					childState = this.getCheckboxState( child );
+					if( childState !== undefined && allChecked )
+						allChecked = childState; 
+				}, this);
+
+				if ( this._setCheckboxState( storeItem, allChecked) ) {
+					this._updateParentCheckbox( storeItem, allChecked);
+				}
+			}),
+			function(err) {
+				console.error(this, ": validating checkbox data: ", err);
+			} 
+		);
+	},
+
+	onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
+		// summary:
+		//		Callback whenever a checkbox state has changed state, so that 
+		//		the Tree can update the checkbox.  This callback is generally
+		//		triggered by the '_setCheckboxState' function. 
+		// tags:
+		//		callback
+	}
+		
+});
+
+dojo.declare( "lib._CheckBoxTreeNode", dijit._TreeNode,
+{
+	// _checkbox: [protected] dojo.doc.element
+	//		Local reference to the dojo.doc.element of type 'checkbox'
+	_checkbox: null,
+
+	_createCheckbox: function() {
+		// summary:
+		//		Create a checkbox on the CheckBoxTreeNode
+		// description:
+		//		Create a checkbox on the CheckBoxTreeNode. The checkbox is ONLY created if a
+		//		valid reference was found in the dojo.data store or the attribute 'checkboxAll'
+		//		is set to true. If the current state is 'undefined' no reference was found and 
+		//		'checkboxAll' is set to false.
+		//		Note: the attribute 'checkboxAll' is validated by the getCheckboxState function
+		//		therefore no need to do that here. (see getCheckboxState for details).
+		//		
+		var	currState = this.tree.model.getCheckboxState( this.item );
+		if( currState !== undefined ) {
+			this._checkbox = new dijit.form.CheckBox();
+			//this._checkbox = dojo.doc.createElement('input');
+			this._checkbox.type    = 'checkbox';
+			this._checkbox.attr('checked', currState);
+			dojo.place(this._checkbox.domNode, this.expandoNode, 'after');
+		}
+	},
+	
+	postCreate: function() {
+		// summary:
+		//		Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
+		// description:
+		//		Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
+		this._createCheckbox();
+		this.inherited( arguments );
+	}
+
+});
+
+dojo.declare( "lib.CheckBoxTree", dijit.Tree,
+{
+	
+	onNodeChecked: function(/*dojo.data.Item*/ storeItem, /*treeNode*/ treeNode) {
+		// summary:
+		//		Callback when a checkbox tree node is checked
+		// tags:
+		//		callback
+	},
+	
+	onNodeUnchecked: function(/*dojo.data.Item*/ storeItem, /* treeNode */ treeNode) {
+		// summary:
+		//		Callback when a checkbox tree node is unchecked
+		// tags:
+		//		callback
+	},
+	
+	_onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e) {
+		// summary:
+		//		Translates click events into commands for the controller to process
+		// description:
+		//		the _onClick function is called whenever a 'click' is detected. This
+		//		instance of _onClick only handles the click events associated with
+		//		the checkbox whos DOM name is INPUT.
+		// 
+		var domElement = e.target;
+
+		// Only handle checkbox clicks here
+		if(domElement.type != 'checkbox') {
+			return this.inherited( arguments );
+		}
+
+		this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
+		// Go tell the model to update the checkbox state
+		
+		this.model.updateCheckbox( nodeWidget.item, nodeWidget._checkbox.checked ); 
+		// Generate some additional events
+		//this.onClick( nodeWidget.item, nodeWidget, e );
+		if(nodeWidget._checkbox.checked) {
+			this.onNodeChecked( nodeWidget.item, nodeWidget);
+		} else {
+			this.onNodeUnchecked( nodeWidget.item, nodeWidget);
+		}
+		this.focusNode(nodeWidget);
+	},
+	
+	_onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
+		// summary:
+		//		Processes notification of a change to a checkbox state (triggered by the model).
+		// description:
+		//		Whenever the model changes the state of a checkbox in the dojo.data.store it will 
+		//		trigger the 'onCheckboxChange' event allowing the Tree to make the same changes 
+		//		on the tree Node. There are several conditions why a tree node or checkbox does not
+		//		exists:
+		//		a) 	The node has not been created yet (the user has not expanded the tree node yet).
+		//		b) 	The checkbox may be null if condition (a) exists or no 'checkbox' attribute was
+		//			specified for the associated dojo.data.item and the attribute 'checkboxAll' is 
+		//			set to false. 
+		// tags:
+		//		callback
+		var model 	 = this.model,
+			identity = model.getIdentity(storeItem),
+			nodes 	 = this._itemNodesMap[identity];
+
+		// As of dijit.Tree 1.4 multiple references (parents) are supported, therefore we may have
+		// to update multiple nodes which are all associated with the same dojo.data.item.
+		if( nodes ) {
+			dojo.forEach( nodes, function(node) {
+				if( node._checkbox != null ) {
+					node._checkbox.attr('checked', this.model.getCheckboxState( storeItem ));
+				}
+			}, this );
+		}
+	}, 
+
+	postCreate: function() {
+		// summary:
+		//		Handle any specifics related to the tree and model after the instanciation of the Tree. 
+		// description:
+		//		Validate if we have a 'write' store first. Subscribe to the 'onCheckboxChange' event 
+		//		(triggered by the model) and kickoff the initial checkbox data validation.
+		//
+		var store = this.model.store;
+		if(!store.getFeatures()['dojo.data.api.Write']){
+			throw new Error("lib.CheckboxTree: store must support dojo.data.Write");
+		}
+		this.connect(this.model, "onCheckboxChange", "_onCheckboxChange");
+		this.model.validateData( this.model.root, this.model );
+		this.inherited(arguments);
+	},
+
+	_createTreeNode: function( args ) {
+		// summary:
+		//		Create a new CheckboxTreeNode instance.
+		// description:
+		//		Create a new CheckboxTreeNode instance.
+		return new lib._CheckBoxTreeNode(args);
+	}
+
+});

+ 64 - 11
modules/pref-feeds.php

@@ -981,6 +981,7 @@
 
 		print "<div dojoType=\"dijit.Toolbar\">";
 
+		/* 
 		print "<div style='float : right'> 
 			<input id=\"feed_search\" size=\"20\" type=\"search\"
 				onfocus=\"disableHotkeys();\" 
@@ -988,22 +989,37 @@
 				onchange=\"updateFeedList()\" value=\"$feed_search\">
 			<button onclick=\"updateFeedList()\">".
 				__('Search')."</button>
-			</div>";
-		
-		print "<button onclick=\"quickAddFeed()\">"
-			.__('Subscribe to feed')."</button> ";
+				</div>"; */
+
+		print "<div dojoType=\"dijit.form.DropDownButton\">".
+			"<span>" . __('Select')."</span>";
+
+		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
+		print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(true)\" 
+			dojoType=\"dijit.MenuItem\">".__('All')."</div>";
+		print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(false)\" 
+			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
+		print "</div>";
+
+		/* print "<div onclick=\"selectTableRows('prefFeedList', 'all')\">".__('All')."</div>,
+			<div href=\"#\" onclick=\"selectTableRows('prefFeedList', 'none')\">".__('None')."</div>"; */
+
+		print "</div>";
 
-		print "<button onclick=\"editSelectedFeed()\">".
-			__('Edit feeds')."</button> ";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"quickAddFeed()\">"
+			.__('Subscribe to feed')."</button dojoType=\"dijit.form.Button\"> ";
+
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"editSelectedFeed()\">".
+			__('Edit feeds')."</button dojoType=\"dijit.form.Button\"> ";
 
 		if (get_pref($link, 'ENABLE_FEED_CATS')) {
 
-			print "<button onclick=\"editFeedCats()\">".
-				__('Edit categories')."</button> ";
+			print "<button dojoType=\"dijit.form.Button\" onclick=\"editFeedCats()\">".
+				__('Edit categories')."</button dojoType=\"dijit.form.Button\"> ";
 		}
 
-		print "<button onclick=\"removeSelectedFeeds()\">"
-			.__('Unsubscribe')."</button> ";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedFeeds()\">"
+			.__('Unsubscribe')."</button dojoType=\"dijit.form.Button\"> ";
 
 		if (defined('_ENABLE_FEED_DEBUGGING')) {
 
@@ -1025,6 +1041,43 @@
 
 		print "</div>"; # toolbar
 
+		print "<div id=\"feedlistLoading\">
+		<img src='images/indicator_tiny.gif'>".
+		 __("Loading, please wait...")."</div>";
+
+		print "<div dojoType=\"dojo.data.ItemFileWriteStore\" jsId=\"feedStore\" 
+			url=\"backend.php?op=feeds&root=1\">
+		</div>
+		<div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"feedModel\" store=\"feedStore\"
+		query=\"{id:'root'}\" rootId=\"root\" rootLabel=\"Feeds\"
+			childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
+		</div>
+		<div dojoType=\"lib.CheckBoxTree\" id=\"feedTree\" _dndController=\"dijit.tree.dndSource\" 
+			betweenThreshold=\"1\"
+			model=\"feedModel\" openOnClick=\"false\">
+		<script type=\"dojo/method\" event=\"onClick\" args=\"item\">
+			var id = String(item.id);
+			var bare_id = id.substr(id.indexOf(':')+1);
+
+			console.log('onClick: ' + id);
+
+			if (id.match('FEED')) {
+				editFeed(bare_id, event);
+			}
+			
+		</script>
+		<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
+			Element.hide(\"feedlistLoading\");
+		</script>
+		<script type=\"dojo/method\" event=\"checkItemAcceptance\" args=\"item, source, position\">
+			var source_item = dijit.getEnclosingWidget(source);
+			console.log(item);
+			console.log(source_item);
+		</script>
+
+		</div>";
+
+		/*
 		$feeds_sort = db_escape_string($_REQUEST["sort"]);
 
 		if (!$feeds_sort || $feeds_sort == "undefined") {
@@ -1234,7 +1287,7 @@
 			}
 			print "</p>";
 
-		}
+		} */
 
 		print "</div>"; # feeds pane
 

+ 10 - 10
modules/pref-filters.php

@@ -299,25 +299,25 @@
 		print "<div id=\"pref-filter-toolbar\" dojoType=\"dijit.Toolbar\">";
 
 		print "<div style='float : right; padding-right : 4px;'>
-			<input id=\"filter_search\" size=\"20\" type=\"search\"
+			<input dojoType=\"dijit.form.TextBox\" id=\"filter_search\" size=\"20\" type=\"search\"
 				onfocus=\"javascript:disableHotkeys();\" 
 				onblur=\"javascript:enableHotkeys();\"
 				onchange=\"javascript:updateFilterList()\" value=\"$filter_search\">
-			<button onclick=\"javascript:updateFilterList()\">".__('Search')."</button>
+			<button dojoType=\"dijit.form.Button\" onclick=\"javascript:updateFilterList()\">".__('Search')."</button>
 		</div>";
 
-		print "<button onclick=\"return quickAddFilter()\">".
-			__('Create filter')."</button> "; 
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return quickAddFilter()\">".
+			__('Create filter')."</button dojoType=\"dijit.form.Button\"> "; 
 
-		print "<button onclick=\"return editSelectedFilter()\">".
-			__('Edit')."</button> ";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return editSelectedFilter()\">".
+			__('Edit')."</button dojoType=\"dijit.form.Button\"> ";
 
-		print "<button onclick=\"return removeSelectedFilters()\">".
-			__('Remove')."</button> ";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return removeSelectedFilters()\">".
+			__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
 
 		if (defined('_ENABLE_FEED_DEBUGGING')) {
-			print "<button onclick=\"rescore_all_feeds()\">".
-				__('Rescore articles')."</button> "; 
+			print "<button dojoType=\"dijit.form.Button\" onclick=\"rescore_all_feeds()\">".
+				__('Rescore articles')."</button dojoType=\"dijit.form.Button\"> "; 
 		}
 
 		print "</div>"; # toolbar

+ 9 - 8
modules/pref-labels.php

@@ -115,7 +115,7 @@
 
 				if (label_create($link, $caption)) {
 					if (!$output) {
-						print T_sprintf("Created label <b>%s</b>", htmlspecialchars($caption));
+						//print T_sprintf("Created label <b>%s</b>", htmlspecialchars($caption));
 					}
 				}
 
@@ -156,20 +156,21 @@
 
 		print "<div style='float : right; padding-right : 4px'>
 			<input id=\"label_search\" size=\"20\" type=\"search\"
+				dojoType=\"dijit.form.TextBox\"
 				onfocus=\"javascript:disableHotkeys();\" 
 				onblur=\"javascript:enableHotkeys();\"
 				onchange=\"javascript:updateLabelList()\" value=\"$label_search\">
-			<button onclick=\"javascript:updateLabelList()\">".__('Search')."</button>
+			<button dojoType=\"dijit.form.Button\" onclick=\"javascript:updateLabelList()\">".__('Search')."</button>
 			</div>";
 
-		print"<button onclick=\"return addLabel()\">".
-			__('Create label')."</button> ";
+		print"<button dojoType=\"dijit.form.Button\" onclick=\"return addLabel()\">".
+			__('Create label')."</button dojoType=\"dijit.form.Button\"> ";
 
-		print "<button onclick=\"removeSelectedLabels()\">".
-			__('Remove')."</button> ";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedLabels()\">".
+			__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
 
-		print "<button onclick=\"labelColorReset()\">".
-			__('Clear colors')."</button>";
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"labelColorReset()\">".
+			__('Clear colors')."</button dojoType=\"dijit.form.Button\">";
 
 
 		print "</div>"; #toolbar

+ 24 - 6
prefs.js

@@ -395,8 +395,8 @@ function editFeed(feed, event) {
 	
 			notify_progress("Loading, please wait...");
 
-			selectTableRows('prefFeedList', 'none');	
-			selectTableRowById('FEEDR-'+feed, 'FRCHK-'+feed, true);
+//			selectTableRows('prefFeedList', 'none');	
+//			selectTableRowById('FEEDR-'+feed, 'FRCHK-'+feed, true);
 	
 			var query = "?op=pref-feeds&subop=editfeed&id=" +
 				param_escape(feed);
@@ -409,9 +409,9 @@ function editFeed(feed, event) {
 					} });
 
 		} else if (event.ctrlKey) {
-			var cb = $('FRCHK-' + feed);
-			cb.checked = !cb.checked;
-			toggleSelectRow(cb);
+//			var cb = $('FRCHK-' + feed);
+//			cb.checked = !cb.checked;
+//			toggleSelectRow(cb);
 		}
 
 
@@ -429,7 +429,15 @@ function getSelectedUsers() {
 }
 
 function getSelectedFeeds() {
-	return getSelectedTableRowIds("prefFeedList");
+	var tree = dijit.byId("feedTree");
+	var items = tree.model.getCheckedItems();
+	var rv = [];
+
+	items.each(function(item) {
+		rv.push(tree.model.store.getValue(item, 'bare_id'));
+	});
+
+	return rv;
 }
 
 function getSelectedFilters() {
@@ -1156,7 +1164,17 @@ function init() {
 		dojo.require("dijit.layout.ContentPane");
 		dojo.require("dijit.Dialog");
 		dojo.require("dijit.form.Button");
+		dojo.require("dijit.form.TextBox");
 		dojo.require("dijit.Toolbar");
+		dojo.require("dojo.data.ItemFileWriteStore");
+		dojo.require("dijit.Tree");
+		dojo.require("dijit.form.DropDownButton");
+		dojo.require("dijit.Menu");
+		dojo.require("dijit.tree.dndSource");
+
+		dojo.registerModulePath("lib", "..");
+
+		dojo.require("lib.CheckBoxTree");
 
 		loading_set_progress(30);