mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support import document from v0.1.x (#2601)
This commit is contained in:
254
frontend/appflowy_flutter/assets/template/readme.afdoc
Normal file
254
frontend/appflowy_flutter/assets/template/readme.afdoc
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
{
|
||||||
|
"document": {
|
||||||
|
"type": "editor",
|
||||||
|
"children": [
|
||||||
|
{ "type": "cover" },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "heading", "heading": "h1" },
|
||||||
|
"delta": [{ "insert": "Welcome to AppFlowy!" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "heading", "heading": "h2" },
|
||||||
|
"delta": [{ "insert": "Here are the basics" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": null },
|
||||||
|
"delta": [{ "insert": "Click anywhere and just start typing." }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": false },
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Highlight ",
|
||||||
|
"attributes": { "backgroundColor": "0x4dffeb3b" }
|
||||||
|
},
|
||||||
|
{ "insert": "any text, and use the editing menu to " },
|
||||||
|
{ "insert": "style", "attributes": { "italic": true } },
|
||||||
|
{ "insert": " " },
|
||||||
|
{ "insert": "your", "attributes": { "bold": true } },
|
||||||
|
{ "insert": " " },
|
||||||
|
{ "insert": "writing", "attributes": { "underline": true } },
|
||||||
|
{ "insert": " " },
|
||||||
|
{ "insert": "however", "attributes": { "code": true } },
|
||||||
|
{ "insert": " you " },
|
||||||
|
{ "insert": "like.", "attributes": { "strikethrough": true } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": null },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "As soon as you type " },
|
||||||
|
{
|
||||||
|
"insert": "/",
|
||||||
|
"attributes": { "code": true, "color": "0xff00b5ff" }
|
||||||
|
},
|
||||||
|
{ "insert": " a menu will pop up. Select " },
|
||||||
|
{
|
||||||
|
"insert": "different types",
|
||||||
|
"attributes": { "backgroundColor": "0x4d9c27b0" }
|
||||||
|
},
|
||||||
|
{ "insert": " of content blocks you can add." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": null },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Type " },
|
||||||
|
{ "insert": "/", "attributes": { "code": true } },
|
||||||
|
{ "insert": " followed by " },
|
||||||
|
{ "insert": "/bullet", "attributes": { "code": true } },
|
||||||
|
{ "insert": " or " },
|
||||||
|
{ "insert": "/num", "attributes": { "code": true } },
|
||||||
|
{ "insert": " to create a list.", "attributes": { "code": false } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": true },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Click " },
|
||||||
|
{ "insert": "+ New Page ", "attributes": { "code": true } },
|
||||||
|
{
|
||||||
|
"insert": "button at the bottom of your sidebar to add a new page."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "checkbox", "checkbox": null },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Click " },
|
||||||
|
{ "insert": "+", "attributes": { "code": true } },
|
||||||
|
{ "insert": " next to any page title in the sidebar to " },
|
||||||
|
{ "insert": "quickly", "attributes": { "color": "0xff8427e0" } },
|
||||||
|
{ "insert": " add a new subpage, " },
|
||||||
|
{ "insert": "Document", "attributes": { "code": true } },
|
||||||
|
{ "insert": ", ", "attributes": { "code": false } },
|
||||||
|
{ "insert": "Grid", "attributes": { "code": true } },
|
||||||
|
{ "insert": ", or ", "attributes": { "code": false } },
|
||||||
|
{ "insert": "Kanban Board", "attributes": { "code": true } },
|
||||||
|
{ "insert": ".", "attributes": { "code": false } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{ "type": "divider" },
|
||||||
|
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
|
"checkbox": null,
|
||||||
|
"heading": "h2"
|
||||||
|
},
|
||||||
|
"delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "number-list",
|
||||||
|
"number": 1,
|
||||||
|
"heading": null
|
||||||
|
},
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Keyboard shortcuts " },
|
||||||
|
{
|
||||||
|
"insert": "guide",
|
||||||
|
"attributes": {
|
||||||
|
"href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "retain": 1, "attributes": { "strikethrough": true } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "number-list",
|
||||||
|
"number": 2,
|
||||||
|
"heading": null
|
||||||
|
},
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Markdown " },
|
||||||
|
{
|
||||||
|
"insert": "reference",
|
||||||
|
"attributes": {
|
||||||
|
"href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "retain": 1, "attributes": { "strikethrough": true } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "number": 3, "subtype": "number-list" },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Type " },
|
||||||
|
{ "insert": "/code", "attributes": { "code": true } },
|
||||||
|
{
|
||||||
|
"insert": " to insert a code block",
|
||||||
|
"attributes": { "code": false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "code_block",
|
||||||
|
"number": 3,
|
||||||
|
"heading": null,
|
||||||
|
"number-list": null,
|
||||||
|
"theme": "vs",
|
||||||
|
"language": "rust"
|
||||||
|
},
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
|
||||||
|
},
|
||||||
|
{ "retain": 1, "attributes": { "strikethrough": true } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
|
"checkbox": null,
|
||||||
|
"heading": "h2"
|
||||||
|
},
|
||||||
|
"delta": [{ "insert": "Have a question❓" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "quote" },
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "Click " },
|
||||||
|
{ "insert": "?", "attributes": { "code": true } },
|
||||||
|
{ "insert": " at the bottom right for help and support." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "callout",
|
||||||
|
"children": [
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "heading", "heading": "h2" },
|
||||||
|
"delta": [{ "insert": "Like AppFlowy? Follow us:" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "bulleted-list" },
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "GitHub",
|
||||||
|
"attributes": {
|
||||||
|
"href": "https://github.com/AppFlowy-IO/AppFlowy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "bulleted-list" },
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Twitter",
|
||||||
|
"attributes": { "href": "https://twitter.com/appflowy" }
|
||||||
|
},
|
||||||
|
{ "insert": ": @appflowy" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "bulleted-list" },
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Newsletter",
|
||||||
|
"attributes": { "href": "https://blog-appflowy.ghost.io/" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": { "emoji": "😀" }
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": null, "heading": null },
|
||||||
|
"delta": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": null, "heading": null },
|
||||||
|
"delta": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,54 @@ import 'dart:convert';
|
|||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||||
show Document, Node, Attributes, Delta, ParagraphBlockKeys;
|
show Document, Node, Attributes, Delta, ParagraphBlockKeys, NodeIterator;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:nanoid/nanoid.dart';
|
||||||
|
|
||||||
extension AppFlowyEditor on DocumentDataPB {
|
extension DocumentDataPBFromTo on DocumentDataPB {
|
||||||
DocumentDataPB? fromDocument(Document document) {
|
static DocumentDataPB? fromDocument(Document document) {
|
||||||
final blocks = <String, BlockPB>{};
|
final startNode = document.first;
|
||||||
|
final endNode = document.last;
|
||||||
|
if (startNode == null || endNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final pageId = document.root.id;
|
final pageId = document.root.id;
|
||||||
|
|
||||||
|
// generate the block
|
||||||
|
final blocks = <String, BlockPB>{};
|
||||||
|
final nodes = NodeIterator(
|
||||||
|
document: document,
|
||||||
|
startNode: startNode,
|
||||||
|
endNode: endNode,
|
||||||
|
).toList();
|
||||||
|
for (final node in nodes) {
|
||||||
|
if (blocks.containsKey(node.id)) {
|
||||||
|
assert(false, 'duplicate node id: ${node.id}');
|
||||||
|
}
|
||||||
|
final parentId = node.parent?.id;
|
||||||
|
final childrenId = nanoid(10);
|
||||||
|
blocks[node.id] = node.toBlock(
|
||||||
|
parentId: parentId,
|
||||||
|
childrenId: childrenId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// root
|
||||||
|
blocks[pageId] = document.root.toBlock(
|
||||||
|
parentId: '',
|
||||||
|
childrenId: pageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// generate the meta
|
||||||
final childrenMap = <String, ChildrenPB>{};
|
final childrenMap = <String, ChildrenPB>{};
|
||||||
|
blocks.forEach((key, value) {
|
||||||
|
final parentId = value.parentId;
|
||||||
|
if (parentId.isNotEmpty) {
|
||||||
|
childrenMap[parentId] ??= ChildrenPB.create();
|
||||||
|
childrenMap[parentId]!.children.add(value.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
final meta = MetaPB(childrenMap: childrenMap);
|
final meta = MetaPB(childrenMap: childrenMap);
|
||||||
|
|
||||||
return DocumentDataPB(
|
return DocumentDataPB(
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
pageId: pageId,
|
pageId: pageId,
|
||||||
@ -91,6 +130,7 @@ extension BlockToNode on BlockPB {
|
|||||||
|
|
||||||
extension NodeToBlock on Node {
|
extension NodeToBlock on Node {
|
||||||
BlockPB toBlock({
|
BlockPB toBlock({
|
||||||
|
String? parentId,
|
||||||
String? childrenId,
|
String? childrenId,
|
||||||
}) {
|
}) {
|
||||||
assert(id.isNotEmpty);
|
assert(id.isNotEmpty);
|
||||||
@ -101,6 +141,9 @@ extension NodeToBlock on Node {
|
|||||||
if (childrenId != null && childrenId.isNotEmpty) {
|
if (childrenId != null && childrenId.isNotEmpty) {
|
||||||
block.childrenId = childrenId;
|
block.childrenId = childrenId;
|
||||||
}
|
}
|
||||||
|
if (parentId != null && parentId.isNotEmpty) {
|
||||||
|
block.parentId = parentId;
|
||||||
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,13 +108,13 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
name: value.name,
|
name: value.name,
|
||||||
desc: value.desc ?? "",
|
desc: value.desc ?? "",
|
||||||
layoutType: value.pluginBuilder.layoutType!,
|
layoutType: value.pluginBuilder.layoutType!,
|
||||||
initialData: value.initialData,
|
initialDataBytes: value.initialDataBytes,
|
||||||
ext: value.ext ?? {},
|
ext: value.ext ?? {},
|
||||||
);
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(view) => emit(
|
(view) => emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
latestCreatedView: view,
|
latestCreatedView: value.openAfterCreated ? view : null,
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -151,10 +151,17 @@ class AppEvent with _$AppEvent {
|
|||||||
PluginBuilder pluginBuilder, {
|
PluginBuilder pluginBuilder, {
|
||||||
String? desc,
|
String? desc,
|
||||||
|
|
||||||
/// The initial data should be the JSON of the document
|
/// ~~The initial data should be the JSON of the document~~
|
||||||
/// For example: {"document":{"type":"editor","children":[]}}
|
/// ~~For example: {"document":{"type":"editor","children":[]}}~~
|
||||||
String? initialData,
|
///
|
||||||
|
/// - Document:
|
||||||
|
/// the initial data should be the string that can be converted into [DocumentDataPB]
|
||||||
|
///
|
||||||
|
List<int>? initialDataBytes,
|
||||||
Map<String, String>? ext,
|
Map<String, String>? ext,
|
||||||
|
|
||||||
|
/// open the view after created
|
||||||
|
@Default(true) bool openAfterCreated,
|
||||||
}) = CreateView;
|
}) = CreateView;
|
||||||
const factory AppEvent.loadViews() = LoadApp;
|
const factory AppEvent.loadViews() = LoadApp;
|
||||||
const factory AppEvent.delete() = DeleteApp;
|
const factory AppEvent.delete() = DeleteApp;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
@ -14,12 +13,9 @@ class AppBackendService {
|
|||||||
String? desc,
|
String? desc,
|
||||||
required ViewLayoutPB layoutType,
|
required ViewLayoutPB layoutType,
|
||||||
|
|
||||||
/// The initial data should be the JSON of the document.
|
/// The initial data should be a JSON that represent the DocumentDataPB.
|
||||||
/// Currently, only support create document with initial data.
|
/// Currently, only support create document with initial data.
|
||||||
///
|
List<int>? initialDataBytes,
|
||||||
/// The initial data must be follow this format as shown below.
|
|
||||||
/// {"document":{"type":"editor","children":[]}}
|
|
||||||
String? initialData,
|
|
||||||
|
|
||||||
/// The [ext] is used to pass through the custom configuration
|
/// The [ext] is used to pass through the custom configuration
|
||||||
/// to the backend.
|
/// to the backend.
|
||||||
@ -33,9 +29,7 @@ class AppBackendService {
|
|||||||
..name = name
|
..name = name
|
||||||
..desc = desc ?? ""
|
..desc = desc ?? ""
|
||||||
..layout = layoutType
|
..layout = layoutType
|
||||||
..initialData = utf8.encode(
|
..initialData = initialDataBytes ?? [];
|
||||||
initialData ?? "",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ext.isNotEmpty) {
|
if (ext.isNotEmpty) {
|
||||||
payload.ext.addAll(ext);
|
payload.ext.addAll(ext);
|
||||||
|
@ -3,7 +3,6 @@ import 'package:appflowy/startup/plugin/plugin.dart';
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' show Document;
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -15,7 +14,8 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
class AddButton extends StatelessWidget {
|
class AddButton extends StatelessWidget {
|
||||||
final Function(
|
final Function(
|
||||||
PluginBuilder,
|
PluginBuilder,
|
||||||
Document? document,
|
List<int>? initialDataBytes,
|
||||||
|
bool openAfterCreated,
|
||||||
) onSelected;
|
) onSelected;
|
||||||
|
|
||||||
const AddButton({
|
const AddButton({
|
||||||
@ -71,14 +71,22 @@ class AddButton extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
onSelected: (action, controller) {
|
onSelected: (action, controller) {
|
||||||
if (action is AddButtonActionWrapper) {
|
if (action is AddButtonActionWrapper) {
|
||||||
onSelected(action.pluginBuilder, null);
|
onSelected(action.pluginBuilder, null, true);
|
||||||
}
|
}
|
||||||
if (action is ImportActionWrapper) {
|
if (action is ImportActionWrapper) {
|
||||||
showImportPanel(context, (document) {
|
showImportPanel(context, (type, initialDataBytes) {
|
||||||
if (document == null) {
|
if (initialDataBytes == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onSelected(action.pluginBuilder, document);
|
switch (type) {
|
||||||
|
case ImportType.historyDocument:
|
||||||
|
case ImportType.historyDatabase:
|
||||||
|
onSelected(action.pluginBuilder, initialDataBytes, false);
|
||||||
|
break;
|
||||||
|
case ImportType.markdownOrText:
|
||||||
|
onSelected(action.pluginBuilder, initialDataBytes, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
@ -110,13 +108,13 @@ class MenuAppHeader extends StatelessWidget {
|
|||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||||
child: AddButton(
|
child: AddButton(
|
||||||
onSelected: (pluginBuilder, document) {
|
onSelected: (pluginBuilder, initialDataBytes, openAfterCreated) {
|
||||||
context.read<AppBloc>().add(
|
context.read<AppBloc>().add(
|
||||||
AppEvent.createView(
|
AppEvent.createView(
|
||||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
pluginBuilder,
|
pluginBuilder,
|
||||||
initialData:
|
initialDataBytes: initialDataBytes,
|
||||||
document != null ? jsonEncode(document.toJson()) : '',
|
openAfterCreated: openAfterCreated,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -11,7 +13,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
typedef ImportCallback = void Function(Document? document);
|
typedef ImportCallback = void Function(ImportType type, List<int>? document);
|
||||||
|
|
||||||
Future<void> showImportPanel(
|
Future<void> showImportPanel(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@ -36,13 +38,19 @@ Future<void> showImportPanel(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _ImportType {
|
enum ImportType {
|
||||||
|
historyDocument,
|
||||||
|
historyDatabase,
|
||||||
markdownOrText;
|
markdownOrText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case _ImportType.markdownOrText:
|
case ImportType.historyDocument:
|
||||||
|
return 'Document from v0.1';
|
||||||
|
case ImportType.historyDatabase:
|
||||||
|
return 'Database from v0.1';
|
||||||
|
case ImportType.markdownOrText:
|
||||||
return 'Text & Markdown';
|
return 'Text & Markdown';
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unsupported Type $this');
|
assert(false, 'Unsupported Type $this');
|
||||||
@ -50,25 +58,52 @@ enum _ImportType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? get icon {
|
Widget? Function(BuildContext context) get icon => (context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case _ImportType.markdownOrText:
|
case ImportType.historyDocument:
|
||||||
return svgWidget('editor/documents');
|
case ImportType.historyDatabase:
|
||||||
|
return svgWidget(
|
||||||
|
'editor/documents',
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
);
|
||||||
|
case ImportType.markdownOrText:
|
||||||
|
return svgWidget(
|
||||||
|
'editor/documents',
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unsupported Type $this');
|
assert(false, 'Unsupported Type $this');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
List<String> get allowedExtensions {
|
List<String> get allowedExtensions {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case _ImportType.markdownOrText:
|
case ImportType.historyDocument:
|
||||||
|
return ['afdoc'];
|
||||||
|
case ImportType.historyDatabase:
|
||||||
|
// FIXME: @nathan.
|
||||||
|
return ['afdb'];
|
||||||
|
case ImportType.markdownOrText:
|
||||||
return ['md', 'txt'];
|
return ['md', 'txt'];
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unsupported Type $this');
|
assert(false, 'Unsupported Type $this');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get allowMultiSelect {
|
||||||
|
switch (this) {
|
||||||
|
case ImportType.historyDocument:
|
||||||
|
return true;
|
||||||
|
case ImportType.historyDatabase:
|
||||||
|
case ImportType.markdownOrText:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
assert(false, 'Unsupported Type $this');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImportPanel extends StatefulWidget {
|
class _ImportPanel extends StatefulWidget {
|
||||||
@ -94,11 +129,11 @@ class _ImportPanelState extends State<_ImportPanel> {
|
|||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
childAspectRatio: 1 / .2,
|
childAspectRatio: 1 / .2,
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
children: _ImportType.values.map(
|
children: ImportType.values.map(
|
||||||
(e) {
|
(e) {
|
||||||
return Card(
|
return Card(
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
leftIcon: e.icon,
|
leftIcon: e.icon(context),
|
||||||
leftIconSize: const Size.square(20),
|
leftIconSize: const Size.square(20),
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
e.toString(),
|
e.toString(),
|
||||||
@ -119,26 +154,37 @@ class _ImportPanelState extends State<_ImportPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _importFile(_ImportType importType) async {
|
Future<void> _importFile(ImportType importType) async {
|
||||||
final result = await getIt<FilePickerService>().pickFiles(
|
final result = await getIt<FilePickerService>().pickFiles(
|
||||||
allowMultiple: false,
|
allowMultiple: importType.allowMultiSelect,
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: importType.allowedExtensions,
|
allowedExtensions: importType.allowedExtensions,
|
||||||
);
|
);
|
||||||
if (result == null || result.files.isEmpty) {
|
if (result == null || result.files.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final path = result.files.single.path!;
|
|
||||||
final plainText = await File(path).readAsString();
|
|
||||||
|
|
||||||
|
for (final file in result.files) {
|
||||||
|
final path = file.path;
|
||||||
|
if (path == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final plainText = await File(path).readAsString();
|
||||||
|
Document? document;
|
||||||
switch (importType) {
|
switch (importType) {
|
||||||
case _ImportType.markdownOrText:
|
case ImportType.markdownOrText:
|
||||||
final document = markdownToDocument(plainText);
|
document = markdownToDocument(plainText);
|
||||||
widget.importCallback(document);
|
break;
|
||||||
|
case ImportType.historyDocument:
|
||||||
|
document = EditorMigration.migrateDocument(plainText);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unsupported Type $importType');
|
assert(false, 'Unsupported Type $importType');
|
||||||
widget.importCallback(null);
|
}
|
||||||
|
if (document != null) {
|
||||||
|
final data = DocumentDataPBFromTo.fromDocument(document);
|
||||||
|
widget.importCallback(importType, data?.writeToBuffer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: ead61af
|
ref: "21f686"
|
||||||
resolved-ref: ead61afb796037e8ceb63ba4bcf439818514ed4b
|
resolved-ref: "21f686d6a43137cf6c6d7d040463a1679d13f858"
|
||||||
url: "https://github.com/LucasXu0/appflowy-editor.git"
|
url: "https://github.com/LucasXu0/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.12"
|
version: "0.1.12"
|
||||||
|
@ -47,7 +47,7 @@ dependencies:
|
|||||||
# path: /Users/lucas.xu/Desktop/appflowy-editor
|
# path: /Users/lucas.xu/Desktop/appflowy-editor
|
||||||
git:
|
git:
|
||||||
url: https://github.com/LucasXu0/appflowy-editor.git
|
url: https://github.com/LucasXu0/appflowy-editor.git
|
||||||
ref: ead61af
|
ref: 21f686
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
@ -179,9 +179,9 @@ flutter:
|
|||||||
- assets/images/login/
|
- assets/images/login/
|
||||||
- assets/images/grid/setting/
|
- assets/images/grid/setting/
|
||||||
- assets/translations/
|
- assets/translations/
|
||||||
- assets/template/readme.json
|
|
||||||
|
|
||||||
# The following assets will be excluded in release.
|
# The following assets will be excluded in release.
|
||||||
# BEGIN: EXCLUDE_IN_RELEASE
|
# BEGIN: EXCLUDE_IN_RELEASE
|
||||||
- assets/test/workspaces/
|
- assets/test/workspaces/
|
||||||
|
- assets/template/
|
||||||
# END: EXCLUDE_IN_RELEASE
|
# END: EXCLUDE_IN_RELEASE
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('editor migration, from v0.1.x to 0.2', () {
|
||||||
|
test('migrate readme', () {
|
||||||
|
// final
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||||
@ -9,6 +10,7 @@ use flowy_database2::entities::DatabaseLayoutPB;
|
|||||||
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
||||||
use flowy_database2::DatabaseManager2;
|
use flowy_database2::DatabaseManager2;
|
||||||
use flowy_document2::document_data::DocumentDataWrapper;
|
use flowy_document2::document_data::DocumentDataWrapper;
|
||||||
|
use flowy_document2::entities::DocumentDataPB;
|
||||||
use flowy_document2::manager::DocumentManager;
|
use flowy_document2::manager::DocumentManager;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_folder2::entities::ViewLayoutPB;
|
use flowy_folder2::entities::ViewLayoutPB;
|
||||||
@ -126,7 +128,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
_user_id: i64,
|
_user_id: i64,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
_name: &str,
|
_name: &str,
|
||||||
_data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
layout: ViewLayout,
|
layout: ViewLayout,
|
||||||
_ext: HashMap<String, String>,
|
_ext: HashMap<String, String>,
|
||||||
) -> FutureResult<(), FlowyError> {
|
) -> FutureResult<(), FlowyError> {
|
||||||
@ -134,9 +136,9 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
// TODO: implement read the document data from custom data.
|
// TODO: implement read the document data from custom data.
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
|
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
manager.create_document(view_id, DocumentDataWrapper::default())?;
|
let data = DocumentDataPB::try_from(Bytes::from(data))?;
|
||||||
|
manager.create_document(view_id, data.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,14 @@ impl DefaultFolderBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// create the document
|
// create the document
|
||||||
let data = initial_read_me().into_bytes();
|
// TODO: use the initial data from the view processor
|
||||||
|
// let data = initial_read_me().into_bytes();
|
||||||
let processor = view_processors.get(&child_view_layout).unwrap();
|
let processor = view_processors.get(&child_view_layout).unwrap();
|
||||||
processor
|
processor
|
||||||
.create_view_with_custom_data(
|
.create_view_with_built_in_data(
|
||||||
uid,
|
uid,
|
||||||
&child_view.id,
|
&child_view.id,
|
||||||
&child_view.name,
|
&child_view.name,
|
||||||
data,
|
|
||||||
child_view_layout.clone(),
|
child_view_layout.clone(),
|
||||||
HashMap::default(),
|
HashMap::default(),
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user