Skip to content

Commit 2e1a1fc

Browse files
Merge pull request #9 from owlistic-notes/import-export
Import from markdown
2 parents 4cdb206 + a1b251b commit 2e1a1fc

File tree

10 files changed

+1123
-398
lines changed

10 files changed

+1123
-398
lines changed

src/frontend/lib/providers/note_editor_provider.dart

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
33
import 'package:super_editor/super_editor.dart' hide Logger;
4+
import 'package:super_editor_markdown/super_editor_markdown.dart';
45

56
import 'package:owlistic/services/block_service.dart';
67
import 'package:owlistic/services/auth_service.dart';
@@ -1526,17 +1527,6 @@ class NoteEditorProvider with ChangeNotifier implements NoteEditorViewModel {
15261527
}
15271528
}
15281529

1529-
@override
1530-
void updateBlockCache(List<Block> blocks) {
1531-
for (final block in blocks) {
1532-
// Only update if this block belongs to an active note
1533-
if (_noteId == block.noteId || _activeNoteIds.contains(block.noteId)) {
1534-
_blocks[block.id] = block;
1535-
_documentBuilder.registerServerBlock(block);
1536-
}
1537-
}
1538-
}
1539-
15401530
@override
15411531
void commitAllContent() {
15421532
_logger.debug('Committing content for all blocks');
@@ -1676,4 +1666,91 @@ class NoteEditorProvider with ChangeNotifier implements NoteEditorViewModel {
16761666
_logger.debug('Block type changed from ${block.type} to $detectedType');
16771667
}
16781668
}
1669+
1670+
@override
1671+
Future<void> importMarkdownContent(String markdown) async {
1672+
if (_noteId == null) {
1673+
throw Exception('Cannot import content: no active note');
1674+
}
1675+
1676+
_isLoading = true;
1677+
notifyListeners();
1678+
1679+
try {
1680+
_logger.info('Importing markdown content to note: $_noteId');
1681+
1682+
// Delete all existing blocks for this note
1683+
final existingBlocks = getBlocksForNote(_noteId!);
1684+
for (final block in existingBlocks) {
1685+
await _blockService.deleteBlock(block.id);
1686+
}
1687+
1688+
// Create blocks for each node
1689+
final document = _documentBuilder.deserializeMarkdownContent(markdown);
1690+
1691+
// Create blocks for each node
1692+
int order = 0;
1693+
for (final node in document) {
1694+
try {
1695+
final blockContent = _documentBuilder.buildBlockContent(node);
1696+
1697+
final blockType = blockContent['type'];
1698+
final payload = {
1699+
"metadata": blockContent['metadata'],
1700+
"content": blockContent['content'],
1701+
};
1702+
1703+
// Create block through BlockService
1704+
await _blockService.createBlock(
1705+
_noteId!,
1706+
payload,
1707+
blockType,
1708+
(order + 1) * 1000.0 // Use increasing order with gaps
1709+
);
1710+
1711+
order++;
1712+
} catch (e) {
1713+
_logger.error('Error creating block for imported markdown: $e');
1714+
}
1715+
}
1716+
1717+
// Refresh blocks from server
1718+
await fetchBlocksForNote(_noteId!, refresh: true);
1719+
1720+
_logger.info('Markdown content imported successfully');
1721+
} catch (e) {
1722+
_logger.error('Error importing markdown content', e);
1723+
_errorMessage = 'Failed to import markdown: ${e.toString()}';
1724+
} finally {
1725+
_isLoading = false;
1726+
_updateCount++;
1727+
notifyListeners();
1728+
}
1729+
}
1730+
1731+
@override
1732+
Future<String> exportToMarkdown() async {
1733+
try {
1734+
if (_noteId == null) {
1735+
throw Exception('Cannot export: no active note');
1736+
}
1737+
1738+
_logger.info('Exporting note $_noteId to markdown');
1739+
1740+
// Make sure all content is committed
1741+
commitAllContent();
1742+
1743+
// Serialize the document to markdown
1744+
final markdown = serializeDocumentToMarkdown(
1745+
_documentBuilder.document,
1746+
syntax: MarkdownSyntax.normal
1747+
);
1748+
1749+
_logger.debug('Note exported to markdown successfully');
1750+
return markdown;
1751+
} catch (e) {
1752+
_logger.error('Error exporting to markdown', e);
1753+
throw Exception('Failed to export note: ${e.toString()}');
1754+
}
1755+
}
16791756
}

src/frontend/lib/providers/notes_provider.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
3+
import 'package:owlistic/utils/document_builder.dart';
34
import 'package:owlistic/viewmodel/notes_viewmodel.dart';
45
import 'package:owlistic/models/note.dart';
56
import 'package:owlistic/services/note_service.dart';
@@ -10,6 +11,7 @@ import 'package:owlistic/utils/websocket_message_parser.dart';
1011
import 'package:owlistic/utils/logger.dart';
1112
import 'package:owlistic/services/block_service.dart';
1213
import 'package:owlistic/services/app_state_service.dart';
14+
import 'package:super_editor_markdown/super_editor_markdown.dart';
1315

1416
class NotesProvider with ChangeNotifier implements NotesViewModel {
1517
final Logger _logger = Logger('NotesProvider');
@@ -526,4 +528,104 @@ class NotesProvider with ChangeNotifier implements NotesViewModel {
526528
notifyListeners();
527529
}
528530
}
531+
532+
@override
533+
Future<Note?> importMarkdownFile(String content, String fileName, String notebookId) async {
534+
try {
535+
_logger.info('Importing markdown file: $fileName to notebook: $notebookId');
536+
537+
if (notebookId.isEmpty) {
538+
throw Exception("Notebook ID is required to import a note");
539+
}
540+
541+
// Extract title from filename (remove .md extension if present)
542+
String title = fileName;
543+
if (title.toLowerCase().endsWith('.md')) {
544+
title = title.substring(0, title.length - 3);
545+
}
546+
547+
// Create the note first
548+
final note = await _noteService.createNote(notebookId, title);
549+
_logger.debug('Created note: ${note.id} for markdown import');
550+
551+
final documentBuilder = DocumentBuilder();
552+
553+
// Create blocks for each node
554+
final document = documentBuilder.deserializeMarkdownContent(content);
555+
556+
// Create blocks for each node
557+
int order = 0;
558+
for (final node in document) {
559+
try {
560+
final blockContent = documentBuilder.buildBlockContent(node);
561+
562+
final blockType = blockContent['type'];
563+
final payload = {
564+
"metadata": blockContent['metadata'],
565+
"content": blockContent['content'],
566+
};
567+
568+
// Create block through BlockService
569+
await _blockService.createBlock(
570+
note.id,
571+
payload,
572+
blockType,
573+
(order + 1) * 1000.0 // Use increasing order with gaps
574+
);
575+
576+
order++;
577+
} catch (e) {
578+
_logger.error('Error creating block for imported markdown: $e');
579+
}
580+
}
581+
582+
// Add note to local state
583+
_notesMap[note.id] = note;
584+
_updateCount++;
585+
notifyListeners();
586+
587+
return note;
588+
} catch (error) {
589+
_logger.error('Error importing markdown file', error);
590+
_errorMessage = 'Failed to import markdown: ${error.toString()}';
591+
notifyListeners();
592+
return null;
593+
}
594+
}
595+
596+
@override
597+
Future<String> exportNoteToMarkdown(String noteId) async {
598+
try {
599+
_logger.info('Exporting note $noteId to markdown');
600+
601+
// Fetch note if not already in memory
602+
Note? note = _notesMap[noteId];
603+
note = await fetchNoteById(noteId);
604+
605+
if (note == null) {
606+
throw Exception("Note not found");
607+
}
608+
609+
// Fetch all blocks for the note to ensure we have the latest data
610+
final blocks = await _blockService.fetchBlocksForNote(noteId);
611+
612+
// Create a document from the blocks
613+
final documentBuilder = DocumentBuilder();
614+
615+
// Convert blocks to document nodes
616+
documentBuilder.populateDocumentFromBlocks(blocks);
617+
618+
// Serialize document to markdown
619+
final markdown = serializeDocumentToMarkdown(
620+
documentBuilder.document,
621+
syntax: MarkdownSyntax.normal
622+
);
623+
624+
_logger.debug('Note exported to markdown successfully');
625+
return markdown;
626+
} catch (error) {
627+
_logger.error('Error exporting note to markdown', error);
628+
throw Exception('Failed to export note: ${error.toString()}');
629+
}
630+
}
529631
}

0 commit comments

Comments
 (0)