diff --git a/data/ui/notes.ui b/data/ui/notes.ui
index 267477d..c5256ac 100644
--- a/data/ui/notes.ui
+++ b/data/ui/notes.ui
@@ -126,6 +126,14 @@
Keyboard Shortcuts
win.show-help-overlay
+ -
+ Import Notes…
+ app.import
+
+ -
+ Export All Notes…
+ app.export
+
-
About Sticky Notes
app.about
diff --git a/po/pt_BR.po b/po/pt_BR.po
index b65cc0a..8261d26 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -504,3 +504,24 @@ msgstr "Nota vazia"
#~ msgid "First beta release!"
#~ msgstr "Primeiro lançamento beta!"
+
+#: src/application.ts:552
+msgid "Export Notes"
+msgstr "Exportar notas"
+
+#: src/application.ts:581
+msgid "Failed to Export Notes"
+msgstr "Falha ao exportar notas"
+
+#: src/application.ts:584 src/application.ts:613
+msgid "OK"
+msgstr "OK"
+
+#: src/application.ts:592
+msgid "Import Notes"
+msgstr "Importar notas"
+
+#: src/application.ts:610
+msgid "Failed to Import Notes"
+msgstr "Falha ao importar notas"
+
diff --git a/src/application.ts b/src/application.ts
index 8d29ddc..61dadaf 100644
--- a/src/application.ts
+++ b/src/application.ts
@@ -31,7 +31,7 @@ import Gtk from "gi://Gtk?version=4.0";
import Gdk from "gi://Gdk?version=4.0";
import { StickyNotes } from "./notes.js";
-import { Note, settings } from "./util.js";
+import { INote, Note, settings } from "./util.js";
import {
delete_note,
load_notes,
@@ -293,6 +293,14 @@ export class Application extends Adw.Application {
save.connect("activate", () => this.save());
this.add_action(save);
+ const export_notes = new Gio.SimpleAction({ name: "export" });
+ export_notes.connect("activate", () => this.export_notes());
+ this.add_action(export_notes);
+
+ const import_notes = new Gio.SimpleAction({ name: "import" });
+ import_notes.connect("activate", () => this.import_notes());
+ this.add_action(import_notes);
+
this.add_action(settings.create_action("color-scheme"));
this.set_accels_for_action("app.quit", ["q"]);
@@ -539,6 +547,87 @@ export class Application extends Adw.Application {
window.present();
}
+ async export_notes() {
+ const dialog = new Gtk.FileDialog({
+ title: _("Export Notes"),
+ initial_name: "notes.json",
+ });
+
+ try {
+ const file = await dialog.save(this.active_window, null);
+ if (!file) return;
+
+ const notes = this.notes_array();
+ const data = {
+ v: 1,
+ notes: notes.map((n) => n.toJSON()),
+ };
+ const encoder = new TextEncoder();
+ file.replace_contents(
+ encoder.encode(JSON.stringify(data, null, 2)),
+ null,
+ false,
+ Gio.FileCreateFlags.NONE,
+ null,
+ );
+ } catch (err) {
+ if (err instanceof GLib.Error && err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED)) {
+ return;
+ }
+ console.error("Failed to export notes:", err);
+ const error_dialog = Adw.MessageDialog.new(
+ this.active_window,
+ _("Failed to Export Notes"),
+ err instanceof Error ? err.message : String(err)
+ );
+ error_dialog.add_response("ok", _("OK"));
+ error_dialog.present();
+ }
+ }
+
+ async import_notes() {
+ const dialog = new Gtk.FileDialog({
+ title: _("Import Notes"),
+ });
+
+ try {
+ const file = await dialog.open(this.active_window, null);
+ if (!file) return;
+
+ const [success, contents] = file.load_contents(null);
+ if (!success) return;
+
+ const decoder = new TextDecoder();
+ const data = JSON.parse(decoder.decode(contents));
+
+ if (data.v !== 1 || !Array.isArray(data.notes)) return;
+
+ data.notes.forEach((noteData: INote) => {
+ // Avoid duplicates by UUID
+ if (!this.find_note(noteData.uuid)) {
+ const note = new Note(noteData);
+ note.connect("notify::modified", () => save_note(note));
+ note.connect("notify::open", () => save_note(note));
+ this.notes_list.append(note);
+ save_note(note);
+ }
+ });
+ this.sort_notes();
+ } catch (err) {
+ if (err instanceof GLib.Error && err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED)) {
+ return;
+ }
+ console.error("Failed to import notes:", err);
+ const error_dialog = Adw.MessageDialog.new(
+ this.active_window,
+ _("Failed to Import Notes"),
+ err instanceof Error ? err.message : String(err)
+ );
+ error_dialog.add_response("ok", _("OK"));
+ error_dialog.present();
+ }
+ }
+
vfunc_startup() {
super.vfunc_startup();
diff --git a/src/util.ts b/src/util.ts
index 66a36c2..a3f322a 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -41,6 +41,9 @@ Gio._promisify(
"replace_contents_finish",
);
+Gio._promisify(Gtk.FileDialog.prototype, "save", "save_finish");
+Gio._promisify(Gtk.FileDialog.prototype, "open", "open_finish");
+
export interface ITag {
name: string;
start: number;