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

feat: add outline block #2750

Merged
merged 13 commits into from
Jun 29, 2023
Merged

feat: add outline block #2750

merged 13 commits into from
Jun 29, 2023

Conversation

AmanNegi
Copy link
Contributor

@AmanNegi AmanNegi commented Jun 9, 2023

Feature Preview

Fixes: #2688

table_of_contents.mp4

PR Checklist

  • My code adheres to the AppFlowy Style Guide
  • I've listed at least one issue that this PR fixes in the description above.
  • I've added a test(s) to validate changes in this PR, or this PR only contains semantic changes.
  • All existing tests are passing.

@AmanNegi
Copy link
Contributor Author

AmanNegi commented Jun 9, 2023

@LucasXu0 I was able to achieve the following output so far. Some of the issues I am facing are:

  • Outline rebuilds at every transaction in the document.
  • Additional attributes don't show up as expected (eg: hyperlink in heading doesn't show up in outline).

I would love to know what you have to suggest for these issues. Also please let me know about your views on the progress so far.

@LucasXu0
Copy link
Collaborator

Outline rebuilds at every transaction in the document.

You can filter the transaction. only rebuilding if the heading is changed.

Additional attributes don't show up as expected (eg: hyperlink in heading doesn't show up in outline).

I think we just need to display the heading without its inline styles, such as color and href.

class OutlineBlockKeys {
const OutlineBlockKeys._();

static const String type = 'outline_block';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static const String type = 'outline_block';
static const String type = 'outline';

// defining the callout block menu item for selection
SelectionMenuItem outlineItem = SelectionMenuItem.node(
name: 'Outline',
iconData: Icons.clear_all,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clear_all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have selected a few icons which match our purpose here, let me know which feels the best selection here:

icons_selection

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list_alt looks better.

@LucasXu0
Copy link
Collaborator

Let me checkout your branch and add the suggestion comment.

@@ -1,6 +1,7 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_list.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the file path to frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart


// defining the callout block menu item for selection
SelectionMenuItem outlineItem = SelectionMenuItem.node(
name: 'Outline',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use l10n instead. Adding 'outline' to en.json.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked out other plugins for reference but no one uses i10n for the SelectionMenuItem name. Let me know if the following approach would be fine:

en.json

"outline": {
        "name": "Outline",
        "heading": "Table of Contents"
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. The l10n for other selection menu items are missing. You can add the new l10n to ↓

Screenshot 2023-06-13 at 17 57 50

}

@override
bool validate(Node node) => true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate the outline_block.children must be empty.

crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"TABLE OF CONTENTS: ",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l10n

Copy link
Contributor Author

@AmanNegi AmanNegi Jun 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for clarification would the following do fine?

     "mathEquation": {
        "addMathEquation": "Add Math Equation",
        "editMathEquation": "Edit Math Equation"
      },
      "outline":{
        "heading": "TABLE OF CONTENTS:"
      },

Similar to the mathEquation plugin I also created the outline key.

children: [
Text(
"TABLE OF CONTENTS: ",
style: editorState.editorStyle.textStyleConfiguration.text,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
style: editorState.editorStyle.textStyleConfiguration.text,
style: configuration.textStyle(node),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration.textStyle(node) doesn't work as expected when the font size of the document is changed. However the earlier approach does work as expected.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. we should use editorState.editorStyle.textStyleConfiguration.text here. I misread it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR looks good to me, except for the missing test. I will merge it once you add the tests and ensure that all the CI passes.

@LucasXu0
Copy link
Collaborator

@AmanNegi Please check the comments and add the tests.

@codecov
Copy link

codecov bot commented Jun 11, 2023

Codecov Report

Merging #2750 (be490e3) into main (7f74fd6) will increase coverage by 54.34%.
The diff coverage is 9.41%.

@@             Coverage Diff             @@
##             main    #2750       +/-   ##
===========================================
+ Coverage   12.36%   66.70%   +54.34%     
===========================================
  Files         403      407        +4     
  Lines       19093    19333      +240     
===========================================
+ Hits         2360    12897    +10537     
+ Misses      16733     6436    -10297     
Flag Coverage Δ
appflowy_flutter_integrateion_test 64.63% <9.41%> (?)
appflowy_flutter_unit_test 12.22% <0.00%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...lugins/database/referenced_database_menu_item.dart 100.00% <ø> (ø)
...ditor_plugins/outline/outline_block_component.dart 4.93% <4.93%> (ø)
...lib/plugins/document/presentation/editor_page.dart 96.12% <100.00%> (+96.12%) ⬆️

... and 322 files with indirect coverage changes

@override
Node get node => widget.node;

late EditorState editorState = context.read<EditorState>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Late but you assign it a value immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context is not available at the starting phase, so it basically waits for the widget to initialise and then it assigns the variable. If you want I could put the assignment separately in initState for easier understanding.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah fair enough, I'm not used to this way of accessing buildcontext, never used it much tbh.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll change it to more commonly found syntax:

late EditorState editorState;

@override
  void initState() {
    super.initState();
    editorState = context.read<EditorState>();
}

Let me know if this looks good.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, both of them look good to me. I prefer to the existing one, because no need to override the initState.

child = BlockComponentActionWrapper(
node: widget.node,
actionBuilder: widget.actionBuilder!,
child: _buildOutlineBlock(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
child: _buildOutlineBlock(),
child: child,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not advisable to use 'child' here. The widget will not be updated within the builder if 'child' is returned; instead, we should rebuild the widget.

The below code is wrong too. It should return _buildOutlineBlock() directly, not the child.

),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: headingNodes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
children: headingNodes
children: getHeadingNodes()

No need to have a variable with this if you only use it in one place.

const Divider(
color: Colors.white54,
),
Column(
Copy link
Collaborator

@Xazin Xazin Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need a Column here? You're already inside a Column with the exact same crossAxisAlignment.

Just do

...getHeadingNodes().map((e) => OutlineItemWidget(node: e)).toList(),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah simply spreading the list of widgets would be a good idea. Thanks ✨

@AmanNegi
Copy link
Contributor Author

@LucasXu0 @Xazin I have made all the changes requested so far. If more improvements are needed please let me know.

Now I will be working on the tests for this plugin. If some resources/examples are available on how to write tests for editor-plugins that would be helpful as well. 🤝 😃

@AmanNegi
Copy link
Contributor Author

AmanNegi commented Jun 24, 2023

@LucasXu0 @Xazin I have made all the changes requested so far. If more improvements are needed please let me know.

Now I will be working on the tests for this plugin. If some resources/examples are available on how to write tests for editor-plugins that would be helpful as well. 🤝 😃

cc @LucasXu0 Could you please guide me with this.

@AmanNegi
Copy link
Contributor Author

@LucasXu0 I have added the outline tests. Could you please verify the tests.

@LucasXu0
Copy link
Collaborator

@AmanNegi Sure.

extension on Node {
double get verticalSpacing {
if (type != HeadingBlockKeys.type) {
assert(false);
Copy link
Collaborator

@Xazin Xazin Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed this assert, feels like a hack to me. There are two similar ones further below.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because only node with heading type can fetch this values.

Copy link
Collaborator

@Xazin Xazin Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but assert(false) is not meaningful, and does not provide any information that would lead one to resolve it.

Hence why I feel that it is a hack.

It would make more sense to have assert(type == HeadingBlockKeys.type), as that would give a meaningful assertion error. I understand its use, and I'm not saying we should change it since it's only in debugging.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@LucasXu0
Copy link
Collaborator

FYI, @AmanNegi. I refactor the UI part.

  1. adding a hover style
  2. using block selection instead of cursor
  3. remove the content title\
Screenshot 2023-06-28 at 15 51 31

@LucasXu0 LucasXu0 changed the title feat: Add Outline Block feat: add outline block Jun 28, 2023
@LucasXu0 LucasXu0 merged commit 95d4fb6 into AppFlowy-IO:main Jun 29, 2023
@AmanNegi
Copy link
Contributor Author

Merged! 🎉🎊 Thanks a lot for your support @LucasXu0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FR] Outline Plugin for AppFlowy
3 participants