]> _ Git - fluidbook-toolbox.git/commitdiff
wip #7467 @9
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 12 Jun 2025 16:00:45 +0000 (18:00 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 12 Jun 2025 16:00:45 +0000 (18:00 +0200)
resources/linkeditor-stable/js/linkeditor.accessibility.js
resources/linkeditor-stable/js/linkeditor.js
resources/linkeditor-stable/js/linkeditor.links.js
resources/linkeditor-stable/style/inc/_layers.sass
resources/linkeditor-stable/style/inc/_panels.sass
resources/linkeditor-stable/style/inc/_toolbar.sass
resources/linkeditor-stable/style/inc/_variables.sass
resources/views/fluidbook_publication/link_editor.blade.php
resources/views/fluidbook_publication/link_editor_icons.blade.php

index 1feab45f4202b01a3f2b7499015d1a532477cc70..a5beef42ca8fa978ca8672bbe3d6d0d3009b42cb 100644 (file)
@@ -1,3 +1,5 @@
+import Sortable from 'sortablejs';
+
 function LinkeditorAccessibility(linkeditor) {
     this.linkeditor = linkeditor;
 }
@@ -9,6 +11,8 @@ LinkeditorAccessibility.prototype = {
         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');
@@ -21,24 +25,51 @@ LinkeditorAccessibility.prototype = {
     },
 
     reorderSelection: function (way) {
-        let selection = this.getCurrentOrderableSelection();
-        let selectedOrder = parseInt($(selection).eq(0).attr('fb-order'));
-        selection = this.orderLinksByPosition(selection, way);
-        $(selection).each(function () {
-            $(this).attr('fb-order', selectedOrder++);
+        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();
+        console.log(selection);
         let num = selection.length;
         if (num <= 0) {
             return;
         }
         let firstSelected = $(selection).get(0);
-        let firstSelectedOrder = parseInt($(firstSelected).attr('fb-order'));
+        let firstSelectedOrder = parseFloat($(firstSelected).attr('fb-order'));
         let max = this.getOrderableLinksOnPage().length + 1;
 
         let step = 1 / (num + 1);
@@ -46,15 +77,12 @@ LinkeditorAccessibility.prototype = {
         switch (way) {
             case'start':
                 start = -num;
-                step = -1;
                 break;
             case'end':
                 start = max;
-                step = 1;
                 break;
             case'up':
                 start = firstSelectedOrder - 1 - step;
-                step *= -1;
                 break;
             case'down':
                 start = firstSelectedOrder + 1 + step;
@@ -71,10 +99,18 @@ LinkeditorAccessibility.prototype = {
         this.normalizeLinksOrder();
     },
 
-    getOrderableLinksOnPage: function () {
-        return this.filterOrderableLinks(this.linkeditor.links.getLinksOfCurrentPage());
+    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 res = [];
         $.each(links, function () {
@@ -88,20 +124,28 @@ LinkeditorAccessibility.prototype = {
     },
 
     orderLinks: function (links) {
-        return $(links).toArray().sort(function (a, b) {
-            return parseInt($(a).attr('fb-order')) - parseInt($(b).attr('fb-order'));
+        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 pa = 0;
-            let pb = 0;
-            let xa = parseFloat($(a).attr('fb-left'));
-            let xb = parseFloat($(b).attr('fb-left'));
+            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;
@@ -117,21 +161,21 @@ LinkeditorAccessibility.prototype = {
             }
 
 
-            let wa = parseFloat($(a).attr('fb-width'));
-            let wb = parseFloat($(b).attr('fb-width'));
+            let wa = ca.width;
+            let wb = cb.width;
 
             let xTolerance = Math.min(wa, wb) / 2;
 
-            let ha = parseFloat($(a).attr('fb-height'));
-            let hb = parseFloat($(b).attr('fb-height'));
+            let ha = ca.height;
+            let hb = cb.height;
 
             let yTolerance = Math.min(ha, hb) / 2;
 
             xa += wa / 2;
             xb += wb / 2;
 
-            let ya = parseFloat($(a).attr('fb-top')) + ha / 2;
-            let yb = parseFloat($(b).attr('fb-top')) + hb / 2;
+            let ya = ca.y + ha / 2;
+            let yb = cb.y + hb / 2;
 
             let xdiff = xa - xb;
             let ydiff = ya - yb;
@@ -144,6 +188,26 @@ LinkeditorAccessibility.prototype = {
         });
     },
 
+    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());
     },
@@ -156,7 +220,7 @@ LinkeditorAccessibility.prototype = {
         let links = [];
         $('#linkeditor-links .link:not(.pendingCreate)').each(function () {
             let level = $this.getLinkLevel($(this));
-            links.push({link: $(this), interactive: level >= 5, order: parseInt($(this).attr('fb-order'),)});
+            links.push({link: $(this), interactive: level >= 5, order: parseFloat($(this).attr('fb-order'),)});
         });
 
         links.sort(function (a, b) {
@@ -198,7 +262,9 @@ LinkeditorAccessibility.prototype = {
             return;
         }
         var $this = this;
-        this.container.html('');
+        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 () {
@@ -217,6 +283,7 @@ LinkeditorAccessibility.prototype = {
             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>';
@@ -238,20 +305,77 @@ LinkeditorAccessibility.prototype = {
 
         var seenLevels = {};
         $.each(accessibility, function (k, v) {
+            let wrapperClass = 'order' + (v.interactive ? '-interactive' : '-noninteractive') + '-wrapper';
             if (seenLevels[v.interactive] === undefined) {
                 seenLevels[v.interactive] = true;
-                $this.container.append('<h3>' + (v.interactive ? TRANSLATIONS.interactive_links : TRANSLATIONS.noninteractive_links) + '</h3>');
+                wrapper.append('<h3>' + (v.interactive ? TRANSLATIONS.interactive_links : TRANSLATIONS.noninteractive_links) + '</h3><div class="' + wrapperClass + '"></div>');
             }
-            $this.container.append(v.html);
+            $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 () {
index 5d01caf46aa65625cbf69961951d35982384cd67..b8aed2e401c70df4732830be9d26008e3d66ea3a 100644 (file)
@@ -3,6 +3,7 @@ import '../../../vendor/cubist/cms-back/src/public/bunchmultiple/bunchmultiplemo
 import Noty from "noty";
 import GrahamScan from "@lucio/graham-scan";
 
+
 import LinkeditorLinks from './linkeditor.links';
 import LinkeditorLoader from './linkeditor.loader';
 import LinkeditorResize from './linkeditor.resize';
index f222a5dbbd92ac5d5fb77e9e9207908b8e8217e0..98e6b05c5a0c8430cc87b4cf5caaed52a3d96cb5 100644 (file)
@@ -976,12 +976,10 @@ LinkeditorLinks.prototype = {
         }
         let change = false;
         if (link.order === undefined) {
-            console.log('link.order is undefined', link);
             link.order = ++this.pageMaxOrderIndex;
         } else {
             this.pageMaxOrderIndex = Math.max(this.pageMaxOrderIndex, link.order);
         }
-        console.log(link.order);
         if (link.uid === undefined) {
             link.uid = this.linkeditor.utils.generateUID();
             change = true;
@@ -991,7 +989,6 @@ LinkeditorLinks.prototype = {
         if (change) {
             LINKS[link.uid] = link;
         }
-
         link.origuid = link.uid;
 
         let $this = this;
@@ -1255,7 +1252,6 @@ LinkeditorLinks.prototype = {
                 uid = $(this).attr('fb-uid');
                 l = this;
             }
-            console.log(uid, props);
             for (let i = 0; i < props.length; i++) {
                 let prop = props[i];
                 LINKS[uid][prop] = $(l).attr('fb-' + prop);
index 92495968b27bfa397f2c4efacdb9aff4be5b03ef..a7b1b3cd3a4a34499504fd0b89a76ddd7739680d 100644 (file)
     .layer
         position: relative
 
+        &.sortable-ghost
+            opacity: 0.4
+
+        &.sortable-drag
+            visibility: hidden
+
         input
             color: #fff
             position: absolute
                 padding: 2px
                 font-family: "Courier New", Courier, monospace
 
+                &.drag
+                    right: 5px
+                    width: 16px
+                    cursor: grab
+
         &[data-locked="1"]
             label
                 pointer-events: none
index 5509acafd27eca9ab0bccca99748a132b8bb91a5..02552f13d114e6953768237d56464833393de778 100644 (file)
@@ -6,7 +6,7 @@
     @include dark-theme
         background-color: #444
 
-    nav
+    nav
         position: absolute
         left: 0
         width: $sidebar-icons-width
@@ -69,6 +69,7 @@
 
         .linkeditor-panel
             display: none
+
             &.open
                 display: block
 
         .linkeditor-panel-side
             margin-left: $sidebar-handle-width
             margin-right: $sidebar-icons-width
+
+.linkeditor-panel-wrapper
+    position: relative
+    max-height: 100%
+    overflow: scroll
+
+.linkeditor-panel
+    position: relative
+    height: 100%
+
+    &.toolbar-top
+        .linkeditor-panel-wrapper
+            top: 40px
+            max-height: calc(100% - 40px)
+
+    &.toolbar-bottom
+        .linkeditor-panel-wrapper
+            bottom: 40px
+            max-height: calc(100% - 40px)
+
+    &.toolbar-bottom.toolbar-top
+        .linkeditor-panel-wrapper
+            max-height: calc(100% - 80px)
+
+.linkeditor-panel-toolbar
+    position: fixed
+    left: 0
+    width: 100%
+    height: 40px
+
+    .linkeditor-panel-toolbar-top
+        top: 0
+
+    .linkeditor-panel-toolbar-bottom
+        bottom: 0
index e017ce1ec831aad021111b49e81686797f04ebb7..edf1758108f07234cd25e89c14e427ead850d721 100644 (file)
@@ -1,8 +1,8 @@
-#linkeditor-toolbar
-    background-color: #dbdddf
+.linkeditor-toolbar
+    background-color: $toolbar-background-color
 
     @include dark-theme
-        background-color: #444
+        background-color: $toolbar-background-color-dark
 
     color: $toolbar-color
     @include dark-theme
@@ -11,7 +11,6 @@
     height: $toolbar-height
     padding: 5px
 
-
     nav
         padding: 2px
         display: inline-block
             top: 1px
             height: 18px
             width: auto
+
+    &.linkeditor-panel-toolbar
+        position: absolute
+        left: 0
+        background-color: darken($toolbar-background-color, 5%)
+
+        nav
+            width: 100%
+
+        @include dark-theme
+            background-color: lighten($toolbar-background-color-dark, 5%)
+
+        &.linkeditor-panel-toolbar-top
+            top: 0
+
+        &.linkeditor-panel-toolbar-bottom
+            bottom: 0
index 2246963c8073503f1ed978a5e56c988a72b7e69d..d5be2e69d9899a2666162ca5846466a37a2475c3 100644 (file)
@@ -9,6 +9,9 @@ $toolbar-height: 40px
 $toolbar-color: #5d5d5d
 $toolbar-color-dark: #bbb
 
+$toolbar-background-color: #dbdddf
+$toolbar-background-color-dark: #444
+
 $toolbar-color-disabled: #bbb
 $toolbar-color-disabled-dark: #666
 
index f19b666fb1f1dac0a0a9e8c3e8b8026c25f6c5e6..f604809062bb81e85ea76ffcc51b6de39c5e21d6 100644 (file)
         'reorder_selection'=>__('Réordonner la sélection'),
         'reorder_lines'=>__('Par lignes'),
         'reorder_columns'=>__('Par colonnes'),
+        'reorder_selection_lines'=>__('Réordonner la sélection par lignes'),
+        'reorder_selection_columns'=>__('Réordonner la sélection par colonnes'),
+        'move_order_start'=>__('Déplacer la sélection au début'),
+        'move_order_up'=>__('Remonter la sélection'),
+        'move_order_down'=>__('Descendre la sélection'),
+        'move_order_end'=>__('Déplacer la sélection à la fin'),
         'move_up'=>__('Avant'),
         'move_down'=>__('Après'),
         'move_beginning'=>__('Au début'),
         'move_end'=>__('À la fin'),
+        'order_all_lines'=>__('Réordonner les liens de toute la publication par lignes'),
+        'order_all_columns'=>__('Réordonner les liens de toute la publication par colonnes'),
+        'order_page_lines'=>__('Réordonner les liens de la page par lignes'),
+        'order_page_columns'=>__('Réordonner les liens de la page par colonnes'),
         'select_all'=>__('Tout sélectionner'),
         'error_open_image_link'=>__('Impossible d\'ajouter des liens au contenu de ce lien'),
         'empty'=>__('Vide'),
             <div class="handle"></div>
         </aside>
         <div id="linkeditor-main">
-            <div draggable="false" id="linkeditor-toolbar">
+            <div draggable="false" id="linkeditor-toolbar" class="linkeditor-toolbar">
                 <nav id="linkeditor-toolbar-left">
                     <a href="#" data-icon="save" data-action="save.save" data-tooltip="{{__('Sauvegarder')}} (Ctrl+S)"
                        data-key="ctrl+s"></a>
         var DEPTH = @json($depths);
     </script>
     <script
-        src="/packages/linkeditor{{$scriptVersion}}/js/linkeditor.js?v={{filemtime(public_path('packages/linkeditor'.$scriptVersion.'/js/linkeditor.js'))}}"></script>
+            src="/packages/linkeditor{{$scriptVersion}}/js/linkeditor.js?v={{filemtime(public_path('packages/linkeditor'.$scriptVersion.'/js/linkeditor.js'))}}"></script>
 @endpush
 @push('linkeditor_styles')
     <script>window._skipAutoTriggers = true;</script>
index 658d6583d97eaa4e2b157494ddfe6fa443bc3f99..df83965ec4e493698794a9d7c50f49ffe6d801a5 100644 (file)
@@ -79,6 +79,14 @@ Edit here : https://toolbox.fluidbook.com/tool-sprite/3/edit
             </defs>
             <path class="st0" d="M19.7,9.6H4c-.2,0-.5-.1-.6-.4,0-.2,0-.5.1-.7L11.5.7c.2-.2.6-.2.9,0l7.9,7.9c.2.2.2.4.1.7,0,.2-.3.4-.6.4ZM5.5,8.4h12.8l-6.4-6.4-6.4,6.4Z"/>
             <path class="st0" d="M11.9,23.7c-.2,0-.3,0-.4-.2l-7.9-7.9c-.2-.2-.2-.4-.1-.7,0-.2.3-.4.6-.4h15.7c.2,0,.5.1.6.4,0,.2,0,.5-.1.7l-7.9,7.9c-.1.1-.3.2-.4.2ZM5.5,15.9l6.4,6.4,6.4-6.4H5.5Z"/>
+        </symbol><symbol id="linkeditor-move-start" viewBox="0 0 18 18">
+            <path fill="currentColor" d="M14.7,4.4c0,0,0-.1-.1-.2,0,0-.9-.9-1.9-1.9-2.1-2.1-1.9-1.9-2.2-1.9s-.2,0-.2,0c0,0-.5.4-2,1.9-1.1,1.1-1.9,1.9-1.9,2-.2.3,0,.7.3.9,0,0,.1,0,.2,0,.3,0,.1.1,2-1.7l1.7-1.7,1.6,1.6c1,1,1.7,1.7,1.7,1.7,0,0,.1,0,.2,0,.3,0,.5,0,.7-.3,0-.1,0-.3,0-.5M14.7,9.3c0,0,0-.1,0-.2,0,0-.9-.9-1.9-1.9-1.4-1.4-1.9-1.9-1.9-1.9-.1,0-.2,0-.4,0-.2,0-.1,0-2.2,2-2.1,2.1-2,2-2,2.2,0,.1,0,.2,0,.2.1.2.3.4.6.4.1,0,.2,0,.2,0,.1,0,.2-.1,1.4-1.4l1.3-1.3v2.4c0,2,0,2.5,0,2.6-.2,1.4-1.1,2.7-2.3,3.3-.4.2-.8.3-1.2.4-.2,0-.3,0-1.7,0-1,0-1.5,0-1.6,0-.2,0-.3.2-.4.4,0,.1,0,.3,0,.5,0,.1.2.2.3.3h0s1.5,0,1.5,0c1.7,0,1.7,0,2.2-.1.8-.2,1.6-.5,2.3-1.1.2-.2.7-.6.8-.8.5-.7.9-1.5,1.1-2.3.1-.5.1-.4.1-3.2v-2.5s1.3,1.3,1.3,1.3c1.4,1.4,1.4,1.4,1.6,1.4.2,0,.4,0,.5-.2.1-.1.2-.2.2-.4,0-.1,0-.2,0-.2"/>
+        </symbol><symbol id="linkeditor-move-end" viewBox="0 0 18 18">
+            <path fill="currentColor" d="M14.7,13.4c0-.1,0-.3,0-.5-.1-.3-.4-.4-.7-.3,0,0-.1,0-.2,0,0,0-.7.7-1.7,1.7l-1.6,1.6-1.7-1.7c-1.9-1.9-1.7-1.7-2-1.7-.1,0-.2,0-.2,0-.3.1-.5.5-.3.9,0,0,.9.9,1.9,2,1.5,1.5,1.9,1.9,2,1.9,0,0,.1,0,.2,0,.3,0,.1.2,2.2-1.9,1-1,1.9-1.9,1.9-1.9,0,0,0-.1.1-.2M14.7,8.5c0,0,0-.1,0-.2,0-.2,0-.3-.2-.4-.1-.2-.3-.2-.5-.2-.2,0-.2,0-1.6,1.4l-1.3,1.3v-2.5c0-2.8,0-2.6-.1-3.2-.2-.8-.6-1.7-1.1-2.3-.2-.2-.6-.7-.8-.8-.7-.5-1.5-.9-2.3-1.1-.5-.1-.5-.1-2.2-.1h-1.6c-.1.1-.2.2-.3.3,0,.1,0,.3,0,.5,0,.2.2.3.4.4,0,0,.6,0,1.6,0,1.4,0,1.5,0,1.7,0,.4,0,.8.2,1.2.4,1.3.6,2.2,1.9,2.3,3.3,0,.2,0,.7,0,2.6v2.4s-1.3-1.3-1.3-1.3c-1.3-1.2-1.3-1.3-1.4-1.4,0,0-.1,0-.2,0-.3,0-.5.1-.6.4,0,0,0,.1,0,.2,0,.3-.2.1,2,2.2,2,2,2,2,2.2,2,.1,0,.3,0,.4,0,0,0,.5-.5,1.9-1.9,1-1,1.9-1.9,1.9-1.9,0,0,0-.1,0-.2"/>
+        </symbol><symbol id="linkeditor-move-down" viewBox="0 0 18 18">
+            <path fill="currentColor" d="M3.3.5c-.2,0-.3.2-.4.4,0,.1,0,.3,0,.5,0,.1.2.3.3.3h0s1.5,0,1.5,0c.8,0,1.5,0,1.6,0,0,0,.1,0,.2,0,1.1.2,2.1.9,2.8,1.8.3.4.5.9.6,1.4,0,.4,0,.2,0,5.5v4.9l-1.3-1.3c-.9-.9-1.4-1.3-1.4-1.4-.3-.2-.7,0-.8.3,0,0,0,.1,0,.2,0,.3-.1.1,2,2.3,1.6,1.6,1.9,1.9,2,1.9,0,0,.1,0,.2,0,.3,0,.1.2,2.3-2,2.1-2.1,2-2,2-2.3s0-.2,0-.3c-.1-.3-.5-.4-.8-.3,0,0-.4.4-1.4,1.4l-1.3,1.3v-5c0-5.5,0-5-.1-5.6-.2-.8-.6-1.6-1.1-2.3-.2-.2-.6-.7-.8-.8-.7-.6-1.6-1-2.4-1.1-.4,0-.4,0-2,0-1.4,0-1.5,0-1.6,0"/>
+        </symbol><symbol id="linkeditor-move-up" viewBox="0 0 18 18">
+            <path fill="currentColor" d="M3.3,17.5c0,0,.2,0,1.6,0,1.6,0,1.6,0,2,0,.9-.2,1.7-.6,2.4-1.1.2-.2.7-.6.8-.8.5-.7.9-1.5,1.1-2.3.1-.5.1,0,.1-5.6V2.6s1.3,1.3,1.3,1.3c1,1,1.4,1.3,1.4,1.4.3.1.7,0,.8-.3,0,0,0-.1,0-.3,0-.3.2-.1-2-2.3-2.1-2.1-2-2-2.3-2s-.2,0-.2,0c0,0-.4.4-2,1.9-2.2,2.2-2,2-2,2.3,0,.1,0,.2,0,.2.1.3.5.4.8.3,0,0,.5-.5,1.4-1.4l1.3-1.3v4.9c0,5.3,0,5.1,0,5.5-.1.5-.3,1-.6,1.4-.6,1-1.6,1.6-2.8,1.8,0,0-.2,0-.2,0,0,0-.7,0-1.6,0h-1.5s0,0,0,0c-.1,0-.3.2-.3.3,0,.1,0,.3,0,.5,0,.2.2.3.4.4"/>
         </symbol></svg></div>
 <script>
     function getSpriteIcon(icon, attrs, dimensions) {