FEATURE: ability to reorder links in custom sidebar sections (#20626)
Drag and drop to reorder custom sidebar sections
This commit is contained in:
parent
53cadac4b8
commit
db74e9484b
|
@ -1,5 +1,12 @@
|
||||||
{{#if this.displaySection}}
|
{{#if this.displaySection}}
|
||||||
<div class={{concat "sidebar-section-wrapper sidebar-section-" @sectionName}}>
|
<div
|
||||||
|
class={{concat
|
||||||
|
"sidebar-section-wrapper sidebar-section-"
|
||||||
|
@sectionName
|
||||||
|
" "
|
||||||
|
@class
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="sidebar-section-header-wrapper sidebar-row">
|
<div class="sidebar-section-header-wrapper sidebar-row">
|
||||||
<Sidebar::SectionHeader
|
<Sidebar::SectionHeader
|
||||||
@collapsable={{@collapsable}}
|
@collapsable={{@collapsable}}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@collapsable={{true}}
|
@collapsable={{true}}
|
||||||
@headerActions={{section.headerActions}}
|
@headerActions={{section.headerActions}}
|
||||||
@headerActionsIcon="pencil-alt"
|
@headerActionsIcon="pencil-alt"
|
||||||
|
@class={{section.dragCss}}
|
||||||
>
|
>
|
||||||
{{#each section.links as |link|}}
|
{{#each section.links as |link|}}
|
||||||
{{#if link.external}}
|
{{#if link.external}}
|
||||||
|
@ -15,6 +16,12 @@
|
||||||
@prefixType="icon"
|
@prefixType="icon"
|
||||||
@prefixValue={{link.icon}}
|
@prefixValue={{link.icon}}
|
||||||
@href={{link.value}}
|
@href={{link.value}}
|
||||||
|
@class={{link.linkDragCss}}
|
||||||
|
{{draggable
|
||||||
|
didStartDrag=link.didStartDrag
|
||||||
|
didEndDrag=link.didEndDrag
|
||||||
|
dragMove=link.dragMove
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<Sidebar::SectionLink
|
<Sidebar::SectionLink
|
||||||
|
@ -25,6 +32,12 @@
|
||||||
@content={{replace-emoji link.name}}
|
@content={{replace-emoji link.name}}
|
||||||
@prefixType="icon"
|
@prefixType="icon"
|
||||||
@prefixValue={{link.icon}}
|
@prefixValue={{link.icon}}
|
||||||
|
@class={{link.linkDragCss}}
|
||||||
|
{{draggable
|
||||||
|
didStartDrag=link.didStartDrag
|
||||||
|
didEndDrag=link.didEndDrag
|
||||||
|
dragMove=link.dragMove
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import RouteInfoHelper from "discourse/lib/sidebar/route-info-helper";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import Section from "discourse/components/sidebar/user/section";
|
||||||
|
|
||||||
export default class SidebarUserCustomSections extends Component {
|
export default class SidebarUserCustomSections extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
|
@ -23,31 +19,13 @@ export default class SidebarUserCustomSections extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
this.currentUser.sidebarSections.forEach((section) => {
|
return this.currentUser.sidebarSections.map((section) => {
|
||||||
if (!section.public || this.currentUser.staff) {
|
return new Section({
|
||||||
section.headerActions = [
|
section,
|
||||||
{
|
currentUser: this.currentUser,
|
||||||
action: () => {
|
router: this.router,
|
||||||
return showModal("sidebar-section-form", { model: section });
|
|
||||||
},
|
|
||||||
title: I18n.t("sidebar.sections.custom.edit"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
section.decoratedTitle =
|
|
||||||
section.public && this.currentUser.staff
|
|
||||||
? htmlSafe(`${iconHTML("globe")} ${section.title}`)
|
|
||||||
: section.title;
|
|
||||||
section.links.forEach((link) => {
|
|
||||||
if (!link.external) {
|
|
||||||
const routeInfoHelper = new RouteInfoHelper(this.router, link.value);
|
|
||||||
link.route = routeInfoHelper.route;
|
|
||||||
link.models = routeInfoHelper.models;
|
|
||||||
link.query = routeInfoHelper.query;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return this.currentUser.sidebarSections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import RouteInfoHelper from "discourse/lib/sidebar/route-info-helper";
|
||||||
|
|
||||||
|
export default class SectionLink {
|
||||||
|
@tracked linkDragCss;
|
||||||
|
|
||||||
|
constructor({ external, icon, id, name, value }, section, router) {
|
||||||
|
this.external = external;
|
||||||
|
this.icon = icon;
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.section = section;
|
||||||
|
|
||||||
|
if (!this.external) {
|
||||||
|
const routeInfoHelper = new RouteInfoHelper(router, value);
|
||||||
|
this.route = routeInfoHelper.route;
|
||||||
|
this.models = routeInfoHelper.models;
|
||||||
|
this.query = routeInfoHelper.query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
didStartDrag(e) {
|
||||||
|
this.mouseY = e.targetTouches ? e.targetTouches[0].screenY : e.screenY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
didEndDrag() {
|
||||||
|
this.linkDragCss = null;
|
||||||
|
this.mouseY = null;
|
||||||
|
this.section.enable();
|
||||||
|
this.section.reorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
dragMove(e) {
|
||||||
|
const currentMouseY = e.targetTouches
|
||||||
|
? e.targetTouches[0].screenY
|
||||||
|
: e.screenY;
|
||||||
|
const distance = currentMouseY - this.mouseY;
|
||||||
|
if (!this.linkHeight) {
|
||||||
|
this.linkHeight = document.getElementsByClassName(
|
||||||
|
"sidebar-section-link-wrapper"
|
||||||
|
)[0].clientHeight;
|
||||||
|
}
|
||||||
|
if (distance >= this.linkHeight) {
|
||||||
|
if (this.section.links.indexOf(this) !== this.section.links.length - 1) {
|
||||||
|
this.section.moveLinkDown(this);
|
||||||
|
this.mouseY = currentMouseY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (distance <= -this.linkHeight) {
|
||||||
|
if (this.section.links.indexOf(this) !== 0) {
|
||||||
|
this.section.moveLinkUp(this);
|
||||||
|
this.mouseY = currentMouseY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.linkDragCss = "drag";
|
||||||
|
this.section.disable();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import I18n from "I18n";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import SectionLink from "discourse/components/sidebar/user/section-link";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
|
export default class Section {
|
||||||
|
@tracked dragCss;
|
||||||
|
@tracked links;
|
||||||
|
|
||||||
|
constructor({ section, currentUser, router }) {
|
||||||
|
this.section = section;
|
||||||
|
this.router = router;
|
||||||
|
this.currentUser = currentUser;
|
||||||
|
this.slug = section.slug;
|
||||||
|
|
||||||
|
this.links = this.section.links.map((link) => {
|
||||||
|
return new SectionLink(link, this, this.router);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get decoratedTitle() {
|
||||||
|
return this.section.public && this.currentUser.staff
|
||||||
|
? htmlSafe(`${iconHTML("globe")} ${this.section.title}`)
|
||||||
|
: this.section.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerActions() {
|
||||||
|
if (!this.section.public || this.currentUser.staff) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action: () => {
|
||||||
|
return showModal("sidebar-section-form", { model: this.section });
|
||||||
|
},
|
||||||
|
title: I18n.t("sidebar.sections.custom.edit"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
disable() {
|
||||||
|
this.dragCss = "disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
enable() {
|
||||||
|
this.dragCss = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
moveLinkDown(link) {
|
||||||
|
const position = this.links.indexOf(link) + 1;
|
||||||
|
this.links = this.links.removeObject(link);
|
||||||
|
this.links.splice(position, 0, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
moveLinkUp(link) {
|
||||||
|
const position = this.links.indexOf(link) - 1;
|
||||||
|
this.links = this.links.removeObject(link);
|
||||||
|
this.links.splice(position, 0, link);
|
||||||
|
}
|
||||||
|
@bind
|
||||||
|
reorder() {
|
||||||
|
return ajax(`/sidebar_sections/reorder`, {
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
data: JSON.stringify({
|
||||||
|
sidebar_section_id: this.section.id,
|
||||||
|
links_order: this.links.map((link) => link.id),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -172,7 +172,10 @@ export default Controller.extend(ModalFunctionality, {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.currentUser.sidebar_sections.pushObject(data.sidebar_section);
|
this.currentUser.set(
|
||||||
|
"sidebar_sections",
|
||||||
|
this.currentUser.sidebar_sections.concat(data.sidebar_section)
|
||||||
|
);
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
})
|
})
|
||||||
.catch((e) =>
|
.catch((e) =>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default class DraggableModifier extends Modifier {
|
||||||
this.hasStarted = true;
|
this.hasStarted = true;
|
||||||
|
|
||||||
if (this.didStartDragCallback) {
|
if (this.didStartDragCallback) {
|
||||||
this.didStartDragCallback();
|
this.didStartDragCallback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a global event to capture mouse moves when element 'clicked'.
|
// Register a global event to capture mouse moves when element 'clicked'.
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
@import "share_link";
|
@import "share_link";
|
||||||
@import "shared-drafts";
|
@import "shared-drafts";
|
||||||
@import "sidebar";
|
@import "sidebar";
|
||||||
|
@import "sidebar-custom-section";
|
||||||
@import "sidebar-footer";
|
@import "sidebar-footer";
|
||||||
@import "sidebar-section";
|
@import "sidebar-section";
|
||||||
@import "sidebar-more-section-links";
|
@import "sidebar-more-section-links";
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
.sidebar-custom-sections {
|
||||||
|
.sidebar-section-wrapper {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.d-icon-globe {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.5em;
|
||||||
|
height: 0.75em;
|
||||||
|
width: 0.75em;
|
||||||
|
margin-top: 0.15em;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-link-prefix.icon {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-wrapper.disabled {
|
||||||
|
.sidebar-section-link-wrapper {
|
||||||
|
.sidebar-section-link-prefix.icon,
|
||||||
|
.sidebar-section-link {
|
||||||
|
background: none;
|
||||||
|
color: var(--primary-low);
|
||||||
|
}
|
||||||
|
.sidebar-section-link.drag {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-high);
|
||||||
|
.sidebar-section-link-prefix.icon {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,19 +128,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-custom-sections {
|
|
||||||
.sidebar-section-wrapper {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
.d-icon-globe {
|
|
||||||
position: absolute;
|
|
||||||
left: 0.5em;
|
|
||||||
height: 0.75em;
|
|
||||||
width: 0.75em;
|
|
||||||
margin-top: 0.15em;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sidebar-section-form-modal {
|
.sidebar-section-form-modal {
|
||||||
.modal-inner-container {
|
.modal-inner-container {
|
||||||
width: var(--modal-max-width);
|
width: var(--modal-max-width);
|
||||||
|
|
|
@ -56,6 +56,26 @@ class SidebarSectionsController < ApplicationController
|
||||||
render json: failed_json, status: 403
|
render json: failed_json, status: 403
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reorder
|
||||||
|
sidebar_section = SidebarSection.find_by(id: reorder_params["sidebar_section_id"])
|
||||||
|
@guardian.ensure_can_edit!(sidebar_section)
|
||||||
|
|
||||||
|
order = reorder_params["links_order"].map(&:to_i).each_with_index.to_h
|
||||||
|
position_generator =
|
||||||
|
(0..sidebar_section.sidebar_section_links.count * 2).excluding(
|
||||||
|
sidebar_section.sidebar_section_links.map(&:position),
|
||||||
|
).each
|
||||||
|
links =
|
||||||
|
sidebar_section
|
||||||
|
.sidebar_section_links
|
||||||
|
.sort_by { |link| order[link.linkable_id] }
|
||||||
|
.map { |link| link.attributes.merge(position: position_generator.next) }
|
||||||
|
sidebar_section.sidebar_section_links.upsert_all(links, update_only: [:position])
|
||||||
|
render json: sidebar_section
|
||||||
|
rescue Discourse::InvalidAccess
|
||||||
|
render json: failed_json, status: 403
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
sidebar_section = SidebarSection.find_by(id: section_params["id"])
|
sidebar_section = SidebarSection.find_by(id: section_params["id"])
|
||||||
@guardian.ensure_can_delete!(sidebar_section)
|
@guardian.ensure_can_delete!(sidebar_section)
|
||||||
|
@ -82,6 +102,10 @@ class SidebarSectionsController < ApplicationController
|
||||||
params.permit(links: %i[icon name value id _destroy])["links"]
|
params.permit(links: %i[icon name value id _destroy])["links"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reorder_params
|
||||||
|
params.permit(:sidebar_section_id, links_order: [])
|
||||||
|
end
|
||||||
|
|
||||||
def check_if_member_of_group
|
def check_if_member_of_group
|
||||||
### TODO remove when enable_custom_sidebar_sections SiteSetting is removed
|
### TODO remove when enable_custom_sidebar_sections SiteSetting is removed
|
||||||
if !SiteSetting.enable_custom_sidebar_sections.present? ||
|
if !SiteSetting.enable_custom_sidebar_sections.present? ||
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class SidebarSection < ActiveRecord::Base
|
class SidebarSection < ActiveRecord::Base
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
has_many :sidebar_section_links, dependent: :destroy
|
has_many :sidebar_section_links, -> { order("position") }, dependent: :destroy
|
||||||
has_many :sidebar_urls,
|
has_many :sidebar_urls,
|
||||||
through: :sidebar_section_links,
|
through: :sidebar_section_links,
|
||||||
source: :linkable,
|
source: :linkable,
|
||||||
|
|
|
@ -13,6 +13,12 @@ class SidebarSectionLink < ActiveRecord::Base
|
||||||
SUPPORTED_LINKABLE_TYPES = %w[Category Tag SidebarUrl]
|
SUPPORTED_LINKABLE_TYPES = %w[Category Tag SidebarUrl]
|
||||||
|
|
||||||
before_validation { self.user_id ||= self.sidebar_section&.user_id }
|
before_validation { self.user_id ||= self.sidebar_section&.user_id }
|
||||||
|
before_create do
|
||||||
|
if self.user_id && self.sidebar_section
|
||||||
|
self.position = self.sidebar_section.sidebar_section_links.maximum(:position).to_i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
after_destroy { self.linkable.destroy! if self.linkable_type == "SidebarUrl" }
|
after_destroy { self.linkable.destroy! if self.linkable_type == "SidebarUrl" }
|
||||||
|
|
||||||
private def ensure_supported_linkable_type
|
private def ensure_supported_linkable_type
|
||||||
|
@ -37,9 +43,11 @@ end
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# sidebar_section_id :integer
|
# sidebar_section_id :integer
|
||||||
|
# position :integer default(0), not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# idx_unique_sidebar_section_links (user_id,linkable_type,linkable_id) UNIQUE
|
# idx_unique_sidebar_section_links (user_id,linkable_type,linkable_id) UNIQUE
|
||||||
# index_sidebar_section_links_on_linkable_type_and_linkable_id (linkable_type,linkable_id)
|
# index_sidebar_section_links_on_linkable_type_and_linkable_id (linkable_type,linkable_id)
|
||||||
|
# links_user_id_section_id_position (user_id,sidebar_section_id,position) UNIQUE
|
||||||
#
|
#
|
||||||
|
|
|
@ -1594,6 +1594,7 @@ Discourse::Application.routes.draw do
|
||||||
delete "user-status" => "user_status#clear"
|
delete "user-status" => "user_status#clear"
|
||||||
|
|
||||||
resources :sidebar_sections, only: %i[index create update destroy]
|
resources :sidebar_sections, only: %i[index create update destroy]
|
||||||
|
post "/sidebar_sections/reorder" => "sidebar_sections#reorder"
|
||||||
|
|
||||||
get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new
|
get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddPositionToSidebarSectionLinks < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :sidebar_section_links, :position, :integer, default: 0, null: false
|
||||||
|
execute "UPDATE sidebar_section_links SET position = id"
|
||||||
|
add_index :sidebar_section_links,
|
||||||
|
%i[user_id sidebar_section_id position],
|
||||||
|
unique: true,
|
||||||
|
name: "links_user_id_section_id_position"
|
||||||
|
end
|
||||||
|
end
|
|
@ -167,6 +167,7 @@ RSpec.describe SidebarSectionsController do
|
||||||
links: [
|
links: [
|
||||||
{ icon: "link", id: sidebar_url_1.id, name: "latest", value: "/latest" },
|
{ icon: "link", id: sidebar_url_1.id, name: "latest", value: "/latest" },
|
||||||
{ icon: "link", id: sidebar_url_2.id, name: "tags", value: "/tags", _destroy: "1" },
|
{ icon: "link", id: sidebar_url_2.id, name: "tags", value: "/tags", _destroy: "1" },
|
||||||
|
{ icon: "link", name: "homepage", value: "https://discourse.org" },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,10 +179,18 @@ RSpec.describe SidebarSectionsController do
|
||||||
expect { section_link_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
expect { section_link_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
expect { sidebar_url_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
expect { sidebar_url_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
|
||||||
|
expect(sidebar_section.sidebar_section_links.last.position).to eq(2)
|
||||||
|
expect(sidebar_section.sidebar_section_links.last.linkable.name).to eq("homepage")
|
||||||
|
expect(sidebar_section.sidebar_section_links.last.linkable.value).to eq(
|
||||||
|
"https://discourse.org",
|
||||||
|
)
|
||||||
|
|
||||||
user_history = UserHistory.last
|
user_history = UserHistory.last
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:update_public_sidebar_section])
|
expect(user_history.action).to eq(UserHistory.actions[:update_public_sidebar_section])
|
||||||
expect(user_history.subject).to eq("custom section edited")
|
expect(user_history.subject).to eq("custom section edited")
|
||||||
expect(user_history.details).to eq("links: latest - /latest")
|
expect(user_history.details).to eq(
|
||||||
|
"links: latest - /latest, homepage - https://discourse.org",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't allow to edit other's sections" do
|
it "doesn't allow to edit other's sections" do
|
||||||
|
@ -232,6 +241,57 @@ RSpec.describe SidebarSectionsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#reorder" do
|
||||||
|
fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) }
|
||||||
|
fab!(:sidebar_url_1) { Fabricate(:sidebar_url, name: "tags", value: "/tags") }
|
||||||
|
fab!(:sidebar_url_2) { Fabricate(:sidebar_url, name: "categories", value: "/categories") }
|
||||||
|
fab!(:sidebar_url_3) { Fabricate(:sidebar_url, name: "topic", value: "/t/1") }
|
||||||
|
fab!(:section_link_1) do
|
||||||
|
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1)
|
||||||
|
end
|
||||||
|
fab!(:section_link_2) do
|
||||||
|
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
|
||||||
|
end
|
||||||
|
fab!(:section_link_3) do
|
||||||
|
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sorts links" do
|
||||||
|
serializer = SidebarSectionSerializer.new(sidebar_section, root: false).as_json
|
||||||
|
expect(serializer[:links].map(&:id)).to eq(
|
||||||
|
[sidebar_url_1.id, sidebar_url_2.id, sidebar_url_3.id],
|
||||||
|
)
|
||||||
|
|
||||||
|
sign_in(user)
|
||||||
|
post "/sidebar_sections/reorder.json",
|
||||||
|
params: {
|
||||||
|
sidebar_section_id: sidebar_section.id,
|
||||||
|
links_order: [sidebar_url_2.id, sidebar_url_3.id, sidebar_url_1.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer = SidebarSectionSerializer.new(sidebar_section.reload, root: false).as_json
|
||||||
|
expect(serializer[:links].map(&:id)).to eq(
|
||||||
|
[sidebar_url_2.id, sidebar_url_3.id, sidebar_url_1.id],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is not allowed for not own sections" do
|
||||||
|
sidebar_section_2 = Fabricate(:sidebar_section)
|
||||||
|
post "/sidebar_sections/reorder.json",
|
||||||
|
params: {
|
||||||
|
sidebar_section_id: sidebar_section_2.id,
|
||||||
|
links_order: [sidebar_url_2.id, sidebar_url_3.id, sidebar_url_1.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
|
||||||
|
serializer = SidebarSectionSerializer.new(sidebar_section, root: false).as_json
|
||||||
|
expect(serializer[:links].map(&:id)).to eq(
|
||||||
|
[sidebar_url_1.id, sidebar_url_2.id, sidebar_url_3.id],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#destroy" do
|
describe "#destroy" do
|
||||||
fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) }
|
fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) }
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,33 @@ describe "Custom sidebar sections", type: :system, js: true do
|
||||||
expect(page).not_to have_link("Sidebar Categories")
|
expect(page).not_to have_link("Sidebar Categories")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows the user to reorder links in custom section" do
|
||||||
|
sidebar_section = Fabricate(:sidebar_section, title: "My section", user: user)
|
||||||
|
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
|
||||||
|
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1)
|
||||||
|
sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories")
|
||||||
|
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
|
||||||
|
|
||||||
|
visit("/latest")
|
||||||
|
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do
|
||||||
|
expect(page).to have_css(".sidebar-section-link-sidebar-tags")
|
||||||
|
end
|
||||||
|
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(2)") do
|
||||||
|
expect(page).to have_css(".sidebar-section-link-sidebar-categories")
|
||||||
|
end
|
||||||
|
|
||||||
|
tags_link = find(".sidebar-section-link-sidebar-tags")
|
||||||
|
categories_link = find(".sidebar-section-link-sidebar-categories")
|
||||||
|
tags_link.drag_to(categories_link)
|
||||||
|
|
||||||
|
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do
|
||||||
|
expect(page).to have_css(".sidebar-section-link-sidebar-categories")
|
||||||
|
end
|
||||||
|
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(2)") do
|
||||||
|
expect(page).to have_css(".sidebar-section-link-sidebar-tags")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "does not allow the user to edit public section" do
|
it "does not allow the user to edit public section" do
|
||||||
sidebar_section = Fabricate(:sidebar_section, title: "Public section", user: user, public: true)
|
sidebar_section = Fabricate(:sidebar_section, title: "Public section", user: user, public: true)
|
||||||
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
|
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
|
||||||
|
|
Loading…
Reference in New Issue