";
- }
-
}
diff --git a/conf/default.php b/conf/default.php
index cc1cbe7a..efeafc59 100644
--- a/conf/default.php
+++ b/conf/default.php
@@ -2,6 +2,7 @@
$conf['allowrename'] = '@user';
$conf['minor'] = 1;
+$conf['dual'] = 1;
$conf['autoskip'] = 0;
$conf['autorewrite'] = 1;
$conf['pagetools_integration'] = 1;
diff --git a/conf/metadata.php b/conf/metadata.php
index 35e14763..3323029b 100644
--- a/conf/metadata.php
+++ b/conf/metadata.php
@@ -2,6 +2,7 @@
$meta['allowrename'] = array('string');
$meta['minor'] = array('onoff');
+$meta['dual'] = array('onoff');
$meta['autoskip'] = array('onoff');
$meta['autorewrite'] = array('onoff');
$meta['pagetools_integration'] = array('onoff');
diff --git a/images/folder-home.svg b/images/folder-home.svg
new file mode 100644
index 00000000..e88d3e52
--- /dev/null
+++ b/images/folder-home.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lang/cs/lang.php b/lang/cs/lang.php
index 51df5b6e..70616bb1 100644
--- a/lang/cs/lang.php
+++ b/lang/cs/lang.php
@@ -64,7 +64,6 @@
$lang['js']['complete'] = 'Přesun byl dokončen.';
$lang['js']['renameitem'] = 'Přejmenovat tuto položku';
$lang['js']['add'] = 'Vytvořit nový jmenný prostor';
-$lang['js']['duplicate'] = 'Lituji, ale \'%s\' již existuje ve jmenném prosoru.';
$lang['js']['moveButton'] = 'Přesunout soubor';
$lang['js']['dialogIntro'] = 'Zadejte nový cíl souboru. Můžete změnit jmenný prostor, ale ne příponu souboru.';
$lang['root'] = '[Kořen]';
diff --git a/lang/da/lang.php b/lang/da/lang.php
index 73cf2939..f9dd1f46 100644
--- a/lang/da/lang.php
+++ b/lang/da/lang.php
@@ -59,7 +59,6 @@
$lang['js']['complete'] = 'Flytningsoperation færdig.';
$lang['js']['renameitem'] = 'Omdøb denne';
$lang['js']['add'] = 'Opret et nyt navnerum';
-$lang['js']['duplicate'] = 'Beklager, "%s" findes allerede i dette navnerum.';
$lang['root'] = '[Rod navnerum]';
$lang['noscript'] = 'Denne handling kræver JavaScript';
$lang['moveinprogress'] = 'Der er i øjeblikket en anden flytningsoperation i gang, du kan ikke anvende dette værktøj på dette tidspunkt.';
diff --git a/lang/de-informal/lang.php b/lang/de-informal/lang.php
index 2fb0ef3f..f04aa0ce 100644
--- a/lang/de-informal/lang.php
+++ b/lang/de-informal/lang.php
@@ -61,7 +61,7 @@
$lang['js']['complete'] = 'Verschieben abgeschlossen.';
$lang['js']['renameitem'] = 'Dieses Element umbenennen';
$lang['js']['add'] = 'Neuen Namensraum erstellen';
-$lang['js']['duplicate'] = 'Entschuldigung, "%s" existiert in diesem Namensraum bereits. ';
+$lang['js']['duplicate'] = 'Entschuldigung, "%s" existiert bereits.';
$lang['root'] = '[Oberster Namensraum]';
$lang['noscript'] = 'Dieses Feature benötigt JavaScript.';
$lang['moveinprogress'] = 'Eine andere Verschiebeoperation läuft momentan, du kannst dieses Tool gerade nicht benutzen.';
diff --git a/lang/de/lang.php b/lang/de/lang.php
index e6f64799..ccf42f01 100644
--- a/lang/de/lang.php
+++ b/lang/de/lang.php
@@ -63,7 +63,7 @@
$lang['js']['complete'] = 'Verschieben abgeschlossen.';
$lang['js']['renameitem'] = 'Dieses Element umbenennen';
$lang['js']['add'] = 'Neuen Namensraum erstellen';
-$lang['js']['duplicate'] = 'Entschuldigung, "%s" existiert in diesem Namensraum bereits. ';
+$lang['js']['duplicate'] = 'Entschuldigung, "%s" existiert bereits.';
$lang['root'] = '[Oberster Namensraum]';
$lang['noscript'] = 'Dieses Feature benötigt JavaScript.';
$lang['moveinprogress'] = 'Eine andere Verschiebeoperation läuft momentan, Sie können dieses Tool gerade nicht benutzen.';
diff --git a/lang/el/lang.php b/lang/el/lang.php
index 739afc18..aae3c209 100644
--- a/lang/el/lang.php
+++ b/lang/el/lang.php
@@ -59,7 +59,6 @@
$lang['js']['complete'] = 'Η διαδικασία της μετακίνησης ολοκληρώθηκε';
$lang['js']['renameitem'] = 'Μετονομάστε αυτό το τεμάχιο';
$lang['js']['add'] = 'Δημιουργήστε ένα νέο χώρο ονόματος';
-$lang['js']['duplicate'] = 'Λυπάμαι, το "%s" υπάρχει ήδη σε αυτό το χώρο ονόματος';
$lang['root'] = '{Βασικός χώρος ονόματος}';
$lang['noscript'] = 'Αυτό το χαρακτηριστικό χρειάζεται JavaScript';
$lang['moveinprogress'] = 'Υπάρχει μια άλλη διαδικασία μετακίνησης σε εξέλιξη τώρα, δεν μπορείτε να χρησιμοποιήστε αυτό το εργαλείο.';
diff --git a/lang/en/lang.php b/lang/en/lang.php
index 48a81d62..c020a0ee 100644
--- a/lang/en/lang.php
+++ b/lang/en/lang.php
@@ -72,6 +72,7 @@
$lang['js']['rename'] = 'Rename';
$lang['js']['cancel'] = 'Cancel';
$lang['js']['newname'] = 'New name:';
+$lang['js']['rename_media'] = 'Move referenced media files along with the page';
$lang['js']['inprogress'] = 'renaming page and adjusting links...';
$lang['js']['complete'] = 'Move operation finished.';
@@ -79,9 +80,15 @@
$lang['root'] = '[Root namespace]';
$lang['noscript'] = 'This feature requires JavaScript';
$lang['moveinprogress'] = 'There is another move operation in progress currently, you can\'t use this tool right now.';
+$lang['dual0'] = 'Pages and Media combined';
+$lang['dual1'] = 'Pages and Media separated';
$lang['js']['renameitem'] = 'Rename this item';
$lang['js']['add'] = 'Create a new namespace';
-$lang['js']['duplicate'] = 'Sorry, "%s" already exists in this namespace.';
+$lang['js']['duplicate'] = 'Sorry, item "%s" already exists.';
+
+$lang['js']['select'] = 'Select for moving';
+$lang['js']['extchange'] = 'You can not change the extension of a media file';
+
// Media Manager
$lang['js']['moveButton'] = 'Move file';
diff --git a/lang/en/settings.php b/lang/en/settings.php
index 659f2e1a..2aea7ccb 100644
--- a/lang/en/settings.php
+++ b/lang/en/settings.php
@@ -2,6 +2,7 @@
$lang['allowrename'] = 'Allow renaming of pages and media to these groups and users (comma separated).';
$lang['minor'] = 'Mark link adjustments as minor? Minor changes will not be listed in RSS feeds and subscription mails.';
+$lang['dual'] = 'Should pages and media files be shown separatly? If disabled, their namespaces are shown as if they are one tree.';
$lang['autoskip'] = 'Enable automatic skipping of errors in namespace moves by default.';
$lang['autorewrite'] = 'Enable automatic link rewriting after namespace moves by default.';
$lang['pagetools_integration'] = 'Add renaming button to pagetools';
diff --git a/lang/en/tree.txt b/lang/en/tree.txt
index 621abf87..e704c4b1 100644
--- a/lang/en/tree.txt
+++ b/lang/en/tree.txt
@@ -2,6 +2,7 @@
This interface allows you to rearrange your wiki's namespaces, pages and media files via Drag'n'Drop.
-In order to move many namespaces, pages or media files to the same destination, you can use the checkboxes as follows:
- * check the namespaces, pages or media files you want to move;
- * move one of the checked items to the desired destination, all selected items will be moved to this destination.
+Click namespace names to open them, click the icons to select items. All selected items will be moved together. Click the names to rename individual items. Use the edit icon to rename a namespace, page or file.
+
+Click the "Start" button at the bottom to start the move process. You'll have the chance to review all changes resulting from the move before they are applied.
+
diff --git a/lang/es/lang.php b/lang/es/lang.php
index 0c09498f..67474067 100644
--- a/lang/es/lang.php
+++ b/lang/es/lang.php
@@ -60,7 +60,6 @@
$lang['js']['complete'] = 'La operación de mover ha finalizado.';
$lang['js']['renameitem'] = 'Renombrar este elemento';
$lang['js']['add'] = 'Crear un nuevo espacio de nombres';
-$lang['js']['duplicate'] = 'Lo sentimos, "%s" ya existe en este espacio de nombres.';
$lang['root'] = '[Espacio de nombres raíz]';
$lang['noscript'] = 'Esta función requiere JavaScript';
$lang['moveinprogress'] = 'Hay otra operación de mover actualmente en curso, no se puede usar esta herramienta ahora mismo.';
diff --git a/lang/fr/lang.php b/lang/fr/lang.php
index 8e9242a2..bfe3c1a9 100644
--- a/lang/fr/lang.php
+++ b/lang/fr/lang.php
@@ -63,7 +63,6 @@
$lang['js']['complete'] = 'Déplacement effectué.';
$lang['js']['renameitem'] = 'Renommer cet élément';
$lang['js']['add'] = 'Créer une nouvelle catégorie';
-$lang['js']['duplicate'] = 'Désolé, "%s" existe dans cette catégorie.';
$lang['js']['moveButton'] = 'Déplacer le fichier.';
$lang['js']['dialogIntro'] = 'Entrez le nouvel emplacement du fichier. Vous pouvez changer la catégorie, mais pas l\'extension.';
$lang['root'] = '[Catégorie racine]';
diff --git a/lang/hr/lang.php b/lang/hr/lang.php
index ad9f222c..5be84cb3 100644
--- a/lang/hr/lang.php
+++ b/lang/hr/lang.php
@@ -59,7 +59,6 @@
$lang['js']['complete'] = 'Operacija premještanja završila.';
$lang['js']['renameitem'] = 'Preimenuj ovu stavku';
$lang['js']['add'] = 'Kreiraj novi imenski prostor';
-$lang['js']['duplicate'] = 'Isprika ali "%s" već postoji u ovom imenskom prostoru';
$lang['root'] = '[Korijen imenskog prostora]';
$lang['noscript'] = 'Ova osobina zahtijeva JavaScript';
$lang['moveinprogress'] = 'Trenutno je druga operacija premještanja u tijeku, zasada ne možete koristiti ovaj alat.';
diff --git a/lang/id/lang.php b/lang/id/lang.php
index 55a9df30..40f1a533 100644
--- a/lang/id/lang.php
+++ b/lang/id/lang.php
@@ -56,7 +56,6 @@
$lang['js']['complete'] = 'Pemindahan selesai.';
$lang['js']['renameitem'] = 'Ubah nama item ini';
$lang['js']['add'] = 'Buat ruangnama baru';
-$lang['js']['duplicate'] = 'Maaf, %s telah ada di ruangnama ini.';
$lang['root'] = '[Ruang nama root]';
$lang['noscript'] = 'Fitur ini membutuhkan JavaScript';
$lang['moveinprogress'] = 'Ada operasi pemindahan lain yang belum selesai, Anda tidak dapat menggunakan alat ini sekarang.';
diff --git a/lang/ja/lang.php b/lang/ja/lang.php
index 8b3adf86..791dbd88 100644
--- a/lang/ja/lang.php
+++ b/lang/ja/lang.php
@@ -59,7 +59,6 @@
$lang['js']['complete'] = '名称変更操作が完了しました。';
$lang['js']['renameitem'] = 'この項目を名称変更します。';
$lang['js']['add'] = '新しい名前空間の作成';
-$lang['js']['duplicate'] = '"%s" はこの名前空間内に既に存在します。';
$lang['root'] = '[ルート名前空間]';
$lang['noscript'] = 'この機能には JavaScriptが必要です。';
$lang['moveinprogress'] = '別の移動操作を処理中なので、今はこのツールを使用できません。';
diff --git a/lang/ko/lang.php b/lang/ko/lang.php
index 3396dcdf..00e2f800 100644
--- a/lang/ko/lang.php
+++ b/lang/ko/lang.php
@@ -61,7 +61,6 @@
$lang['js']['complete'] = '이동 작업이 완료되었습니다.';
$lang['js']['renameitem'] = '이 항목 이름 바꾸기';
$lang['js']['add'] = '새 이름공간 만들기';
-$lang['js']['duplicate'] = '죄송하지만, "%s" 문서는 이미 이 이름공간에 존재합니다.';
$lang['root'] = '[루트 이름공간]';
$lang['noscript'] = '이 기능은 자바스크립트가 필요합니다';
$lang['moveinprogress'] = '현재 진행 중인 다른 이동 작업이 있으므로, 지금 바로 이 도구를 사용할 수 없습니다.';
diff --git a/lang/nl/lang.php b/lang/nl/lang.php
index 8a11125e..4fef32e0 100644
--- a/lang/nl/lang.php
+++ b/lang/nl/lang.php
@@ -62,7 +62,6 @@
$lang['js']['complete'] = 'Verplaatsing compleet.';
$lang['js']['renameitem'] = 'Hernoem dit item';
$lang['js']['add'] = 'Maak een nieuwe namespace';
-$lang['js']['duplicate'] = 'Sorry, "%s" bestaat al in deze namespace.';
$lang['root'] = '[Hoofdnamespace]';
$lang['noscript'] = 'Deze mogelijkheid vereist Javascript';
$lang['moveinprogress'] = 'Er is een andere verplaatsingsactie gaande, gebruik van deze tool is momenteel niet mogelijk.';
diff --git a/lang/no/lang.php b/lang/no/lang.php
index 4fd72358..d645494c 100644
--- a/lang/no/lang.php
+++ b/lang/no/lang.php
@@ -63,7 +63,6 @@
$lang['js']['complete'] = 'Flytting avsluttet';
$lang['js']['renameitem'] = 'Endre navn ';
$lang['js']['add'] = 'Lag et nytt navnerom';
-$lang['js']['duplicate'] = 'Beklager, "%s" finnes allerede i dette navnerommet.';
$lang['root'] = '[Rot navnerom]';
$lang['noscript'] = 'Denne funksjonen krever Javascript';
$lang['moveinprogress'] = 'En annen flyttingsjobb pågår for øyeblikket så denne funksjonen kan ikke brukes akkurat nå. ';
diff --git a/lang/pt-br/lang.php b/lang/pt-br/lang.php
index f68c2dd4..5338887f 100644
--- a/lang/pt-br/lang.php
+++ b/lang/pt-br/lang.php
@@ -60,7 +60,6 @@
$lang['js']['complete'] = 'Operação de movimentação concluída.';
$lang['js']['renameitem'] = 'Renomear este item';
$lang['js']['add'] = 'Criar um novo domínio';
-$lang['js']['duplicate'] = 'Desculpe, "%s" já existe neste domínio.';
$lang['root'] = '[Domínio raiz]';
$lang['noscript'] = 'Este recurso requer JavaScript';
$lang['moveinprogress'] = 'Há outra operação de movimentação em andamento no momento, você não pode usar esta ferramenta agora.';
diff --git a/lang/ru/lang.php b/lang/ru/lang.php
index 508ca584..8830d24a 100644
--- a/lang/ru/lang.php
+++ b/lang/ru/lang.php
@@ -1,7 +1,7 @@
+ * @author Viktor Kristian
*/
$lang['menu'] = 'Presun/premenovanie stránky';
@@ -69,4 +69,4 @@
$lang['moveinprogress'] = 'Práve prebieha iná operácia presunu, tento nástroj momentálne nemôžete použiť.';
$lang['js']['renameitem'] = 'Premenovať túto položku';
$lang['js']['add'] = 'Vytvoriť nový menný priestor';
-$lang['js']['duplicate'] = 'Ľutujeme, \'%s\' už v tomto mennom priestore existuje.';
+
diff --git a/lang/sv/lang.php b/lang/sv/lang.php
index 281223ff..d599a279 100644
--- a/lang/sv/lang.php
+++ b/lang/sv/lang.php
@@ -46,6 +46,5 @@
$lang['js']['complete'] = 'Flytt/Namnbyte avklarat.';
$lang['js']['renameitem'] = 'Ändra namn på denna post';
$lang['js']['add'] = 'Skapa en ny namnrymd';
-$lang['js']['duplicate'] = 'Tyvärr, "%s" existerar redan i denna namnrymd.';
$lang['root'] = '[Rotnamnrymd]';
$lang['noscript'] = 'Denna funktion kräver JavaScript.';
diff --git a/lang/vi/lang.php b/lang/vi/lang.php
index ad97f750..9984b404 100644
--- a/lang/vi/lang.php
+++ b/lang/vi/lang.php
@@ -59,7 +59,6 @@
$lang['js']['complete'] = 'Đã hoàn thành hoạt động di chuyển.';
$lang['js']['renameitem'] = 'Đổi tên mục này';
$lang['js']['add'] = 'Tạo không gian tên mới';
-$lang['js']['duplicate'] = 'Xin lỗi, đã tồn tại "%s" trong không gian tên này.';
$lang['root'] = '[Không gian tên Gốc]';
$lang['noscript'] = 'Tính năng này yêu cầu JavaScript';
$lang['moveinprogress'] = 'Hiện tại đang diễn ra một hoạt động di chuyển khác, bạn không thể sử dụng công cụ này ngay bây giờ.';
diff --git a/lang/zh-tw/lang.php b/lang/zh-tw/lang.php
index ce4a6603..58a02ec4 100644
--- a/lang/zh-tw/lang.php
+++ b/lang/zh-tw/lang.php
@@ -60,7 +60,6 @@
$lang['js']['complete'] = '移動操作完畢。';
$lang['js']['renameitem'] = '重新命名該項';
$lang['js']['add'] = '產生新的目錄';
-$lang['js']['duplicate'] = '抱歉,"%s"在該目錄已存在';
$lang['root'] = '[根目錄]';
$lang['noscript'] = '此功能需要JavaScript';
$lang['moveinprogress'] = '另一個移動操作正在進行,您現在無法使用該工具';
diff --git a/lang/zh/lang.php b/lang/zh/lang.php
index 2752eb16..2502c28f 100644
--- a/lang/zh/lang.php
+++ b/lang/zh/lang.php
@@ -65,7 +65,6 @@
$lang['js']['complete'] = '移动操作完毕。';
$lang['js']['renameitem'] = '重命名该项';
$lang['js']['add'] = '创建一个新的名称空间';
-$lang['js']['duplicate'] = '抱歉,"%s"在该目录已存在';
$lang['js']['moveButton'] = '文件移动';
$lang['js']['dialogIntro'] = '输入新文件的目标位置。您可以更改命名空间,但无法更改文件扩展名';
$lang['root'] = '[跟目录]';
diff --git a/script.js b/script.js
index 9fba837b..9c4604a3 100644
--- a/script.js
+++ b/script.js
@@ -7,9 +7,19 @@
/* DOKUWIKI:include_once script/json2.js */
/* DOKUWIKI:include script/MoveMediaManager.js */
-jQuery(function() {
+jQuery(function () {
/* DOKUWIKI:include script/form.js */
/* DOKUWIKI:include script/progress.js */
- /* DOKUWIKI:include script/tree.js */
/* DOKUWIKI:include script/rename.js */
+
+
+ // lazy load the tree manager
+ const $tree = jQuery('#plugin_move__tree');
+ if ($tree.length) {
+ jQuery.getScript(
+ DOKU_BASE + 'lib/plugins/move/script/tree.js',
+ () => new PluginMoveTree($tree.get(0))
+ );
+ }
+
});
diff --git a/script/rename.js b/script/rename.js
index 03a8e5f5..f5f49744 100644
--- a/script/rename.js
+++ b/script/rename.js
@@ -14,6 +14,9 @@
'' +
+ '' +
'' +
''
);
@@ -25,6 +28,9 @@
const renameFN = function () {
const newid = $dialog.find('input[name=id]').val();
if (!newid) return false;
+ if (newid === JSINFO.id) return false;
+
+ const doMedia = $dialog.find('input[name=media]').is(':checked');
// remove buttons and show throbber
$dialog.html(
@@ -39,12 +45,13 @@
{
call: 'plugin_move_rename',
id: JSINFO.id,
- newid: newid
+ newid: newid,
+ media: doMedia ? 1 : 0,
},
// redirect or display error
function (result) {
if (result.error) {
- $dialog.html(result.error.msg);
+ $dialog.html(result.error);
} else {
window.location.href = result.redirect_url;
}
diff --git a/script/tree.js b/script/tree.js
index 6dcd650b..bd309e88 100644
--- a/script/tree.js
+++ b/script/tree.js
@@ -1,260 +1,672 @@
/**
- * Script for the tree management interface
+ * The Tree Move Manager
+ *
+ * This script handles the move tree and all its interactions.
+ *
+ * The script supports combined and separate page/media trees. Items have their orignal ID in data-orig and their
+ * current ID in data-id.
+ *
+ * This is pure vanilla JavaScript without any dependencies to jQuery. It is lazy loaded by the main script.
*/
+class PluginMoveTree {
+ #ENDPOINT = DOKU_BASE + 'lib/exe/ajax.php?call=plugin_move_tree';
-var $GUI = jQuery('#plugin_move__tree');
+ icons = {
+ 'close': 'M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z',
+ 'open': 'M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z',
+ 'page': 'M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z',
+ 'media': 'M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z',
+ 'rename': 'M18,4V3A1,1 0 0,0 17,2H5A1,1 0 0,0 4,3V7A1,1 0 0,0 5,8H17A1,1 0 0,0 18,7V6H19V10H9V21A1,1 0 0,0 10,22H12A1,1 0 0,0 13,21V12H21V4H18Z',
+ 'drag': 'M4 4V22H20V24H4C2.9 24 2 23.1 2 22V4H4M15 7H20.5L15 1.5V7M8 0H16L22 6V18C22 19.11 21.11 20 20 20H8C6.89 20 6 19.1 6 18V2C6 .89 6.89 0 8 0M17 16V14H8V16H17M20 12V10H8V12H20Z',
+ };
-$GUI.show();
-jQuery('#plugin_move__treelink').show();
+ #mainElement;
+ #mediaTree;
+ #pageTree;
+ #dragTarget;
+ #dragIcon;
-/**
- * Checks if the given list item was moved in the tree
- *
- * Moved elements are highlighted and a title shows where they came from
- *
- * @param {jQuery} $li
- */
-var checkForMovement = function ($li) {
- // we need to check this LI and all previously moved sub LIs
- var $all = $li.add($li.find('li.moved'));
- $all.each(function () {
- var $this = jQuery(this);
- var oldid = $this.data('id');
- var newid = determineNewID($this);
-
- if (newid != oldid && !$this.hasClass('created')) {
- $this.addClass('moved');
- $this.children('div').attr('title', oldid + ' -> ' + newid);
- } else {
- $this.removeClass('moved');
- $this.children('div').attr('title', '');
+ /**
+ * Initialize the base tree and attach all event handlers
+ *
+ * @param {HTMLElement} main
+ */
+ constructor(main) {
+ this.#mainElement = main;
+ this.#mediaTree = this.#mainElement.querySelector('.move-media');
+ this.#pageTree = this.#mainElement.querySelector('.move-pages');
+
+
+ this.#dragIcon = this.icon('drag');
+ this.#dragIcon.classList.add('drag-icon');
+ this.#mainElement.appendChild(this.#dragIcon);
+
+ this.#mainElement.addEventListener('click', this.clickHandler.bind(this));
+ this.#mainElement.addEventListener('dragstart', this.dragStartHandler.bind(this));
+ this.#mainElement.addEventListener('dragover', this.dragOverHandler.bind(this));
+ this.#mainElement.addEventListener('drop', this.dragDropHandler.bind(this));
+ this.#mainElement.addEventListener('dragend', this.dragEndHandler.bind(this));
+ this.#mainElement.querySelector('form').addEventListener('submit', this.submitHandler.bind(this));
+
+ // load and open the initial tree
+ this.#init();
+
+ // make tree visible
+ this.#mainElement.style.display = 'block';
+ }
+
+ /**
+ * Initialize the tree
+ *
+ * @returns {Promise}
+ */
+ async #init() {
+ await Promise.all([
+ this.loadSubTree('', 'pages'),
+ this.loadSubTree('', 'media'),
+ ]);
+
+ await this.openNamespace(JSINFO.namespace);
+ }
+
+ /**
+ * Handle all item clicks
+ *
+ * @param {MouseEvent} ev
+ */
+ clickHandler(ev) {
+ const target = ev.target;
+ const li = target.closest('li');
+ if (!li) return;
+
+ // we want to handle clicks on these elements only
+ const clicked = target.closest('i,button,span');
+ if (!clicked) return;
+
+ // ignore clicks on the root element
+ if(li.classList.contains('tree-root')) return;
+
+ // icon click selects the item
+ if (clicked.tagName.toLowerCase() === 'i') {
+ ev.stopPropagation();
+ li.classList.toggle('selected');
+ return;
}
- });
-};
-/**
- * Check if the given name is allowed in the given parent
- *
- * @param {jQuery} $li the edited or moved LI
- * @param {jQuery} $parent the (new) parent of the edited or moved LI
- * @param {string} name the (new) name to check
- * @returns {boolean}
- */
-var checkNameAllowed = function ($li, $parent, name) {
- var ok = true;
- $parent.children('li').each(function () {
- if (this === $li[0]) return;
- var cname = 'type-f';
- if ($li.hasClass('type-d')) cname = 'type-d';
-
- var $this = jQuery(this);
- if ($this.data('name') == name && $this.hasClass(cname)) ok = false;
- });
- return ok;
-};
+ // button click opens rename dialog
+ if (clicked.tagName.toLowerCase() === 'button') {
+ ev.stopPropagation();
+ this.renameGui(li);
+ return;
+ }
-/**
- * Returns the new ID of a given list item
- *
- * @param {jQuery} $li
- * @returns {string}
- */
-var determineNewID = function ($li) {
- var myname = $li.data('name');
-
- var $parent = $li.parent().closest('li');
- if ($parent.length) {
- return (determineNewID($parent) + ':' + myname).replace(/^:/, '');
- } else {
- return myname;
+ // click on name opens/closes namespace
+ if (clicked.tagName.toLowerCase() === 'span' && li.classList.contains('move-ns')) {
+ ev.stopPropagation();
+ this.toggleNamespace(li);
+ }
}
-};
-/**
- * Very simplistic cleanID() in JavaScript
- *
- * Strips out namespaces
- *
- * @param {string} id
- */
-var cleanID = function (id) {
- if (!id) return '';
+ /**
+ * Submit the data for the move operation
+ *
+ * @param {FormDataEvent} ev
+ */
+ submitHandler(ev) {
+ // gather all changed items
+ const data = [];
+ this.#mainElement.querySelectorAll('.changed').forEach(li => {
+ let entry = {
+ src: li.dataset.orig,
+ dst: li.dataset.id,
+ type: this.isItemMedia(li) ? 'media' : 'page',
+ class: this.isItemNamespace(li) ? 'ns' : 'doc',
+ };
+ data.push(entry);
+
+ // if this is a namspace that is shared between media and pages, add a second entry
+ if (entry.class === 'ns' && entry.type === 'media' && this.isItemPage(li)) {
+ entry = {...entry}; // clone
+ entry.type = 'page';
+ data.push(entry);
+ }
+ });
- id = id.replace(/[!"#$%§&\'()+,/;<=>?@\[\]^`\{|\}~\\;:\/\*]+/g, '_');
- id = id.replace(/^_+/, '');
- id = id.replace(/_+$/, '');
- id = id.toLowerCase();
+ // add JSON data to form, then let the event continue
+ const input = document.createElement('input');
+ input.type = 'hidden';
+ input.name = 'json';
+ input.value = JSON.stringify(data);
+ ev.target.appendChild(input);
+ }
- return id;
-};
+ /**
+ * Begin drag operation
+ *
+ * @param {DragEvent} ev
+ */
+ dragStartHandler(ev) {
+ if (!ev.target) return;
+ const li = ev.target.closest('li');
+ if (!li) return;
-/**
- * Initialize the drag & drop-tree at the given li (must be this).
- */
-var initTree = function () {
- var $li = jQuery(this);
- var my_root = $li.closest('.tree_root')[0];
- $li.draggable({
- revert: true,
- revertDuration: 0,
- opacity: 0.5,
- stop : function(event, ui) {
- ui.helper.css({height: "auto", width: "auto"});
+ ev.dataTransfer.setData('text/plain', li.dataset.id); // FIXME needed?
+ ev.dataTransfer.effectAllowed = 'move';
+ ev.dataTransfer.setDragImage(this.#dragIcon, -12, -12);
+
+ // the dragged element is always selected
+ li.classList.add('selected');
+ }
+
+ /**
+ * Higlight drop zone and allow dropping
+ *
+ * @param {DragEvent} ev
+ */
+ dragOverHandler(ev) {
+ // remove any previous drop zone
+ if (this.#dragTarget) {
+ this.#dragTarget.classList.remove('drop-zone');
+ }
+
+ if (!ev.target) return; // the element the mouse is over
+
+ const li = ev.target.closest('li');
+ if (!li) return;
+
+ let ul; // the UL we drop into
+ if (li.classList.contains('move-ns')) {
+ // drop on a namespace, use its UL
+ ul = li.querySelector('ul');
+ } else {
+ // drop on a file or page, use parent UL
+ ul = ev.target.closest('ul');
}
- }).droppable({
- tolerance: 'pointer',
- greedy: true,
- accept : function(draggable) {
- return my_root == draggable.closest('.tree_root')[0];
- },
- drop : function (event, ui) {
- var $dropped = ui.draggable;
- var $me = jQuery(this);
-
- if ($dropped.children('div.li').children('input').prop('checked')) {
- $dropped = $dropped.add(
- jQuery(my_root)
- .find('input')
- .filter(function() {
- return jQuery(this).prop('checked');
- }).parent().parent()
- );
+ if (!ul) return;
+ if(ul.classList.contains('open') === false) return; // only drop into open namespaces
+ ev.preventDefault(); // allow drop
+
+ this.#dragTarget = ul;
+ this.#dragTarget.classList.add('drop-zone');
+ }
+
+ /**
+ * Handle the Drop operation
+ *
+ * @param {DragEvent} ev
+ */
+ dragDropHandler(ev) {
+ if (!ev.target) return;
+
+ const dst = this.#dragTarget; // the UL we drop into
+
+ // move all selected items to the drop target
+ const elements = this.#mainElement.querySelectorAll('.selected');
+ elements.forEach(src => {
+ const newID = this.getNewId(src.dataset.id, dst.dataset.id);
+ console.log('move started', src.dataset.id + ' → ' + newID);
+
+ // ensure that item stays in its own tree, ignore cross-tree moves
+ if (this.itemTree(src).contains(dst) === false) {
+ return;
+ }
+
+ // same ID? we consider this an abort
+ if (newID === src.dataset.id) {
+ src.classList.remove('selected');
+ return;
+ }
+
+ // check if item with same ID and type already exists
+ let dupSelector = `li[data-id="${newID}"]`;
+ if (this.isItemMedia(src)) {
+ dupSelector += '.move-media';
+ } else {
+ dupSelector += '.move-pages';
+ }
+ if (this.isItemNamespace(src)) {
+ dupSelector += '.move-ns';
+ } else {
+ dupSelector += ':not(.move-ns)';
+ }
+ if (this.itemTree(src).querySelector(dupSelector)) {
+ alert(LANG.plugins.move.duplicate.replace('%s', newID));
+ src.classList.remove('selected');
+ return;
}
- if ($me.parents().addBack().is($dropped)) {
+ try {
+ dst.append(src);
+ } catch (e) {
+ console.log('move aborted', e.message); // moved into itself
+ src.classList.remove('selected');
return;
}
+ this.updateMovedItem(src, newID);
+ });
+ this.updatePassiveSubNamespaces(dst);
+ this.sortList(dst);
+ }
+
+ /**
+ * Clean up after drag'n'drop operation
+ *
+ * @param {DragEvent} ev
+ */
+ dragEndHandler(ev) {
+ if (this.#dragTarget) {
+ this.#dragTarget.classList.remove('drop-zone');
+ }
+ }
- var insert_child = !($me.hasClass("type-f") || $me.hasClass("closed"));
- var $new_parent = insert_child ? $me.children('ul') : $me.parent();
- var allowed = true;
+ /**
+ * Open the given namespace and all its parents
+ *
+ * @param {string} namespace
+ * @returns {Promise}
+ */
+ async openNamespace(namespace) {
+ const namespaces = namespace.split(':');
- $dropped.each(function () {
- var $this = jQuery(this);
- allowed &= checkNameAllowed($this, $new_parent, $this.data('name'));
- });
+ for (let i = 0; i < namespaces.length; i++) {
+ const ns = namespaces.slice(0, i + 1).join(':');
+ const li = this.#mainElement.querySelectorAll(`li[data-orig="${ns}"].move-ns`);
+ if (!li.length) return;
- if (allowed) {
- if (insert_child) {
- $dropped.prependTo($new_parent);
- } else {
- $dropped.insertAfter($me);
+ // we might have multiple namespaces with the same ID (media and pages)
+ // we open both in parallel and wait for them
+ const promises = [];
+ for (const el of li) {
+ const ul = el.querySelector('ul');
+ if (!ul) {
+ promises.push(this.toggleNamespace(el));
}
}
+ await Promise.all(promises);
+ }
+ }
+
+ /**
+ * Rename an item via a prompt dialog
+ *
+ * @param li
+ */
+ renameGui(li) {
+ const basename = this.getBase(li.dataset.id);
+ const newname = window.prompt(LANG.plugins.move.renameitem, basename);
+ const clean = this.cleanID(newname);
- checkForMovement($dropped);
+ if (!clean || clean === basename || newname === basename ) {
+ return;
}
- })
- // add title to rename icon
- .find('img.rename').attr('title', LANG.plugins.move.renameitem)
- .end()
- .find('img.add').attr('title', LANG.plugins.move.add);
-};
-var add_template = '