mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 07:46:30 +01:00
feat(react): port move to
This commit is contained in:
@@ -226,7 +226,7 @@
|
||||
"notes_to_move": "需要移动的笔记",
|
||||
"target_parent_note": "目标父笔记",
|
||||
"search_placeholder": "通过名称搜索笔记",
|
||||
"move_button": "移动到选定的笔记 <kbd>回车</kbd>",
|
||||
"move_button": "移动到选定的笔记",
|
||||
"error_no_path": "没有可以移动到的路径。",
|
||||
"move_success_message": "所选笔记已移动到"
|
||||
},
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
"notes_to_move": "Notizen zum Verschieben",
|
||||
"target_parent_note": "Ziel-Elternnotiz",
|
||||
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
|
||||
"move_button": "Zur ausgewählten Notiz wechseln <kbd>Eingabetaste</kbd>",
|
||||
"move_button": "Zur ausgewählten Notiz wechseln",
|
||||
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
|
||||
"move_success_message": "Ausgewählte Notizen wurden verschoben"
|
||||
},
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
"notes_to_move": "Notes to move",
|
||||
"target_parent_note": "Target parent note",
|
||||
"search_placeholder": "search for note by its name",
|
||||
"move_button": "Move to selected note <kbd>enter</kbd>",
|
||||
"move_button": "Move to selected note",
|
||||
"error_no_path": "No path to move to.",
|
||||
"move_success_message": "Selected notes have been moved into "
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
"notes_to_move": "Notas a mover",
|
||||
"target_parent_note": "Nota padre de destino",
|
||||
"search_placeholder": "buscar nota por su nombre",
|
||||
"move_button": "Mover a la nota seleccionada <kbd>enter</kbd>",
|
||||
"move_button": "Mover a la nota seleccionada",
|
||||
"error_no_path": "No hay ruta a donde mover.",
|
||||
"move_success_message": "Las notas seleccionadas se han movido a "
|
||||
},
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
"notes_to_move": "Notes à déplacer",
|
||||
"target_parent_note": "Note parent cible",
|
||||
"search_placeholder": "rechercher une note par son nom",
|
||||
"move_button": "Déplacer vers la note sélectionnée <kbd>entrer</kbd>",
|
||||
"move_button": "Déplacer vers la note sélectionnée",
|
||||
"error_no_path": "Aucun chemin vers lequel déplacer.",
|
||||
"move_success_message": "Les notes sélectionnées ont été déplacées dans "
|
||||
},
|
||||
|
||||
@@ -817,7 +817,7 @@
|
||||
"move_to": {
|
||||
"dialog_title": "Mută notițele în...",
|
||||
"error_no_path": "Nicio cale la care să poată fi mutate.",
|
||||
"move_button": "Mută la notița selectată <kbd>enter</kbd>",
|
||||
"move_button": "Mută la notița selectată",
|
||||
"move_success_message": "Notițele selectate au fost mutate în",
|
||||
"notes_to_move": "Notițe de mutat",
|
||||
"search_placeholder": "căutați notița după denumirea ei",
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
"notes_to_move": "Beleške za premeštanje",
|
||||
"target_parent_note": "Ciljana nadbeleška",
|
||||
"search_placeholder": "potraži belešku po njenom imenu",
|
||||
"move_button": "Pređi na izabranu belešku <kbd>enter</kbd>",
|
||||
"move_button": "Pređi na izabranu belešku",
|
||||
"error_no_path": "Nema putanje za premeštanje.",
|
||||
"move_success_message": "Izabrane beleške su premeštene u "
|
||||
},
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
"notes_to_move": "需要移動的筆記",
|
||||
"target_parent_note": "目標上級筆記",
|
||||
"search_placeholder": "通過名稱搜尋筆記",
|
||||
"move_button": "移動到選定的筆記 <kbd>Enter</kbd>",
|
||||
"move_button": "移動到選定的筆記",
|
||||
"error_no_path": "沒有可以移動到的路徑。",
|
||||
"move_success_message": "已移動所選筆記到 "
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CSSProperties, useRef, useState } from "preact/compat";
|
||||
import { useRef, useState } from "preact/compat";
|
||||
import appContext, { EventData } from "../../components/app_context";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
|
||||
import { t } from "../../services/i18n";
|
||||
@@ -6,8 +6,6 @@ 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";
|
||||
@@ -16,6 +14,7 @@ import { logError } from "../../services/ws";
|
||||
import tree from "../../services/tree";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
import NoteList from "../react/NoteList";
|
||||
|
||||
interface CloneToDialogProps {
|
||||
clonedNoteIds: string[];
|
||||
@@ -66,26 +65,6 @@ function CloneToDialogComponent({ clonedNoteIds }: CloneToDialogProps) {
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import treeService from "../../services/tree.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="move-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 me-auto">${t("move_to.dialog_title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("move_to.close")}"></button>
|
||||
</div>
|
||||
<form class="move-to-form">
|
||||
<div class="modal-body">
|
||||
<h5>${t("move_to.notes_to_move")}</h5>
|
||||
|
||||
<ul class="move-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
|
||||
|
||||
<div class="form-group">
|
||||
<label style="width: 100%">
|
||||
${t("move_to.target_parent_note")}
|
||||
<div class="input-group">
|
||||
<input class="move-to-note-autocomplete form-control" placeholder="${t("move_to.search_placeholder")}">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">${t("move_to.move_button")}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class MoveToDialog extends BasicWidget {
|
||||
|
||||
private movedBranchIds: string[] | null;
|
||||
private $form!: JQuery<HTMLElement>;
|
||||
private $noteAutoComplete!: JQuery<HTMLElement>;
|
||||
private $noteList!: JQuery<HTMLElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.movedBranchIds = null;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$form = this.$widget.find(".move-to-form");
|
||||
this.$noteAutoComplete = this.$widget.find(".move-to-note-autocomplete");
|
||||
this.$noteList = this.$widget.find(".move-to-note-list");
|
||||
|
||||
this.$form.on("submit", () => {
|
||||
const notePath = this.$noteAutoComplete.getSelectedNotePath();
|
||||
|
||||
if (notePath) {
|
||||
this.$widget.modal("hide");
|
||||
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (parentNoteId) {
|
||||
froca.getBranchId(parentNoteId, noteId).then((branchId) => {
|
||||
if (branchId) {
|
||||
this.moveNotesTo(branchId);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
logError(t("move_to.error_no_path"));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async moveBranchIdsToEvent({ branchIds }: EventData<"moveBranchIdsTo">) {
|
||||
this.movedBranchIds = branchIds;
|
||||
|
||||
openDialog(this.$widget);
|
||||
|
||||
this.$noteAutoComplete.val("").trigger("focus");
|
||||
|
||||
this.$noteList.empty();
|
||||
|
||||
for (const branchId of this.movedBranchIds) {
|
||||
const branch = froca.getBranch(branchId);
|
||||
if (!branch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const note = await froca.getNote(branch.noteId);
|
||||
if (!note) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.$noteList.append($("<li>").text(note.title));
|
||||
}
|
||||
|
||||
noteAutocompleteService.initNoteAutocomplete(this.$noteAutoComplete);
|
||||
noteAutocompleteService.showRecentNotes(this.$noteAutoComplete);
|
||||
}
|
||||
|
||||
async moveNotesTo(parentBranchId: string) {
|
||||
if (this.movedBranchIds) {
|
||||
await branchService.moveToParentNote(this.movedBranchIds, parentBranchId);
|
||||
}
|
||||
|
||||
const parentBranch = froca.getBranch(parentBranchId);
|
||||
const parentNote = await parentBranch?.getNote();
|
||||
|
||||
toastService.showMessage(`${t("move_to.move_success_message")} ${parentNote?.title}`);
|
||||
}
|
||||
}
|
||||
95
apps/client/src/widgets/dialogs/move_to.tsx
Normal file
95
apps/client/src/widgets/dialogs/move_to.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
|
||||
import { EventData } from "../../components/app_context";
|
||||
import NoteList from "../react/NoteList";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import Button from "../react/Button";
|
||||
import { useRef, useState } from "preact/compat";
|
||||
import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
|
||||
import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
|
||||
interface MoveToDialogProps {
|
||||
movedBranchIds?: string[];
|
||||
}
|
||||
|
||||
function MoveToDialogComponent({ movedBranchIds }: MoveToDialogProps) {
|
||||
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
|
||||
const autoCompleteRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
async function onSubmit() {
|
||||
const notePath = suggestion?.notePath;
|
||||
if (!notePath) {
|
||||
logError(t("move_to.error_no_path"));
|
||||
return;
|
||||
}
|
||||
|
||||
closeActiveDialog();
|
||||
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!parentNoteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
if (branchId) {
|
||||
moveNotesTo(movedBranchIds, branchId);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="move-to-dialog"
|
||||
size="lg" maxWidth={1000}
|
||||
title={t("move_to.dialog_title")}
|
||||
footer={<Button text={t("move_to.move_button")} keyboardShortcut="Enter" />}
|
||||
onSubmit={onSubmit}
|
||||
onShown={() => {
|
||||
autoCompleteRef.current?.focus();
|
||||
note_autocomplete.showRecentNotes($(autoCompleteRef.current));
|
||||
}}
|
||||
>
|
||||
<h5>{t("move_to.notes_to_move")}</h5>
|
||||
<NoteList branchIds={movedBranchIds} />
|
||||
|
||||
<FormGroup label={t("move_to.target_parent_note")}>
|
||||
<NoteAutocomplete
|
||||
onChange={setSuggestion}
|
||||
inputRef={autoCompleteRef}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class MoveToDialog extends ReactBasicWidget {
|
||||
|
||||
private props: MoveToDialogProps = {};
|
||||
|
||||
get component() {
|
||||
return <MoveToDialogComponent {...this.props} />;
|
||||
}
|
||||
|
||||
async moveBranchIdsToEvent({ branchIds }: EventData<"moveBranchIdsTo">) {
|
||||
const movedBranchIds = branchIds;
|
||||
this.props = { movedBranchIds };
|
||||
this.doRender();
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function moveNotesTo(movedBranchIds: string[] | undefined, parentBranchId: string) {
|
||||
if (movedBranchIds) {
|
||||
await branches.moveToParentNote(movedBranchIds, parentBranchId);
|
||||
}
|
||||
|
||||
const parentBranch = froca.getBranch(parentBranchId);
|
||||
const parentNote = await parentBranch?.getNote();
|
||||
|
||||
toast.showMessage(`${t("move_to.move_success_message")} ${parentNote?.title}`);
|
||||
}
|
||||
36
apps/client/src/widgets/react/NoteList.tsx
Normal file
36
apps/client/src/widgets/react/NoteList.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import type FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import { CSSProperties } from "preact/compat";
|
||||
|
||||
interface NoteListProps {
|
||||
noteIds?: string[];
|
||||
branchIds?: string[];
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export default function NoteList({ noteIds, branchIds, style }: NoteListProps) {
|
||||
const [ notes, setNotes ] = useState<FNote[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let notesToLoad: string[];
|
||||
if (noteIds) {
|
||||
notesToLoad = noteIds;
|
||||
} else if (branchIds) {
|
||||
notesToLoad = froca.getBranches(branchIds).map(b => b.noteId);
|
||||
} else {
|
||||
notesToLoad = [];
|
||||
}
|
||||
froca.getNotes(notesToLoad).then((notes) => setNotes(notes));
|
||||
}, [noteIds, branchIds]);
|
||||
|
||||
return (notes &&
|
||||
<ul style={style}>
|
||||
{notes.map(note => (
|
||||
<li key={note.noteId}>
|
||||
{note.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user