mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-07-01 04:07:53 +08:00
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:
@ -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:
|
||||||
|
@ -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) {
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user