From 12bb1970280914309ffca8ca796fcac80663c4cf Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 4 Oct 2013 17:21:52 +0200 Subject: [PATCH] JS version of the OCP\ITags interface --- core/css/jquery.ocdialog.css | 1 + core/css/styles.css | 24 ++- core/js/tags.js | 353 +++++++++++++++++++++++++++++++++++ core/routes.php | 50 +++-- core/tags/controller.php | 114 +++++++++++ core/templates/tags.html | 14 ++ 6 files changed, 534 insertions(+), 22 deletions(-) create mode 100644 core/js/tags.js create mode 100644 core/tags/controller.php create mode 100644 core/templates/tags.html diff --git a/core/css/jquery.ocdialog.css b/core/css/jquery.ocdialog.css index aa72eaf847..236968e324 100644 --- a/core/css/jquery.ocdialog.css +++ b/core/css/jquery.ocdialog.css @@ -29,6 +29,7 @@ bottom: 0; display: block; margin-top: 10px; + width: 100%; } .oc-dialog-close { diff --git a/core/css/styles.css b/core/css/styles.css index 06b61f0fa6..b0b247de9b 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -726,15 +726,21 @@ span.ui-icon {float: left; margin: 3px 7px 30px 0;} height: 16px; } - -/* ---- CATEGORIES ---- */ -#categoryform .scrollarea { position:absolute; left:10px; top:10px; right:10px; bottom:50px; overflow:auto; border:1px solid #ddd; background:#f8f8f8; } -#categoryform .bottombuttons { position:absolute; bottom:10px;} -#categoryform .bottombuttons * { float:left;} -/*#categorylist { border:1px solid #ddd;}*/ -#categorylist li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; -moz-transition:background-color 500ms; -o-transition:background-color 500ms; transition:background-color 500ms; } -#categorylist li:hover, #categorylist li:active { background:#eee; } -#category_addinput { width:10em; } +/* ---- TAGS ---- */ +#tagsdialog .content { + width: 100%; height: 280px; +} +#tagsdialog .scrollarea { + overflow:auto; border:1px solid #ddd; + width: 100%; height: 240px; +} +#tagsdialog .bottombuttons { + width: 100%; height: 30px; +} +#tagsdialog .bottombuttons * { float:left;} +#tagsdialog .taglist li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; -moz-transition:background-color 500ms; -o-transition:background-color 500ms; transition:background-color 500ms; } +#tagsdialog .taglist li:hover, #tagsdialog .taglist li:active { background:#eee; } +#tagsdialog .addinput { width: 90%; clear: both; } /* ---- APP SETTINGS ---- */ .popup { background-color:white; border-radius:10px 10px 10px 10px; box-shadow:0 0 20px #888; color:#333; padding:10px; position:fixed !important; z-index:100; } diff --git a/core/js/tags.js b/core/js/tags.js new file mode 100644 index 0000000000..a4c42905ee --- /dev/null +++ b/core/js/tags.js @@ -0,0 +1,353 @@ +OC.Tags= { + edit:function(type, cb) { + if(!type && !this.type) { + throw { name: 'MissingParameter', message: t('core', 'The object type is not specified.') }; + } + type = type ? type : this.type; + var self = this; + $.when(this._getTemplate()).then(function($tmpl) { + if(self.$dialog) { + self.$dialog.ocdialog('close'); + } + self.$dialog = $tmpl.octemplate({ + addText: t('core', 'Enter new') + }); + $('body').append(self.$dialog); + + self.$dialog.ready(function() { + self.$taglist = self.$dialog.find('.taglist'); + self.$taginput = self.$dialog.find('.addinput'); + self.$taglist.on('change', 'input:checkbox', function(event) { + self._handleChanges(self.$taglist, self.$taginput); + }); + self.$taginput.on('input', function(event) { + self._handleChanges(self.$taglist, self.$taginput); + }); + self.deleteButton = { + text: t('core', 'Delete'), + click: function() {self._deleteTags(self, type, self._selectedIds())}, + }; + self.addButton = { + text: t('core', 'Add'), + click: function() {self._addTag(self, type, self.$taginput.val())}, + }; + + self._fillTagList(type, self.$taglist); + }); + + self.$dialog.ocdialog({ + title: t('core', 'Edit tags'), + closeOnEscape: true, + width: 250, + height: 'auto', + modal: true, + //buttons: buttonlist, + close: function(event, ui) { + try { + $(this).ocdialog('destroy').remove(); + } catch(e) {console.warn(e);} + self.$dialog = null; + } + }); + }) + .fail(function(status, error) { + // If the method is called while navigating away + // from the page, it is probably not needed ;) + if(status !== 0) { + alert(t('core', 'Error loading dialog template: {error}', {error: error})); + } + }); + }, + /** + * @param string type + * @return jQuery.Promise which resolves with an array of ids + */ + getIdsForTag:function(type, tag) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_ids_for_tag', {type: type}); + $.getJSON(url, {tag: tag}, function(response) { + if(response.status === 'success') { + defer.resolve(response.ids); + } else { + defer.reject(response); + } + }); + return defer.promise(); + }, + /** + * @param string type + * @return jQuery.Promise which resolves with an array of ids + */ + getFavorites:function(type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_favorites', {type: type}); + $.getJSON(url, function(response) { + if(response.status === 'success') { + defer.resolve(response.ids); + } else { + defer.reject(response); + } + }); + return defer.promise(); + }, + /** + * @param string type + * @return jQuery.Promise which resolves with an array of id/name objects + */ + getTags:function(type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_tags', {type: type}); + $.getJSON(url, function(response) { + if(response.status === 'success') { + defer.resolve(response.tags); + } else { + defer.reject(response); + } + }); + return defer.promise(); + }, + /** + * @param int id + * @param string type + * @return jQuery.Promise + */ + tagAs:function(id, tag, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_tag', {type: type, id: id}); + $.post(url, {tag: tag}, function(response) { + if(response.result === 'success') { + defer.resolve(response); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + /** + * @param int id + * @param string type + * @return jQuery.Promise + */ + unTag:function(id, tag, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_untag', {type: type, id: id}); + $.post(url, {tag: tag}, function(response) { + if(response.result === 'success') { + defer.resolve(response); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + /** + * @param int id + * @param string type + * @return jQuery.Promise + */ + addToFavorites:function(id, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_favorite', {type: type, id: id}); + $.post(url, function(response) { + if(response.result === 'success') { + defer.resolve(response); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + /** + * @param int id + * @param string type + * @return jQuery.Promise + */ + removeFromFavorites:function(id, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_unfavorite', {type: type, id: id}); + $.post(url, function(response) { + if(response.result === 'success') { + defer.resolve(); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + /** + * @param string tag + * @param string type + * @return jQuery.Promise which resolves with an object with the name and the new id + */ + addTag:function(tag, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_add', {type: type}); + $.post(url,{tag:tag}, function(response) { + if(typeof cb == 'function') { + cb(response); + } + if(response.status === 'success') { + defer.resolve({id:response.id, name: tag}); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + /** + * @param array tags + * @param string type + * @return jQuery.Promise + */ + deleteTags:function(tags, type) { + if(!type && !this.type) { + throw new Error('The object type is not specified.'); + } + type = type ? type : this.type; + var defer = $.Deferred(), + self = this, + url = OC.Router.generate('core_tags_delete', {type: type}); + if(!tags || !tags.length) { + throw new Error(t('core', 'No tags selected for deletion.')); + } + var self = this; + $.post(url, {tags:tags}, function(response) { + if(response.status === 'success') { + defer.resolve(response.tags); + } else { + defer.reject(response); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + return defer.promise(); + }, + _update:function(tags, type) { + if(!this.$dialog) { + return; + } + var $taglist = this.$dialog.find('.taglist'), + self = this; + $taglist.empty(); + $.each(tags, function(idx, tag) { + var $item = self.$listTmpl.octemplate({id: tag.id, name: tag.name}); + $item.appendTo($taglist); + }); + $(this).trigger('change', {type: type, tags: tags}); + if(typeof this.changed === 'function') { + this.changed(tags); + } + }, + _getTemplate: function() { + var defer = $.Deferred(); + if(!this.$template) { + var self = this; + $.get(OC.filePath('core', 'templates', 'tags.html'), function(tmpl) { + self.$template = $(tmpl); + self.$listTmpl = self.$template.find('.taglist li:first-child').detach(); + defer.resolve(self.$template); + }) + .fail(function(jqXHR, textStatus, errorThrown) { + defer.reject(jqXHR.status, errorThrown); + }); + } else { + defer.resolve(this.$template); + } + return defer.promise(); + }, + _fillTagList: function(type) { + var self = this; + $.when(this.getTags(type)) + .then(function(tags) { + self._update(tags, type); + }) + .fail(function(response) { + console.warn(response); + }); + }, + _selectedIds: function() { + return $.map(this.$taglist.find('input:checked'), function(b) {return $(b).val();}); + }, + _handleChanges: function($list, $input) { + var ids = this._selectedIds(); + var buttons = []; + if($input.val().length) { + buttons.push(this.addButton); + } + if(ids.length) { + buttons.push(this.deleteButton); + } + this.$dialog.ocdialog('option', 'buttons', buttons); + }, + _deleteTags: function(self, type, ids) { + $.when(self.deleteTags(ids, type)) + .then(function() { + self._fillTagList(type); + self.$dialog.ocdialog('option', 'buttons', []); + }) + .fail(function(response) { + console.warn(response); + }); + }, + _addTag: function(self, type, tag) { + $.when(self.addTag(tag, type)) + .then(function(tag) { + self._fillTagList(type); + self.$taginput.val('').trigger('input'); + }) + .fail(function(response) { + console.warn(response); + }); + } +} + diff --git a/core/routes.php b/core/routes.php index 57e25c0f1f..4163bdcd5b 100644 --- a/core/routes.php +++ b/core/routes.php @@ -23,19 +23,43 @@ $this->create('core_ajax_share', '/core/ajax/share.php') // Translations $this->create('core_ajax_translations', '/core/ajax/translations.php') ->actionInclude('core/ajax/translations.php'); -// VCategories -$this->create('core_ajax_vcategories_add', '/core/ajax/vcategories/add.php') - ->actionInclude('core/ajax/vcategories/add.php'); -$this->create('core_ajax_vcategories_delete', '/core/ajax/vcategories/delete.php') - ->actionInclude('core/ajax/vcategories/delete.php'); -$this->create('core_ajax_vcategories_addtofavorites', '/core/ajax/vcategories/addToFavorites.php') - ->actionInclude('core/ajax/vcategories/addToFavorites.php'); -$this->create('core_ajax_vcategories_removefromfavorites', '/core/ajax/vcategories/removeFromFavorites.php') - ->actionInclude('core/ajax/vcategories/removeFromFavorites.php'); -$this->create('core_ajax_vcategories_favorites', '/core/ajax/vcategories/favorites.php') - ->actionInclude('core/ajax/vcategories/favorites.php'); -$this->create('core_ajax_vcategories_edit', '/core/ajax/vcategories/edit.php') - ->actionInclude('core/ajax/vcategories/edit.php'); +// Tags +$this->create('core_tags_tags', '/tags/{type}') + ->get() + ->action('OC\Core\Tags\Controller', 'getTags') + ->requirements(array('type')); +$this->create('core_tags_favorites', '/tags/{type}/favorites') + ->get() + ->action('OC\Core\Tags\Controller', 'getFavorites') + ->requirements(array('type')); +$this->create('core_tags_ids_for_tag', '/tags/{type}/ids') + ->get() + ->action('OC\Core\Tags\Controller', 'getIdsForTag') + ->requirements(array('type')); +$this->create('core_tags_favorite', '/tags/{type}/favorite/{id}/') + ->post() + ->action('OC\Core\Tags\Controller', 'favorite') + ->requirements(array('type', 'id')); +$this->create('core_tags_unfavorite', '/tags/{type}/infavorite/{id}/') + ->post() + ->action('OC\Core\Tags\Controller', 'unFavorite') + ->requirements(array('type', 'id')); +$this->create('core_tags_tag', '/tags/{type}/tag/{id}/') + ->post() + ->action('OC\Core\Tags\Controller', 'tagAs') + ->requirements(array('type', 'id')); +$this->create('core_tags_untag', '/tags/{type}/untag/{id}/') + ->post() + ->action('OC\Core\Tags\Controller', 'unTag') + ->requirements(array('type', 'id')); +$this->create('core_tags_add', '/tags/{type}/add') + ->post() + ->action('OC\Core\Tags\Controller', 'addTag') + ->requirements(array('type')); +$this->create('core_tags_delete', '/tags/{type}/delete') + ->post() + ->action('OC\Core\Tags\Controller', 'deleteTags') + ->requirements(array('type')); // oC JS config $this->create('js_config', '/core/js/config.js') ->actionInclude('core/js/config.php'); diff --git a/core/tags/controller.php b/core/tags/controller.php new file mode 100644 index 0000000000..c790d43345 --- /dev/null +++ b/core/tags/controller.php @@ -0,0 +1,114 @@ +getTagManager()->load($type); + return $tagger; + } catch(\Exception $e) { + \OCP\Util::writeLog('core', __METHOD__ . ' Exception: ' . $e->getMessage(), \OCP\Util::ERROR); + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error loading tags'))); + exit; + } + } + + public static function getTags($args) { + $tagger = self::getTagger($args['type']); + \OC_JSON::success(array('tags'=> $tagger->getTags())); + } + + public static function getFavorites($args) { + $tagger = self::getTagger($args['type']); + \OC_JSON::success(array('ids'=> $tagger->getFavorites())); + } + + public static function getIdsForTag($args) { + $tagger = self::getTagger($args['type']); + \OC_JSON::success(array('ids'=> $tagger->getIdsForTag($_GET['tag']))); + } + + public static function addTag($args) { + $tagger = self::getTagger($args['type']); + + $id = $tagger->add(strip_tags($_POST['tag'])); + if($id === false) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Tag already exists'))); + } else { + \OC_JSON::success(array('id'=> $id)); + } + } + + public static function deleteTags($args) { + $tags = $_POST['tags']; + if(!is_array($tags)) { + $tags = array($tags); + } + + $tagger = self::getTagger($args['type']); + + if(!$tagger->delete($tags)) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error deleting tag(s)'))); + } else { + \OC_JSON::success(); + } + } + + public static function tagAs($args) { + $tagger = self::getTagger($args['type']); + + if(!$tagger->tagAs($args['id'], $_POST['tag'])) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error tagging'))); + } else { + \OC_JSON::success(); + } + } + + public static function unTag($args) { + $tagger = self::getTagger($args['type']); + + if(!$tagger->unTag($args['id'], $_POST['tag'])) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error untagging'))); + } else { + \OC_JSON::success(); + } + } + + public static function favorite($args) { + $tagger = self::getTagger($args['type']); + + if(!$tagger->addToFavorites($args['id'])) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error favoriting'))); + } else { + \OC_JSON::success(); + } + } + + public static function unFavorite($args) { + $tagger = self::getTagger($args['type']); + + if(!$tagger->removeFromFavorites($args['id'])) { + $l = new \OC_L10n('core'); + \OC_JSON::error(array('message'=> $l->t('Error unfavoriting'))); + } else { + \OC_JSON::success(); + } + } + +} diff --git a/core/templates/tags.html b/core/templates/tags.html new file mode 100644 index 0000000000..ae3d072b38 --- /dev/null +++ b/core/templates/tags.html @@ -0,0 +1,14 @@ +
+
+
+
    +
  • + +
  • +
+
+
+ +
+
+