mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	feat(react/dialogs): port clone_to
This commit is contained in:
		@@ -68,7 +68,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "按名称搜索笔记",
 | 
					    "search_for_note_by_its_name": "按名称搜索笔记",
 | 
				
			||||||
    "cloned_note_prefix_title": "克隆的笔记将在笔记树中显示给定的前缀",
 | 
					    "cloned_note_prefix_title": "克隆的笔记将在笔记树中显示给定的前缀",
 | 
				
			||||||
    "prefix_optional": "前缀(可选)",
 | 
					    "prefix_optional": "前缀(可选)",
 | 
				
			||||||
    "clone_to_selected_note": "克隆到选定的笔记 <kbd>回车</kbd>",
 | 
					    "clone_to_selected_note": "克隆到选定的笔记",
 | 
				
			||||||
    "no_path_to_clone_to": "没有克隆路径。",
 | 
					    "no_path_to_clone_to": "没有克隆路径。",
 | 
				
			||||||
    "note_cloned": "笔记 \"{{clonedTitle}}\" 已克隆到 \"{{targetTitle}}\""
 | 
					    "note_cloned": "笔记 \"{{clonedTitle}}\" 已克隆到 \"{{targetTitle}}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "Suche nach einer Notiz anhand ihres Namens",
 | 
					    "search_for_note_by_its_name": "Suche nach einer Notiz anhand ihres Namens",
 | 
				
			||||||
    "cloned_note_prefix_title": "Die geklonte Notiz wird im Notizbaum mit dem angegebenen Präfix angezeigt",
 | 
					    "cloned_note_prefix_title": "Die geklonte Notiz wird im Notizbaum mit dem angegebenen Präfix angezeigt",
 | 
				
			||||||
    "prefix_optional": "Präfix (optional)",
 | 
					    "prefix_optional": "Präfix (optional)",
 | 
				
			||||||
    "clone_to_selected_note": "Auf ausgewählte Notiz klonen <kbd>Eingabe</kbd>",
 | 
					    "clone_to_selected_note": "Auf ausgewählte Notiz klonen",
 | 
				
			||||||
    "no_path_to_clone_to": "Kein Pfad zum Klonen.",
 | 
					    "no_path_to_clone_to": "Kein Pfad zum Klonen.",
 | 
				
			||||||
    "note_cloned": "Die Notiz \"{{clonedTitle}}\" wurde in \"{{targetTitle}}\" hinein geklont"
 | 
					    "note_cloned": "Die Notiz \"{{clonedTitle}}\" wurde in \"{{targetTitle}}\" hinein geklont"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "search for note by its name",
 | 
					    "search_for_note_by_its_name": "search for note by its name",
 | 
				
			||||||
    "cloned_note_prefix_title": "Cloned note will be shown in note tree with given prefix",
 | 
					    "cloned_note_prefix_title": "Cloned note will be shown in note tree with given prefix",
 | 
				
			||||||
    "prefix_optional": "Prefix (optional)",
 | 
					    "prefix_optional": "Prefix (optional)",
 | 
				
			||||||
    "clone_to_selected_note": "Clone to selected note <kbd>enter</kbd>",
 | 
					    "clone_to_selected_note": "Clone to selected note",
 | 
				
			||||||
    "no_path_to_clone_to": "No path to clone to.",
 | 
					    "no_path_to_clone_to": "No path to clone to.",
 | 
				
			||||||
    "note_cloned": "Note \"{{clonedTitle}}\" has been cloned into \"{{targetTitle}}\""
 | 
					    "note_cloned": "Note \"{{clonedTitle}}\" has been cloned into \"{{targetTitle}}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "buscar nota por su nombre",
 | 
					    "search_for_note_by_its_name": "buscar nota por su nombre",
 | 
				
			||||||
    "cloned_note_prefix_title": "La nota clonada se mostrará en el árbol de notas con el prefijo dado",
 | 
					    "cloned_note_prefix_title": "La nota clonada se mostrará en el árbol de notas con el prefijo dado",
 | 
				
			||||||
    "prefix_optional": "Prefijo (opcional)",
 | 
					    "prefix_optional": "Prefijo (opcional)",
 | 
				
			||||||
    "clone_to_selected_note": "Clonar a nota seleccionada <kbd>enter</kbd>",
 | 
					    "clone_to_selected_note": "Clonar a nota seleccionada",
 | 
				
			||||||
    "no_path_to_clone_to": "No hay ruta para clonar.",
 | 
					    "no_path_to_clone_to": "No hay ruta para clonar.",
 | 
				
			||||||
    "note_cloned": "La nota \"{{clonedTitle}}\" a sido clonada en \"{{targetTitle}}\""
 | 
					    "note_cloned": "La nota \"{{clonedTitle}}\" a sido clonada en \"{{targetTitle}}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "rechercher une note par son nom",
 | 
					    "search_for_note_by_its_name": "rechercher une note par son nom",
 | 
				
			||||||
    "cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné",
 | 
					    "cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné",
 | 
				
			||||||
    "prefix_optional": "Préfixe (facultatif)",
 | 
					    "prefix_optional": "Préfixe (facultatif)",
 | 
				
			||||||
    "clone_to_selected_note": "Cloner vers la note sélectionnée <kbd>entrer</kbd>",
 | 
					    "clone_to_selected_note": "Cloner vers la note sélectionnée",
 | 
				
			||||||
    "no_path_to_clone_to": "Aucun chemin vers lequel cloner.",
 | 
					    "no_path_to_clone_to": "Aucun chemin vers lequel cloner.",
 | 
				
			||||||
    "note_cloned": "La note \"{{clonedTitle}}\" a été clonée dans \"{{targetTitle}}\""
 | 
					    "note_cloned": "La note \"{{clonedTitle}}\" a été clonée dans \"{{targetTitle}}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -342,7 +342,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "clone_to": {
 | 
					  "clone_to": {
 | 
				
			||||||
    "clone_notes_to": "Clonează notițele către...",
 | 
					    "clone_notes_to": "Clonează notițele către...",
 | 
				
			||||||
    "clone_to_selected_note": "Clonează notița selectată <kbd>enter</kbd>",
 | 
					    "clone_to_selected_note": "Clonează notița selectată",
 | 
				
			||||||
    "cloned_note_prefix_title": "Notița clonată va fi afișată în ierarhia notiței utilizând prefixul dat",
 | 
					    "cloned_note_prefix_title": "Notița clonată va fi afișată în ierarhia notiței utilizând prefixul dat",
 | 
				
			||||||
    "help_on_links": "Informații despre legături",
 | 
					    "help_on_links": "Informații despre legături",
 | 
				
			||||||
    "no_path_to_clone_to": "Nicio cale de clonat.",
 | 
					    "no_path_to_clone_to": "Nicio cale de clonat.",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,7 +66,7 @@
 | 
				
			|||||||
    "search_for_note_by_its_name": "按名稱搜尋筆記",
 | 
					    "search_for_note_by_its_name": "按名稱搜尋筆記",
 | 
				
			||||||
    "cloned_note_prefix_title": "複製的筆記將在筆記樹中顯示給定的前綴",
 | 
					    "cloned_note_prefix_title": "複製的筆記將在筆記樹中顯示給定的前綴",
 | 
				
			||||||
    "prefix_optional": "前綴(可選)",
 | 
					    "prefix_optional": "前綴(可選)",
 | 
				
			||||||
    "clone_to_selected_note": "複製到選定的筆記 <kbd>Enter</kbd>",
 | 
					    "clone_to_selected_note": "複製到選定的筆記",
 | 
				
			||||||
    "no_path_to_clone_to": "沒有複製路徑。",
 | 
					    "no_path_to_clone_to": "沒有複製路徑。",
 | 
				
			||||||
    "note_cloned": "筆記 \"{{clonedTitle}}\" 已複製到 \"{{targetTitle}}\""
 | 
					    "note_cloned": "筆記 \"{{clonedTitle}}\" 已複製到 \"{{targetTitle}}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,140 +0,0 @@
 | 
				
			|||||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
 | 
					 | 
				
			||||||
import treeService from "../../services/tree.js";
 | 
					 | 
				
			||||||
import toastService from "../../services/toast.js";
 | 
					 | 
				
			||||||
import froca from "../../services/froca.js";
 | 
					 | 
				
			||||||
import branchService from "../../services/branches.js";
 | 
					 | 
				
			||||||
import appContext from "../../components/app_context.js";
 | 
					 | 
				
			||||||
import BasicWidget from "../basic_widget.js";
 | 
					 | 
				
			||||||
import { t } from "../../services/i18n.js";
 | 
					 | 
				
			||||||
import type { EventData } from "../../components/app_context.js";
 | 
					 | 
				
			||||||
import { openDialog } from "../../services/dialog.js";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const TPL = /*html*/`
 | 
					 | 
				
			||||||
<div class="clone-to-dialog modal mx-auto" tabindex="-1" role="dialog">
 | 
					 | 
				
			||||||
    <div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
 | 
					 | 
				
			||||||
        <div class="modal-content">
 | 
					 | 
				
			||||||
            <div class="modal-header">
 | 
					 | 
				
			||||||
                <h5 class="modal-title flex-grow-1">${t("clone_to.clone_notes_to")}</h5>
 | 
					 | 
				
			||||||
                <button type="button" class="help-button" title="${t("clone_to.help_on_links")}" data-help-page="cloning-notes.html">?</button>
 | 
					 | 
				
			||||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("clone_to.close")}"></button>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <form class="clone-to-form">
 | 
					 | 
				
			||||||
                <div class="modal-body">
 | 
					 | 
				
			||||||
                    <h5>${t("clone_to.notes_to_clone")}</h5>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <ul class="clone-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <div class="form-group">
 | 
					 | 
				
			||||||
                        <label style="width: 100%">
 | 
					 | 
				
			||||||
                            ${t("clone_to.target_parent_note")}
 | 
					 | 
				
			||||||
                            <div class="input-group">
 | 
					 | 
				
			||||||
                                <input class="clone-to-note-autocomplete form-control" placeholder="${t("clone_to.search_for_note_by_its_name")}">
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <div class="form-group" title="${t("clone_to.cloned_note_prefix_title")}">
 | 
					 | 
				
			||||||
                        <label style="width: 100%">
 | 
					 | 
				
			||||||
                            ${t("clone_to.prefix_optional")}
 | 
					 | 
				
			||||||
                            <input class="clone-prefix form-control" style="width: 100%;">
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div class="modal-footer">
 | 
					 | 
				
			||||||
                    <button type="submit" class="btn btn-primary">${t("clone_to.clone_to_selected_note")}</button>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </form>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</div>`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class CloneToDialog extends BasicWidget {
 | 
					 | 
				
			||||||
    private $form!: JQuery<HTMLElement>;
 | 
					 | 
				
			||||||
    private $noteAutoComplete!: JQuery<HTMLElement>;
 | 
					 | 
				
			||||||
    private $clonePrefix!: JQuery<HTMLElement>;
 | 
					 | 
				
			||||||
    private $noteList!: JQuery<HTMLElement>;
 | 
					 | 
				
			||||||
    private clonedNoteIds: string[] | null = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor() {
 | 
					 | 
				
			||||||
        super();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    doRender() {
 | 
					 | 
				
			||||||
        this.$widget = $(TPL);
 | 
					 | 
				
			||||||
        this.$form = this.$widget.find(".clone-to-form");
 | 
					 | 
				
			||||||
        this.$noteAutoComplete = this.$widget.find(".clone-to-note-autocomplete");
 | 
					 | 
				
			||||||
        this.$clonePrefix = this.$widget.find(".clone-prefix");
 | 
					 | 
				
			||||||
        this.$noteList = this.$widget.find(".clone-to-note-list");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.$form.on("submit", () => {
 | 
					 | 
				
			||||||
            const notePath = this.$noteAutoComplete.getSelectedNotePath();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (notePath) {
 | 
					 | 
				
			||||||
                this.$widget.modal("hide");
 | 
					 | 
				
			||||||
                this.cloneNotesTo(notePath);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                logError(t("clone_to.no_path_to_clone_to"));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async cloneNoteIdsToEvent({ noteIds }: EventData<"cloneNoteIdsTo">) {
 | 
					 | 
				
			||||||
        if (!noteIds || noteIds.length === 0) {
 | 
					 | 
				
			||||||
            noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.clonedNoteIds = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const noteId of noteIds) {
 | 
					 | 
				
			||||||
            if (!this.clonedNoteIds.includes(noteId)) {
 | 
					 | 
				
			||||||
                this.clonedNoteIds.push(noteId);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        openDialog(this.$widget);
 | 
					 | 
				
			||||||
        this.$noteAutoComplete.val("").trigger("focus");
 | 
					 | 
				
			||||||
        this.$noteList.empty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const noteId of this.clonedNoteIds) {
 | 
					 | 
				
			||||||
            const note = await froca.getNote(noteId);
 | 
					 | 
				
			||||||
            if (!note) {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this.$noteList.append($("<li>").text(note.title));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        noteAutocompleteService.initNoteAutocomplete(this.$noteAutoComplete);
 | 
					 | 
				
			||||||
        noteAutocompleteService.showRecentNotes(this.$noteAutoComplete);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async cloneNotesTo(notePath: string) {
 | 
					 | 
				
			||||||
        const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
 | 
					 | 
				
			||||||
        if (!noteId || !parentNoteId) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
 | 
					 | 
				
			||||||
        if (!targetBranchId || !this.clonedNoteIds) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const cloneNoteId of this.clonedNoteIds) {
 | 
					 | 
				
			||||||
            await branchService.cloneNoteToBranch(cloneNoteId, targetBranchId, this.$clonePrefix.val() as string);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const clonedNote = await froca.getNote(cloneNoteId);
 | 
					 | 
				
			||||||
            const targetBranch = froca.getBranch(targetBranchId);
 | 
					 | 
				
			||||||
            if (!clonedNote || !targetBranch) {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const targetNote = await targetBranch.getNote();
 | 
					 | 
				
			||||||
            if (!targetNote) {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            toastService.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										143
									
								
								apps/client/src/widgets/dialogs/clone_to.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								apps/client/src/widgets/dialogs/clone_to.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					import { CSSProperties, useRef, useState } from "preact/compat";
 | 
				
			||||||
 | 
					import appContext, { EventData } from "../../components/app_context";
 | 
				
			||||||
 | 
					import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
				
			||||||
 | 
					import { t } from "../../services/i18n";
 | 
				
			||||||
 | 
					import Modal from "../react/Modal";
 | 
				
			||||||
 | 
					import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
				
			||||||
 | 
					import NoteAutocomplete from "../react/NoteAutocomplete";
 | 
				
			||||||
 | 
					import froca from "../../services/froca";
 | 
				
			||||||
 | 
					import { useEffect } from "react";
 | 
				
			||||||
 | 
					import FNote from "../../entities/fnote";
 | 
				
			||||||
 | 
					import FormGroup from "../react/FormGroup";
 | 
				
			||||||
 | 
					import FormTextBox from "../react/FormTextBox";
 | 
				
			||||||
 | 
					import Button from "../react/Button";
 | 
				
			||||||
 | 
					import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
 | 
				
			||||||
 | 
					import { logError } from "../../services/ws";
 | 
				
			||||||
 | 
					import tree from "../../services/tree";
 | 
				
			||||||
 | 
					import branches from "../../services/branches";
 | 
				
			||||||
 | 
					import toast from "../../services/toast";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CloneToDialogProps {
 | 
				
			||||||
 | 
					    clonedNoteIds: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function CloneToDialogComponent({ clonedNoteIds }: CloneToDialogProps) {
 | 
				
			||||||
 | 
					    const [ prefix, setPrefix ] = useState("");
 | 
				
			||||||
 | 
					    const [ suggestion, setSuggestion ] = useState<Suggestion>(null);
 | 
				
			||||||
 | 
					    const autoCompleteRef = useRef<HTMLInputElement>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onSubmit() {
 | 
				
			||||||
 | 
					        const notePath = suggestion?.notePath;
 | 
				
			||||||
 | 
					        if (!notePath) {
 | 
				
			||||||
 | 
					            logError(t("clone_to.no_path_to_clone_to"));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        closeActiveDialog();
 | 
				
			||||||
 | 
					        cloneNotesTo(notePath, clonedNoteIds, prefix);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Modal
 | 
				
			||||||
 | 
					            className="clone-to-dialog"
 | 
				
			||||||
 | 
					            title={t("clone_to.clone_notes_to")}
 | 
				
			||||||
 | 
					            helpPageId="IakOLONlIfGI"
 | 
				
			||||||
 | 
					            size="lg"
 | 
				
			||||||
 | 
					            footer={<Button text={t("clone_to.clone_to_selected_note")} keyboardShortcut="Enter" />}
 | 
				
			||||||
 | 
					            onSubmit={onSubmit}
 | 
				
			||||||
 | 
					            onShown={() => {
 | 
				
			||||||
 | 
					                autoCompleteRef.current?.focus();
 | 
				
			||||||
 | 
					                note_autocomplete.showRecentNotes($(autoCompleteRef.current));
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <h5>{t("clone_to.notes_to_clone")}</h5>
 | 
				
			||||||
 | 
					            <NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} />
 | 
				
			||||||
 | 
					            <FormGroup label={t("clone_to.target_parent_note")}>
 | 
				
			||||||
 | 
					                <NoteAutocomplete
 | 
				
			||||||
 | 
					                    placeholder={t("clone_to.search_for_note_by_its_name")}
 | 
				
			||||||
 | 
					                    onChange={setSuggestion}
 | 
				
			||||||
 | 
					                    inputRef={autoCompleteRef}
 | 
				
			||||||
 | 
					                />      
 | 
				
			||||||
 | 
					            </FormGroup>
 | 
				
			||||||
 | 
					            <FormGroup label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}>
 | 
				
			||||||
 | 
					                <FormTextBox name="clone-prefix" onChange={setPrefix} />
 | 
				
			||||||
 | 
					            </FormGroup>
 | 
				
			||||||
 | 
					        </Modal>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function NoteList({ noteIds, style }: { noteIds?: string[], style: CSSProperties }) {
 | 
				
			||||||
 | 
					    const [ notes, setNotes ] = useState<FNote[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (noteIds) {
 | 
				
			||||||
 | 
					            froca.getNotes(noteIds).then((notes) => setNotes(notes));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [noteIds]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (notes &&
 | 
				
			||||||
 | 
					        <ul style={style}>
 | 
				
			||||||
 | 
					            {notes.map(note => (
 | 
				
			||||||
 | 
					                <li key={note.noteId}>
 | 
				
			||||||
 | 
					                    {note.title}
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class CloneToDialog extends ReactBasicWidget {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private props: CloneToDialogProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get component() {
 | 
				
			||||||
 | 
					        return <CloneToDialogComponent {...this.props} />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async cloneNoteIdsToEvent({ noteIds }: EventData<"cloneNoteIdsTo">) {
 | 
				
			||||||
 | 
					        if (!noteIds || noteIds.length === 0) {
 | 
				
			||||||
 | 
					            noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const clonedNoteIds = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const noteId of noteIds) {
 | 
				
			||||||
 | 
					            if (!clonedNoteIds.includes(noteId)) {
 | 
				
			||||||
 | 
					                clonedNoteIds.push(noteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.props = { clonedNoteIds };
 | 
				
			||||||
 | 
					        this.doRender();
 | 
				
			||||||
 | 
					        openDialog(this.$widget);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function cloneNotesTo(notePath: string, clonedNoteIds: string[], prefix?: string) {
 | 
				
			||||||
 | 
					    const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
 | 
				
			||||||
 | 
					    if (!noteId || !parentNoteId) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
 | 
				
			||||||
 | 
					    if (!targetBranchId || !clonedNoteIds) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const cloneNoteId of clonedNoteIds) {
 | 
				
			||||||
 | 
					        await branches.cloneNoteToBranch(cloneNoteId, targetBranchId, prefix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const clonedNote = await froca.getNote(cloneNoteId);
 | 
				
			||||||
 | 
					        const targetBranch = froca.getBranch(targetBranchId);
 | 
				
			||||||
 | 
					        if (!clonedNote || !targetBranch) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const targetNote = await targetBranch.getNote();
 | 
				
			||||||
 | 
					        if (!targetNote) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        toast.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,6 +9,7 @@ import FormTextBox from "../react/FormTextBox";
 | 
				
			|||||||
import Modal from "../react/Modal";
 | 
					import Modal from "../react/Modal";
 | 
				
			||||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
					import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
				
			||||||
import server from "../../services/server";
 | 
					import server from "../../services/server";
 | 
				
			||||||
 | 
					import FormGroup from "../react/FormGroup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string }) {
 | 
					function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string }) {
 | 
				
			||||||
    const [ sortBy, setSortBy ] = useState("title");
 | 
					    const [ sortBy, setSortBy ] = useState("title");
 | 
				
			||||||
@@ -75,13 +76,12 @@ function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string
 | 
				
			|||||||
                label={t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
 | 
					                label={t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
 | 
				
			||||||
                currentValue={sortNatural} onChange={setSortNatural}
 | 
					                currentValue={sortNatural} onChange={setSortNatural}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					            <FormGroup className="form-check" label={t("sort_child_notes.natural_sort_language")} description={t("sort_child_notes.the_language_code_for_natural_sort")}>
 | 
				
			||||||
                <FormTextBox
 | 
					                <FormTextBox
 | 
				
			||||||
                className="form-check"
 | 
					 | 
				
			||||||
                    name="sort-locale"                                        
 | 
					                    name="sort-locale"                                        
 | 
				
			||||||
                label={t("sort_child_notes.natural_sort_language")}
 | 
					 | 
				
			||||||
                description={t("sort_child_notes.the_language_code_for_natural_sort")}
 | 
					 | 
				
			||||||
                    currentValue={sortLocale} onChange={setSortLocale}
 | 
					                    currentValue={sortLocale} onChange={setSortLocale}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					            </FormGroup>
 | 
				
			||||||
        </Modal>
 | 
					        </Modal>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,16 +2,21 @@ import { ComponentChildren } from "preact";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface FormGroupProps {
 | 
					interface FormGroupProps {
 | 
				
			||||||
    label: string;
 | 
					    label: string;
 | 
				
			||||||
 | 
					    title?: string;
 | 
				
			||||||
 | 
					    className?: string;
 | 
				
			||||||
    children: ComponentChildren;
 | 
					    children: ComponentChildren;
 | 
				
			||||||
 | 
					    description?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function FormGroup({ label, children }: FormGroupProps) {
 | 
					export default function FormGroup({ label, title, className, children, description }: FormGroupProps) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group">
 | 
					        <div className={`form-group ${className}`} title={title}>
 | 
				
			||||||
            <label style={{ width: "100%" }}>
 | 
					            <label style={{ width: "100%" }}>
 | 
				
			||||||
                {label}
 | 
					                {label}
 | 
				
			||||||
                {children}
 | 
					                {children}
 | 
				
			||||||
            </label>
 | 
					            </label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {description && <small className="form-text text-muted">{description}</small>}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,25 +1,17 @@
 | 
				
			|||||||
interface FormTextBoxProps {
 | 
					interface FormTextBoxProps {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    label: string;
 | 
					 | 
				
			||||||
    currentValue?: string;
 | 
					    currentValue?: string;
 | 
				
			||||||
    className?: string;
 | 
					    className?: string;
 | 
				
			||||||
    description?: string;
 | 
					 | 
				
			||||||
    onChange?(newValue: string): void;
 | 
					    onChange?(newValue: string): void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function FormTextBox({ name, label, description, className, currentValue, onChange }: FormTextBoxProps) {
 | 
					export default function FormTextBox({ name, className, currentValue, onChange }: FormTextBoxProps) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className={className}>
 | 
					 | 
				
			||||||
            <label>
 | 
					 | 
				
			||||||
                {label}
 | 
					 | 
				
			||||||
        <input
 | 
					        <input
 | 
				
			||||||
            type="text"
 | 
					            type="text"
 | 
				
			||||||
                    className="form-control"
 | 
					            className={`form-control ${className}`}
 | 
				
			||||||
            name={name}
 | 
					            name={name}
 | 
				
			||||||
            value={currentValue}
 | 
					            value={currentValue}
 | 
				
			||||||
            onInput={e => onChange?.(e.currentTarget.value)} />
 | 
					            onInput={e => onChange?.(e.currentTarget.value)} />
 | 
				
			||||||
                {description && <small className="form-text text-muted">{description}</small>}
 | 
					 | 
				
			||||||
            </label>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user