]> _ Git - fluidbook-toolbox.git/commitdiff
wait #7812 master
authorsoufiane <soufiane@cubedesigners.com>
Tue, 24 Mar 2026 17:22:00 +0000 (18:22 +0100)
committersoufiane <soufiane@cubedesigners.com>
Tue, 24 Mar 2026 17:22:00 +0000 (18:22 +0100)
resources/linkeditor/js/linkeditor.accessibility.js [new file with mode: 0644]

diff --git a/resources/linkeditor/js/linkeditor.accessibility.js b/resources/linkeditor/js/linkeditor.accessibility.js
new file mode 100644 (file)
index 0000000..59a01b8
--- /dev/null
@@ -0,0 +1,411 @@
+import Sortable from 'sortablejs';
+
+function LinkeditorAccessibility(linkeditor) {
+    this.linkeditor = linkeditor;
+    this.interactiveThreshold = 5;
+    this.nonInteractiveTypes = [14, 15, 39];
+}
+
+LinkeditorAccessibility.prototype = {
+    init: function () {
+        var $this = this;
+
+        this.container = $("#linkeditor-panel-accessibility");
+        this.maskCheckEvents = false;
+
+        this.sortable = false;
+
+        $(document).on('click', '#linkeditor-panel-accessibility label span.uid', function () {
+            navigator.clipboard.writeText($(this).attr('fb-uid'));
+            let tippy = $(this).data('tippyinstance');
+            tippy.setContent(TRANSLATIONS.id_copied);
+            tippy.show();
+            return false;
+        });
+
+        this.update();
+    },
+
+    reorderSelection: function (way) {
+        return this.reorderLinks(this.getCurrentOrderableSelection(), way);
+    },
+
+    reorderLinks: function (links, way) {
+        let selectedOrder = parseInt($(links).eq(0).attr('fb-order'));
+        links = this.orderLinksByPosition(links, way);
+        let step = 1 / (links.length + 1);
+        $(links).each(function () {
+            let s = selectedOrder += step;
+            $(this).attr('fb-order', s);
+        });
+        this.normalizeLinksOrder();
+    },
+
+    reorderPageLinks: function (way) {
+        return this.reorderLinks(this.getOrderableLinksOnPage(), way);
+    },
+
+    reorderAllLinks: function (way) {
+        let $this = this;
+        let step = this.linkeditor.single ? 1 : 2;
+        for (let i = 0; i < FLUIDBOOK_DATA.settings.pages; i += step) {
+            let o = 0;
+            let links = this.orderLinksByPosition(this.getOrderableLinksOnPage(i), way);
+            $.each(links, function (k, v) {
+                LINKS[v.uid].order = o++;
+                if (i === $this.linkeditor.getCurrentPage()) {
+                    $('.link[fb-uid="' + v.uid + '"]').attr('fb-order', o);
+                }
+            });
+            this.linkeditor.hasChanged();
+        }
+        this.normalizeLinksOrder(true);
+    },
+
+    moveSelectionOrder: function (way) {
+        let start;
+        let selection = this.getCurrentOrderableSelection();
+        let num = selection.length;
+        if (num <= 0) {
+            return;
+        }
+        let firstSelected = $(selection).get(0);
+        let firstSelectedOrder = parseFloat($(firstSelected).attr('fb-order'));
+        let max = this.getOrderableLinksOnPage().length + 1;
+
+        let step = 1 / (num + 1);
+
+        switch (way) {
+            case'start':
+                start = -num;
+                break;
+            case'end':
+                start = max;
+                break;
+            case'up':
+                start = firstSelectedOrder - 1 - step;
+                break;
+            case'down':
+                start = firstSelectedOrder + 1 + step;
+                break;
+        }
+
+        let selectedOrder = start;
+
+        $(selection).each(function () {
+            $(this).attr('fb-order', selectedOrder);
+            selectedOrder += step;
+        });
+
+        this.normalizeLinksOrder();
+    },
+
+    getOrderableLinksOnPage: function (page) {
+        let links;
+        if (page === undefined) {
+            links = this.linkeditor.links.getLinksOfCurrentPage();
+        } else {
+            links = this.linkeditor.links.getLinksOfPage(page);
+        }
+
+        return this.filterOrderableLinks(links);
+    },
+
+
+    filterOrderableLinks: function (links) {
+        let $this = this;
+        let res = [];
+        $.each(links, function () {
+            if ($this.isInteractive(this)) {
+                res.push(this);
+            }
+        });
+
+        return this.orderLinks(res);
+    },
+
+    isInteractive: function (link) {
+        link = $(link);
+        if (link.attr('fb-calc-depth') < this.interactiveThreshold * 10) {
+            return false;
+        }
+        if (this.nonInteractiveTypes.indexOf(parseInt(link.attr('fb-type'))) > -1) {
+            return false;
+        }
+        let x = parseFloat(link.attr('fb-left'));
+        let y = parseFloat(link.attr('fb-top'));
+        let w = parseFloat(link.attr('fb-width'));
+        let h = parseFloat(link.attr('fb-height'));
+        if (x > this.linkeditor.fw || y > this.linkeditor.fh) {
+            return false;
+        }
+        if (x + w < 0 || y + h < 0) {
+            return false;
+        }
+        return true;
+
+    },
+
+    orderLinks: function (links) {
+        let arr = links;
+        if (links instanceof jQuery) {
+            arr = $(links).toArray();
+        }
+        return arr.sort(function (a, b) {
+            return parseFloat($(a).attr('fb-order')) - parseFloat($(b).attr('fb-order'));
+        });
+    },
+
+    orderLinksByPosition: function (links, way) {
+        let $this = this;
+        let doublePage = !this.linkeditor.single && !this.linkeditor.utils.isSpecialPage(this.linkeditor.currentPage);
+        let pw = this.linkeditor.pw;
+
+        return $(links).toArray().sort(function (a, b) {
+            let ca = $this.getLinkDimensions(a);
+            let cb = $this.getLinkDimensions(b);
+
+            let pa = ca.page;
+            let pb = cb.page;
+            let xa = ca.x;
+            let xb = cb.x;
+            if (doublePage) {
+                if (xa >= pw) {
+                    xa -= pw;
+                    pa++;
+                }
+                if (xb >= pw) {
+                    xb -= pw;
+                    pb++;
+                }
+                if (pa !== pb) {
+                    return pa - pb;
+                }
+            }
+
+
+            let wa = ca.width;
+            let wb = cb.width;
+
+            let xTolerance = Math.min(wa, wb) / 2;
+
+            let ha = ca.height;
+            let hb = cb.height;
+
+            let yTolerance = Math.min(ha, hb) / 2;
+
+            xa += wa / 2;
+            xb += wb / 2;
+
+            let ya = ca.y + ha / 2;
+            let yb = cb.y + hb / 2;
+
+            let xdiff = xa - xb;
+            let ydiff = ya - yb;
+
+            if (way === 'columns') {
+                return Math.abs(xdiff) > xTolerance ? xdiff : ydiff;
+            } else if (way === 'lines') {
+                return Math.abs(ydiff) > yTolerance ? ydiff : xdiff;
+            }
+        });
+    },
+
+    getLinkDimensions: function (link) {
+        if (link.left !== undefined) {
+            return {
+                x: parseFloat(link.left),
+                y: parseFloat(link.top),
+                width: parseFloat(link.width),
+                height: parseFloat(link.height),
+                page: parseInt(link.page),
+            }
+        } else {
+            return {
+                x: parseFloat($(link).attr('fb-left')),
+                y: parseFloat($(link).attr('fb-top')),
+                width: parseFloat($(link).attr('fb-width')),
+                height: parseFloat($(link).attr('fb-height')),
+                page: $(link).attr('fb-page'),
+            };
+        }
+    },
+
+    getCurrentOrderableSelection: function () {
+        return this.filterOrderableLinks(this.linkeditor.links.getCurrentSelection());
+    },
+
+    normalizeLinksOrder: function (refresh) {
+        if (refresh === undefined) {
+            refresh = true;
+        }
+        let $this = this;
+        let links = [];
+        $('#linkeditor-links .link:not(.pendingCreate)').each(function () {
+            links.push({
+                link: $(this),
+                interactive: $this.isInteractive($(this)),
+                order: parseFloat($(this).attr('fb-order'),)
+            });
+        });
+
+        links.sort(function (a, b) {
+            if (a.interactive === b.interactive) {
+                return a.order - b.order
+            }
+            return b.interactive - a.interactive;
+        });
+
+        let i = 0;
+        let wrapper = $("#linkeditor-links");
+        $(links).each(function (k, v) {
+            $(v.link).attr('fb-order', i++);
+            $(v.link).attr('fb-orderable', v.interactive ? '1' : '0');
+            $(wrapper).append($(v.link));
+        });
+
+        if (refresh) {
+            this.linkeditor.links.updateLinksData(links, ['order']);
+            this.linkeditor.hasChanged();
+        }
+        this.linkeditor.links.pageMaxOrderIndex = i;
+    },
+
+    getLinkLevel: function (link) {
+        let d = parseInt($(link).attr('fb-calc-depth'));
+        var m = 1;
+        if (d >= 30 && d < this.interactiveThreshold * 10) {
+            m = 10;
+        }
+        return Math.floor((m * d) / 10) / m;
+    },
+
+    update: function () {
+        if (this.container === undefined) {
+            return;
+        }
+        if (!this.container.hasClass('open')) {
+            return;
+        }
+        var $this = this;
+        this.container.addClass('toolbar-top').addClass('toolbar-bottom');
+        this.container.html(this.getTopToolbar() + '<div class="linkeditor-panel-wrapper"></div>');
+        let wrapper = this.container.find('.linkeditor-panel-wrapper');
+        var accessibility = [];
+        this.normalizeLinksOrder(false);
+        $(this.orderLinks(this.linkeditor.links.getLinksOfCurrentPage())).each(function () {
+            let type = $(this).attr('fb-type');
+            let dest = $(this).attr('fb-to');
+            let uid = $(this).attr('fb-uid');
+            let interactive = $this.isInteractive($(this));
+
+            if (dest === '') {
+                dest = '<em>' + TRANSLATIONS.empty + '</em>';
+            }
+            var l = '<div class="layer" fb-type="' + type + '">';
+            l += '<input name="' + uid + '" type="checkbox"> ';
+            l += '<label class="layer" data-uid="' + uid + '">';
+            l += dest;
+            if (interactive) {
+                l += '<span class="order">#' + $(this).attr('fb-order') + '</span>';
+                l += '<span class="drag">' + getSpriteIcon('linkeditor-drag-drop') + '</span>';
+            }
+            l += '</label>';
+            l += '</div>';
+
+            accessibility.push({
+                interactive: interactive,
+                zindex: parseInt($(this).attr('fb-calc-zindex')),
+                html: l,
+                order: parseInt($(this).attr('fb-order')),
+            });
+        });
+
+        accessibility.sort(function (a, b) {
+            if (a.interactive === b.interactive) {
+                return a.order - b.order
+            }
+            return b.interactive - a.interactive;
+        });
+
+        var seenLevels = {};
+        $.each(accessibility, function (k, v) {
+            let wrapperClass = 'order' + (v.interactive ? '-interactive' : '-noninteractive') + '-wrapper';
+            if (seenLevels[v.interactive] === undefined) {
+                seenLevels[v.interactive] = true;
+                wrapper.append('<h3>' + (v.interactive ? TRANSLATIONS.interactive_links : TRANSLATIONS.noninteractive_links) + '</h3><div class="' + wrapperClass + '"></div>');
+            }
+            $this.container.find('.' + wrapperClass).append(v.html);
+        });
+        this.container.append(this.getBottomToolbar());
+
+        let sortableWrapper = $('.order-interactive-wrapper').get(0);
+        try {
+            this.sortable.destroy();
+        } catch (e) {
+
+        }
+        if ($('.order-interactive-wrapper').length > 0) {
+            this.sortable = Sortable.create(sortableWrapper, {
+                handle: '.drag', onSort: function (e) {
+                    let i = 0;
+                    $(sortableWrapper).find('div.layer').each(function () {
+                        let uid = $(this).find('label.layer').data('uid');
+                        let link = $('#linkeditor-links .link[fb-uid="' + uid + '"]');
+                        $(link).attr('fb-order', i++);
+                    });
+                    $this.normalizeLinksOrder();
+                },
+            });
+        }
+
+        this.updateSelection();
+        this.linkeditor.initTooltips();
+        this.linkeditor.initIcons();
+    },
+
+    getTopToolbar: function () {
+        let res = '<div class="linkeditor-toolbar linkeditor-panel-toolbar linkeditor-panel-toolbar-top"><nav>';
+        res += '<a href="#" data-icon="order-horizontal" data-action="accessibility.reorderPageLinks" data-action-args="lines" data-tooltip="' + TRANSLATIONS.order_page_lines + '"></a>';
+        res += '<a href="#" data-icon="order-vertical"  data-action="accessibility.reorderPageLinks" data-action-args="columns"  data-tooltip="' + TRANSLATIONS.order_page_columns + '"></a>';
+        res += '<div class="separator"></div>';
+        res += '<a href="#" data-icon="order-horizontal-all" data-action="accessibility.reorderAllLinks" data-action-args="lines" data-tooltip="' + TRANSLATIONS.order_all_lines + '"></a>';
+        res += '<a href="#" data-icon="order-vertical-all"  data-action="accessibility.reorderAllLinks" data-action-args="columns" data-tooltip="' + TRANSLATIONS.order_all_columns + '"></a>';
+        res += '</nav></div>';
+        return res;
+    },
+
+    getBottomToolbar: function () {
+        let res = '<div class="linkeditor-toolbar linkeditor-panel-toolbar linkeditor-panel-toolbar-bottom"><nav>';
+        res += '<a href="#" data-icon="order-horizontal" data-action="accessibility.reorderSelection" data-action-args="lines" data-tooltip="' + TRANSLATIONS.reorder_selection_lines + '"></a>';
+        res += '<a href="#" data-icon="order-vertical"  data-action="accessibility.reorderSelection" data-action-args="columns"  data-tooltip="' + TRANSLATIONS.reorder_selection_columns + '"></a>';
+        res += '<div class="separator"></div>';
+        res += '<a href="#" data-icon="move-start" data-action="accessibility.moveSelectionOrder" data-action-args="start" data-tooltip="' + TRANSLATIONS.move_order_start + '"></a>';
+        res += '<a href="#" data-icon="move-up" data-action="accessibility.moveSelectionOrder" data-action-args="up" data-tooltip="' + TRANSLATIONS.move_order_up + '"></a>';
+        res += '<a href="#" data-icon="move-down" data-action="accessibility.moveSelectionOrder" data-action-args="down" data-tooltip="' + TRANSLATIONS.move_order_down + '"></a>';
+        res += '<a href="#" data-icon="move-end" data-action="accessibility.moveSelectionOrder" data-action-args="end" data-tooltip="' + TRANSLATIONS.move_order_end + '"></a>';
+        res += '</nav></div>';
+        return res;
+    },
+
+    updateSelection() {
+        this.linkeditor.panels.updatePanelSelection(this);
+        let l = this.getCurrentOrderableSelection().length;
+        if (l < 1) {
+            $('[data-action="accessibility.moveSelectionOrder"]').addClass('disabled');
+        } else {
+            $('[data-action="accessibility.moveSelectionOrder"]').removeClass('disabled');
+        }
+        if (l < 2) {
+            $('[data-action="accessibility.reorderSelection"]').addClass('disabled');
+        } else {
+            $('[data-action="accessibility.reorderSelection"]').removeClass('disabled');
+        }
+    },
+
+    resize: function () {
+
+    },
+}
+
+export default LinkeditorAccessibility;