Allow 'title' saving to be configured

We now allow it be to either saved as the H1 or in the YAML metadata.
I'm not sure what the default should be. Still deciding.

Fixes #112
This commit is contained in:
Vishesh Handa
2020-07-28 17:25:14 +02:00
parent 15b978cacb
commit 41447027f2
5 changed files with 188 additions and 28 deletions

View File

@ -45,6 +45,10 @@ settings:
modified: Modified Field modified: Modified Field
example: example:
title: Example Title title: Example Title
titleMetaData:
title: Title
fromH1: Text Header 1
fromYaml: From YAML 'title'
privacy: Privacy Policy privacy: Privacy Policy
terms: Terms and Conditions terms: Terms and Conditions
experimental: experimental:

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter_emoji/flutter_emoji.dart'; import 'package:flutter_emoji/flutter_emoji.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
@ -20,6 +22,8 @@ class NoteSerializationSettings {
String titleKey = "title"; String titleKey = "title";
String typeKey = "type"; String typeKey = "type";
String tagsKey = "tags"; String tagsKey = "tags";
bool saveTitleAsH1 = Settings.instance.saveTitleInH1;
} }
class NoteSerializer implements NoteSerializerInterface { class NoteSerializer implements NoteSerializerInterface {
@ -27,6 +31,8 @@ class NoteSerializer implements NoteSerializerInterface {
@override @override
void encode(Note note, MdYamlDoc data) { void encode(Note note, MdYamlDoc data) {
data.body = emojiParser.unemojify(note.body);
if (note.created != null) { if (note.created != null) {
data.props[settings.createdKey] = toIso8601WithTimezone(note.created); data.props[settings.createdKey] = toIso8601WithTimezone(note.created);
} else { } else {
@ -40,11 +46,17 @@ class NoteSerializer implements NoteSerializerInterface {
} }
if (note.title != null) { if (note.title != null) {
var title = note.title.trim(); var title = emojiParser.unemojify(note.title.trim());
if (title.isNotEmpty) { if (settings.saveTitleAsH1) {
data.props[settings.titleKey] = emojiParser.unemojify(note.title); if (title.isNotEmpty) {
data.body = '# $title\n\n${data.body}';
}
} else { } else {
data.props.remove(settings.titleKey); if (title.isNotEmpty) {
data.props[settings.titleKey] = title;
} else {
data.props.remove(settings.titleKey);
}
} }
} else { } else {
data.props.remove(settings.titleKey); data.props.remove(settings.titleKey);
@ -62,8 +74,6 @@ class NoteSerializer implements NoteSerializerInterface {
} else { } else {
data.props[settings.tagsKey] = note.tags.toList(); data.props[settings.tagsKey] = note.tags.toList();
} }
data.body = emojiParser.unemojify(note.body);
} }
@override @override
@ -89,8 +99,29 @@ class NoteSerializer implements NoteSerializerInterface {
note.body = emojiParser.emojify(data.body); note.body = emojiParser.emojify(data.body);
note.created = parseDateTime(data.props[settings.createdKey]?.toString()); note.created = parseDateTime(data.props[settings.createdKey]?.toString());
var title = data.props[settings.titleKey]?.toString() ?? ""; //
note.title = emojiParser.emojify(title); // Title parsing
//
if (settings.saveTitleAsH1) {
if (note.body.startsWith('#')) {
var titleEndIndex = note.body.indexOf('\n');
if (titleEndIndex == -1 || titleEndIndex == note.body.length) {
note.title = note.body.substring(1).trim();
note.body = "";
} else {
note.title = note.body.substring(1, titleEndIndex).trim();
note.body = note.body.substring(titleEndIndex + 1).trim();
}
}
for (var line in LineSplitter.split(note.body)) {
if (note.title.isEmpty && line.trim().isEmpty) {
continue;
}
}
} else {
var title = data.props[settings.titleKey]?.toString() ?? "";
note.title = emojiParser.emojify(title);
}
var type = data.props[settings.typeKey]; var type = data.props[settings.typeKey];
switch (type) { switch (type) {

View File

@ -1,11 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:gitjournal/core/md_yaml_doc.dart';
import 'package:gitjournal/core/md_yaml_doc_codec.dart'; import 'package:gitjournal/core/md_yaml_doc_codec.dart';
import 'package:gitjournal/core/note.dart';
import 'package:gitjournal/core/note_serializer.dart';
import 'package:gitjournal/editors/note_body_editor.dart';
import 'package:gitjournal/editors/note_title_editor.dart';
import 'package:gitjournal/screens/settings_widgets.dart'; import 'package:gitjournal/screens/settings_widgets.dart';
import 'package:gitjournal/settings.dart'; import 'package:gitjournal/settings.dart';
import 'package:gitjournal/utils/datetime.dart';
import 'package:gitjournal/widgets/pro_overlay.dart'; import 'package:gitjournal/widgets/pro_overlay.dart';
class NoteMetadataSettingsScreen extends StatefulWidget { class NoteMetadataSettingsScreen extends StatefulWidget {
@ -19,11 +23,12 @@ class _NoteMetadataSettingsScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme; var textTheme = Theme.of(context).textTheme;
String yamlHeader = " \n";
if (Settings.instance.yamlHeaderEnabled) { // FIXME: Translate these
var map = _buildMap(); var note = Note(null, "fileName.md");
yamlHeader = MarkdownYAMLCodec.toYamlHeader(map).trim(); note.title = "Pigeons";
} note.body = _buildBody();
note.created = DateTime.now();
var body = Column( var body = Column(
children: <Widget>[ children: <Widget>[
@ -35,7 +40,9 @@ class _NoteMetadataSettingsScreenState
), ),
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
NoteMetaDataExample(yamlHeader), NoteInputExample(note),
const SizedBox(height: 16.0),
NoteOutputExample(note),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
const Divider(), const Divider(),
SwitchListTile( SwitchListTile(
@ -44,6 +51,9 @@ class _NoteMetadataSettingsScreenState
onChanged: (bool newVal) { onChanged: (bool newVal) {
setState(() { setState(() {
Settings.instance.yamlHeaderEnabled = newVal; Settings.instance.yamlHeaderEnabled = newVal;
if (newVal == false) {
Settings.instance.saveTitleInH1 = true;
}
Settings.instance.save(); Settings.instance.save();
}); });
}, },
@ -67,6 +77,26 @@ class _NoteMetadataSettingsScreenState
enabled: Settings.instance.yamlHeaderEnabled, enabled: Settings.instance.yamlHeaderEnabled,
), ),
), ),
ProOverlay(
child: ListPreference(
title: tr("settings.noteMetaData.titleMetaData.title"),
options: [
tr("settings.noteMetaData.titleMetaData.fromH1"),
if (Settings.instance.yamlHeaderEnabled)
tr("settings.noteMetaData.titleMetaData.fromYaml"),
],
currentOption: Settings.instance.saveTitleInH1
? tr("settings.noteMetaData.titleMetaData.fromH1")
: tr("settings.noteMetaData.titleMetaData.fromYaml"),
onChange: (String newVal) {
setState(() {
Settings.instance.saveTitleInH1 =
newVal == tr("settings.noteMetaData.titleMetaData.fromH1");
Settings.instance.save();
});
},
),
),
], ],
); );
@ -80,24 +110,20 @@ class _NoteMetadataSettingsScreenState
}, },
), ),
), ),
body: body, body: SingleChildScrollView(child: body),
); );
} }
Map<String, dynamic> _buildMap() { // FIXME: Add some random text
var created = DateTime.now(); String _buildBody() {
return { return "I think they might be evil. Even more evil than penguins.";
'created': toIso8601WithTimezone(created),
Settings.instance.yamlModifiedKey: toIso8601WithTimezone(created),
'title': tr("settings.noteMetaData.example.title"),
};
} }
} }
class NoteMetaDataExample extends StatelessWidget { class NoteOutputExample extends StatelessWidget {
final String yamlHeader; final Note note;
NoteMetaDataExample(this.yamlHeader); NoteOutputExample(this.note);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -105,10 +131,82 @@ class NoteMetaDataExample extends StatelessWidget {
var style = theme.textTheme.subtitle1; var style = theme.textTheme.subtitle1;
style = style.copyWith(fontFamily: "Roboto Mono"); style = style.copyWith(fontFamily: "Roboto Mono");
var doc = MdYamlDoc();
NoteSerializer().encode(note, doc);
var codec = MarkdownYAMLCodec();
var noteStr = codec.encode(doc);
return Container( return Container(
color: theme.highlightColor, color: theme.highlightColor,
child: Text(yamlHeader, style: style), child: Stack(
padding: const EdgeInsets.all(0), children: <Widget>[
Padding(
padding: const EdgeInsets.all(32.0),
child: Text(noteStr, style: style),
),
_FileNameText(note.fileName),
],
),
);
}
}
class NoteInputExample extends StatelessWidget {
final Note note;
NoteInputExample(this.note);
@override
Widget build(BuildContext context) {
final titleController = TextEditingController(text: note.title);
final bodyController = TextEditingController(text: note.body);
var theme = Theme.of(context);
return IgnorePointer(
child: Container(
color: theme.highlightColor,
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
children: <Widget>[
NoteTitleEditor(titleController, () {}),
NoteBodyEditor(
textController: bodyController,
autofocus: false,
onChanged: () {},
),
],
),
),
_FileNameText(note.fileName),
],
),
),
);
}
}
class _FileNameText extends StatelessWidget {
final String text;
_FileNameText(this.text);
@override
Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme;
return Positioned.fill(
child: Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(text, style: textTheme.caption),
),
),
); );
} }
} }

View File

@ -53,6 +53,7 @@ class Settings {
bool experimentalFs = false; bool experimentalFs = false;
bool zenMode = false; bool zenMode = false;
bool saveTitleInH1 = true;
void load(SharedPreferences pref) { void load(SharedPreferences pref) {
gitAuthor = pref.getString("gitAuthor") ?? gitAuthor; gitAuthor = pref.getString("gitAuthor") ?? gitAuthor;
@ -118,6 +119,7 @@ class Settings {
experimentalFs = pref.getBool("experimentalFs") ?? experimentalFs; experimentalFs = pref.getBool("experimentalFs") ?? experimentalFs;
zenMode = pref.getBool("zenMode") ?? zenMode; zenMode = pref.getBool("zenMode") ?? zenMode;
saveTitleInH1 = pref.getBool("saveTitleInH1") ?? saveTitleInH1;
} }
Future save() async { Future save() async {
@ -184,6 +186,7 @@ class Settings {
defaultSet.experimentalBacklinks); defaultSet.experimentalBacklinks);
_setBool(pref, "experimentalFs", experimentalFs, defaultSet.experimentalFs); _setBool(pref, "experimentalFs", experimentalFs, defaultSet.experimentalFs);
_setBool(pref, "zenMode", zenMode, defaultSet.zenMode); _setBool(pref, "zenMode", zenMode, defaultSet.zenMode);
_setBool(pref, "saveTitleInH1", saveTitleInH1, defaultSet.saveTitleInH1);
pref.setInt("settingsVersion", version); pref.setInt("settingsVersion", version);
} }
@ -244,6 +247,7 @@ class Settings {
'experimentalBacklinks': experimentalBacklinks.toString(), 'experimentalBacklinks': experimentalBacklinks.toString(),
'experimentalFs': experimentalFs.toString(), 'experimentalFs': experimentalFs.toString(),
'zenMode': zenMode.toString(), 'zenMode': zenMode.toString(),
'saveTitleInH1': saveTitleInH1.toString(),
}; };
} }

View File

@ -14,6 +14,8 @@ void main() {
var doc = MdYamlDoc("I :heart: you", props); var doc = MdYamlDoc("I :heart: you", props);
var serializer = NoteSerializer(); var serializer = NoteSerializer();
serializer.settings.saveTitleAsH1 = false;
var note = Note(null, "file-path-not-important"); var note = Note(null, "file-path-not-important");
serializer.decode(doc, note); serializer.decode(doc, note);
@ -27,5 +29,26 @@ void main() {
expect(doc.body, "Why not :coffee:?"); expect(doc.body, "Why not :coffee:?");
expect(doc.props['title'].toString(), "I :heart: you"); expect(doc.props['title'].toString(), "I :heart: you");
}); });
test('Test Title', () {
var props = <String, dynamic>{};
var doc = MdYamlDoc("# Why not :coffee:?\n\nI :heart: you", props);
var serializer = NoteSerializer();
serializer.settings.saveTitleAsH1 = true;
var note = Note(null, "file-path-not-important");
serializer.decode(doc, note);
expect(note.body, "I ❤️ you");
expect(note.title, "Why not ☕?");
note.body = "Why not ☕?";
note.title = "I ❤️ you";
serializer.encode(note, doc);
expect(doc.body, "# I :heart: you\n\nWhy not :coffee:?");
expect(doc.props.length, 0);
});
}); });
} }