summaryrefslogtreecommitdiff
path: root/addons/website/static/src/snippets/s_table_of_content
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website/static/src/snippets/s_table_of_content
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website/static/src/snippets/s_table_of_content')
-rw-r--r--addons/website/static/src/snippets/s_table_of_content/000.js77
-rw-r--r--addons/website/static/src/snippets/s_table_of_content/000.scss65
-rw-r--r--addons/website/static/src/snippets/s_table_of_content/options.js122
3 files changed, 264 insertions, 0 deletions
diff --git a/addons/website/static/src/snippets/s_table_of_content/000.js b/addons/website/static/src/snippets/s_table_of_content/000.js
new file mode 100644
index 00000000..fcae4ed7
--- /dev/null
+++ b/addons/website/static/src/snippets/s_table_of_content/000.js
@@ -0,0 +1,77 @@
+odoo.define('website.s_table_of_content', function (require) {
+'use strict';
+
+const publicWidget = require('web.public.widget');
+const {extraMenuUpdateCallbacks} = require('website.content.menu');
+
+const TableOfContent = publicWidget.Widget.extend({
+ selector: 'section .s_table_of_content_navbar_sticky',
+ disabledInEditableMode: false,
+
+ /**
+ * @override
+ */
+ async start() {
+ await this._super(...arguments);
+ this._updateTableOfContentNavbarPosition();
+ extraMenuUpdateCallbacks.push(this._updateTableOfContentNavbarPosition.bind(this));
+ },
+ /**
+ * @override
+ */
+ destroy() {
+ this.$target.css('top', '');
+ this.$target.find('.s_table_of_content_navbar').css('top', '');
+ this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _updateTableOfContentNavbarPosition() {
+ let position = 0;
+ const $fixedElements = $('.o_top_fixed_element');
+ _.each($fixedElements, el => position += $(el).outerHeight());
+ const isHorizontalNavbar = this.$target.hasClass('s_table_of_content_horizontal_navbar');
+ this.$target.css('top', isHorizontalNavbar ? position : '');
+ this.$target.find('.s_table_of_content_navbar').css('top', isHorizontalNavbar ? '' : position + 20);
+ const $mainNavBar = $('#oe_main_menu_navbar');
+ position += $mainNavBar.length ? $mainNavBar.outerHeight() : 0;
+ position += isHorizontalNavbar ? this.$target.outerHeight() : 0;
+ $().getScrollingElement().scrollspy({target: '.s_table_of_content_navbar', method: 'offset', offset: position + 100, alwaysKeepFirstActive: true});
+ },
+});
+
+publicWidget.registry.anchorSlide.include({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Overridden to add the height of the horizontal sticky navbar at the scroll value
+ * when the link is from the table of content navbar
+ *
+ * @override
+ * @private
+ */
+ _computeExtraOffset() {
+ let extraOffset = this._super(...arguments);
+ if (this.$el.hasClass('table_of_content_link')) {
+ const tableOfContentNavbarEl = this.$el.closest('.s_table_of_content_navbar_sticky.s_table_of_content_horizontal_navbar');
+ if (tableOfContentNavbarEl.length > 0) {
+ extraOffset += $(tableOfContentNavbarEl).outerHeight();
+ }
+ }
+ return extraOffset;
+ },
+});
+
+publicWidget.registry.snippetTableOfContent = TableOfContent;
+
+return TableOfContent;
+});
diff --git a/addons/website/static/src/snippets/s_table_of_content/000.scss b/addons/website/static/src/snippets/s_table_of_content/000.scss
new file mode 100644
index 00000000..670e1365
--- /dev/null
+++ b/addons/website/static/src/snippets/s_table_of_content/000.scss
@@ -0,0 +1,65 @@
+.s_table_of_content:not([data-vcss]) {
+ .s_table_of_content_navbar_wrap {
+ &.s_table_of_content_navbar_sticky {
+ &.s_table_of_content_horizontal_navbar, &.s_table_of_content_vertical_navbar .s_table_of_content_navbar {
+ @include o-position-sticky($top: 0px);
+ }
+ }
+ &:not(.s_table_of_content_navbar_sticky) {
+ &, .s_table_of_content_navbar {
+ top: 0px !important;
+ }
+ }
+ &.s_table_of_content_vertical_navbar .s_table_of_content_navbar {
+ > a.list-group-item-action {
+ background: none;
+ color: inherit;
+ opacity: 0.7;
+ font-weight: $font-weight-normal + 100;
+ padding-left: 3px;
+ transition: padding 0.1s;
+
+ &:before {
+ @include o-position-absolute(10px, auto, 10px, 0);
+ width: 2px;
+ content: "";
+ }
+ &:hover {
+ opacity: 1;
+ }
+ &:focus {
+ background: none;
+ }
+ &.active {
+ background: none;
+ padding-left: 8px;
+ opacity: 1;
+
+ &:before {
+ background-color: theme-color('primary');
+ }
+ }
+ }
+ }
+ &.s_table_of_content_horizontal_navbar {
+ z-index: 1;
+ padding-top: $navbar-padding-y;
+ padding-bottom: $navbar-padding-y;
+ margin-bottom: $spacer * 2;
+
+ .s_table_of_content_navbar {
+ display: inline;
+
+ > a {
+ &.list-group-item-action {
+ width: auto;
+ }
+ &.list-group-item {
+ display: inline-block;
+ margin-bottom: 2px;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/website/static/src/snippets/s_table_of_content/options.js b/addons/website/static/src/snippets/s_table_of_content/options.js
new file mode 100644
index 00000000..3feb0c74
--- /dev/null
+++ b/addons/website/static/src/snippets/s_table_of_content/options.js
@@ -0,0 +1,122 @@
+odoo.define('website.s_table_of_content_options', function (require) {
+'use strict';
+
+const options = require('web_editor.snippets.options');
+
+options.registry.TableOfContent = options.Class.extend({
+ /**
+ * @override
+ */
+ start: function () {
+ this.targetedElements = 'h1, h2';
+ const $headings = this.$target.find(this.targetedElements);
+ if ($headings.length > 0) {
+ this._generateNav();
+ }
+ // Generate the navbar if the content changes
+ const targetNode = this.$target.find('.s_table_of_content_main')[0];
+ const config = {attributes: false, childList: true, subtree: true, characterData: true};
+ this.observer = new MutationObserver(() => this._generateNav());
+ this.observer.observe(targetNode, config);
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ onClone: function () {
+ this._generateNav();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _generateNav: function (ev) {
+ const $nav = this.$target.find('.s_table_of_content_navbar');
+ const $headings = this.$target.find(this.targetedElements);
+ $nav.empty();
+ _.each($headings, el => {
+ const $el = $(el);
+ const id = 'table_of_content_heading_' + _.now() + '_' + _.uniqueId();
+ $('<a>').attr('href', "#" + id)
+ .addClass('table_of_content_link list-group-item list-group-item-action py-2 border-0 rounded-0')
+ .text($el.text())
+ .appendTo($nav);
+ $el.attr('id', id);
+ $el[0].dataset.anchor = 'true';
+ });
+ $nav.find('a:first').addClass('active');
+ },
+});
+
+options.registry.TableOfContentNavbar = options.Class.extend({
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Change the navbar position.
+ *
+ * @see this.selectClass for parameters
+ */
+ navbarPosition: function (previewMode, widgetValue, params) {
+ const $navbar = this.$target;
+ const $mainContent = this.$target.parent().find('.s_table_of_content_main');
+ if (widgetValue === 'top' || widgetValue === 'left') {
+ $navbar.prev().before($navbar);
+ }
+ if (widgetValue === 'left' || widgetValue === 'right') {
+ $navbar.removeClass('s_table_of_content_horizontal_navbar col-lg-12').addClass('s_table_of_content_vertical_navbar col-lg-3');
+ $mainContent.removeClass('col-lg-12').addClass('col-lg-9');
+ $navbar.find('.s_table_of_content_navbar').removeClass('list-group-horizontal-md');
+ }
+ if (widgetValue === 'right') {
+ $navbar.next().after($navbar);
+ }
+ if (widgetValue === 'top') {
+ $navbar.removeClass('s_table_of_content_vertical_navbar col-lg-3').addClass('s_table_of_content_horizontal_navbar col-lg-12');
+ $navbar.find('.s_table_of_content_navbar').addClass('list-group-horizontal-md');
+ $mainContent.removeClass('col-lg-9').addClass('col-lg-12');
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _computeWidgetState: function (methodName, params) {
+ switch (methodName) {
+ case 'navbarPosition': {
+ const $navbar = this.$target;
+ if ($navbar.hasClass('s_table_of_content_horizontal_navbar')) {
+ return 'top';
+ } else {
+ const $mainContent = $navbar.parent().find('.s_table_of_content_main');
+ return $navbar.prev().is($mainContent) === true ? 'right' : 'left';
+ }
+ }
+ }
+ return this._super(...arguments);
+ },
+});
+
+options.registry.TableOfContentMainColumns = options.Class.extend({
+ forceNoDeleteButton: true,
+
+ /**
+ * @override
+ */
+ start: function () {
+ const leftPanelEl = this.$overlay.data('$optionsSection')[0];
+ leftPanelEl.querySelector('.oe_snippet_clone').classList.add('d-none'); // TODO improve the way to do that
+ return this._super.apply(this, arguments);
+ },
+});
+});