mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-28 01:45:55 +08:00
Preserve the YAML sorting order
Also refactor this a bit so Note contains a NoteData. I'm really not convinced about today's abstractions, and feel I might have made this entire thing much much worse.
This commit is contained in:
@ -13,18 +13,17 @@ enum NoteLoadState {
|
|||||||
class Note implements Comparable<Note> {
|
class Note implements Comparable<Note> {
|
||||||
String filePath = "";
|
String filePath = "";
|
||||||
DateTime created;
|
DateTime created;
|
||||||
String body = "";
|
NoteData data = NoteData();
|
||||||
|
|
||||||
var _loadState = NoteLoadState.None;
|
var _loadState = NoteLoadState.None;
|
||||||
var _serializer = MarkdownYAMLSerializer();
|
var _serializer = MarkdownYAMLSerializer();
|
||||||
|
|
||||||
// FIXME: Make it an ordered Map
|
Note([this.filePath]) {
|
||||||
Map<String, dynamic> props = {};
|
|
||||||
|
|
||||||
Note({this.created, this.body, this.filePath, this.props}) {
|
|
||||||
created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
props = props ?? <String, dynamic>{};
|
}
|
||||||
body = body ?? "";
|
|
||||||
|
String get body {
|
||||||
|
return data.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasValidDate() {
|
bool hasValidDate() {
|
||||||
@ -45,15 +44,12 @@ class Note implements Comparable<Note> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final string = await file.readAsString();
|
final string = await file.readAsString();
|
||||||
var noteData = _serializer.decode(string);
|
data = _serializer.decode(string);
|
||||||
|
|
||||||
body = noteData.body;
|
if (data.props.containsKey("created")) {
|
||||||
props = noteData.props;
|
var createdStr = data.props['created'].toString();
|
||||||
|
|
||||||
if (props.containsKey("created")) {
|
|
||||||
var createdStr = props['created'].toString();
|
|
||||||
try {
|
try {
|
||||||
created = DateTime.parse(props['created']).toLocal();
|
created = DateTime.parse(data.props['created']).toLocal();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Ignore it
|
// Ignore it
|
||||||
}
|
}
|
||||||
@ -80,13 +76,16 @@ class Note implements Comparable<Note> {
|
|||||||
// FIXME: What about error handling?
|
// FIXME: What about error handling?
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
assert(filePath != null);
|
assert(filePath != null);
|
||||||
|
assert(data != null);
|
||||||
|
assert(data.body != null);
|
||||||
|
assert(data.props != null);
|
||||||
|
|
||||||
if (hasValidDate()) {
|
if (hasValidDate()) {
|
||||||
props['created'] = toIso8601WithTimezone(created);
|
data.props['created'] = toIso8601WithTimezone(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = File(filePath);
|
var file = File(filePath);
|
||||||
var contents = _serializer.encode(NoteData(body, props));
|
var contents = _serializer.encode(data);
|
||||||
await file.writeAsString(contents);
|
await file.writeAsString(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +97,7 @@ class Note implements Comparable<Note> {
|
|||||||
|
|
||||||
// FIXME: Can't this part be auto-generated?
|
// FIXME: Can't this part be auto-generated?
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => filePath.hashCode ^ created.hashCode ^ data.hashCode;
|
||||||
filePath.hashCode ^ created.hashCode ^ body.hashCode ^ props.hashCode;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@ -107,19 +105,11 @@ class Note implements Comparable<Note> {
|
|||||||
other is Note &&
|
other is Note &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
filePath == other.filePath &&
|
filePath == other.filePath &&
|
||||||
body == other.body &&
|
data == other.data;
|
||||||
created == other.created &&
|
|
||||||
_equalMaps(props, other.props);
|
|
||||||
|
|
||||||
static bool _equalMaps(Map a, Map b) {
|
|
||||||
if (a.length != b.length) return false;
|
|
||||||
return a.keys
|
|
||||||
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Note{filePath: $filePath, body: $body, created: $created, props: $props}';
|
return 'Note{filePath: $filePath, created: $created, data: $data}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -57,7 +57,7 @@ class NoteEditorState extends State<NoteEditor> {
|
|||||||
icon: Icon(Icons.check),
|
icon: Icon(Icons.check),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final stateContainer = StateContainer.of(context);
|
final stateContainer = StateContainer.of(context);
|
||||||
note.body = _textController.text;
|
note.data.body = _textController.text;
|
||||||
if (note.body.isNotEmpty) {
|
if (note.body.isNotEmpty) {
|
||||||
newNote
|
newNote
|
||||||
? stateContainer.addNote(note)
|
? stateContainer.addNote(note)
|
||||||
|
@ -84,7 +84,7 @@ class GitNoteRepository {
|
|||||||
var notes = <Note>[];
|
var notes = <Note>[];
|
||||||
var lister = dir.list(recursive: false);
|
var lister = dir.list(recursive: false);
|
||||||
await for (var fileEntity in lister) {
|
await for (var fileEntity in lister) {
|
||||||
var note = Note(filePath: fileEntity.path);
|
var note = Note(fileEntity.path);
|
||||||
if (!note.filePath.toLowerCase().endsWith('.md')) {
|
if (!note.filePath.toLowerCase().endsWith('.md')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:fimber/fimber.dart';
|
import 'package:fimber/fimber.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
class NoteData {
|
class NoteData {
|
||||||
String body;
|
String body = "";
|
||||||
Map<String, dynamic> props = {};
|
LinkedHashMap<String, dynamic> props = LinkedHashMap<String, dynamic>();
|
||||||
|
|
||||||
NoteData(this.body, this.props);
|
NoteData([this.body, this.props]) {
|
||||||
|
body = body ?? "";
|
||||||
|
// ignore: prefer_collection_literals
|
||||||
|
props = props ?? LinkedHashMap<String, dynamic>();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => body.hashCode ^ props.hashCode;
|
int get hashCode => body.hashCode ^ props.hashCode;
|
||||||
@ -23,6 +29,11 @@ class NoteData {
|
|||||||
return a.keys
|
return a.keys
|
||||||
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
|
.every((dynamic key) => b.containsKey(key) && a[key] == b[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NoteData{bodt: $body, props: $props}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class NoteSerializer {
|
abstract class NoteSerializer {
|
||||||
@ -54,7 +65,7 @@ class MarkdownYAMLSerializer implements NoteSerializer {
|
|||||||
return NoteData(body, map);
|
return NoteData(body, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NoteData(str, <String, dynamic>{});
|
return NoteData(str, LinkedHashMap<String, dynamic>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -578,7 +578,7 @@ packages:
|
|||||||
name: yaml
|
name: yaml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.16"
|
version: "2.2.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.4.0 <3.0.0"
|
dart: ">=2.4.0 <3.0.0"
|
||||||
flutter: ">=1.6.0 <2.0.0"
|
flutter: ">=1.6.0 <2.0.0"
|
||||||
|
@ -8,7 +8,7 @@ dependencies:
|
|||||||
intl: "^0.15.6"
|
intl: "^0.15.6"
|
||||||
path: ^1.6.2
|
path: ^1.6.2
|
||||||
uuid: ^2.0.1
|
uuid: ^2.0.1
|
||||||
yaml: ^2.1.15
|
yaml: ^2.2.0
|
||||||
firebase_analytics: ^4.0.2
|
firebase_analytics: ^4.0.2
|
||||||
flutter_crashlytics: ^1.0.0
|
flutter_crashlytics: ^1.0.0
|
||||||
shared_preferences: ^0.5.2
|
shared_preferences: ^0.5.2
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:collection';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:journal/datetime_utils.dart';
|
||||||
import 'package:journal/note.dart';
|
import 'package:journal/note.dart';
|
||||||
|
import 'package:journal/storage/serializers.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@ -15,12 +18,20 @@ void main() {
|
|||||||
tempDir = await Directory.systemTemp.createTemp('__storage_test__');
|
tempDir = await Directory.systemTemp.createTemp('__storage_test__');
|
||||||
|
|
||||||
var dt = DateTime(2019, 12, 2, 5, 4, 2);
|
var dt = DateTime(2019, 12, 2, 5, 4, 2);
|
||||||
|
// ignore: prefer_collection_literals
|
||||||
|
var props = LinkedHashMap<String, dynamic>();
|
||||||
|
props['created'] = toIso8601WithTimezone(dt);
|
||||||
|
|
||||||
n1Path = p.join(tempDir.path, "1.md");
|
n1Path = p.join(tempDir.path, "1.md");
|
||||||
n2Path = p.join(tempDir.path, "2.md");
|
n2Path = p.join(tempDir.path, "2.md");
|
||||||
notes = <Note>[
|
|
||||||
Note(filePath: n1Path, body: "test", created: dt),
|
var n1 = Note(n1Path);
|
||||||
Note(filePath: n2Path, body: "test2", created: dt),
|
n1.data = NoteData("test", props);
|
||||||
];
|
|
||||||
|
var n2 = Note(n2Path);
|
||||||
|
n2.data = NoteData("test2", props);
|
||||||
|
|
||||||
|
notes = [n1, n2];
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDownAll(() async {
|
tearDownAll(() async {
|
||||||
@ -37,7 +48,7 @@ void main() {
|
|||||||
|
|
||||||
var loadedNotes = <Note>[];
|
var loadedNotes = <Note>[];
|
||||||
await Future.forEach(notes, (origNote) async {
|
await Future.forEach(notes, (origNote) async {
|
||||||
var note = Note(filePath: origNote.filePath);
|
var note = Note(origNote.filePath);
|
||||||
var r = await note.load();
|
var r = await note.load();
|
||||||
expect(r, NoteLoadState.Loaded);
|
expect(r, NoteLoadState.Loaded);
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:journal/storage/serializers.dart';
|
import 'package:journal/storage/serializers.dart';
|
||||||
import 'package:journal/datetime_utils.dart';
|
import 'package:journal/datetime_utils.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
@ -10,7 +12,8 @@ DateTime nowWithoutMicro() {
|
|||||||
void main() {
|
void main() {
|
||||||
group('Serializers', () {
|
group('Serializers', () {
|
||||||
var created = toIso8601WithTimezone(nowWithoutMicro());
|
var created = toIso8601WithTimezone(nowWithoutMicro());
|
||||||
var note = NoteData("This is the body", {"created": created});
|
var note =
|
||||||
|
NoteData("This is the body", LinkedHashMap.from({"created": created}));
|
||||||
|
|
||||||
test('Markdown Serializer', () {
|
test('Markdown Serializer', () {
|
||||||
var serializer = MarkdownYAMLSerializer();
|
var serializer = MarkdownYAMLSerializer();
|
||||||
@ -47,7 +50,6 @@ Alright.""";
|
|||||||
expect(actualStr, note.body);
|
expect(actualStr, note.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
test('Markdown Serializer YAML Order', () {
|
test('Markdown Serializer YAML Order', () {
|
||||||
var str = """---
|
var str = """---
|
||||||
type: Journal
|
type: Journal
|
||||||
@ -63,6 +65,5 @@ Alright.""";
|
|||||||
|
|
||||||
expect(actualStr, str);
|
expect(actualStr, str);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user