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
example:
title: Example Title
titleMetaData:
title: Title
fromH1: Text Header 1
fromYaml: From YAML 'title'
privacy: Privacy Policy
terms: Terms and Conditions
experimental:

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter_emoji/flutter_emoji.dart';
import 'package:yaml/yaml.dart';
@ -20,6 +22,8 @@ class NoteSerializationSettings {
String titleKey = "title";
String typeKey = "type";
String tagsKey = "tags";
bool saveTitleAsH1 = Settings.instance.saveTitleInH1;
}
class NoteSerializer implements NoteSerializerInterface {
@ -27,6 +31,8 @@ class NoteSerializer implements NoteSerializerInterface {
@override
void encode(Note note, MdYamlDoc data) {
data.body = emojiParser.unemojify(note.body);
if (note.created != null) {
data.props[settings.createdKey] = toIso8601WithTimezone(note.created);
} else {
@ -40,11 +46,17 @@ class NoteSerializer implements NoteSerializerInterface {
}
if (note.title != null) {
var title = note.title.trim();
if (title.isNotEmpty) {
data.props[settings.titleKey] = emojiParser.unemojify(note.title);
var title = emojiParser.unemojify(note.title.trim());
if (settings.saveTitleAsH1) {
if (title.isNotEmpty) {
data.body = '# $title\n\n${data.body}';
}
} else {
data.props.remove(settings.titleKey);
if (title.isNotEmpty) {
data.props[settings.titleKey] = title;
} else {
data.props.remove(settings.titleKey);
}
}
} else {
data.props.remove(settings.titleKey);
@ -62,8 +74,6 @@ class NoteSerializer implements NoteSerializerInterface {
} else {
data.props[settings.tagsKey] = note.tags.toList();
}
data.body = emojiParser.unemojify(note.body);
}
@override
@ -89,8 +99,29 @@ class NoteSerializer implements NoteSerializerInterface {
note.body = emojiParser.emojify(data.body);
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];
switch (type) {

View File

@ -1,11 +1,15 @@
import 'package:flutter/material.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/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/settings.dart';
import 'package:gitjournal/utils/datetime.dart';
import 'package:gitjournal/widgets/pro_overlay.dart';
class NoteMetadataSettingsScreen extends StatefulWidget {
@ -19,11 +23,12 @@ class _NoteMetadataSettingsScreenState
@override
Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme;
String yamlHeader = " \n";
if (Settings.instance.yamlHeaderEnabled) {
var map = _buildMap();
yamlHeader = MarkdownYAMLCodec.toYamlHeader(map).trim();
}
// FIXME: Translate these
var note = Note(null, "fileName.md");
note.title = "Pigeons";
note.body = _buildBody();
note.created = DateTime.now();
var body = Column(
children: <Widget>[
@ -35,7 +40,9 @@ class _NoteMetadataSettingsScreenState
),
),
const SizedBox(height: 16.0),
NoteMetaDataExample(yamlHeader),
NoteInputExample(note),
const SizedBox(height: 16.0),
NoteOutputExample(note),
const SizedBox(height: 16.0),
const Divider(),
SwitchListTile(
@ -44,6 +51,9 @@ class _NoteMetadataSettingsScreenState
onChanged: (bool newVal) {
setState(() {
Settings.instance.yamlHeaderEnabled = newVal;
if (newVal == false) {
Settings.instance.saveTitleInH1 = true;
}
Settings.instance.save();
});
},
@ -67,6 +77,26 @@ class _NoteMetadataSettingsScreenState
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() {
var created = DateTime.now();
return {
'created': toIso8601WithTimezone(created),
Settings.instance.yamlModifiedKey: toIso8601WithTimezone(created),
'title': tr("settings.noteMetaData.example.title"),
};
// FIXME: Add some random text
String _buildBody() {
return "I think they might be evil. Even more evil than penguins.";
}
}
class NoteMetaDataExample extends StatelessWidget {
final String yamlHeader;
class NoteOutputExample extends StatelessWidget {
final Note note;
NoteMetaDataExample(this.yamlHeader);
NoteOutputExample(this.note);
@override
Widget build(BuildContext context) {
@ -105,10 +131,82 @@ class NoteMetaDataExample extends StatelessWidget {
var style = theme.textTheme.subtitle1;
style = style.copyWith(fontFamily: "Roboto Mono");
var doc = MdYamlDoc();
NoteSerializer().encode(note, doc);
var codec = MarkdownYAMLCodec();
var noteStr = codec.encode(doc);
return Container(
color: theme.highlightColor,
child: Text(yamlHeader, style: style),
padding: const EdgeInsets.all(0),
child: Stack(
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 zenMode = false;
bool saveTitleInH1 = true;
void load(SharedPreferences pref) {
gitAuthor = pref.getString("gitAuthor") ?? gitAuthor;
@ -118,6 +119,7 @@ class Settings {
experimentalFs = pref.getBool("experimentalFs") ?? experimentalFs;
zenMode = pref.getBool("zenMode") ?? zenMode;
saveTitleInH1 = pref.getBool("saveTitleInH1") ?? saveTitleInH1;
}
Future save() async {
@ -184,6 +186,7 @@ class Settings {
defaultSet.experimentalBacklinks);
_setBool(pref, "experimentalFs", experimentalFs, defaultSet.experimentalFs);
_setBool(pref, "zenMode", zenMode, defaultSet.zenMode);
_setBool(pref, "saveTitleInH1", saveTitleInH1, defaultSet.saveTitleInH1);
pref.setInt("settingsVersion", version);
}
@ -244,6 +247,7 @@ class Settings {
'experimentalBacklinks': experimentalBacklinks.toString(),
'experimentalFs': experimentalFs.toString(),
'zenMode': zenMode.toString(),
'saveTitleInH1': saveTitleInH1.toString(),
};
}

View File

@ -14,6 +14,8 @@ void main() {
var doc = MdYamlDoc("I :heart: you", props);
var serializer = NoteSerializer();
serializer.settings.saveTitleAsH1 = false;
var note = Note(null, "file-path-not-important");
serializer.decode(doc, note);
@ -27,5 +29,26 @@ void main() {
expect(doc.body, "Why not :coffee:?");
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);
});
});
}