diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index ae7e1a26..fe3a84c4 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -54,41 +54,6 @@ Helpy.admin = function(){
}
});
- $('div.adoptable').sortable({
- revert: true,
- });
- $('div.category-row').droppable({
- classes: {
- "ui-droppable-hover": "ui-state-active"
- },
- tolerance: "intersect",
- drop: function (event, ui) {
- var parentId = $(this).data('obj-id');
- var childId = ui.draggable.data('obj-id');
- $.ajax({
- type: 'PATCH',
- url: '/admin/categories/' + childId + '/set_parent',
- dataType: 'json',
- data: { id: childId, parent_id: parentId, remote: true },
- complete: function (data) {
- $('.front-categories').html(data.responseText);
- Helpy.ready();
- Helpy.track();
- Helpy.admin();
- }
- });
- }
- });
-
- $('.adoptable').off().on('mousein', function(){
- // $(this).next('.move-down').show();
- // $('#root-').show();
- });
- $('.adoptable').off().on('mouseout', function(){
- // $(this).next('.move-down').hide();
- // $('#root-').hide();
- });
-
$('.settings-link').off().on('click', function(){
// Clean up any select-styled links
diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb
index b67a4f97..626954ff 100644
--- a/app/controllers/admin/categories_controller.rb
+++ b/app/controllers/admin/categories_controller.rb
@@ -57,23 +57,9 @@ class Admin::CategoriesController < Admin::BaseController
end
end
- def set_parent
- @child = Category.find(params[:id])
- if params[:parent_id].present?
- @parent = Category.find(params[:parent_id]) || nil
- @child.parent_id = @parent.id
- else
- @child.parent_id = nil
- end
-
- if @child.save
- @public_categories = Category.roots.publicly.featured.ordered.includes(:docs)
- respond_to do |format|
- format.html {
- render partial: 'cat', collection: @public_categories
- }
- end
- end
+ def reorganize
+ Category.reorganize(params[:structure])
+ render nothing: true
end
private
diff --git a/app/models/category.rb b/app/models/category.rb
index f1f838e4..e57d0c8a 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -79,4 +79,15 @@ class Category < ActiveRecord::Base
RebuildSearchJob.perform_later
end
+ def self.reorganize(structure, parent_id=nil, parent_rank=0)
+ logger.info structure
+ structure.each_with_index do |s,i|
+ category = Category.find(s[1]["id"])
+ category.rank = parent_rank+i+1
+ category.parent_id = parent_id
+ category.save!
+ Category.reorganize(s[1]["children"], category.id, category.rank*10) if s[1]["children"].present?
+ end
+ end
+
end
diff --git a/app/themes/singular/views/categories/show.html.erb b/app/themes/singular/views/categories/show.html.erb
index c946097b..619fbcc4 100644
--- a/app/themes/singular/views/categories/show.html.erb
+++ b/app/themes/singular/views/categories/show.html.erb
@@ -2,7 +2,7 @@
<% meta_tag :description, "#{@category.meta_description}" %>
<% meta_tag :keywords, "#{@category.keywords}" %>
- <% Category.children_of(@category).each do |category| %>
+ <% Category.children_of(@category).ordered.each do |category| %>
<%# if category.docs.all.with_translations(I18n.locale).count > 0 %>
<%= render partial: 'categories/category_panel', object: category %>
<%# end %>
diff --git a/app/views/admin/categories/_cat.html.erb b/app/views/admin/categories/_cat.html.erb
index f9c812a3..0236aaf6 100644
--- a/app/views/admin/categories/_cat.html.erb
+++ b/app/views/admin/categories/_cat.html.erb
@@ -1,29 +1,19 @@
-
+
-
+
<%= content_tag :span, class: 'more-important' do
- cat.ancestors.count.times do
- concat " ".html_safe
- concat " ".html_safe
- concat " ".html_safe
- end
- concat link_to("#{cat.name} (#{cat.id})", admin_category_path(cat.id))
+ link_to("#{cat.name}", admin_category_path(cat.id))
end
%>
<%= content_tag :span do
- cat.ancestors.count.times do
- concat " ".html_safe
- concat " ".html_safe
- concat " ".html_safe
- end
concat cat.meta_description
end
%>
<%= cat.docs.size %>
- <%#= i18n_icons(cat) if AppSettings['i18n.available_locales'].count > 1 %>
+ <%= i18n_icons(cat) if AppSettings['i18n.available_locales'].count > 1 %>
- <% cat.children.each do |child| %>
- <%= render partial: 'admin/categories/cat', object: child %>
+ <% if cat.children.size > 0 %>
+
+ <% cat.children.ordered.each do |child| %>
+ <%= render partial: 'admin/categories/cat', object: child %>
+ <% end %>
+
<% end %>
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/views/admin/categories/index.html.erb b/app/views/admin/categories/index.html.erb
index 924740a2..e3470e9d 100644
--- a/app/views/admin/categories/index.html.erb
+++ b/app/views/admin/categories/index.html.erb
@@ -1,23 +1,45 @@
<% title "#{admin_title} #{t(:content, default: 'Content')}" %>
-
+<%= javascript_include_tag "jquery.nestable.js", "data-turbolinks-track" => true %>
@@ -39,11 +61,11 @@ $(document).ready(function(){
<%= t(:front_page_categories, default: 'Front Page Categories') %>
-
-
-
- <%= render partial: 'cat', collection: @public_categories %>
+
+
+ <%= render partial: 'cat', collection: @public_categories %>
+
@@ -54,7 +76,7 @@ $(document).ready(function(){
<%= t(:other_categories, default: 'Other Categories') %>
-
+
<%= render partial: 'cat', collection: @public_nonfeatured_categories %>
@@ -65,9 +87,28 @@ $(document).ready(function(){
<%= t(:internal_categories, default: 'Private/Internal Categories') %>
-
+
<%= render partial: 'cat', collection: @internal_categories %>
+
+
\ No newline at end of file
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index c5326ebb..76f78dd5 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -19,4 +19,5 @@ Rails.application.config.assets.precompile += %w( rtl.js )
Rails.application.config.assets.precompile += %w( mousetrap.min.js )
Rails.application.config.assets.precompile += %w( shortcuts.js )
Rails.application.config.assets.precompile += %w( agent_assistant.js )
+Rails.application.config.assets.precompile += %w( jquery.nestable.js )
Rails.application.config.assets.precompile += %w( foundation_emails.css )
diff --git a/config/routes.rb b/config/routes.rb
index d3b4bb37..f6c8811c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -143,9 +143,9 @@ Rails.application.routes.draw do
get 'internal_docs/search' => 'internal_categories#search', as: :internal_docs_search
+ patch 'categories/reorganize' => 'categories#reorganize', as: :reorganize
resources :categories do
resources :docs, except: [:index, :show]
- patch 'set_parent' => 'categories#set_parent', as: :set_parent
end
diff --git a/vendor/assets/javascripts/jquery.nestable.js b/vendor/assets/javascripts/jquery.nestable.js
new file mode 100644
index 00000000..2176261e
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nestable.js
@@ -0,0 +1,461 @@
+/*!
+ * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
+ * Dual-licensed under the BSD or MIT licenses
+ */
+; (function ($, window, document, undefined) {
+ var hasTouch = 'ontouchstart' in document;
+
+ /**
+ * Detect CSS pointer-events property
+ * events are normally disabled on the dragging element to avoid conflicts
+ * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
+ */
+ var hasPointerEvents = (function () {
+ var el = document.createElement('div'),
+ docEl = document.documentElement;
+ if (!('pointerEvents' in el.style)) {
+ return false;
+ }
+ el.style.pointerEvents = 'auto';
+ el.style.pointerEvents = 'x';
+ docEl.appendChild(el);
+ var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
+ docEl.removeChild(el);
+ return !!supports;
+ })();
+
+ var defaults = {
+ listNodeName: 'ol',
+ itemNodeName: 'li',
+ rootClass: 'dd',
+ listClass: 'dd-list',
+ itemClass: 'dd-item',
+ dragClass: 'dd-dragel',
+ handleClass: 'dd-handle',
+ collapsedClass: 'dd-collapsed',
+ placeClass: 'dd-placeholder',
+ noDragClass: 'dd-nodrag',
+ emptyClass: 'dd-empty',
+ expandBtnHTML: '',
+ collapseBtnHTML: '',
+ group: 0,
+ maxDepth: 5,
+ threshold: 20
+ };
+
+ function Plugin(element, options) {
+ this.w = $(document);
+ this.el = $(element);
+ this.options = $.extend({}, defaults, options);
+ this.init();
+ }
+
+ Plugin.prototype = {
+
+ init: function () {
+ var list = this;
+
+ list.reset();
+
+ list.el.data('nestable-group', this.options.group);
+
+ list.placeEl = $('');
+
+ $.each(this.el.find(list.options.itemNodeName), function (k, el) {
+ list.setParent($(el));
+ });
+
+ list.el.on('click', 'button', function (e) {
+ if (list.dragEl) {
+ return;
+ }
+ var target = $(e.currentTarget),
+ action = target.data('action'),
+ item = target.parent(list.options.itemNodeName);
+ if (action === 'collapse') {
+ list.collapseItem(item);
+ }
+ if (action === 'expand') {
+ list.expandItem(item);
+ }
+ });
+
+ var onStartEvent = function (e) {
+ var handle = $(e.target);
+ if (!handle.hasClass(list.options.handleClass)) {
+ if (handle.closest('.' + list.options.noDragClass).length) {
+ return;
+ }
+ handle = handle.closest('.' + list.options.handleClass);
+ }
+
+ if (!handle.length || list.dragEl) {
+ return;
+ }
+
+ list.isTouch = /^touch/.test(e.type);
+ if (list.isTouch && e.touches.length !== 1) {
+ return;
+ }
+
+ e.preventDefault();
+ list.dragStart(e.touches ? e.touches[0] : e);
+ };
+
+ var onMoveEvent = function (e) {
+ if (list.dragEl) {
+ e.preventDefault();
+ list.dragMove(e.touches ? e.touches[0] : e);
+ }
+ };
+
+ var onEndEvent = function (e) {
+ if (list.dragEl) {
+ e.preventDefault();
+ list.dragStop(e.touches ? e.touches[0] : e);
+ }
+ };
+
+ if (hasTouch) {
+ list.el[0].addEventListener('touchstart', onStartEvent, false);
+ window.addEventListener('touchmove', onMoveEvent, false);
+ window.addEventListener('touchend', onEndEvent, false);
+ window.addEventListener('touchcancel', onEndEvent, false);
+ }
+
+ list.el.on('mousedown', onStartEvent);
+ list.w.on('mousemove', onMoveEvent);
+ list.w.on('mouseup', onEndEvent);
+
+ },
+
+ serialize: function () {
+ var data,
+ depth = 0,
+ list = this;
+ step = function (level, depth) {
+ var array = [],
+ items = level.children(list.options.itemNodeName);
+ items.each(function () {
+ var li = $(this),
+ item = $.extend({}, li.data()),
+ sub = li.children(list.options.listNodeName);
+ if (sub.length) {
+ item.children = step(sub, depth + 1);
+ }
+ array.push(item);
+ });
+ return array;
+ };
+ data = step(list.el.find(list.options.listNodeName).first(), depth);
+ return data;
+ },
+
+ serialise: function () {
+ return this.serialize();
+ },
+
+ reset: function () {
+ this.mouse = {
+ offsetX: 0,
+ offsetY: 0,
+ startX: 0,
+ startY: 0,
+ lastX: 0,
+ lastY: 0,
+ nowX: 0,
+ nowY: 0,
+ distX: 0,
+ distY: 0,
+ dirAx: 0,
+ dirX: 0,
+ dirY: 0,
+ lastDirX: 0,
+ lastDirY: 0,
+ distAxX: 0,
+ distAxY: 0
+ };
+ this.isTouch = false;
+ this.moving = false;
+ this.dragEl = null;
+ this.dragRootEl = null;
+ this.dragDepth = 0;
+ this.hasNewRoot = false;
+ this.pointEl = null;
+ },
+
+ expandItem: function (li) {
+ li.removeClass(this.options.collapsedClass);
+ li.children('[data-action="expand"]').hide();
+ li.children('[data-action="collapse"]').show();
+ li.children(this.options.listNodeName).show();
+ },
+
+ collapseItem: function (li) {
+ var lists = li.children(this.options.listNodeName);
+ if (lists.length) {
+ li.addClass(this.options.collapsedClass);
+ li.children('[data-action="collapse"]').hide();
+ li.children('[data-action="expand"]').show();
+ li.children(this.options.listNodeName).hide();
+ }
+ },
+
+ expandAll: function () {
+ var list = this;
+ list.el.find(list.options.itemNodeName).each(function () {
+ list.expandItem($(this));
+ });
+ },
+
+ collapseAll: function () {
+ var list = this;
+ list.el.find(list.options.itemNodeName).each(function () {
+ list.collapseItem($(this));
+ });
+ },
+
+ setParent: function (li) {
+ if (li.children(this.options.listNodeName).length) {
+ li.prepend($(this.options.expandBtnHTML));
+ li.prepend($(this.options.collapseBtnHTML));
+ }
+ li.children('[data-action="expand"]').hide();
+ },
+
+ unsetParent: function (li) {
+ li.removeClass(this.options.collapsedClass);
+ li.children('[data-action]').remove();
+ li.children(this.options.listNodeName).remove();
+ },
+
+ dragStart: function (e) {
+ var mouse = this.mouse,
+ target = $(e.target),
+ dragItem = target.closest(this.options.itemNodeName);
+
+ this.placeEl.css('height', dragItem.height());
+
+ mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
+ mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
+ mouse.startX = mouse.lastX = e.pageX;
+ mouse.startY = mouse.lastY = e.pageY;
+
+ this.dragRootEl = this.el;
+
+ this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
+ this.dragEl.css('width', dragItem.width());
+
+ dragItem.after(this.placeEl);
+ dragItem[0].parentNode.removeChild(dragItem[0]);
+ dragItem.appendTo(this.dragEl);
+
+ $(document.body).append(this.dragEl);
+ this.dragEl.css({
+ 'left': e.pageX - mouse.offsetX,
+ 'top': e.pageY - mouse.offsetY
+ });
+ // total depth of dragging item
+ var i, depth,
+ items = this.dragEl.find(this.options.itemNodeName);
+ for (i = 0; i < items.length; i++) {
+ depth = $(items[i]).parents(this.options.listNodeName).length;
+ if (depth > this.dragDepth) {
+ this.dragDepth = depth;
+ }
+ }
+ },
+
+ dragStop: function (e) {
+ var el = this.dragEl.children(this.options.itemNodeName).first();
+ el[0].parentNode.removeChild(el[0]);
+ this.placeEl.replaceWith(el);
+
+ this.dragEl.remove();
+ this.el.trigger('change');
+ if (this.hasNewRoot) {
+ this.dragRootEl.trigger('change');
+ }
+ this.reset();
+ },
+
+ dragMove: function (e) {
+ var list, parent, prev, next, depth,
+ opt = this.options,
+ mouse = this.mouse;
+
+ this.dragEl.css({
+ 'left': e.pageX - mouse.offsetX,
+ 'top': e.pageY - mouse.offsetY
+ });
+
+ // mouse position last events
+ mouse.lastX = mouse.nowX;
+ mouse.lastY = mouse.nowY;
+ // mouse position this events
+ mouse.nowX = e.pageX;
+ mouse.nowY = e.pageY;
+ // distance mouse moved between events
+ mouse.distX = mouse.nowX - mouse.lastX;
+ mouse.distY = mouse.nowY - mouse.lastY;
+ // direction mouse was moving
+ mouse.lastDirX = mouse.dirX;
+ mouse.lastDirY = mouse.dirY;
+ // direction mouse is now moving (on both axis)
+ mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
+ mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
+ // axis mouse is now moving on
+ var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
+
+ // do nothing on first move
+ if (!mouse.moving) {
+ mouse.dirAx = newAx;
+ mouse.moving = true;
+ return;
+ }
+
+ // calc distance moved on this axis (and direction)
+ if (mouse.dirAx !== newAx) {
+ mouse.distAxX = 0;
+ mouse.distAxY = 0;
+ } else {
+ mouse.distAxX += Math.abs(mouse.distX);
+ if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
+ mouse.distAxX = 0;
+ }
+ mouse.distAxY += Math.abs(mouse.distY);
+ if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
+ mouse.distAxY = 0;
+ }
+ }
+ mouse.dirAx = newAx;
+
+ /**
+ * move horizontal
+ */
+ if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
+ // reset move distance on x-axis for new phase
+ mouse.distAxX = 0;
+ prev = this.placeEl.prev(opt.itemNodeName);
+ // increase horizontal level if previous sibling exists and is not collapsed
+ if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
+ // cannot increase level when item above is collapsed
+ list = prev.find(opt.listNodeName).last();
+ // check if depth limit has reached
+ depth = this.placeEl.parents(opt.listNodeName).length;
+ if (depth + this.dragDepth <= opt.maxDepth) {
+ // create new sub-level if one doesn't exist
+ if (!list.length) {
+ list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
+ list.append(this.placeEl);
+ prev.append(list);
+ this.setParent(prev);
+ } else {
+ // else append to next level up
+ list = prev.children(opt.listNodeName).last();
+ list.append(this.placeEl);
+ }
+ }
+ }
+ // decrease horizontal level
+ if (mouse.distX < 0) {
+ // we can't decrease a level if an item preceeds the current one
+ next = this.placeEl.next(opt.itemNodeName);
+ if (!next.length) {
+ parent = this.placeEl.parent();
+ this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
+ if (!parent.children().length) {
+ this.unsetParent(parent.parent());
+ }
+ }
+ }
+ }
+
+ var isEmpty = false;
+
+ // find list item under cursor
+ if (!hasPointerEvents) {
+ this.dragEl[0].style.visibility = 'hidden';
+ }
+ this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
+ if (!hasPointerEvents) {
+ this.dragEl[0].style.visibility = 'visible';
+ }
+ if (this.pointEl.hasClass(opt.handleClass)) {
+ this.pointEl = this.pointEl.parent(opt.itemNodeName);
+ }
+ if (this.pointEl.hasClass(opt.emptyClass)) {
+ isEmpty = true;
+ }
+ else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
+ return;
+ }
+
+ // find parent list of item under cursor
+ var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
+ isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
+
+ /**
+ * move vertical
+ */
+ if (!mouse.dirAx || isNewRoot || isEmpty) {
+ // check if groups match if dragging over new root
+ if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
+ return;
+ }
+ // check depth limit
+ depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
+ if (depth > opt.maxDepth) {
+ return;
+ }
+ var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
+ parent = this.placeEl.parent();
+ // if empty create new list to replace empty placeholder
+ if (isEmpty) {
+ list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
+ list.append(this.placeEl);
+ this.pointEl.replaceWith(list);
+ }
+ else if (before) {
+ this.pointEl.before(this.placeEl);
+ }
+ else {
+ this.pointEl.after(this.placeEl);
+ }
+ if (!parent.children().length) {
+ this.unsetParent(parent.parent());
+ }
+ if (!this.dragRootEl.find(opt.itemNodeName).length) {
+ this.dragRootEl.append('');
+ }
+ // parent root list has changed
+ if (isNewRoot) {
+ this.dragRootEl = pointElRoot;
+ this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
+ }
+ }
+ }
+
+ };
+
+ $.fn.nestable = function (params) {
+ var lists = this,
+ retval = this;
+
+ lists.each(function () {
+ var plugin = $(this).data("nestable");
+
+ if (!plugin) {
+ $(this).data("nestable", new Plugin(this, params));
+ $(this).data("nestable-id", new Date().getTime());
+ } else {
+ if (typeof params === 'string' && typeof plugin[params] === 'function') {
+ retval = plugin[params]();
+ }
+ }
+ });
+
+ return retval || lists;
+ };
+
+})(window.jQuery || window.Zepto, window, document);
\ No newline at end of file