Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #43

Closed
wants to merge 10 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
15 changes: 10 additions & 5 deletions doc/quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ Add `zefyr` package as a dependency to your `pubspec.yaml`:

```yaml
dependencies:
zefyr: ^0.1.0
zefyr: ^0.3.0
```

And run `flutter packages get` to install. This installs both `zefyr`
and `notus` packages.

### Usage

There are 3 main objects you would normally interact with in your code:
There are 4 main objects you would normally interact with in your code:

* `NotusDocument`, represents a rich text document and provides
high-level methods for manipulating the document's state, like
Expand All @@ -30,6 +30,9 @@ There are 3 main objects you would normally interact with in your code:
* `ZefyrEditor`, a Flutter widget responsible for rendering of rich text
on the screen and reacting to user actions.
* `ZefyrController`, ties the above two objects together.
* `ZefyrScaffold`, allows embedding Zefyr toolbar into any custom layout.

`ZefyrEditor` depends on presence of `ZefyrScaffold` somewhere up the widget tree.

Normally you would need to place `ZefyrEditor` inside of a
`StatefulWidget`. Shown below is a minimal setup required to use the
Expand Down Expand Up @@ -60,9 +63,11 @@ class MyWidgetState extends State<MyWidget> {

@override
Widget build(BuildContext context) {
return ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
return ZefyrScaffold(
child: ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
),
);
}
}
Expand Down
Empty file modified packages/notus/lib/convert.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/notus.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/convert/markdown.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/document.dart
100644 → 100755
Empty file.
10 changes: 9 additions & 1 deletion packages/notus/lib/src/document/attributes.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ class EmbedAttributeBuilder
NotusAttribute<Map<String, dynamic>> image(String source) =>
EmbedAttribute.image(source);

NotusAttribute<Map<String, dynamic>> goods(dynamic source) =>
EmbedAttribute.goods(source);

@override
NotusAttribute<Map<String, dynamic>> get unset => EmbedAttribute._(null);

Expand All @@ -398,13 +401,14 @@ class EmbedAttributeBuilder
}

/// Type of embedded content.
enum EmbedType { horizontalRule, image }
enum EmbedType { horizontalRule, image, goods }

class EmbedAttribute extends NotusAttribute<Map<String, dynamic>> {
static const _kValueEquality = const MapEquality<String, dynamic>();
static const _kEmbed = 'embed';
static const _kHorizontalRuleEmbed = 'hr';
static const _kImageEmbed = 'image';
static const _kGoodsEmbed = 'goods';

EmbedAttribute._(Map<String, dynamic> value)
: super._(_kEmbed, NotusAttributeScope.inline, value);
Expand All @@ -415,10 +419,14 @@ class EmbedAttribute extends NotusAttribute<Map<String, dynamic>> {
EmbedAttribute.image(String source)
: this._(<String, dynamic>{'type': _kImageEmbed, 'source': source});

EmbedAttribute.goods(dynamic source)
: this._(<String, dynamic>{'type': _kGoodsEmbed, 'source': source});

/// Type of this embed.
EmbedType get type {
if (value['type'] == _kHorizontalRuleEmbed) return EmbedType.horizontalRule;
if (value['type'] == _kImageEmbed) return EmbedType.image;
if (value['type'] == _kGoodsEmbed) return EmbedType.goods;
assert(false, 'Unknown embed attribute value $value.');
return null;
}
Expand Down
Empty file modified packages/notus/lib/src/document/block.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/document/leaf.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/document/line.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/document/node.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/heuristics.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/heuristics/delete_rules.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/heuristics/format_rules.dart
100644 → 100755
Empty file.
Empty file modified packages/notus/lib/src/heuristics/insert_rules.dart
100644 → 100755
Empty file.
14 changes: 14 additions & 0 deletions packages/zefyr/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 0.3.0

This version introduces new widget `ZefyrScaffold` which allows embedding Zefyr in custom
layouts, like forms with multiple input fields.

It is now required to always wrap `ZefyrEditor` with an instance of this new widget. See examples
and readme for more details.

There is also new `ZefyrField` widget which integrates Zefyr with material design decorations.

* Breaking change: `ZefyrEditor` requires an ancestor `ZefyrScaffold`.
* Exposed `ZefyrEditor.physics` property to allow customization of `ScrollPhysics`.
* Added basic `ZefyrField` widget.

## 0.2.0

* Breaking change: `ZefyrImageDelegate.createImageProvider` replaced with
Expand Down
127 changes: 35 additions & 92 deletions packages/zefyr/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,125 +1,68 @@
// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:quill_delta/quill_delta.dart';
import 'package:zefyr/zefyr.dart';

import 'src/full_page.dart';
import 'src/form.dart';

void main() {
runApp(new ZefyrApp());
}

class ZefyrLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Ze'),
FlutterLogo(size: 24.0),
Text('yr'),
],
);
}
}

class ZefyrApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Zefyr Editor',
theme: new ThemeData(primarySwatch: Colors.cyan),
home: new MyHomePage(),
theme: ThemeData(primarySwatch: Colors.cyan),
home: HomePage(),
routes: {
"/fullPage": buildFullPage,
"/form": buildFormPage,
},
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}

final doc =
r'[{"insert":"Zefyr"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":'
r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr'
r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle'
r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin'
r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]';
Widget buildFullPage(BuildContext context) {
return FullPageEditorScreen();
}

Delta getDelta() {
return Delta.fromJson(json.decode(doc));
Widget buildFormPage(BuildContext context) {
return FormEmbeddedScreen();
}
}

class _MyHomePageState extends State<MyHomePage> {
final ZefyrController _controller =
ZefyrController(NotusDocument.fromDelta(getDelta()));
final FocusNode _focusNode = new FocusNode();
bool _editing = false;

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = new ZefyrThemeData(
toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith(
color: Colors.grey.shade800,
toggleColor: Colors.grey.shade900,
iconColor: Colors.white,
disabledIconColor: Colors.grey.shade500,
),
);

final done = _editing
? [new FlatButton(onPressed: _stopEditing, child: Text('DONE'))]
: [new FlatButton(onPressed: _startEditing, child: Text('EDIT'))];
final nav = Navigator.of(context);
return Scaffold(
resizeToAvoidBottomPadding: true,
appBar: AppBar(
elevation: 1.0,
backgroundColor: Colors.grey.shade200,
brightness: Brightness.light,
title: ZefyrLogo(),
actions: done,
),
body: ZefyrTheme(
data: theme,
child: ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
enabled: _editing,
imageDelegate: new CustomImageDelegate(),
),
body: Column(
children: <Widget>[
Expanded(child: Container()),
FlatButton(
onPressed: () => nav.pushNamed('/fullPage'),
child: Text('Full page editor'),
color: Colors.lightBlue,
textColor: Colors.white,
),
FlatButton(
onPressed: () => nav.pushNamed('/form'),
child: Text('Embedded in a form'),
color: Colors.lightBlue,
textColor: Colors.white,
),
Expanded(child: Container()),
],
),
);
}

void _startEditing() {
setState(() {
_editing = true;
});
}

void _stopEditing() {
setState(() {
_editing = false;
});
}
}

/// Custom image delegate used by this example to load image from application
/// assets.
///
/// Default image delegate only supports [FileImage]s.
class CustomImageDelegate extends ZefyrDefaultImageDelegate {
@override
Widget buildImage(BuildContext context, String imageSource) {
// We use custom "asset" scheme to distinguish asset images from other files.
if (imageSource.startsWith('asset://')) {
final asset = new AssetImage(imageSource.replaceFirst('asset://', ''));
return new Image(image: asset);
} else {
return super.buildImage(context, imageSource);
}
}
}
65 changes: 65 additions & 0 deletions packages/zefyr/example/lib/src/form.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:zefyr/zefyr.dart';

import 'full_page.dart';

class FormEmbeddedScreen extends StatefulWidget {
@override
_FormEmbeddedScreenState createState() => _FormEmbeddedScreenState();
}

class _FormEmbeddedScreenState extends State<FormEmbeddedScreen> {
final ZefyrController _controller = ZefyrController(NotusDocument());
final FocusNode _focusNode = new FocusNode();

@override
Widget build(BuildContext context) {
final form = ListView(
children: <Widget>[
TextField(decoration: InputDecoration(labelText: 'Name')),
buildEditor(),
TextField(decoration: InputDecoration(labelText: 'Email')),
],
);

return Scaffold(
resizeToAvoidBottomPadding: true,
appBar: AppBar(
elevation: 1.0,
backgroundColor: Colors.grey.shade200,
brightness: Brightness.light,
title: ZefyrLogo(),
),
body: ZefyrScaffold(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: form,
),
),
);
}

Widget buildEditor() {
final theme = new ZefyrThemeData(
toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith(
color: Colors.grey.shade800,
toggleColor: Colors.grey.shade900,
iconColor: Colors.white,
disabledIconColor: Colors.grey.shade500,
),
);

return ZefyrTheme(
data: theme,
child: ZefyrField(
height: 200.0,
decoration: InputDecoration(labelText: 'Description'),
controller: _controller,
focusNode: _focusNode,
autofocus: false,
imageDelegate: new CustomImageDelegate(),
physics: ClampingScrollPhysics(),
),
);
}
}
Loading