mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-29 18:38:36 +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
|
||||
example:
|
||||
title: Example Title
|
||||
titleMetaData:
|
||||
title: Title
|
||||
fromH1: Text Header 1
|
||||
fromYaml: From YAML 'title'
|
||||
privacy: Privacy Policy
|
||||
terms: Terms and Conditions
|
||||
experimental:
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user