Skip to content
Snippets Groups Projects
Commit 5a9c7da9 authored by Arnaud Levy's avatar Arnaud Levy
Browse files

Merge branches 'better-contents' and 'main' of github.com:noesya/osuny

parents f6275272 cb8229df
No related branches found
No related tags found
No related merge requests found
Showing
with 267 additions and 152 deletions
......@@ -103,7 +103,7 @@ GEM
autoprefixer-rails (10.4.13.0)
execjs (~> 2)
aws-eventstream (1.2.0)
aws-partitions (1.788.0)
aws-partitions (1.790.0)
aws-sdk-core (3.178.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
......@@ -112,7 +112,7 @@ GEM
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.130.0)
aws-sdk-s3 (1.131.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
......@@ -131,7 +131,7 @@ GEM
rails (>= 3.1)
breadcrumbs_on_rails (4.1.0)
railties (>= 5.0)
bugsnag (6.25.2)
bugsnag (6.26.0)
concurrent-ruby (~> 1.0)
builder (3.2.4)
byebug (11.1.3)
......
......@@ -10,124 +10,98 @@ window.osuny.contentEditor = {
}
this.sortHeadingsUrl = this.container.getAttribute('data-sort-headings-url');
this.sortBlocksUrl = this.container.getAttribute('data-sort-blocks-url');
this.initElements();
this.modeWriteContainer = this.container.querySelector('#mode-write-container');
this.modeStructureContainer = this.container.querySelector('#mode-structure-container');
this.initTabs();
this.initSortable();
},
initElements: function () {
initTabs: function () {
'use strict';
var elementsContainers = this.container.querySelectorAll('.js-content-editor-element'),
elementInstance,
var tabs = document.querySelectorAll('[data-bs-toggle="tab"]'),
i;
this.elements = [];
for (i = 0; i < elementsContainers.length; i += 1) {
elementInstance = new window.osuny.contentEditor.Element(elementsContainers[i]);
this.elements.push(elementInstance);
for (i = 0; i < tabs.length; i += 1) {
tabs[i].addEventListener('shown.bs.tab', this.tabChanged.bind(this));
}
},
initSortable: function () {
'use strict';
var sortableContainers = this.container.querySelectorAll('.js-content-editor-sortable-container'),
sortableInstance,
i;
this.sortableRootContainer = document.getElementById('content-editor-elements-root');
this.sortableInstances = [];
for (i = 0; i < sortableContainers.length; i += 1) {
sortableInstance = new Sortable(sortableContainers[i], {
group: 'nested',
new Sortable(sortableContainers[i], {
handle: '.content-editor__elements__handle',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onUnchoose: this.onSortableUnchoose.bind(this),
onStart: this.onSortableStart.bind(this),
onMove: this.onSortableMove.bind(this),
fallbackOnBody: false,
onEnd: this.onSortableEnd.bind(this)
});
this.sortableInstances.push(sortableInstance);
}
},
onSortableStart: function (event) {
tabChanged: function (event) {
'use strict';
var item = event.item,
kind = item.dataset.kind;
this.sortableRootContainer.classList.add('content-editor__elements__root--dragging');
if (kind === 'block') {
this.sortableRootContainer.classList.add('content-editor__elements__root--dragging-block');
} else if (kind === 'heading') {
this.sortableRootContainer.classList.add('content-editor__elements__root--dragging-heading');
}
var tab = event.target,
id = tab.getAttribute('data-bs-target'),
div = this.container.querySelector(id),
source = div.dataset.source;
this.target = this.container.querySelector(div.dataset.target);
this.target.innerHTML = '';
this.xhr = new XMLHttpRequest();
this.xhr.open('GET', source, true);
this.xhr.onreadystatechange = this.tabLoaded.bind(this);
this.xhr.send();
},
onSortableMove: function (event) {
tabLoaded: function () {
'use strict';
var draggedKind = event.dragged.dataset.kind,
relatedKind = event.related.dataset.kind,
firstHeading = this.sortableRootContainer.querySelector('.js-content-editor-element[data-kind="heading"]');
if (draggedKind === 'block') {
// Prevent dragging a block after a heading, instead of inside
return relatedKind !== 'heading' || !event.willInsertAfter && event.related === firstHeading;
if (this.xhr.readyState === XMLHttpRequest.DONE) {
if (this.xhr.status === 200) {
this.target.innerHTML = this.xhr.responseText;
this.initSortable();
}
}
return true;
},
onSortableEnd: function (event) {
'use strict';
var item = event.item,
kind = item.dataset.kind,
url = this.getUrlFromKind(kind),
to = event.to,
ids = [],
headingId = null,
child,
i;
if (to.id !== 'content-editor-elements-root') {
// Dragged to heading's children list
headingId = event.to.parentNode.dataset.id;
if (event.from.classList.contains('content-editor--write')) {
this.sortModeWrite(event.to);
} else if (event.from.classList.contains('content-editor--organize')) {
this.sortModeOrganize(event.to);
}
},
// Mode écriture du contenu
sortModeWrite: function (to) {
'use strict';
var ids = [],
child,
i;
for (i = 0; i < to.children.length; i += 1) {
child = to.children[i];
if (child.dataset.kind === kind) {
ids.push(child.dataset.id);
}
// Nous utilisons une route déjà existante, dédiée aux blocs,
// pour gérer à la fois des blocs et des headings.
// Ca manque d'élégance.
ids.push({
id: child.dataset.id,
kind: child.dataset.kind
});
}
// call to application
$.post(url, {
heading: headingId,
ids: ids
});
$.post(this.sortBlocksUrl, { ids: ids });
},
onSortableUnchoose: function () {
// Mode organisation du plan
sortModeOrganize: function (to) {
'use strict';
this.sortableRootContainer.classList.remove('content-editor__elements__root--dragging',
'content-editor__elements__root--dragging-block',
'content-editor__elements__root--dragging-heading');
},
getUrlFromKind: function (kind) {
'use strict';
if (kind === 'block') {
return this.sortBlocksUrl;
} else if (kind === 'heading') {
return this.sortHeadingsUrl;
var ids = [],
child,
i;
for (i = 0; i < to.children.length; i += 1) {
child = to.children[i];
ids.push(child.dataset.id);
}
return null;
},
getElementById: function (id) {
'use strict';
return this.elements[id];
$.post(this.sortHeadingsUrl, { ids: ids });
},
invoke: function () {
......
.content-editor
&__elements
&__root
&--dragging-heading
.content-editor__elements__element--block
display: none
&__element
.draft
.kind
......@@ -13,18 +9,14 @@
&.sortable-chosen
.content-editor__elements
display: none
&--mode-structure
.content-editor__elements__handle
cursor: move
&--hover
opacity: 0
transition: opacity 0.25s ease
// @alex @olivia désolé
// j'ai besoin de rendre visible le hover du heading mais pas tous les enfants pour autant
// @arnaud
&:hover
> .content-editor__elements__element--hover,
> * > .content-editor__elements__element--hover,
> * > * > .content-editor__elements__element--hover,
> * > * > * > .content-editor__elements__element--hover,
> * > * > * > * > .content-editor__elements__element--hover
.content-editor__elements__element--hover
opacity: 1
&__handle
margin-left: 10px
......
.treeview
&__element
a
a:not(.action)
text-decoration: none
& > .treeview__children .treeview__empty
display: none
......
......@@ -9,6 +9,8 @@ class Admin::Communication::Blocks::HeadingsController < Admin::Communication::B
ids.each.with_index do |id, index|
@heading = current_university.communication_block_headings.find(id)
@heading.update_columns parent_id: parent_id,
parent_id: nil,
level: Communication::Block::Heading::DEFAULT_LEVEL,
position: index + 1
end
if @heading.about&.respond_to?(:is_direct_object?)
......
......@@ -4,17 +4,17 @@ class Admin::Communication::BlocksController < Admin::Communication::Application
through_association: :communication_blocks
def reorder
heading_id = params[:heading]
ids = params[:ids] || []
ids.each.with_index do |id, index|
@block = current_university.communication_blocks.find(id)
@block.update_columns position: index + 1,
heading_id: heading_id
end
if @block.about&.respond_to?(:is_direct_object?)
@block.about.is_direct_object? ? @block.about.sync_with_git
: @block.about.touch # Sync indirect object's direct sources through after_touch
# Cette action est très étrange, elle ne met pas en ordre les blocs seuls.
# En fait, elle met en ordre dans le mode "Ecrire le contenu", à la fois les headings et les blocks.
@ids = params[:ids] || []
@index_block = 0
@index_heading = 0
@heading = nil
@ids.values.each do |object|
@object = object
reorder_object
end
sync_after_reorder
end
def new
......@@ -66,6 +66,32 @@ class Admin::Communication::BlocksController < Admin::Communication::Application
protected
def reorder_object
@id = @object[:id]
@object[:kind] == 'heading' ? reorder_heading
: reorder_block
end
def reorder_heading
@heading = current_university.communication_block_headings.find(@id)
@heading.update_columns position: @index_heading
@index_block = 0
@index_heading += 1
end
def reorder_block
@block = current_university.communication_blocks.find(@id)
@block.update_columns position: @index_block,
heading_id: @heading&.id
@index_block += 1
end
def sync_after_reorder
return unless @block && @block.about&.respond_to?(:is_direct_object?)
@block.about.is_direct_object? ? @block.about.sync_with_git
: @block.about.touch # Sync indirect object's direct sources through after_touch
end
def website_id
params[:website_id] || @block.about&.website.id
rescue
......
class Admin::Communication::ContentsController < Admin::Communication::ApplicationController
before_action :load_about
layout false
# /admin/communication/contents/Communication::Website::Page/a788f3ab-a3a8-4d26-9440-6cb12fbf442c/write
def write
end
# /admin/communication/contents/Communication::Website::Page/a788f3ab-a3a8-4d26-9440-6cb12fbf442c/structure
def structure
end
protected
def load_about
@about = PolymorphicObjectFinder.find(params, :about)
raise_403_unless @about.university == current_university
raise_403_unless can?(:edit, @about)
end
end
\ No newline at end of file
<% if about.available_languages.many? %>
<br> <%= t('admin.i18n.in', lang: language_name(about.language.iso_code).downcase) %>
<%= t('admin.i18n.in', lang: language_name(about.language.iso_code).downcase) %>
<%
route_args = about.is_direct_object? ? [:admin, about.becomes(about.class.base_class)]
......@@ -12,5 +12,5 @@
[*route_args, lang: language.iso_code]
}.compact
%>
<br>(<%= t('admin.i18n.switch_to', choices: links.to_sentence).html_safe %>)
(<%= t('admin.i18n.switch_to', choices: links.to_sentence).html_safe %>)
<% end %>
\ No newline at end of file
......@@ -19,7 +19,7 @@
<span class=" content-editor__elements__handle
content-editor__elements__element--hover">
<span class="handle">
<span class="small"><%= t 'organize' %></span>
<span class="small"><%= t 'move' %></span>
<i class="<%= Icon::SORT %>"></i>
</span>
</span>
......
<%
mode_expert = about.headings.many?
%>
<div class="js-content-editor mb-5"
data-sort-blocks-url="<%= reorder_admin_communication_blocks_path(lang: nil, website_id: nil, extranet_id: nil) %>"
data-sort-headings-url="<%= reorder_admin_communication_headings_path(lang: nil, website_id: nil, extranet_id: nil) %>">
<div class=" content-editor__elements
content-editor__elements__root
js-content-editor-sortable-container"
id="content-editor-elements-root">
<% about.blocks.without_heading.ordered.each do |block| %>
<%= render 'admin/communication/blocks/block', block: block %>
<% end %>
<% about.headings.root.ordered.each do |heading| %>
<%= render 'admin/communication/blocks/headings/heading', heading: heading %>
<% end %>
</div>
<div class="content-editor__actions row mt-5">
<div class="col-lg-4">
<%= link_to t('admin.communication.blocks.headings.add'),
new_admin_communication_heading_path(about_id: about.id, about_type: about.class.name),
class: 'py-5 px-2 d-block bg-light text-center border h4' if can? :create, Communication::Block::Heading %>
data-sort-headings-url="<%= reorder_admin_communication_headings_path(lang: nil, website_id: nil, extranet_id: nil) %>"
>
<% if mode_expert %>
<ul class="nav nav-tabs justify-content-md-end">
<li class="nav-item small">
<a class="nav-link active"
id="mode-write-tab"
data-bs-toggle="tab"
data-bs-target="#mode-write"
type="button"
role="tab"
aria-controls="mode-write"
aria-selected="true">
<%= t('admin.communication.contents.modes.write.tab') %>
</a>
</li>
<li class="nav-item small">
<a class="nav-link"
id="mode-structure-tab"
data-bs-toggle="tab"
data-bs-target="#mode-structure"
type="button"
role="tab"
aria-controls="mode-structure"
aria-selected="false">
<%= t('admin.communication.contents.modes.structure.tab') %>
</a>
</li>
</ul>
<% end %>
<div class="tab-content">
<div class="tab-pane show active"
id="mode-write"
data-source="<%= admin_communication_contents_write_path(about_type: about.class.polymorphic_name, about_id: about.id) %>"
data-target="#mode-write-container"
role="tabpanel"
aria-labelledby="mode-write-tab"
tabindex="0">
<% if mode_expert %>
<div class="row">
<div class="offset-xl-6 col-xl-6">
<p class="text-lg-end text-muted small mt-2">
<%= t('admin.communication.contents.modes.write.description') %>
</p>
</div>
</div>
<% end %>
<div id="mode-write-container">
<%= render 'admin/communication/contents/write', about: about %>
</div>
</div>
<div class="col-lg-8">
<%= link_to t('admin.communication.blocks.add'),
new_admin_communication_block_path(about_id: about.id, about_type: about.class.name),
class: 'py-5 px-2 d-block bg-light text-center border h4' if can? :create, Communication::Block %>
<div class="tab-pane"
id="mode-structure"
data-source="<%= admin_communication_contents_structure_path(about_type: about.class.polymorphic_name, about_id: about.id) %>"
data-target="#mode-structure-container"
role="tabpanel"
aria-labelledby="profile-tab"
tabindex="1">
<div class="row">
<div class="offset-xl-6 col-xl-6">
<p class="text-lg-end text-muted small mt-2">
<%= t('admin.communication.contents.modes.structure.description') %>
</p>
</div>
</div>
<div id="mode-structure-container"></div>
</div>
</div>
</div>
<div class=" content-editor__elements__element
content-editor__elements__element--heading
content-editor__elements__element--mode-structure
js-content-editor-element
mt-3"
data-id="<%= heading.id %>"
data-level="<%= heading.level %>"
data-kind="heading">
<div class="content-editor__elements__handle"
style="padding-left: <%= (heading.level - Communication::Block::Heading::DEFAULT_LEVEL) * 48 %>px">
<span class="h4"><%= heading %></span>
<i class="<%= Icon::SORT %>"></i>
<p class="small text-muted">
<%= t('admin.communication.contents.blocks.quantity', count: heading.blocks.count) %>
</p>
</div>
</div>
<% heading.children.ordered.each do |child| %>
<%= render 'admin/communication/blocks/headings/heading', heading: child %>
<% end %>
......@@ -9,24 +9,21 @@
<span class="h4"><%= heading %></span>
<%= link_to t('edit'),
edit_admin_communication_heading_path(heading),
class: 'action ms-2' %>
class: 'action ms-2'%>
<% if can?(:update, heading) %>
<span class=" content-editor__elements__handle
content-editor__elements__element--hover">
<span class="handle">
<span class="small"><%= t 'organize' %></span>
<span class="small"><%= t 'move' %></span>
<i class="<%= Icon::SORT %>"></i>
</span>
</span>
<% end %>
</div>
<div class="content-editor__elements
js-content-editor-sortable-container">
<% heading.blocks.ordered.each do |block| %>
<%= render 'admin/communication/blocks/block', block: block %>
<% end %>
<% heading.children.ordered.each do |child| %>
<%= render 'admin/communication/blocks/headings/heading', heading: child %>
<% end %>
</div>
</div>
<% heading.blocks.ordered.each do |block| %>
<%= render 'admin/communication/blocks/block', block: block %>
<% end %>
<% heading.children.ordered.each do |child| %>
<%= render 'admin/communication/blocks/headings/heading', heading: child %>
<% end %>
<div class=" content-editor__elements
content-editor--organize
js-content-editor-sortable-container">
<% about.headings.root.ordered.each do |heading| %>
<%= render 'admin/communication/blocks/headings/heading-for-mode-structure', heading: heading %>
<% end %>
</div>
<div class=" content-editor__elements
content-editor--write
content-editor__elements__root
js-content-editor-sortable-container"
id="content-editor-elements-root">
<% about.blocks.without_heading.ordered.each do |block| %>
<%= render 'admin/communication/blocks/block', block: block %>
<% end %>
<% about.headings.root.ordered.each do |heading| %>
<%= render 'admin/communication/blocks/headings/heading', heading: heading %>
<% end %>
</div>
<div class="content-editor__actions row mt-5">
<div class="col-lg-4">
<%= link_to t('admin.communication.blocks.headings.add'),
new_admin_communication_heading_path(about_id: about.id, about_type: about.class.name),
class: 'py-5 px-2 d-block bg-light text-center border h4' if can? :create, Communication::Block::Heading %>
</div>
<div class="col-lg-8">
<%= link_to t('admin.communication.blocks.add'),
new_admin_communication_block_path(about_id: about.id, about_type: about.class.name),
class: 'py-5 px-2 d-block bg-light text-center border h4' if can? :create, Communication::Block %>
</div>
</div>
\ No newline at end of file
<%= render 'admin/communication/contents/structure', about: @about %>
\ No newline at end of file
<%= render 'admin/communication/contents/write', about: @about %>
\ No newline at end of file
......@@ -15,10 +15,15 @@
<div class="btn-group ms-auto" role="group">
<%= link_to t('show'),
admin_communication_website_menu_item_path(website_id: item.website.id, menu_id: item.menu.id, id: item.id),
class: 'ps-3' %>
class: 'action ps-3' %>
<%= link_to t('edit'),
edit_admin_communication_website_menu_item_path(website_id: item.website.id, menu_id: item.menu.id, id: item.id),
class: 'ps-3' %>
class: 'action ps-3' %>
<%= link_to t('delete'),
admin_communication_website_menu_item_path(website_id: item.website.id, menu_id: item.menu.id, id: item.id),
method: :delete,
data: { confirm: t('please_confirm') },
class: 'action text-danger ps-3' %>
</div>
</div>
<ul class="list-unstyled treeview__children js-treeview-children <%= 'js-treeview-sortable-container' if can?(:reorder, item) %> ms-4" data-id="<%= item.id %>">
......
......@@ -18,7 +18,7 @@
<% end %>
</div>
<div class="btn-group">
<%= link_to t('admin.communication.website.pages.show'), admin_communication_website_page_path(page) %>
<%= link_to t('admin.communication.website.pages.show'), admin_communication_website_page_path(page), class: 'action' %>
</div>
</div>
<ul class="list-unstyled treeview__children js-treeview-children <%= 'js-treeview-sortable-container' if can?(:reorder, page) %> ms-4" data-id="<%= page.id %>">
......
......@@ -14,27 +14,25 @@
<%= render 'admin/application/i18n/inline', about: @page %>
<% if @page.parent %>
<br>
Dans
<% if @page.parent && !@page.parent.is_home? %>
dans
<%= link_to_if can?(:read, @page.parent),
@page.parent,
admin_communication_website_page_path(
website_id: @website.id,
id: @page.parent.id
) %>
) %>.
<% end %>
<% if @page.children.any? %>
<br>
<%= Communication::Website::Page.human_attribute_name('children') %> :
avec comme pages enfants :
<%= raw @page.children.ordered.map { |child|
link_to_if can?(:read, child),
child,
admin_communication_website_page_path( website_id: @website.id, id: child.id),
class: "#{'draft' unless child.published?}"
}.join(', ') %>
}.join(', ') %>.
<% end %>
<% if @page.full_width %>
......
......@@ -35,7 +35,7 @@
<span class="me-3 show-on-hover"><%= t("communication.website.pages.defaults.home.title") %></span>
</div>
<div class="btn-group">
<%= link_to t('admin.communication.website.pages.show'), admin_communication_website_page_path(@homepage) %>
<%= link_to t('admin.communication.website.pages.show'), admin_communication_website_page_path(@homepage), class: 'action' %>
</div>
</div>
<ul class="list-unstyled ms-4 treeview__children js-treeview <%= 'treeview--sortable js-treeview-sortable js-treeview-sortable-container' if can?(:reorder, @homepage) %>"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment