chore: merge wih main
72
.github/workflows/deploy_test_web.yaml
vendored
@ -1,72 +0,0 @@
|
|||||||
name: Deploy Web (Test)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- build/test
|
|
||||||
env:
|
|
||||||
NODE_VERSION: "18.16.0"
|
|
||||||
PNPM_VERSION: "8.5.0"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.WEB_TEST_SSH_PRIVATE_KEY }}
|
|
||||||
REMOTE_HOST: ${{ secrets.WEB_TEST_REMOTE_HOST }}
|
|
||||||
REMOTE_USER: ${{ secrets.WEB_TEST_REMOTE_USER }}
|
|
||||||
SSL_CERTIFICATE: ${{ secrets.WEB_TEST_SSL_CERTIFICATE }}
|
|
||||||
SSL_CERTIFICATE_KEY: ${{ secrets.WEB_TEST_SSL_CERTIFICATE_KEY }}
|
|
||||||
ENV_FILE: test.env
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: setup node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
- name: setup pnpm
|
|
||||||
uses: pnpm/action-setup@v2
|
|
||||||
with:
|
|
||||||
version: ${{ env.PNPM_VERSION }}
|
|
||||||
- name: Node_modules cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: frontend/appflowy_web_app/node_modules
|
|
||||||
key: node-modules-${{ runner.os }}
|
|
||||||
- name: install frontend dependencies
|
|
||||||
working-directory: frontend/appflowy_web_app
|
|
||||||
run: |
|
|
||||||
pnpm install
|
|
||||||
- name: copy env file
|
|
||||||
working-directory: frontend/appflowy_web_app
|
|
||||||
run: |
|
|
||||||
cp ${{ env.ENV_FILE }} .env
|
|
||||||
- name: test and lint
|
|
||||||
working-directory: frontend/appflowy_web_app
|
|
||||||
run: |
|
|
||||||
pnpm run lint
|
|
||||||
- name: build
|
|
||||||
working-directory: frontend/appflowy_web_app
|
|
||||||
run: |
|
|
||||||
pnpm run build
|
|
||||||
- name: generate SSL certificate
|
|
||||||
run: |
|
|
||||||
echo "${{ env.SSL_CERTIFICATE }}" > nginx-signed.crt
|
|
||||||
echo "${{ env.SSL_CERTIFICATE_KEY }}" > nginx-signed.key
|
|
||||||
- name: Deploy to EC2
|
|
||||||
uses: easingthemes/ssh-deploy@main
|
|
||||||
with:
|
|
||||||
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
|
|
||||||
ARGS: "-rlgoDzvc -i"
|
|
||||||
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/server.cjs frontend/appflowy_web_app/start.sh frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
|
|
||||||
REMOTE_HOST: ${{ env.REMOTE_HOST }}
|
|
||||||
REMOTE_USER: ${{ env.REMOTE_USER }}
|
|
||||||
EXCLUDE: "frontend/appflowy_web_app/dist/, frontend/appflowy_web_app/node_modules/"
|
|
||||||
SCRIPT_AFTER: |
|
|
||||||
docker build -t appflowy-web-app .
|
|
||||||
docker rm -f appflowy-web-app || true
|
|
||||||
docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app
|
|
4
.github/workflows/release.yml
vendored
@ -135,7 +135,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { target: x86_64-apple-darwin, os: macos-11, extra-build-args: "" }
|
- { target: x86_64-apple-darwin, os: macos-latest, extra-build-args: "" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -233,7 +233,7 @@ jobs:
|
|||||||
job:
|
job:
|
||||||
- {
|
- {
|
||||||
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
||||||
os: macos-11,
|
os: macos-latest,
|
||||||
extra-build-args: "",
|
extra-build-args: "",
|
||||||
}
|
}
|
||||||
steps:
|
steps:
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
## Version 0.6.3 - 08/07/2024
|
||||||
|
### New Features
|
||||||
|
- Publish a Document to the Web
|
||||||
|
|
||||||
## Version 0.6.2 - 01/07/2024
|
## Version 0.6.2 - 01/07/2024
|
||||||
### New Features
|
### New Features
|
||||||
- Added support for duplicating spaces.
|
- Added support for duplicating spaces.
|
||||||
|
@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
|||||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||||
LIB_NAME = "dart_ffi"
|
LIB_NAME = "dart_ffi"
|
||||||
APPFLOWY_VERSION = "0.6.2"
|
APPFLOWY_VERSION = "0.6.3"
|
||||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||||
PRODUCT_NAME = "AppFlowy"
|
PRODUCT_NAME = "AppFlowy"
|
||||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||||
|
@ -8,9 +8,10 @@ void main() {
|
|||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('Empty', () {
|
group('Empty', () {
|
||||||
testWidgets('toggle theme mode', (tester) async {
|
testWidgets('empty test', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapAnonymousSignInButton();
|
await tester.tapAnonymousSignInButton();
|
||||||
|
await tester.wait(1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -82,11 +82,11 @@ void main() {
|
|||||||
HeadingBlockKeys.type,
|
HeadingBlockKeys.type,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
importedPageEditorState.getNodeAtPath([1])!.type,
|
||||||
HeadingBlockKeys.type,
|
HeadingBlockKeys.type,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
importedPageEditorState.getNodeAtPath([4])!.type,
|
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||||
TableBlockKeys.type,
|
TableBlockKeys.type,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -51,13 +51,14 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final shareButton = find.byType(ShareActionList);
|
final shareButton = find.byType(DocumentShareButton);
|
||||||
final shareButtonState =
|
final shareButtonState =
|
||||||
tester.state(shareButton) as ShareActionListState;
|
tester.widget(shareButton) as DocumentShareButton;
|
||||||
|
|
||||||
final path = await mockSaveFilePath(
|
final path = await mockSaveFilePath(
|
||||||
p.join(
|
p.join(
|
||||||
context.applicationDataDirectory,
|
context.applicationDataDirectory,
|
||||||
'${shareButtonState.name}.md',
|
'${shareButtonState.view.name}.md',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
7
frontend/appflowy_flutter/lib/env/env.dart
vendored
@ -29,4 +29,11 @@ abstract class Env {
|
|||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
)
|
)
|
||||||
static const String afCloudUrl = _Env.afCloudUrl;
|
static const String afCloudUrl = _Env.afCloudUrl;
|
||||||
|
|
||||||
|
@EnviedField(
|
||||||
|
obfuscate: false,
|
||||||
|
varName: 'INTERNAL_BUILD',
|
||||||
|
defaultValue: '',
|
||||||
|
)
|
||||||
|
static const String internalBuild = _Env.internalBuild;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
|||||||
final uniqueMessages = {...allMessages, ...messages}.toList()
|
final uniqueMessages = {...allMessages, ...messages}.toList()
|
||||||
..sort((a, b) => b.id.compareTo(a.id));
|
..sort((a, b) => b.id.compareTo(a.id));
|
||||||
uniqueMessages.insertAll(0, onetimeMessages);
|
uniqueMessages.insertAll(0, onetimeMessages);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
messages: uniqueMessages,
|
messages: uniqueMessages,
|
||||||
@ -380,7 +379,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) {
|
Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) {
|
||||||
final streamMessageId = nanoid();
|
final streamMessageId = (questionMessageId + 1).toString();
|
||||||
|
|
||||||
lastStreamMessageId = streamMessageId;
|
lastStreamMessageId = streamMessageId;
|
||||||
|
|
||||||
return TextMessage(
|
return TextMessage(
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/chat_input.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -176,9 +182,20 @@ class CopyButton extends StatelessWidget {
|
|||||||
size: const Size.square(14),
|
size: const Size.square(14),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Clipboard.setData(ClipboardData(text: textMessage.text));
|
final document = markdownToDocument(textMessage.text);
|
||||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
await getIt<ClipboardService>().setData(
|
||||||
|
ClipboardServiceData(
|
||||||
|
plainText: textMessage.text,
|
||||||
|
inAppJson: jsonEncode(document.toJson()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.grid_url_copiedNotification.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.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';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'chat_input.dart';
|
import 'chat_input.dart';
|
||||||
|
|
||||||
|
@ -0,0 +1,377 @@
|
|||||||
|
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:markdown_widget/markdown_widget.dart';
|
||||||
|
|
||||||
|
import 'selectable_highlight.dart';
|
||||||
|
|
||||||
|
enum AIMarkdownType {
|
||||||
|
appflowyEditor,
|
||||||
|
markdownWidget,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the appflowy_editor or markdown_widget as a chat text message widget
|
||||||
|
class AIMarkdownText extends StatelessWidget {
|
||||||
|
const AIMarkdownText({
|
||||||
|
super.key,
|
||||||
|
required this.markdown,
|
||||||
|
this.type = AIMarkdownType.appflowyEditor,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String markdown;
|
||||||
|
final AIMarkdownType type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
switch (type) {
|
||||||
|
case AIMarkdownType.appflowyEditor:
|
||||||
|
return _AppFlowyEditorMarkdown(markdown: markdown);
|
||||||
|
case AIMarkdownType.markdownWidget:
|
||||||
|
return _ThirdPartyMarkdown(markdown: markdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppFlowyEditorMarkdown extends StatefulWidget {
|
||||||
|
const _AppFlowyEditorMarkdown({
|
||||||
|
required this.markdown,
|
||||||
|
});
|
||||||
|
|
||||||
|
// the text should be the markdown format
|
||||||
|
final String markdown;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_AppFlowyEditorMarkdown> createState() =>
|
||||||
|
_AppFlowyEditorMarkdownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> {
|
||||||
|
late EditorState editorState;
|
||||||
|
late final styleCustomizer = EditorStyleCustomizer(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
);
|
||||||
|
late final editorStyle = styleCustomizer.style().copyWith(
|
||||||
|
// hide the cursor
|
||||||
|
cursorColor: Colors.transparent,
|
||||||
|
cursorWidth: 0,
|
||||||
|
);
|
||||||
|
late EditorScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
editorState = _parseMarkdown(widget.markdown);
|
||||||
|
scrollController = EditorScrollController(
|
||||||
|
editorState: editorState,
|
||||||
|
shrinkWrap: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant _AppFlowyEditorMarkdown oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.markdown != widget.markdown) {
|
||||||
|
editorState.dispose();
|
||||||
|
editorState = _parseMarkdown(widget.markdown);
|
||||||
|
scrollController.dispose();
|
||||||
|
scrollController = EditorScrollController(
|
||||||
|
editorState: editorState,
|
||||||
|
shrinkWrap: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
editorState.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final blockBuilders = getEditorBuilderMap(
|
||||||
|
context: context,
|
||||||
|
editorState: editorState,
|
||||||
|
styleCustomizer: styleCustomizer,
|
||||||
|
// the editor is not editable in the chat
|
||||||
|
editable: false,
|
||||||
|
);
|
||||||
|
return IntrinsicHeight(
|
||||||
|
child: AppFlowyEditor(
|
||||||
|
shrinkWrap: true,
|
||||||
|
// the editor is not editable in the chat
|
||||||
|
editable: false,
|
||||||
|
editorStyle: editorStyle,
|
||||||
|
editorScrollController: scrollController,
|
||||||
|
blockComponentBuilders: blockBuilders,
|
||||||
|
commandShortcutEvents: [customCopyCommand],
|
||||||
|
editorState: editorState,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorState _parseMarkdown(String markdown) {
|
||||||
|
final document = markdownToDocument(
|
||||||
|
markdown,
|
||||||
|
markdownParsers: [
|
||||||
|
const MarkdownCodeBlockParser(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final editorState = EditorState(document: document);
|
||||||
|
return editorState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThirdPartyMarkdown extends StatelessWidget {
|
||||||
|
const _ThirdPartyMarkdown({
|
||||||
|
required this.markdown,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String markdown;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MarkdownWidget(
|
||||||
|
data: markdown,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
config: configFromContext(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownConfig configFromContext(BuildContext context) {
|
||||||
|
return MarkdownConfig(
|
||||||
|
configs: [
|
||||||
|
HrConfig(color: AFThemeExtension.of(context).textColor),
|
||||||
|
_ChatH1Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
),
|
||||||
|
_ChatH2Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
),
|
||||||
|
_ChatH3Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
),
|
||||||
|
H4Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
H5Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
H6Config(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PreConfig(
|
||||||
|
builder: (code, language) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 800,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||||
|
child: SelectableHighlightView(
|
||||||
|
code,
|
||||||
|
language: language,
|
||||||
|
theme: getHighlightTheme(context),
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PConfig(
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CodeConfig(
|
||||||
|
style: TextStyle(
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlockquoteConfig(
|
||||||
|
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
textColor: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, TextStyle> getHighlightTheme(BuildContext context) {
|
||||||
|
return {
|
||||||
|
'root': TextStyle(
|
||||||
|
color: const Color(0xffabb2bf),
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
||||||
|
),
|
||||||
|
'comment': const TextStyle(
|
||||||
|
color: Color(0xff5c6370),
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
'quote': const TextStyle(
|
||||||
|
color: Color(0xff5c6370),
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'formula': const TextStyle(color: Color(0xffc678dd)),
|
||||||
|
'section': const TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'name': const TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'subst': const TextStyle(color: Color(0xffe06c75)),
|
||||||
|
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
||||||
|
'string': const TextStyle(color: Color(0xff98c379)),
|
||||||
|
'regexp': const TextStyle(color: Color(0xff98c379)),
|
||||||
|
'addition': const TextStyle(color: Color(0xff98c379)),
|
||||||
|
'attribute': const TextStyle(color: Color(0xff98c379)),
|
||||||
|
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
||||||
|
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
||||||
|
'attr': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'variable': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'type': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'number': const TextStyle(color: Color(0xffd19a66)),
|
||||||
|
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'link': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'meta': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'title': const TextStyle(color: Color(0xff61aeee)),
|
||||||
|
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatH1Config extends HeadingConfig {
|
||||||
|
const _ChatH1Config({
|
||||||
|
this.style = const TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
height: 40 / 32,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
required this.dividerColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final TextStyle style;
|
||||||
|
final Color dividerColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => MarkdownTag.h1.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HeadingDivider? get divider => HeadingDivider(
|
||||||
|
space: 10,
|
||||||
|
color: dividerColor,
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
///config class for h2
|
||||||
|
class _ChatH2Config extends HeadingConfig {
|
||||||
|
const _ChatH2Config({
|
||||||
|
this.style = const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
height: 30 / 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
required this.dividerColor,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
final TextStyle style;
|
||||||
|
final Color dividerColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => MarkdownTag.h2.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HeadingDivider? get divider => HeadingDivider(
|
||||||
|
space: 10,
|
||||||
|
color: dividerColor,
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatH3Config extends HeadingConfig {
|
||||||
|
const _ChatH3Config({
|
||||||
|
this.style = const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
height: 30 / 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
required this.dividerColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final TextStyle style;
|
||||||
|
final Color dividerColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => MarkdownTag.h3.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HeadingDivider? get divider => HeadingDivider(
|
||||||
|
space: 10,
|
||||||
|
color: dividerColor,
|
||||||
|
height: 10,
|
||||||
|
);
|
||||||
|
}
|
@ -2,19 +2,15 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_loading.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/chat_loading.dart';
|
||||||
import 'package:appflowy/util/theme_extension.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||||
import 'package:markdown_widget/markdown_widget.dart';
|
|
||||||
|
|
||||||
import 'selectable_highlight.dart';
|
|
||||||
|
|
||||||
class ChatAITextMessageWidget extends StatelessWidget {
|
class ChatAITextMessageWidget extends StatelessWidget {
|
||||||
const ChatAITextMessageWidget({
|
const ChatAITextMessageWidget({
|
||||||
@ -59,248 +55,12 @@ class ChatAITextMessageWidget extends StatelessWidget {
|
|||||||
if (state.text.isEmpty) {
|
if (state.text.isEmpty) {
|
||||||
return const ChatAILoading();
|
return const ChatAILoading();
|
||||||
} else {
|
} else {
|
||||||
return _textWidgetBuilder(user, context, state.text);
|
return AIMarkdownText(markdown: state.text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _textWidgetBuilder(
|
|
||||||
User user,
|
|
||||||
BuildContext context,
|
|
||||||
String text,
|
|
||||||
) {
|
|
||||||
return MarkdownWidget(
|
|
||||||
data: text,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
config: configFromContext(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkdownConfig configFromContext(BuildContext context) {
|
|
||||||
return MarkdownConfig(
|
|
||||||
configs: [
|
|
||||||
HrConfig(color: AFThemeExtension.of(context).textColor),
|
|
||||||
ChatH1Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
),
|
|
||||||
ChatH2Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
),
|
|
||||||
ChatH3Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
),
|
|
||||||
H4Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
H5Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
H6Config(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PreConfig(
|
|
||||||
builder: (code, language) {
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
minWidth: 800,
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
|
||||||
child: SelectableHighlightView(
|
|
||||||
code,
|
|
||||||
language: language,
|
|
||||||
theme: getHightlineTheme(context),
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
textStyle: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
PConfig(
|
|
||||||
textStyle: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
CodeConfig(
|
|
||||||
style: TextStyle(
|
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BlockquoteConfig(
|
|
||||||
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
textColor: AFThemeExtension.of(context).textColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, TextStyle> getHightlineTheme(BuildContext context) {
|
|
||||||
return {
|
|
||||||
'root': TextStyle(
|
|
||||||
color: const Color(0xffabb2bf),
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
|
||||||
),
|
|
||||||
'comment':
|
|
||||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
|
||||||
'quote':
|
|
||||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
|
||||||
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
|
||||||
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
|
||||||
'formula': const TextStyle(color: Color(0xffc678dd)),
|
|
||||||
'section': const TextStyle(color: Color(0xffe06c75)),
|
|
||||||
'name': const TextStyle(color: Color(0xffe06c75)),
|
|
||||||
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
|
||||||
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
|
||||||
'subst': const TextStyle(color: Color(0xffe06c75)),
|
|
||||||
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
|
||||||
'string': const TextStyle(color: Color(0xff98c379)),
|
|
||||||
'regexp': const TextStyle(color: Color(0xff98c379)),
|
|
||||||
'addition': const TextStyle(color: Color(0xff98c379)),
|
|
||||||
'attribute': const TextStyle(color: Color(0xff98c379)),
|
|
||||||
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
|
||||||
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
|
||||||
'attr': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'variable': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'type': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'number': const TextStyle(color: Color(0xffd19a66)),
|
|
||||||
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'link': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'meta': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'title': const TextStyle(color: Color(0xff61aeee)),
|
|
||||||
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
|
||||||
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatH1Config extends HeadingConfig {
|
|
||||||
const ChatH1Config({
|
|
||||||
this.style = const TextStyle(
|
|
||||||
fontSize: 32,
|
|
||||||
height: 40 / 32,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
required this.dividerColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
final TextStyle style;
|
|
||||||
final Color dividerColor;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tag => MarkdownTag.h1.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HeadingDivider? get divider => HeadingDivider(
|
|
||||||
space: 10,
|
|
||||||
color: dividerColor,
|
|
||||||
height: 10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///config class for h2
|
|
||||||
class ChatH2Config extends HeadingConfig {
|
|
||||||
const ChatH2Config({
|
|
||||||
this.style = const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
height: 30 / 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
required this.dividerColor,
|
|
||||||
});
|
|
||||||
@override
|
|
||||||
final TextStyle style;
|
|
||||||
final Color dividerColor;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tag => MarkdownTag.h2.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HeadingDivider? get divider => HeadingDivider(
|
|
||||||
space: 10,
|
|
||||||
color: dividerColor,
|
|
||||||
height: 10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChatH3Config extends HeadingConfig {
|
|
||||||
const ChatH3Config({
|
|
||||||
this.style = const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
height: 30 / 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
required this.dividerColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
final TextStyle style;
|
|
||||||
final Color dividerColor;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tag => MarkdownTag.h3.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
HeadingDivider? get divider => HeadingDivider(
|
|
||||||
space: 10,
|
|
||||||
color: dividerColor,
|
|
||||||
height: 10,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StreamingError extends StatelessWidget {
|
class StreamingError extends StatelessWidget {
|
||||||
|
@ -52,7 +52,6 @@ class TextMessageText extends StatelessWidget {
|
|||||||
text,
|
text,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
lineHeight: 1.5,
|
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
color: AFThemeExtension.of(context).textColor,
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
@ -18,6 +18,7 @@ import 'package:appflowy/util/color_to_hex_string.dart';
|
|||||||
import 'package:appflowy/util/debounce.dart';
|
import 'package:appflowy/util/debounce.dart';
|
||||||
import 'package:appflowy/util/throttle.dart';
|
import 'package:appflowy/util/throttle.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
@ -37,6 +38,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
|
|
||||||
part 'document_bloc.freezed.dart';
|
part 'document_bloc.freezed.dart';
|
||||||
|
|
||||||
|
bool enableDocumentInternalLog = false;
|
||||||
|
|
||||||
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||||
DocumentBloc({
|
DocumentBloc({
|
||||||
required this.documentId,
|
required this.documentId,
|
||||||
@ -212,6 +215,10 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<EditorState?> _initAppFlowyEditorState(DocumentDataPB data) async {
|
Future<EditorState?> _initAppFlowyEditorState(DocumentDataPB data) async {
|
||||||
|
if (enableDocumentInternalLog) {
|
||||||
|
Log.info('document data: ${data.toProto3Json()}');
|
||||||
|
}
|
||||||
|
|
||||||
final document = data.toDocument();
|
final document = data.toDocument();
|
||||||
if (document == null) {
|
if (document == null) {
|
||||||
assert(false, 'document is null');
|
assert(false, 'document is null');
|
||||||
|
@ -180,6 +180,8 @@ extension NodeToBlock on Node {
|
|||||||
String? parentId,
|
String? parentId,
|
||||||
String? childrenId,
|
String? childrenId,
|
||||||
Attributes? attributes,
|
Attributes? attributes,
|
||||||
|
String? externalId,
|
||||||
|
String? externalType,
|
||||||
}) {
|
}) {
|
||||||
assert(id.isNotEmpty);
|
assert(id.isNotEmpty);
|
||||||
final block = BlockPB.create()
|
final block = BlockPB.create()
|
||||||
@ -192,6 +194,12 @@ extension NodeToBlock on Node {
|
|||||||
if (parentId != null && parentId.isNotEmpty) {
|
if (parentId != null && parentId.isNotEmpty) {
|
||||||
block.parentId = parentId;
|
block.parentId = parentId;
|
||||||
}
|
}
|
||||||
|
if (externalId != null && externalId.isNotEmpty) {
|
||||||
|
block.externalId = externalId;
|
||||||
|
}
|
||||||
|
if (externalType != null && externalType.isNotEmpty) {
|
||||||
|
block.externalType = externalType;
|
||||||
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,41 +1,181 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'document_share_bloc.freezed.dart';
|
part 'document_share_bloc.freezed.dart';
|
||||||
|
|
||||||
|
const _url = 'https://appflowy.com';
|
||||||
|
|
||||||
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
||||||
DocumentShareBloc({
|
DocumentShareBloc({
|
||||||
required this.view,
|
required this.view,
|
||||||
}) : super(const DocumentShareState.initial()) {
|
}) : super(DocumentShareState.initial()) {
|
||||||
on<DocumentShareEvent>((event, emit) async {
|
on<DocumentShareEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
|
initial: () async {
|
||||||
|
viewListener = ViewListener(viewId: view.id)
|
||||||
|
..start(
|
||||||
|
onViewUpdated: (value) {
|
||||||
|
add(DocumentShareEvent.updateViewName(value.name));
|
||||||
|
},
|
||||||
|
onViewMoveToTrash: (p0) {
|
||||||
|
add(const DocumentShareEvent.setPublishStatus(false));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
add(const DocumentShareEvent.updatePublishStatus());
|
||||||
|
},
|
||||||
share: (type, path) async {
|
share: (type, path) async {
|
||||||
if (DocumentShareType.unimplemented.contains(type)) {
|
if (DocumentShareType.unimplemented.contains(type)) {
|
||||||
Log.error('DocumentShareType $type is not implemented');
|
Log.error('DocumentShareType $type is not implemented');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(const DocumentShareState.loading());
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
final exporter = DocumentExporter(view);
|
final result = await _export(type, path);
|
||||||
final FlowyResult<ExportDataPB, FlowyError> result =
|
|
||||||
await exporter.export(type.exportType).then((value) {
|
emit(
|
||||||
return value.fold(
|
state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
exportResult: result,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
publish: (nameSpace, publishName) async {
|
||||||
|
// set space name
|
||||||
|
try {
|
||||||
|
final result =
|
||||||
|
await ViewBackendService.getPublishNameSpace().getOrThrow();
|
||||||
|
|
||||||
|
await ViewBackendService.publish(
|
||||||
|
view,
|
||||||
|
name: publishName,
|
||||||
|
).getOrThrow();
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: true,
|
||||||
|
publishResult: FlowySuccess(null),
|
||||||
|
unpublishResult: null,
|
||||||
|
url: '$_url/${result.namespace}/$publishName',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('publish error: $e');
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: false,
|
||||||
|
publishResult: FlowyResult.failure(
|
||||||
|
FlowyError(msg: 'publish error: $e'),
|
||||||
|
),
|
||||||
|
unpublishResult: null,
|
||||||
|
url: '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unPublish: () async {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
publishResult: null,
|
||||||
|
unpublishResult: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await ViewBackendService.unpublish(view);
|
||||||
|
final isPublished = !result.isSuccess;
|
||||||
|
result.onFailure((f) {
|
||||||
|
Log.error('unpublish error: $f');
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: isPublished,
|
||||||
|
publishResult: null,
|
||||||
|
unpublishResult: result,
|
||||||
|
url: result.fold((_) => '', (_) => state.url),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateViewName: (viewName) async {
|
||||||
|
emit(state.copyWith(viewName: viewName));
|
||||||
|
},
|
||||||
|
setPublishStatus: (isPublished) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: isPublished,
|
||||||
|
url: isPublished ? state.url : '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updatePublishStatus: () async {
|
||||||
|
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
||||||
|
final enablePublish =
|
||||||
|
await UserBackendService.getCurrentUserProfile().fold(
|
||||||
|
(v) => v.authenticator == AuthenticatorPB.AppFlowyCloud,
|
||||||
|
(p) => false,
|
||||||
|
);
|
||||||
|
publishInfo.fold((s) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: true,
|
||||||
|
url: '$_url/${s.namespace}/${s.publishName}',
|
||||||
|
viewName: view.name,
|
||||||
|
enablePublish: enablePublish,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, (f) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: false,
|
||||||
|
url: '',
|
||||||
|
viewName: view.name,
|
||||||
|
enablePublish: enablePublish,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
late final ViewListener viewListener;
|
||||||
|
|
||||||
|
late final exporter = DocumentExporter(view);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await viewListener.stop();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<FlowyResult<ExportDataPB, FlowyError>> _export(
|
||||||
|
DocumentShareType type,
|
||||||
|
String? path,
|
||||||
|
) async {
|
||||||
|
final result = await exporter.export(type.exportType);
|
||||||
|
return result.fold(
|
||||||
(s) {
|
(s) {
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DocumentShareType.markdown:
|
case DocumentShareType.markdown:
|
||||||
return FlowyResult.success(_saveMarkdownToPath(s, path));
|
return FlowySuccess(_saveMarkdownToPath(s, path));
|
||||||
case DocumentShareType.html:
|
case DocumentShareType.html:
|
||||||
return FlowyResult.success(_saveHTMLToPath(s, path));
|
return FlowySuccess(_saveHTMLToPath(s, path));
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -44,16 +184,8 @@ class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
|||||||
},
|
},
|
||||||
(f) => FlowyResult.failure(f),
|
(f) => FlowyResult.failure(f),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
emit(DocumentShareState.finish(result));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final ViewPB view;
|
|
||||||
|
|
||||||
ExportDataPB _saveMarkdownToPath(String markdown, String path) {
|
ExportDataPB _saveMarkdownToPath(String markdown, String path) {
|
||||||
File(path).writeAsStringSync(markdown);
|
File(path).writeAsStringSync(markdown);
|
||||||
return ExportDataPB()
|
return ExportDataPB()
|
||||||
@ -93,15 +225,41 @@ enum DocumentShareType {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DocumentShareEvent with _$DocumentShareEvent {
|
class DocumentShareEvent with _$DocumentShareEvent {
|
||||||
const factory DocumentShareEvent.share(DocumentShareType type, String? path) =
|
const factory DocumentShareEvent.initial() = _Initial;
|
||||||
Share;
|
const factory DocumentShareEvent.share(
|
||||||
|
DocumentShareType type,
|
||||||
|
String? path,
|
||||||
|
) = _Share;
|
||||||
|
const factory DocumentShareEvent.publish(
|
||||||
|
String nameSpace,
|
||||||
|
String pageId,
|
||||||
|
) = _Publish;
|
||||||
|
const factory DocumentShareEvent.unPublish() = _UnPublish;
|
||||||
|
const factory DocumentShareEvent.updateViewName(String name) =
|
||||||
|
_UpdateViewName;
|
||||||
|
const factory DocumentShareEvent.updatePublishStatus() = _UpdatePublishStatus;
|
||||||
|
const factory DocumentShareEvent.setPublishStatus(bool isPublished) =
|
||||||
|
_SetPublishStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DocumentShareState with _$DocumentShareState {
|
class DocumentShareState with _$DocumentShareState {
|
||||||
const factory DocumentShareState.initial() = _Initial;
|
const factory DocumentShareState({
|
||||||
const factory DocumentShareState.loading() = _Loading;
|
required bool isPublished,
|
||||||
const factory DocumentShareState.finish(
|
required bool isLoading,
|
||||||
FlowyResult<ExportDataPB, FlowyError> successOrFail,
|
required String url,
|
||||||
) = _Finish;
|
required String viewName,
|
||||||
|
required bool enablePublish,
|
||||||
|
FlowyResult<ExportDataPB, FlowyError>? exportResult,
|
||||||
|
FlowyResult<void, FlowyError>? publishResult,
|
||||||
|
FlowyResult<void, FlowyError>? unpublishResult,
|
||||||
|
}) = _DocumentShareState;
|
||||||
|
|
||||||
|
factory DocumentShareState.initial() => const DocumentShareState(
|
||||||
|
isLoading: false,
|
||||||
|
isPublished: false,
|
||||||
|
enablePublish: true,
|
||||||
|
url: '',
|
||||||
|
viewName: '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_service.dart';
|
import 'package:appflowy/plugins/document/application/document_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -21,6 +22,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:nanoid/nanoid.dart';
|
import 'package:nanoid/nanoid.dart';
|
||||||
|
|
||||||
|
const _kExternalTextType = 'text';
|
||||||
|
|
||||||
/// Uses to adjust the data structure between the editor and the backend.
|
/// Uses to adjust the data structure between the editor and the backend.
|
||||||
///
|
///
|
||||||
/// The editor uses a tree structure to represent the document, while the backend uses a flat structure.
|
/// The editor uses a tree structure to represent the document, while the backend uses a flat structure.
|
||||||
@ -34,11 +37,9 @@ class TransactionAdapter {
|
|||||||
final DocumentService documentService;
|
final DocumentService documentService;
|
||||||
final String documentId;
|
final String documentId;
|
||||||
|
|
||||||
final bool _enableDebug = false;
|
|
||||||
|
|
||||||
Future<void> apply(Transaction transaction, EditorState editorState) async {
|
Future<void> apply(Transaction transaction, EditorState editorState) async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
if (_enableDebug) {
|
if (enableDocumentInternalLog) {
|
||||||
Log.debug('transaction => ${transaction.toJson()}');
|
Log.debug('transaction => ${transaction.toJson()}');
|
||||||
}
|
}
|
||||||
final actions = transaction.operations
|
final actions = transaction.operations
|
||||||
@ -60,8 +61,10 @@ class TransactionAdapter {
|
|||||||
textId: payload.textId,
|
textId: payload.textId,
|
||||||
delta: payload.delta,
|
delta: payload.delta,
|
||||||
);
|
);
|
||||||
if (_enableDebug) {
|
if (enableDocumentInternalLog) {
|
||||||
Log.debug('create external text: ${payload.delta}');
|
Log.debug(
|
||||||
|
'[editor_transaction_adapter] create external text: ${payload.delta}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (type == TextDeltaType.update) {
|
} else if (type == TextDeltaType.update) {
|
||||||
await documentService.updateExternalText(
|
await documentService.updateExternalText(
|
||||||
@ -69,8 +72,10 @@ class TransactionAdapter {
|
|||||||
textId: payload.textId,
|
textId: payload.textId,
|
||||||
delta: payload.delta,
|
delta: payload.delta,
|
||||||
);
|
);
|
||||||
if (_enableDebug) {
|
if (enableDocumentInternalLog) {
|
||||||
Log.debug('update external text: ${payload.delta}');
|
Log.debug(
|
||||||
|
'[editor_transaction_adapter] update external text: ${payload.delta}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,9 +87,9 @@ class TransactionAdapter {
|
|||||||
);
|
);
|
||||||
final elapsed = stopwatch.elapsedMilliseconds;
|
final elapsed = stopwatch.elapsedMilliseconds;
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
if (_enableDebug) {
|
if (enableDocumentInternalLog) {
|
||||||
Log.debug(
|
Log.debug(
|
||||||
'apply transaction cost: total $elapsed ms, converter action $actionCostTime ms, apply action ${elapsed - actionCostTime} ms',
|
'[editor_transaction_adapter] apply transaction cost: total $elapsed ms, converter action $actionCostTime ms, apply action ${elapsed - actionCostTime} ms',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,8 +141,9 @@ extension on InsertOperation {
|
|||||||
// create the external text if the node contains the delta in its data.
|
// create the external text if the node contains the delta in its data.
|
||||||
final delta = node.delta;
|
final delta = node.delta;
|
||||||
TextDeltaPayloadPB? textDeltaPayloadPB;
|
TextDeltaPayloadPB? textDeltaPayloadPB;
|
||||||
|
String? textId;
|
||||||
if (delta != null) {
|
if (delta != null) {
|
||||||
final textId = nanoid(6);
|
textId = nanoid(6);
|
||||||
|
|
||||||
textDeltaPayloadPB = TextDeltaPayloadPB(
|
textDeltaPayloadPB = TextDeltaPayloadPB(
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
@ -148,13 +154,17 @@ extension on InsertOperation {
|
|||||||
// sync the text id to the node
|
// sync the text id to the node
|
||||||
node.externalValues = ExternalValues(
|
node.externalValues = ExternalValues(
|
||||||
externalId: textId,
|
externalId: textId,
|
||||||
externalType: 'text',
|
externalType: _kExternalTextType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the delta from the data when the incremental update is stable.
|
// remove the delta from the data when the incremental update is stable.
|
||||||
final payload = BlockActionPayloadPB()
|
final payload = BlockActionPayloadPB()
|
||||||
..block = node.toBlock(childrenId: nanoid(6))
|
..block = node.toBlock(
|
||||||
|
childrenId: nanoid(6),
|
||||||
|
externalId: textId,
|
||||||
|
externalType: textId != null ? _kExternalTextType : null,
|
||||||
|
)
|
||||||
..parentId = parentId
|
..parentId = parentId
|
||||||
..prevId = prevId;
|
..prevId = prevId;
|
||||||
|
|
||||||
@ -248,7 +258,7 @@ extension on UpdateOperation {
|
|||||||
|
|
||||||
node.externalValues = ExternalValues(
|
node.externalValues = ExternalValues(
|
||||||
externalId: textId,
|
externalId: textId,
|
||||||
externalType: 'text',
|
externalType: _kExternalTextType,
|
||||||
);
|
);
|
||||||
|
|
||||||
actions.add(
|
actions.add(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_close_keyboard_or_menu_button.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_close_keyboard_or_menu_button.dart';
|
||||||
@ -9,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -284,6 +286,14 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||||||
|
|
||||||
if (canUpdateCachedKeyboardHeight) {
|
if (canUpdateCachedKeyboardHeight) {
|
||||||
cachedKeyboardHeight.value = height;
|
cachedKeyboardHeight.value = height;
|
||||||
|
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||||
|
// cache the keyboard height with the view padding in Android
|
||||||
|
if (cachedKeyboardHeight.value != 0) {
|
||||||
|
cachedKeyboardHeight.value +=
|
||||||
|
MediaQuery.of(context).viewPadding.bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height == 0) {
|
if (height == 0) {
|
||||||
@ -385,13 +395,19 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
valueListenable: showMenuNotifier,
|
valueListenable: showMenuNotifier,
|
||||||
builder: (_, showingMenu, __) {
|
builder: (_, showingMenu, __) {
|
||||||
var paddingHeight = height;
|
var keyboardHeight = height;
|
||||||
if (Platform.isAndroid) {
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||||
paddingHeight =
|
if (!showingMenu) {
|
||||||
height + MediaQuery.of(context).viewPadding.bottom;
|
// take the max value of the keyboard height and the view padding
|
||||||
|
// to make sure the toolbar is above the keyboard
|
||||||
|
keyboardHeight = max(
|
||||||
|
keyboardHeight,
|
||||||
|
MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: paddingHeight,
|
height: keyboardHeight,
|
||||||
child: (showingMenu && selectedMenuIndex != null)
|
child: (showingMenu && selectedMenuIndex != null)
|
||||||
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
|
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
|
||||||
context,
|
context,
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
import 'package:markdown/markdown.dart' as md;
|
||||||
|
|
||||||
|
class MarkdownCodeBlockParser extends CustomMarkdownParser {
|
||||||
|
const MarkdownCodeBlockParser();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Node> transform(
|
||||||
|
md.Node element,
|
||||||
|
List<CustomMarkdownParser> parsers, {
|
||||||
|
MarkdownListType listType = MarkdownListType.unknown,
|
||||||
|
int? startNumber,
|
||||||
|
}) {
|
||||||
|
if (element is! md.Element) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.tag != 'pre') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final ec = element.children;
|
||||||
|
if (ec == null || ec.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final code = ec.first;
|
||||||
|
if (code is! md.Element || code.tag != 'code') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
String? language;
|
||||||
|
if (code.attributes.containsKey('class')) {
|
||||||
|
final classes = code.attributes['class']!.split(' ');
|
||||||
|
final languageClass = classes.firstWhere(
|
||||||
|
(c) => c.startsWith('language-'),
|
||||||
|
orElse: () => '',
|
||||||
|
);
|
||||||
|
language = languageClass.substring('language-'.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
final deltaDecoder = DeltaMarkdownDecoder();
|
||||||
|
|
||||||
|
return [
|
||||||
|
codeBlockNode(
|
||||||
|
language: language,
|
||||||
|
delta: deltaDecoder.convertNodes(code.children),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
export 'callout_node_parser.dart';
|
export 'callout_node_parser.dart';
|
||||||
|
export 'markdown_code_parser.dart';
|
||||||
export 'math_equation_node_parser.dart';
|
export 'math_equation_node_parser.dart';
|
||||||
export 'toggle_list_node_parser.dart';
|
export 'toggle_list_node_parser.dart';
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/util/string_extension.dart';
|
||||||
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ExportTab extends StatelessWidget {
|
||||||
|
const ExportTab({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const VSpace(10),
|
||||||
|
_ExportButton(
|
||||||
|
title: LocaleKeys.shareAction_html.tr(),
|
||||||
|
svg: FlowySvgs.export_html_s,
|
||||||
|
onTap: () => _exportHTML(context),
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
_ExportButton(
|
||||||
|
title: LocaleKeys.shareAction_markdown.tr(),
|
||||||
|
svg: FlowySvgs.export_markdown_s,
|
||||||
|
onTap: () => _exportMarkdown(context),
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
_ExportButton(
|
||||||
|
title: LocaleKeys.shareAction_clipboard.tr(),
|
||||||
|
svg: FlowySvgs.duplicate_s,
|
||||||
|
onTap: () => _exportToClipboard(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportHTML(BuildContext context) async {
|
||||||
|
final viewName = context.read<DocumentShareBloc>().state.viewName;
|
||||||
|
final exportPath = await getIt<FilePickerService>().saveFile(
|
||||||
|
dialogTitle: '',
|
||||||
|
fileName: '${viewName.toFileName()}.html',
|
||||||
|
);
|
||||||
|
if (context.mounted && exportPath != null) {
|
||||||
|
context.read<DocumentShareBloc>().add(
|
||||||
|
DocumentShareEvent.share(
|
||||||
|
DocumentShareType.html,
|
||||||
|
exportPath,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportMarkdown(BuildContext context) async {
|
||||||
|
final viewName = context.read<DocumentShareBloc>().state.viewName;
|
||||||
|
final exportPath = await getIt<FilePickerService>().saveFile(
|
||||||
|
dialogTitle: '',
|
||||||
|
fileName: '${viewName.toFileName()}.md',
|
||||||
|
);
|
||||||
|
if (context.mounted && exportPath != null) {
|
||||||
|
context.read<DocumentShareBloc>().add(
|
||||||
|
DocumentShareEvent.share(
|
||||||
|
DocumentShareType.markdown,
|
||||||
|
exportPath,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _exportToClipboard(BuildContext context) async {
|
||||||
|
final documentExporter =
|
||||||
|
DocumentExporter(context.read<DocumentShareBloc>().view);
|
||||||
|
final result = await documentExporter.export(DocumentExportType.markdown);
|
||||||
|
result.fold(
|
||||||
|
(markdown) {
|
||||||
|
getIt<ClipboardService>().setData(
|
||||||
|
ClipboardServiceData(plainText: markdown),
|
||||||
|
);
|
||||||
|
showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.grid_url_copiedNotification.tr(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => showToastNotification(context, message: error.msg),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExportButton extends StatelessWidget {
|
||||||
|
const _ExportButton({
|
||||||
|
required this.title,
|
||||||
|
required this.svg,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final FlowySvgData svg;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final color = Theme.of(context).isLightMode
|
||||||
|
? const Color(0x1E14171B)
|
||||||
|
: Colors.white.withOpacity(0.1);
|
||||||
|
final radius = BorderRadius.circular(10.0);
|
||||||
|
return FlowyButton(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
||||||
|
iconPadding: 12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
borderRadius: radius,
|
||||||
|
),
|
||||||
|
radius: radius,
|
||||||
|
text: FlowyText(title),
|
||||||
|
leftIcon: FlowySvg(svg),
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ShareMenuColors {
|
||||||
|
static Color borderColor(BuildContext context) {
|
||||||
|
final borderColor = Theme.of(context).isLightMode
|
||||||
|
? const Color(0x1E14171B)
|
||||||
|
: Colors.white.withOpacity(0.1);
|
||||||
|
return borderColor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
String replaceInvalidChars(String input) {
|
||||||
|
final RegExp invalidCharsRegex = RegExp('[^a-zA-Z0-9-]');
|
||||||
|
return input.replaceAll(invalidCharsRegex, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> generateNameSpace() async {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// The backend limits the publish name to a maximum of 120 characters.
|
||||||
|
// If the combined length of the ID and the name exceeds 120 characters,
|
||||||
|
// we will truncate the name to ensure the final result is within the limit.
|
||||||
|
// The name should only contain alphanumeric characters and hyphens.
|
||||||
|
Future<String> generatePublishName(String id, String name) async {
|
||||||
|
if (name.length >= 120 - id.length) {
|
||||||
|
name = name.substring(0, 120 - id.length);
|
||||||
|
}
|
||||||
|
return replaceInvalidChars('$name-$id');
|
||||||
|
}
|
@ -0,0 +1,284 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/pubish_color_extension.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/publish_name_generator.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class PublishTab extends StatelessWidget {
|
||||||
|
const PublishTab({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocConsumer<DocumentShareBloc, DocumentShareState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
_showToast(context, state);
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.isPublished
|
||||||
|
? _PublishedWidget(
|
||||||
|
url: state.url,
|
||||||
|
onVisitSite: () {},
|
||||||
|
onUnPublish: () {
|
||||||
|
context
|
||||||
|
.read<DocumentShareBloc>()
|
||||||
|
.add(const DocumentShareEvent.unPublish());
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _UnPublishWidget(
|
||||||
|
onPublish: () async {
|
||||||
|
final id = context.read<DocumentShareBloc>().view.id;
|
||||||
|
final publishName = await generatePublishName(
|
||||||
|
id,
|
||||||
|
state.viewName,
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
context.read<DocumentShareBloc>().add(
|
||||||
|
DocumentShareEvent.publish('', publishName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showToast(BuildContext context, DocumentShareState state) {
|
||||||
|
if (state.publishResult != null) {
|
||||||
|
state.publishResult!.fold(
|
||||||
|
(value) => showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.publish_publishSuccessfully.tr(),
|
||||||
|
),
|
||||||
|
(error) => showToastNotification(
|
||||||
|
context,
|
||||||
|
message: '${LocaleKeys.publish_publishFailed.tr()}: ${error.code}',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (state.unpublishResult != null) {
|
||||||
|
state.unpublishResult!.fold(
|
||||||
|
(value) => showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||||
|
),
|
||||||
|
(error) => showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.publish_unpublishFailed.tr(),
|
||||||
|
description: error.msg,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublishedWidget extends StatefulWidget {
|
||||||
|
const _PublishedWidget({
|
||||||
|
required this.url,
|
||||||
|
required this.onVisitSite,
|
||||||
|
required this.onUnPublish,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String url;
|
||||||
|
final VoidCallback onVisitSite;
|
||||||
|
final VoidCallback onUnPublish;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_PublishedWidget> createState() => _PublishedWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublishedWidgetState extends State<_PublishedWidget> {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
controller.text = widget.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const VSpace(16),
|
||||||
|
const _PublishTabHeader(),
|
||||||
|
const VSpace(16),
|
||||||
|
_PublishUrl(
|
||||||
|
controller: controller,
|
||||||
|
onCopy: (url) {
|
||||||
|
getIt<ClipboardService>().setData(
|
||||||
|
ClipboardServiceData(plainText: url),
|
||||||
|
);
|
||||||
|
|
||||||
|
showToastNotification(
|
||||||
|
context,
|
||||||
|
message: LocaleKeys.grid_url_copy.tr(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSubmitted: (url) {},
|
||||||
|
),
|
||||||
|
const VSpace(16),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildUnpublishButton(),
|
||||||
|
const Spacer(),
|
||||||
|
_buildVisitSiteButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUnpublishButton() {
|
||||||
|
return SizedBox(
|
||||||
|
height: 36,
|
||||||
|
width: 184,
|
||||||
|
child: FlowyButton(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: ShareMenuColors.borderColor(context)),
|
||||||
|
),
|
||||||
|
radius: BorderRadius.circular(10),
|
||||||
|
text: FlowyText.regular(
|
||||||
|
LocaleKeys.shareAction_unPublish.tr(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
onTap: widget.onUnPublish,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVisitSiteButton() {
|
||||||
|
return RoundedTextButton(
|
||||||
|
onPressed: () {
|
||||||
|
safeLaunchUrl(controller.text);
|
||||||
|
},
|
||||||
|
title: LocaleKeys.shareAction_visitSite.tr(),
|
||||||
|
width: 184,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||||
|
fillColor: Theme.of(context).colorScheme.primary,
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnPublishWidget extends StatelessWidget {
|
||||||
|
const _UnPublishWidget({
|
||||||
|
required this.onPublish,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onPublish;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const VSpace(16),
|
||||||
|
const _PublishTabHeader(),
|
||||||
|
const VSpace(16),
|
||||||
|
RoundedTextButton(
|
||||||
|
height: 36,
|
||||||
|
title: LocaleKeys.shareAction_publish.tr(),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 9.0),
|
||||||
|
fontSize: 14.0,
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
onPressed: onPublish,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublishTabHeader extends StatelessWidget {
|
||||||
|
const _PublishTabHeader();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const FlowySvg(FlowySvgs.share_publish_s),
|
||||||
|
const HSpace(6),
|
||||||
|
FlowyText(LocaleKeys.shareAction_publishToTheWeb.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const VSpace(4),
|
||||||
|
FlowyText.regular(
|
||||||
|
LocaleKeys.shareAction_publishToTheWebHint.tr(),
|
||||||
|
fontSize: 12,
|
||||||
|
maxLines: 3,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublishUrl extends StatelessWidget {
|
||||||
|
const _PublishUrl({
|
||||||
|
required this.controller,
|
||||||
|
required this.onCopy,
|
||||||
|
required this.onSubmitted,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final void Function(String url) onCopy;
|
||||||
|
final void Function(String url) onSubmitted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: FlowyTextField(
|
||||||
|
readOnly: true,
|
||||||
|
autoFocus: false,
|
||||||
|
controller: controller,
|
||||||
|
enableBorderColor: ShareMenuColors.borderColor(context),
|
||||||
|
suffixIcon: _buildCopyLinkIcon(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCopyLinkIcon(BuildContext context) {
|
||||||
|
return FlowyHover(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => onCopy(controller.text),
|
||||||
|
child: Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(left: BorderSide(color: Color(0x141F2329))),
|
||||||
|
),
|
||||||
|
child: const FlowySvg(
|
||||||
|
FlowySvgs.m_toolbar_link_m,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/share_menu.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/string_extension.dart';
|
import 'package:appflowy/util/string_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||||
@ -13,6 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -28,23 +30,44 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<DocumentShareBloc>(param1: view),
|
create: (context) => getIt<DocumentShareBloc>(param1: view)
|
||||||
|
..add(const DocumentShareEvent.initial()),
|
||||||
child: BlocListener<DocumentShareBloc, DocumentShareState>(
|
child: BlocListener<DocumentShareBloc, DocumentShareState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
state.mapOrNull(
|
if (state.isLoading == false && state.exportResult != null) {
|
||||||
finish: (state) {
|
state.exportResult!.fold(
|
||||||
state.successOrFail.fold(
|
|
||||||
(data) => _handleExportData(context, data),
|
(data) => _handleExportData(context, data),
|
||||||
_handleExportError,
|
_handleExportError,
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: BlocBuilder<DocumentShareBloc, DocumentShareState>(
|
child: BlocBuilder<DocumentShareBloc, DocumentShareState>(
|
||||||
builder: (context, state) => SizedBox(
|
builder: (context, state) {
|
||||||
|
final tabs = [
|
||||||
|
if (state.enablePublish) ShareMenuTab.publish,
|
||||||
|
ShareMenuTab.exportAs,
|
||||||
|
];
|
||||||
|
final shareBloc = context.read<DocumentShareBloc>();
|
||||||
|
return SizedBox(
|
||||||
height: 32.0,
|
height: 32.0,
|
||||||
child: IntrinsicWidth(child: ShareActionList(view: view)),
|
child: IntrinsicWidth(
|
||||||
|
child: AppFlowyPopover(
|
||||||
|
direction: PopoverDirection.bottomWithRightAligned,
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 422,
|
||||||
),
|
),
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
popupBuilder: (context) => BlocProvider.value(
|
||||||
|
value: shareBloc,
|
||||||
|
child: ShareMenu(
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const InnerDocumentShareButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -75,6 +98,24 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InnerDocumentShareButton extends StatelessWidget {
|
||||||
|
const InnerDocumentShareButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RoundedTextButton(
|
||||||
|
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(10.0),
|
||||||
|
),
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ShareActionList extends StatefulWidget {
|
class ShareActionList extends StatefulWidget {
|
||||||
const ShareActionList({
|
const ShareActionList({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -0,0 +1,186 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/home/tab/_round_underline_tab_indicator.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/export_tab.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'publish_tab.dart';
|
||||||
|
|
||||||
|
enum ShareMenuTab {
|
||||||
|
share,
|
||||||
|
publish,
|
||||||
|
exportAs;
|
||||||
|
|
||||||
|
String get i18n {
|
||||||
|
switch (this) {
|
||||||
|
case ShareMenuTab.share:
|
||||||
|
return LocaleKeys.shareAction_shareTab.tr();
|
||||||
|
case ShareMenuTab.publish:
|
||||||
|
return LocaleKeys.shareAction_publishTab.tr();
|
||||||
|
case ShareMenuTab.exportAs:
|
||||||
|
return LocaleKeys.shareAction_exportAsTab.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareMenu extends StatefulWidget {
|
||||||
|
const ShareMenu({
|
||||||
|
super.key,
|
||||||
|
required this.tabs,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ShareMenuTab> tabs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShareMenu> createState() => _ShareMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShareMenuState extends State<ShareMenu>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late ShareMenuTab selectedTab = widget.tabs.first;
|
||||||
|
late final tabController = TabController(
|
||||||
|
length: widget.tabs.length,
|
||||||
|
vsync: this,
|
||||||
|
initialIndex: widget.tabs.indexOf(selectedTab),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.tabs.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const VSpace(10),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
height: 30,
|
||||||
|
child: _buildTabBar(context),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
height: 1,
|
||||||
|
thickness: 1,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||||
|
child: _buildTab(context),
|
||||||
|
),
|
||||||
|
const VSpace(20),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTabBar(BuildContext context) {
|
||||||
|
final children = [
|
||||||
|
for (final tab in widget.tabs)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: _Segment(
|
||||||
|
tab: tab,
|
||||||
|
isSelected: selectedTab == tab,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
return TabBar(
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
indicator: RoundUnderlineTabIndicator(
|
||||||
|
width: 68.0,
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
width: 3,
|
||||||
|
),
|
||||||
|
insets: const EdgeInsets.only(bottom: -2),
|
||||||
|
),
|
||||||
|
isScrollable: true,
|
||||||
|
controller: tabController,
|
||||||
|
tabs: children,
|
||||||
|
onTap: (index) {
|
||||||
|
setState(() {
|
||||||
|
selectedTab = widget.tabs[index];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTab(BuildContext context) {
|
||||||
|
switch (selectedTab) {
|
||||||
|
case ShareMenuTab.publish:
|
||||||
|
return const PublishTab();
|
||||||
|
case ShareMenuTab.exportAs:
|
||||||
|
return const ExportTab();
|
||||||
|
default:
|
||||||
|
return const Center(
|
||||||
|
child: FlowyText('🏡 under construction'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Segment extends StatefulWidget {
|
||||||
|
const _Segment({
|
||||||
|
required this.tab,
|
||||||
|
required this.isSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isSelected;
|
||||||
|
final ShareMenuTab tab;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_Segment> createState() => _SegmentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SegmentState extends State<_Segment> {
|
||||||
|
bool isHovered = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color? textColor = Theme.of(context).hintColor;
|
||||||
|
if (isHovered) {
|
||||||
|
textColor = const Color(0xFF00BCF0);
|
||||||
|
} else if (widget.isSelected) {
|
||||||
|
textColor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget child = MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => isHovered = true),
|
||||||
|
onExit: (_) => setState(() => isHovered = false),
|
||||||
|
child: FlowyText(
|
||||||
|
widget.tab.i18n,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (widget.tab == ShareMenuTab.publish) {
|
||||||
|
final isPublished = context.watch<DocumentShareBloc>().state.isPublished;
|
||||||
|
// show checkmark icon if published
|
||||||
|
if (isPublished) {
|
||||||
|
child = Row(
|
||||||
|
children: [
|
||||||
|
const FlowySvg(
|
||||||
|
FlowySvgs.published_checkmark_s,
|
||||||
|
blendMode: null,
|
||||||
|
),
|
||||||
|
const HSpace(6),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'
|
|||||||
show WorkspaceSettingPB;
|
show WorkspaceSettingPB;
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'home_bloc.freezed.dart';
|
part 'home_bloc.freezed.dart';
|
||||||
|
|
||||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||||
|
@ -54,9 +54,11 @@ class SidebarSectionsBloc
|
|||||||
_initial(userProfile, workspaceId);
|
_initial(userProfile, workspaceId);
|
||||||
final sectionViews = await _getSectionViews();
|
final sectionViews = await _getSectionViews();
|
||||||
if (sectionViews != null) {
|
if (sectionViews != null) {
|
||||||
|
final containsSpace = _containsSpace(sectionViews);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
section: sectionViews,
|
section: sectionViews,
|
||||||
|
containsSpace: containsSpace,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -65,9 +67,11 @@ class SidebarSectionsBloc
|
|||||||
_reset(userProfile, workspaceId);
|
_reset(userProfile, workspaceId);
|
||||||
final sectionViews = await _getSectionViews();
|
final sectionViews = await _getSectionViews();
|
||||||
if (sectionViews != null) {
|
if (sectionViews != null) {
|
||||||
|
final containsSpace = _containsSpace(sectionViews);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
section: sectionViews,
|
section: sectionViews,
|
||||||
|
containsSpace: containsSpace,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -160,9 +164,11 @@ class SidebarSectionsBloc
|
|||||||
_initial(userProfile, workspaceId);
|
_initial(userProfile, workspaceId);
|
||||||
final sectionViews = await _getSectionViews();
|
final sectionViews = await _getSectionViews();
|
||||||
if (sectionViews != null) {
|
if (sectionViews != null) {
|
||||||
|
final containsSpace = _containsSpace(sectionViews);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
section: sectionViews,
|
section: sectionViews,
|
||||||
|
containsSpace: containsSpace,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// try to open the fist view in public section or private section
|
// try to open the fist view in public section or private section
|
||||||
@ -229,6 +235,11 @@ class SidebarSectionsBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _containsSpace(SidebarSection section) {
|
||||||
|
return section.publicViews.any((view) => view.isSpace) ||
|
||||||
|
section.privateViews.any((view) => view.isSpace);
|
||||||
|
}
|
||||||
|
|
||||||
void _initial(UserProfilePB userProfile, String workspaceId) {
|
void _initial(UserProfilePB userProfile, String workspaceId) {
|
||||||
_workspaceService = WorkspaceService(workspaceId: workspaceId);
|
_workspaceService = WorkspaceService(workspaceId: workspaceId);
|
||||||
|
|
||||||
@ -292,6 +303,7 @@ class SidebarSectionsState with _$SidebarSectionsState {
|
|||||||
required SidebarSection section,
|
required SidebarSection section,
|
||||||
@Default(null) ViewPB? lastCreatedRootView,
|
@Default(null) ViewPB? lastCreatedRootView,
|
||||||
FlowyResult<void, FlowyError>? createRootViewResult,
|
FlowyResult<void, FlowyError>? createRootViewResult,
|
||||||
|
@Default(true) bool containsSpace,
|
||||||
}) = _SidebarSectionsState;
|
}) = _SidebarSectionsState;
|
||||||
|
|
||||||
factory SidebarSectionsState.initial() => const SidebarSectionsState(
|
factory SidebarSectionsState.initial() => const SidebarSectionsState(
|
||||||
|
@ -4,12 +4,14 @@ import 'dart:convert';
|
|||||||
import 'package:appflowy/core/config/kv.dart';
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
import 'package:appflowy/core/config/kv_keys.dart';
|
import 'package:appflowy/core/config/kv_keys.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/shared/list_extension.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/workspace/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_sections_listener.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_sections_listener.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
@ -20,6 +22,7 @@ import 'package:appflowy_result/appflowy_result.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/uuid.dart';
|
import 'package:flowy_infra/uuid.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
@ -68,6 +71,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
|||||||
_initial(userProfile, workspaceId);
|
_initial(userProfile, workspaceId);
|
||||||
|
|
||||||
final (spaces, publicViews, privateViews) = await _getSpaces();
|
final (spaces, publicViews, privateViews) = await _getSpaces();
|
||||||
|
|
||||||
final shouldShowUpgradeDialog = await this.shouldShowUpgradeDialog(
|
final shouldShowUpgradeDialog = await this.shouldShowUpgradeDialog(
|
||||||
spaces: spaces,
|
spaces: spaces,
|
||||||
publicViews: publicViews,
|
publicViews: publicViews,
|
||||||
@ -82,14 +86,13 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
|||||||
currentSpace: currentSpace,
|
currentSpace: currentSpace,
|
||||||
isExpanded: isExpanded,
|
isExpanded: isExpanded,
|
||||||
shouldShowUpgradeDialog: shouldShowUpgradeDialog,
|
shouldShowUpgradeDialog: shouldShowUpgradeDialog,
|
||||||
|
isInitialized: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldShowUpgradeDialog) {
|
if (shouldShowUpgradeDialog && !integrationMode().isTest) {
|
||||||
if (!integrationMode().isTest) {
|
|
||||||
add(const SpaceEvent.migrate());
|
add(const SpaceEvent.migrate());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (openFirstPage) {
|
if (openFirstPage) {
|
||||||
if (currentSpace != null) {
|
if (currentSpace != null) {
|
||||||
@ -192,13 +195,31 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
|||||||
open: (space) async {
|
open: (space) async {
|
||||||
await _openSpace(space);
|
await _openSpace(space);
|
||||||
final isExpanded = await _getSpaceExpandStatus(space);
|
final isExpanded = await _getSpaceExpandStatus(space);
|
||||||
emit(state.copyWith(currentSpace: space, isExpanded: isExpanded));
|
final views = await ViewBackendService.getChildViews(
|
||||||
|
viewId: space.id,
|
||||||
|
);
|
||||||
|
final currentSpace = views.fold(
|
||||||
|
(views) {
|
||||||
|
space.freeze();
|
||||||
|
return space.rebuild((b) {
|
||||||
|
b.childViews.clear();
|
||||||
|
b.childViews.addAll(views);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(_) => space,
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
currentSpace: currentSpace,
|
||||||
|
isExpanded: isExpanded,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// don't open the page automatically on mobile
|
// don't open the page automatically on mobile
|
||||||
if (PlatformExtension.isDesktop) {
|
if (PlatformExtension.isDesktop) {
|
||||||
// open the first page by default
|
// open the first page by default
|
||||||
if (space.childViews.isNotEmpty) {
|
if (currentSpace.childViews.isNotEmpty) {
|
||||||
final firstPage = space.childViews.first;
|
final firstPage = currentSpace.childViews.first;
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
lastCreatedPage: firstPage,
|
lastCreatedPage: firstPage,
|
||||||
@ -330,8 +351,9 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
|||||||
if (sectionViews == null || sectionViews.views.isEmpty) {
|
if (sectionViews == null || sectionViews.views.isEmpty) {
|
||||||
return (<ViewPB>[], <ViewPB>[], <ViewPB>[]);
|
return (<ViewPB>[], <ViewPB>[], <ViewPB>[]);
|
||||||
}
|
}
|
||||||
final publicViews = sectionViews.publicViews;
|
|
||||||
final privateViews = sectionViews.privateViews;
|
final publicViews = sectionViews.publicViews.unique((e) => e.id);
|
||||||
|
final privateViews = sectionViews.privateViews.unique((e) => e.id);
|
||||||
|
|
||||||
final publicSpaces = publicViews.where((e) => e.isSpace);
|
final publicSpaces = publicViews.where((e) => e.isSpace);
|
||||||
final privateSpaces = privateViews.where((e) => e.isSpace);
|
final privateSpaces = privateViews.where((e) => e.isSpace);
|
||||||
@ -690,6 +712,7 @@ class SpaceState with _$SpaceState {
|
|||||||
FlowyResult<void, FlowyError>? createPageResult,
|
FlowyResult<void, FlowyError>? createPageResult,
|
||||||
@Default(false) bool shouldShowUpgradeDialog,
|
@Default(false) bool shouldShowUpgradeDialog,
|
||||||
@Default(false) bool isDuplicatingSpace,
|
@Default(false) bool isDuplicatingSpace,
|
||||||
|
@Default(false) bool isInitialized,
|
||||||
}) = _SpaceState;
|
}) = _SpaceState;
|
||||||
|
|
||||||
factory SpaceState.initial() => const SpaceState();
|
factory SpaceState.initial() => const SpaceState();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy/core/config/kv.dart';
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_listener.dart';
|
|||||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
@ -138,11 +140,18 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
delete: (e) async {
|
delete: (e) async {
|
||||||
|
// unpublish the page and all its child pages if they are published
|
||||||
|
await _unpublishPage(view);
|
||||||
|
|
||||||
final result = await ViewBackendService.delete(viewId: view.id);
|
final result = await ViewBackendService.delete(viewId: view.id);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) =>
|
(l) {
|
||||||
state.copyWith(successOrFailure: FlowyResult.success(null)),
|
return state.copyWith(
|
||||||
|
successOrFailure: FlowyResult.success(null),
|
||||||
|
);
|
||||||
|
},
|
||||||
(error) => state.copyWith(
|
(error) => state.copyWith(
|
||||||
successOrFailure: FlowyResult.failure(error),
|
successOrFailure: FlowyResult.failure(error),
|
||||||
),
|
),
|
||||||
@ -180,8 +189,11 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) =>
|
(l) {
|
||||||
state.copyWith(successOrFailure: FlowyResult.success(null)),
|
return state.copyWith(
|
||||||
|
successOrFailure: FlowyResult.success(null),
|
||||||
|
);
|
||||||
|
},
|
||||||
(error) => state.copyWith(
|
(error) => state.copyWith(
|
||||||
successOrFailure: FlowyResult.failure(error),
|
successOrFailure: FlowyResult.failure(error),
|
||||||
),
|
),
|
||||||
@ -236,6 +248,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
}
|
}
|
||||||
add(const ViewEvent.setIsExpanded(false));
|
add(const ViewEvent.setIsExpanded(false));
|
||||||
},
|
},
|
||||||
|
unpublish: (value) async {
|
||||||
|
if (value.sync) {
|
||||||
|
await _unpublishPage(view);
|
||||||
|
} else {
|
||||||
|
unawaited(_unpublishPage(view));
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -383,6 +402,20 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unpublish the page and all its child pages
|
||||||
|
Future<void> _unpublishPage(ViewPB views) async {
|
||||||
|
final (_, publishedPages) = await ViewBackendService.containPublishedPage(
|
||||||
|
view,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
publishedPages.map((view) async {
|
||||||
|
Log.info('unpublishing page: ${view.id}, ${view.name}');
|
||||||
|
await ViewBackendService.unpublish(view);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool _isSameViewIgnoreChildren(ViewPB from, ViewPB to) {
|
bool _isSameViewIgnoreChildren(ViewPB from, ViewPB to) {
|
||||||
return _hash(from) == _hash(to);
|
return _hash(from) == _hash(to);
|
||||||
}
|
}
|
||||||
@ -430,6 +463,8 @@ class ViewEvent with _$ViewEvent {
|
|||||||
) = UpdateViewVisibility;
|
) = UpdateViewVisibility;
|
||||||
const factory ViewEvent.updateIcon(String? icon) = UpdateIcon;
|
const factory ViewEvent.updateIcon(String? icon) = UpdateIcon;
|
||||||
const factory ViewEvent.collapseAllPages() = CollapseAllPages;
|
const factory ViewEvent.collapseAllPages() = CollapseAllPages;
|
||||||
|
// this event will unpublish the page and all its child pages if they are published
|
||||||
|
const factory ViewEvent.unpublish({required bool sync}) = Unpublish;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -291,4 +291,78 @@ class ViewBackendService {
|
|||||||
);
|
);
|
||||||
return FolderEventUpdateViewVisibilityStatus(payload).send();
|
return FolderEventUpdateViewVisibilityStatus(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<FlowyResult<PublishInfoResponsePB, FlowyError>> getPublishInfo(
|
||||||
|
ViewPB view,
|
||||||
|
) async {
|
||||||
|
final payload = ViewIdPB()..value = view.id;
|
||||||
|
return FolderEventGetPublishInfo(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<FlowyResult<void, FlowyError>> publish(
|
||||||
|
ViewPB view, {
|
||||||
|
String? name,
|
||||||
|
}) async {
|
||||||
|
final payload = PublishViewParamsPB()..viewId = view.id;
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
payload.publishName = name;
|
||||||
|
}
|
||||||
|
return FolderEventPublishView(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<FlowyResult<void, FlowyError>> unpublish(
|
||||||
|
ViewPB view,
|
||||||
|
) async {
|
||||||
|
final payload = UnpublishViewsPayloadPB(viewIds: [view.id]);
|
||||||
|
return FolderEventUnpublishViews(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<FlowyResult<void, FlowyError>> setPublishNameSpace(
|
||||||
|
String name,
|
||||||
|
) async {
|
||||||
|
final payload = SetPublishNamespacePayloadPB()..newNamespace = name;
|
||||||
|
return FolderEventSetPublishNamespace(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<FlowyResult<PublishNamespacePB, FlowyError>>
|
||||||
|
getPublishNameSpace() async {
|
||||||
|
return FolderEventGetPublishNamespace().send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<ViewPB>> getAllChildViews(ViewPB view) async {
|
||||||
|
final views = <ViewPB>[];
|
||||||
|
|
||||||
|
final childViews =
|
||||||
|
await ViewBackendService.getChildViews(viewId: view.id).fold(
|
||||||
|
(s) => s,
|
||||||
|
(f) => [],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final child in childViews) {
|
||||||
|
// filter the view itself
|
||||||
|
if (child.id == view.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
views.add(child);
|
||||||
|
views.addAll(await getAllChildViews(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
return views;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<(bool, List<ViewPB>)> containPublishedPage(ViewPB view) async {
|
||||||
|
final childViews = await ViewBackendService.getAllChildViews(view);
|
||||||
|
final views = [view, ...childViews];
|
||||||
|
final List<ViewPB> publishedPages = [];
|
||||||
|
|
||||||
|
for (final view in views) {
|
||||||
|
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
||||||
|
if (publishInfo.isSuccess) {
|
||||||
|
publishedPages.add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (publishedPages.isNotEmpty, publishedPages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,14 @@ class ViewTitleBarBloc extends Bloc<ViewTitleBarEvent, ViewTitleBarState> {
|
|||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
add(const ViewTitleBarEvent.reload());
|
add(const ViewTitleBarEvent.reload());
|
||||||
|
|
||||||
|
viewListener = ViewListener(
|
||||||
|
viewId: view.id,
|
||||||
|
)..start(
|
||||||
|
onViewUpdated: (p0) {
|
||||||
|
add(const ViewTitleBarEvent.reload());
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
reload: () async {
|
reload: () async {
|
||||||
final List<ViewPB> ancestors =
|
final List<ViewPB> ancestors =
|
||||||
@ -30,6 +38,13 @@ class ViewTitleBarBloc extends Bloc<ViewTitleBarEvent, ViewTitleBarState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
|
late final ViewListener viewListener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
viewListener.stop();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
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/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/markdown_code_parser.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/share/import_service.dart';
|
import 'package:appflowy/workspace/application/settings/share/import_service.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/import/import_type.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/import/import_type.dart';
|
||||||
@ -220,7 +221,12 @@ class _ImportPanelState extends State<ImportPanel> {
|
|||||||
Uint8List? _documentDataFrom(ImportType importType, String data) {
|
Uint8List? _documentDataFrom(ImportType importType, String data) {
|
||||||
switch (importType) {
|
switch (importType) {
|
||||||
case ImportType.markdownOrText:
|
case ImportType.markdownOrText:
|
||||||
final document = markdownToDocument(data);
|
final document = markdownToDocument(
|
||||||
|
data,
|
||||||
|
markdownParsers: [
|
||||||
|
const MarkdownCodeBlockParser(),
|
||||||
|
],
|
||||||
|
);
|
||||||
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
||||||
case ImportType.historyDocument:
|
case ImportType.historyDocument:
|
||||||
final document = EditorMigration.migrateDocument(data);
|
final document = EditorMigration.migrateDocument(data);
|
||||||
|
@ -11,6 +11,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
typedef MovePageMenuOnSelected = void Function(ViewPB space, ViewPB view);
|
||||||
|
|
||||||
class MovePageMenu extends StatefulWidget {
|
class MovePageMenu extends StatefulWidget {
|
||||||
const MovePageMenu({
|
const MovePageMenu({
|
||||||
super.key,
|
super.key,
|
||||||
@ -23,7 +25,7 @@ class MovePageMenu extends StatefulWidget {
|
|||||||
final ViewPB sourceView;
|
final ViewPB sourceView;
|
||||||
final UserProfilePB userProfile;
|
final UserProfilePB userProfile;
|
||||||
final String workspaceId;
|
final String workspaceId;
|
||||||
final void Function(ViewPB view) onSelected;
|
final MovePageMenuOnSelected onSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MovePageMenu> createState() => _MovePageMenuState();
|
State<MovePageMenu> createState() => _MovePageMenuState();
|
||||||
@ -88,7 +90,7 @@ class _MovePageMenuState extends State<MovePageMenu> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: _buildGroupedViews(state.queryResults!),
|
child: _buildGroupedViews(space, state.queryResults!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -99,7 +101,7 @@ class _MovePageMenuState extends State<MovePageMenu> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGroupedViews(List<ViewPB> views) {
|
Widget _buildGroupedViews(ViewPB space, List<ViewPB> views) {
|
||||||
final groupedViews = views
|
final groupedViews = views
|
||||||
.where(
|
.where(
|
||||||
(view) =>
|
(view) =>
|
||||||
@ -108,7 +110,7 @@ class _MovePageMenuState extends State<MovePageMenu> {
|
|||||||
.toList();
|
.toList();
|
||||||
return _MovePageGroupedViews(
|
return _MovePageGroupedViews(
|
||||||
views: groupedViews,
|
views: groupedViews,
|
||||||
onSelected: widget.onSelected,
|
onSelected: (view) => widget.onSelected(space, view),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ class _MovePageMenuState extends State<MovePageMenu> {
|
|||||||
child: CurrentSpace(
|
child: CurrentSpace(
|
||||||
onTapBlankArea: () {
|
onTapBlankArea: () {
|
||||||
// move the page to current space
|
// move the page to current space
|
||||||
widget.onSelected(space);
|
widget.onSelected(space, space);
|
||||||
},
|
},
|
||||||
space: space,
|
space: space,
|
||||||
),
|
),
|
||||||
@ -145,7 +147,7 @@ class _MovePageMenuState extends State<MovePageMenu> {
|
|||||||
disableSelectedStatus: true,
|
disableSelectedStatus: true,
|
||||||
// hide the ... and + buttons
|
// hide the ... and + buttons
|
||||||
rightIconsBuilder: (context, view) => [],
|
rightIconsBuilder: (context, view) => [],
|
||||||
onSelected: (_, view) => widget.onSelected(view),
|
onSelected: (_, view) => widget.onSelected(space, view),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/blank/blank.dart';
|
import 'package:appflowy/plugins/blank/blank.dart';
|
||||||
@ -32,11 +30,12 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/side
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
Loading? _duplicateSpaceLoading;
|
Loading? _duplicateSpaceLoading;
|
||||||
@ -360,10 +359,24 @@ class _SidebarState extends State<_Sidebar> {
|
|||||||
Widget _renderFolderOrSpace(EdgeInsets menuHorizontalInset) {
|
Widget _renderFolderOrSpace(EdgeInsets menuHorizontalInset) {
|
||||||
final spaceState = context.read<SpaceBloc>().state;
|
final spaceState = context.read<SpaceBloc>().state;
|
||||||
final workspaceState = context.read<UserWorkspaceBloc>().state;
|
final workspaceState = context.read<UserWorkspaceBloc>().state;
|
||||||
|
|
||||||
|
if (!spaceState.isInitialized) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
// there's no space or the workspace is not collaborative,
|
// there's no space or the workspace is not collaborative,
|
||||||
// show the folder section (Workspace, Private, Personal)
|
// show the folder section (Workspace, Private, Personal)
|
||||||
// otherwise, show the space
|
// otherwise, show the space
|
||||||
return spaceState.spaces.isEmpty || !workspaceState.isCollabWorkspaceOn
|
final sidebarSectionBloc = context.watch<SidebarSectionsBloc>();
|
||||||
|
final containsSpace = sidebarSectionBloc.state.containsSpace;
|
||||||
|
|
||||||
|
if (containsSpace && spaceState.spaces.isEmpty) {
|
||||||
|
context.read<SpaceBloc>().add(const SpaceEvent.didReceiveSpaceUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
return !containsSpace ||
|
||||||
|
spaceState.spaces.isEmpty ||
|
||||||
|
!workspaceState.isCollabWorkspaceOn
|
||||||
? Expanded(
|
? Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: menuHorizontalInset - const EdgeInsets.only(right: 6),
|
padding: menuHorizontalInset - const EdgeInsets.only(right: 6),
|
||||||
|
@ -19,6 +19,7 @@ import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SpacePermissionSwitch extends StatefulWidget {
|
class SpacePermissionSwitch extends StatefulWidget {
|
||||||
@ -222,14 +223,37 @@ class SpaceCancelOrConfirmButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteSpacePopup extends StatelessWidget {
|
class ConfirmDeletionPopup extends StatefulWidget {
|
||||||
const DeleteSpacePopup({super.key});
|
const ConfirmDeletionPopup({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.onConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final VoidCallback onConfirm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConfirmDeletionPopup> createState() => _ConfirmDeletionPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConfirmDeletionPopupState extends State<ConfirmDeletionPopup> {
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final space = context.read<SpaceBloc>().state.currentSpace;
|
return KeyboardListener(
|
||||||
final name = space != null ? space.name : '';
|
focusNode: focusNode,
|
||||||
return Padding(
|
autofocus: true,
|
||||||
|
onKeyEvent: (event) {
|
||||||
|
if (event is KeyDownEvent &&
|
||||||
|
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 20.0,
|
vertical: 20.0,
|
||||||
horizontal: 20.0,
|
horizontal: 20.0,
|
||||||
@ -240,11 +264,14 @@ class DeleteSpacePopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
FlowyText(
|
Flexible(
|
||||||
LocaleKeys.space_deleteConfirmation.tr() + name,
|
child: FlowyText(
|
||||||
|
widget.title,
|
||||||
fontSize: 14.0,
|
fontSize: 14.0,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
),
|
||||||
|
const HSpace(6.0),
|
||||||
FlowyButton(
|
FlowyButton(
|
||||||
useIntrinsicWidth: true,
|
useIntrinsicWidth: true,
|
||||||
text: const FlowySvg(FlowySvgs.upgrade_close_s),
|
text: const FlowySvg(FlowySvgs.upgrade_close_s),
|
||||||
@ -254,7 +281,7 @@ class DeleteSpacePopup extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const VSpace(8.0),
|
const VSpace(8.0),
|
||||||
FlowyText.regular(
|
FlowyText.regular(
|
||||||
LocaleKeys.space_deleteConfirmationDescription.tr(),
|
widget.description,
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
@ -264,7 +291,7 @@ class DeleteSpacePopup extends StatelessWidget {
|
|||||||
SpaceCancelOrConfirmButton(
|
SpaceCancelOrConfirmButton(
|
||||||
onCancel: () => Navigator.of(context).pop(),
|
onCancel: () => Navigator.of(context).pop(),
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
context.read<SpaceBloc>().add(const SpaceEvent.delete(null));
|
widget.onConfirm();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
confirmButtonName: LocaleKeys.space_delete.tr(),
|
confirmButtonName: LocaleKeys.space_delete.tr(),
|
||||||
@ -272,6 +299,7 @@ class DeleteSpacePopup extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,18 +227,14 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
|
|||||||
|
|
||||||
void _showDeleteSpaceDialog(BuildContext context) {
|
void _showDeleteSpaceDialog(BuildContext context) {
|
||||||
final spaceBloc = context.read<SpaceBloc>();
|
final spaceBloc = context.read<SpaceBloc>();
|
||||||
showDialog(
|
final space = spaceBloc.state.currentSpace;
|
||||||
|
final name = space != null ? space.name : '';
|
||||||
|
showConfirmDeletionDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) {
|
name: name,
|
||||||
return Dialog(
|
description: LocaleKeys.space_deleteConfirmationDescription.tr(),
|
||||||
shape: RoundedRectangleBorder(
|
onConfirm: () {
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
context.read<SpaceBloc>().add(const SpaceEvent.delete(null));
|
||||||
),
|
|
||||||
child: BlocProvider.value(
|
|
||||||
value: spaceBloc,
|
|
||||||
child: const SizedBox(width: 440, child: DeleteSpacePopup()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -105,14 +105,16 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell {
|
|||||||
case WorkspaceMoreAction.divider:
|
case WorkspaceMoreAction.divider:
|
||||||
break;
|
break;
|
||||||
case WorkspaceMoreAction.delete:
|
case WorkspaceMoreAction.delete:
|
||||||
await NavigatorAlertDialog(
|
await showConfirmDeletionDialog(
|
||||||
title: LocaleKeys.workspace_deleteWorkspaceHintText.tr(),
|
context: context,
|
||||||
confirm: () {
|
name: workspace.name,
|
||||||
|
description: LocaleKeys.workspace_deleteWorkspaceHintText.tr(),
|
||||||
|
onConfirm: () {
|
||||||
workspaceBloc.add(
|
workspaceBloc.add(
|
||||||
UserWorkspaceEvent.deleteWorkspace(workspace.workspaceId),
|
UserWorkspaceEvent.deleteWorkspace(workspace.workspaceId),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).show(context);
|
);
|
||||||
case WorkspaceMoreAction.rename:
|
case WorkspaceMoreAction.rename:
|
||||||
await NavigatorTextFieldDialog(
|
await NavigatorTextFieldDialog(
|
||||||
title: LocaleKeys.workspace_renameWorkspace.tr(),
|
title: LocaleKeys.workspace_renameWorkspace.tr(),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||||
@ -5,6 +7,7 @@ import 'package:appflowy/startup/startup.dart';
|
|||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
@ -17,9 +20,10 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.d
|
|||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart';
|
import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -334,6 +338,7 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
|||||||
onMove: widget.isPlaceholder
|
onMove: widget.isPlaceholder
|
||||||
? (from, to) => _moveViewCrossSection(
|
? (from, to) => _moveViewCrossSection(
|
||||||
context,
|
context,
|
||||||
|
null,
|
||||||
widget.view,
|
widget.view,
|
||||||
widget.parentView,
|
widget.parentView,
|
||||||
widget.spaceType,
|
widget.spaceType,
|
||||||
@ -690,7 +695,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
spaceType: widget.spaceType,
|
spaceType: widget.spaceType,
|
||||||
onEditing: (value) =>
|
onEditing: (value) =>
|
||||||
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
||||||
onAction: (action, data) {
|
onAction: (action, data) async {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ViewMoreActionType.favorite:
|
case ViewMoreActionType.favorite:
|
||||||
case ViewMoreActionType.unFavorite:
|
case ViewMoreActionType.unFavorite:
|
||||||
@ -699,6 +704,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
.add(FavoriteEvent.toggle(widget.view));
|
.add(FavoriteEvent.toggle(widget.view));
|
||||||
break;
|
break;
|
||||||
case ViewMoreActionType.rename:
|
case ViewMoreActionType.rename:
|
||||||
|
unawaited(
|
||||||
NavigatorTextFieldDialog(
|
NavigatorTextFieldDialog(
|
||||||
title: LocaleKeys.disclosureAction_rename.tr(),
|
title: LocaleKeys.disclosureAction_rename.tr(),
|
||||||
autoSelectAllText: true,
|
autoSelectAllText: true,
|
||||||
@ -707,10 +713,27 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
onConfirm: (newValue, _) {
|
onConfirm: (newValue, _) {
|
||||||
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
|
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
|
||||||
},
|
},
|
||||||
).show(context);
|
).show(context),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case ViewMoreActionType.delete:
|
case ViewMoreActionType.delete:
|
||||||
|
// get if current page contains published child views
|
||||||
|
final (containPublishedPage, _) =
|
||||||
|
await ViewBackendService.containPublishedPage(
|
||||||
|
widget.view,
|
||||||
|
);
|
||||||
|
if (containPublishedPage && context.mounted) {
|
||||||
|
await showConfirmDeletionDialog(
|
||||||
|
context: context,
|
||||||
|
name: widget.view.name,
|
||||||
|
description: LocaleKeys.publish_containsPublishedPage.tr(),
|
||||||
|
onConfirm: () {
|
||||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (context.mounted) {
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ViewMoreActionType.duplicate:
|
case ViewMoreActionType.duplicate:
|
||||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||||
@ -726,22 +749,22 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final result = data;
|
final result = data;
|
||||||
ViewBackendService.updateViewIcon(
|
await ViewBackendService.updateViewIcon(
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
viewIcon: result.emoji,
|
viewIcon: result.emoji,
|
||||||
iconType: result.type.toProto(),
|
iconType: result.type.toProto(),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ViewMoreActionType.moveTo:
|
case ViewMoreActionType.moveTo:
|
||||||
final target = data;
|
final value = data;
|
||||||
if (target is! ViewPB) {
|
if (value is! (ViewPB, ViewPB)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugPrint(
|
final space = value.$1;
|
||||||
'Move view ${widget.view.id}, ${widget.view.name} to ${target.id}, ${target.name}',
|
final target = value.$2;
|
||||||
);
|
|
||||||
_moveViewCrossSection(
|
_moveViewCrossSection(
|
||||||
context,
|
context,
|
||||||
|
space,
|
||||||
widget.view,
|
widget.view,
|
||||||
widget.parentView,
|
widget.parentView,
|
||||||
widget.spaceType,
|
widget.spaceType,
|
||||||
@ -802,6 +825,7 @@ bool isReferencedDatabaseView(ViewPB view, ViewPB? parentView) {
|
|||||||
|
|
||||||
void _moveViewCrossSection(
|
void _moveViewCrossSection(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
ViewPB? toSpace,
|
||||||
ViewPB view,
|
ViewPB view,
|
||||||
ViewPB? parentView,
|
ViewPB? parentView,
|
||||||
FolderSpaceType spaceType,
|
FolderSpaceType spaceType,
|
||||||
@ -822,6 +846,17 @@ void _moveViewCrossSection(
|
|||||||
final toSection = spaceType == FolderSpaceType.public
|
final toSection = spaceType == FolderSpaceType.public
|
||||||
? ViewSectionPB.Public
|
? ViewSectionPB.Public
|
||||||
: ViewSectionPB.Private;
|
: ViewSectionPB.Private;
|
||||||
|
|
||||||
|
final currentSpace = context.read<SpaceBloc>().state.currentSpace;
|
||||||
|
if (currentSpace != null &&
|
||||||
|
toSpace != null &&
|
||||||
|
currentSpace.id != toSpace.id) {
|
||||||
|
Log.info(
|
||||||
|
'Move view(${from.name}) to another space(${toSpace.name}), unpublish the view',
|
||||||
|
);
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.unpublish(sync: false));
|
||||||
|
}
|
||||||
|
|
||||||
context.read<ViewBloc>().add(
|
context.read<ViewBloc>().add(
|
||||||
ViewEvent.move(
|
ViewEvent.move(
|
||||||
from,
|
from,
|
||||||
|
@ -192,8 +192,8 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
|
|||||||
sourceView: sourceView,
|
sourceView: sourceView,
|
||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
onSelected: (view) {
|
onSelected: (space, view) {
|
||||||
onTap(controller, view);
|
onTap(controller, (space, view));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -53,7 +53,7 @@ class SettingsAIView extends StatelessWidget {
|
|||||||
AIModelSelection(),
|
AIModelSelection(),
|
||||||
_AISearchToggle(value: false),
|
_AISearchToggle(value: false),
|
||||||
// Disable local AI configuration for now. It's not ready for production.
|
// Disable local AI configuration for now. It's not ready for production.
|
||||||
// LocalAIConfiguration(),
|
LocalAIConfiguration(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/util/color_to_hex_string.dart';
|
import 'package:appflowy/util/color_to_hex_string.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flex_color_picker/flex_color_picker.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DocumentColorSettingButton extends StatefulWidget {
|
class DocumentColorSettingButton extends StatefulWidget {
|
||||||
const DocumentColorSettingButton({
|
const DocumentColorSettingButton({
|
||||||
@ -90,24 +92,11 @@ class DocumentColorSettingDialogState
|
|||||||
late TextEditingController hexController;
|
late TextEditingController hexController;
|
||||||
late TextEditingController opacityController;
|
late TextEditingController opacityController;
|
||||||
|
|
||||||
void updateSelectedColor() {
|
|
||||||
if (widget.formKey.currentState!.validate()) {
|
|
||||||
setState(() {
|
|
||||||
final colorValue = int.tryParse(
|
|
||||||
hexController.text.combineHexWithOpacity(opacityController.text),
|
|
||||||
);
|
|
||||||
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
|
|
||||||
selectedColorOnDialog = Color(colorValue!);
|
|
||||||
widget.onChanged(selectedColorOnDialog!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
selectedColorOnDialog = widget.currentColor;
|
selectedColorOnDialog = widget.currentColor;
|
||||||
currentColorHexString = widget.currentColor.toHexString();
|
currentColorHexString = ColorExtension(widget.currentColor).toHexString();
|
||||||
hexController = TextEditingController(
|
hexController = TextEditingController(
|
||||||
text: currentColorHexString.extractHex(),
|
text: currentColorHexString.extractHex(),
|
||||||
);
|
);
|
||||||
@ -145,17 +134,25 @@ class DocumentColorSettingDialogState
|
|||||||
controller: hexController,
|
controller: hexController,
|
||||||
labelText: LocaleKeys.editor_hexValue.tr(),
|
labelText: LocaleKeys.editor_hexValue.tr(),
|
||||||
hintText: '6fc9e7',
|
hintText: '6fc9e7',
|
||||||
onChanged: (_) => updateSelectedColor(),
|
onChanged: (_) => _updateSelectedColor(),
|
||||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||||
validator: (v) => validateHexValue(v, opacityController.text),
|
validator: (v) => validateHexValue(v, opacityController.text),
|
||||||
|
suffixIcon: GestureDetector(
|
||||||
|
onTap: () => _showColorPickerDialog(
|
||||||
|
context: context,
|
||||||
|
currentColor: widget.currentColor,
|
||||||
|
updateColor: _updateColor,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.color_lens_rounded),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const VSpace(8),
|
const VSpace(8),
|
||||||
_ColorSettingTextField(
|
_ColorSettingTextField(
|
||||||
controller: opacityController,
|
controller: opacityController,
|
||||||
labelText: LocaleKeys.editor_opacity.tr(),
|
labelText: LocaleKeys.editor_opacity.tr(),
|
||||||
hintText: '50',
|
hintText: '50',
|
||||||
onChanged: (_) => updateSelectedColor(),
|
onChanged: (_) => _updateSelectedColor(),
|
||||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||||
validator: (value) => validateOpacityValue(value),
|
validator: (value) => validateOpacityValue(value),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -164,6 +161,28 @@ class DocumentColorSettingDialogState
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateSelectedColor() {
|
||||||
|
if (widget.formKey.currentState!.validate()) {
|
||||||
|
setState(() {
|
||||||
|
final colorValue = int.tryParse(
|
||||||
|
hexController.text.combineHexWithOpacity(opacityController.text),
|
||||||
|
);
|
||||||
|
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
|
||||||
|
selectedColorOnDialog = Color(colorValue!);
|
||||||
|
widget.onChanged(selectedColorOnDialog!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateColor(Color color) {
|
||||||
|
setState(() {
|
||||||
|
hexController.text = ColorExtension(color).toHexString().extractHex();
|
||||||
|
opacityController.text =
|
||||||
|
ColorExtension(color).toHexString().extractOpacity();
|
||||||
|
});
|
||||||
|
_updateSelectedColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ColorSettingTextField extends StatelessWidget {
|
class _ColorSettingTextField extends StatelessWidget {
|
||||||
@ -172,6 +191,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
required this.labelText,
|
required this.labelText,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
required this.onFieldSubmitted,
|
required this.onFieldSubmitted,
|
||||||
|
this.suffixIcon,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.validator,
|
this.validator,
|
||||||
});
|
});
|
||||||
@ -180,6 +200,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
final String labelText;
|
final String labelText;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final void Function(String) onFieldSubmitted;
|
final void Function(String) onFieldSubmitted;
|
||||||
|
final Widget? suffixIcon;
|
||||||
final void Function(String)? onChanged;
|
final void Function(String)? onChanged;
|
||||||
final String? Function(String?)? validator;
|
final String? Function(String?)? validator;
|
||||||
|
|
||||||
@ -191,6 +212,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: labelText,
|
labelText: labelText,
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
suffixIcon: suffixIcon,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide(color: style.colorScheme.outline),
|
borderSide: BorderSide(color: style.colorScheme.outline),
|
||||||
),
|
),
|
||||||
@ -241,3 +263,145 @@ String? validateOpacityValue(String? value) {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _kColorCircleWidth = 46.0;
|
||||||
|
const _kColorCircleHeight = 46.0;
|
||||||
|
const _kColorCircleRadius = 23.0;
|
||||||
|
const _kColorOpacityThumbRadius = 23.0;
|
||||||
|
const _kDialogButtonPaddingHorizontal = 24.0;
|
||||||
|
const _kDialogButtonPaddingVertical = 12.0;
|
||||||
|
const _kColorsColumnSpacing = 3.0;
|
||||||
|
|
||||||
|
class _ColorPicker extends StatelessWidget {
|
||||||
|
const _ColorPicker({
|
||||||
|
required this.selectedColor,
|
||||||
|
required this.onColorChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Color selectedColor;
|
||||||
|
final void Function(Color) onColorChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: ColorPicker(
|
||||||
|
width: _kColorCircleWidth,
|
||||||
|
height: _kColorCircleHeight,
|
||||||
|
borderRadius: _kColorCircleRadius,
|
||||||
|
enableOpacity: true,
|
||||||
|
opacityThumbRadius: _kColorOpacityThumbRadius,
|
||||||
|
columnSpacing: _kColorsColumnSpacing,
|
||||||
|
enableTooltips: false,
|
||||||
|
pickersEnabled: const {
|
||||||
|
ColorPickerType.both: false,
|
||||||
|
ColorPickerType.primary: true,
|
||||||
|
ColorPickerType.accent: true,
|
||||||
|
ColorPickerType.wheel: true,
|
||||||
|
},
|
||||||
|
subheading: Text(
|
||||||
|
LocaleKeys.settings_appearance_documentSettings_colorShade.tr(),
|
||||||
|
style: theme.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
opacitySubheading: Text(
|
||||||
|
LocaleKeys.settings_appearance_documentSettings_opacity.tr(),
|
||||||
|
style: theme.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
onColorChanged: onColorChanged,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColorPickerActions extends StatelessWidget {
|
||||||
|
const _ColorPickerActions({
|
||||||
|
required this.onReset,
|
||||||
|
required this.onUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onReset;
|
||||||
|
final VoidCallback onUpdate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 24,
|
||||||
|
child: FlowyTextButton(
|
||||||
|
LocaleKeys.button_cancel.tr(),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: _kDialogButtonPaddingHorizontal,
|
||||||
|
vertical: _kDialogButtonPaddingVertical,
|
||||||
|
),
|
||||||
|
fontColor: AFThemeExtension.of(context).textColor,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
radius: Corners.s12Border,
|
||||||
|
onPressed: onReset,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: FlowyTextButton(
|
||||||
|
LocaleKeys.button_done.tr(),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: _kDialogButtonPaddingHorizontal,
|
||||||
|
vertical: _kDialogButtonPaddingVertical,
|
||||||
|
),
|
||||||
|
radius: Corners.s12Border,
|
||||||
|
fontHoverColor: Colors.white,
|
||||||
|
fillColor: Theme.of(context).colorScheme.primary,
|
||||||
|
hoverColor: const Color(0xFF005483),
|
||||||
|
onPressed: onUpdate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showColorPickerDialog({
|
||||||
|
required BuildContext context,
|
||||||
|
String? title,
|
||||||
|
required Color currentColor,
|
||||||
|
required void Function(Color) updateColor,
|
||||||
|
}) {
|
||||||
|
final style = Theme.of(context);
|
||||||
|
Color selectedColor = currentColor;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: const Color.fromARGB(128, 0, 0, 0),
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
icon: const Icon(Icons.palette),
|
||||||
|
title: Text(
|
||||||
|
title ??
|
||||||
|
LocaleKeys.settings_appearance_documentSettings_pickColor.tr(),
|
||||||
|
style: style.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
content: _ColorPicker(
|
||||||
|
selectedColor: selectedColor,
|
||||||
|
onColorChanged: (color) => selectedColor = color,
|
||||||
|
),
|
||||||
|
actionsPadding: const EdgeInsets.all(8),
|
||||||
|
actions: [
|
||||||
|
_ColorPickerActions(
|
||||||
|
onReset: () {
|
||||||
|
updateColor(currentColor);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
onUpdate: () {
|
||||||
|
updateColor(selectedColor);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
@ -10,6 +9,8 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
|||||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toastification/toastification.dart';
|
||||||
|
|
||||||
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
|
|
||||||
@ -283,3 +284,58 @@ class OkCancelButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showToastNotification(
|
||||||
|
BuildContext context, {
|
||||||
|
required String message,
|
||||||
|
String? description,
|
||||||
|
}) {
|
||||||
|
toastification.show(
|
||||||
|
context: context,
|
||||||
|
type: ToastificationType.success,
|
||||||
|
style: ToastificationStyle.flat,
|
||||||
|
title: FlowyText(message),
|
||||||
|
description: description != null
|
||||||
|
? FlowyText.regular(
|
||||||
|
description,
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
maxLines: 3,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
autoCloseDuration: const Duration(milliseconds: 3000),
|
||||||
|
showProgressBar: false,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.withOpacity(0.4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showConfirmDeletionDialog({
|
||||||
|
required BuildContext context,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
required VoidCallback onConfirm,
|
||||||
|
}) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
final title = LocaleKeys.space_deleteConfirmation.tr() + name;
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 440,
|
||||||
|
child: ConfirmDeletionPopup(
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
|
import 'package:appflowy/env/env.dart';
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||||
import 'package:appflowy/startup/tasks/rust_sdk.dart';
|
import 'package:appflowy/startup/tasks/rust_sdk.dart';
|
||||||
import 'package:appflowy/util/theme_extension.dart';
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.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_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
@ -203,10 +206,25 @@ class FlowyVersionDescription extends CustomActionCell {
|
|||||||
thickness: 1.0,
|
thickness: 1.0,
|
||||||
),
|
),
|
||||||
const VSpace(6),
|
const VSpace(6),
|
||||||
FlowyText(
|
GestureDetector(
|
||||||
"$appName $version",
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (Env.internalBuild != '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enableDocumentInternalLog = !enableDocumentInternalLog;
|
||||||
|
showToastNotification(
|
||||||
|
context,
|
||||||
|
message: enableDocumentInternalLog
|
||||||
|
? 'Enabled Internal Log'
|
||||||
|
: 'Disabled Internal Log',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: FlowyText(
|
||||||
|
'$appName $version',
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(
|
).padding(
|
||||||
horizontal: ActionListSizes.itemHPadding,
|
horizontal: ActionListSizes.itemHPadding,
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
enum ViewActionType {
|
enum ViewActionType {
|
||||||
@ -46,8 +47,8 @@ class ViewAction extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
context.read<ViewBloc>().add(type.actionEvent);
|
await _onAction(context);
|
||||||
mutex?.close();
|
mutex?.close();
|
||||||
},
|
},
|
||||||
text: FlowyText.regular(
|
text: FlowyText.regular(
|
||||||
@ -63,4 +64,25 @@ class ViewAction extends StatelessWidget {
|
|||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onAction(BuildContext context) async {
|
||||||
|
if (type == ViewActionType.delete) {
|
||||||
|
final (containPublishedPage, _) =
|
||||||
|
await ViewBackendService.containPublishedPage(view);
|
||||||
|
if (containPublishedPage && context.mounted) {
|
||||||
|
await showConfirmDeletionDialog(
|
||||||
|
context: context,
|
||||||
|
name: view.name,
|
||||||
|
description: LocaleKeys.publish_containsPublishedPage.tr(),
|
||||||
|
onConfirm: () {
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (context.mounted) {
|
||||||
|
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.read<ViewBloc>().add(type.actionEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,9 @@ class ViewTitleBar extends StatelessWidget {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 24,
|
height: 24,
|
||||||
child: Row(children: _buildViewTitles(context, ancestors)),
|
child: Row(
|
||||||
|
children: _buildViewTitles(context, ancestors),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -76,6 +78,7 @@ class ViewTitleBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final child = FlowyTooltip(
|
final child = FlowyTooltip(
|
||||||
|
key: ValueKey(view.id),
|
||||||
message: view.name,
|
message: view.name,
|
||||||
child: _ViewTitle(
|
child: _ViewTitle(
|
||||||
view: view,
|
view: view,
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra/size.dart';
|
|
||||||
|
|
||||||
class FlowyTextField extends StatefulWidget {
|
class FlowyTextField extends StatefulWidget {
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
final String? text;
|
final String? text;
|
||||||
@ -37,6 +36,8 @@ class FlowyTextField extends StatefulWidget {
|
|||||||
final List<TextInputFormatter>? inputFormatters;
|
final List<TextInputFormatter>? inputFormatters;
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
final bool isDense;
|
final bool isDense;
|
||||||
|
final bool readOnly;
|
||||||
|
final Color? enableBorderColor;
|
||||||
|
|
||||||
const FlowyTextField({
|
const FlowyTextField({
|
||||||
super.key,
|
super.key,
|
||||||
@ -71,6 +72,8 @@ class FlowyTextField extends StatefulWidget {
|
|||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.isDense = true,
|
this.isDense = true,
|
||||||
|
this.readOnly = false,
|
||||||
|
this.enableBorderColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -144,6 +147,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
|
readOnly: widget.readOnly,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
onChanged: (text) {
|
onChanged: (text) {
|
||||||
@ -178,7 +182,8 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
|||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: widget.enableBorderColor ??
|
||||||
|
Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isDense: false,
|
isDense: false,
|
||||||
@ -199,7 +204,10 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
|||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: widget.readOnly
|
||||||
|
? widget.enableBorderColor ??
|
||||||
|
Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
|
@ -13,6 +13,7 @@ class RoundedTextButton extends StatelessWidget {
|
|||||||
final Color? hoverColor;
|
final Color? hoverColor;
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
final double? fontSize;
|
final double? fontSize;
|
||||||
|
final FontWeight? fontWeight;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
const RoundedTextButton({
|
const RoundedTextButton({
|
||||||
@ -27,6 +28,7 @@ class RoundedTextButton extends StatelessWidget {
|
|||||||
this.hoverColor,
|
this.hoverColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
this.fontSize,
|
this.fontSize,
|
||||||
|
this.fontWeight,
|
||||||
this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ class RoundedTextButton extends StatelessWidget {
|
|||||||
child: SizedBox.expand(
|
child: SizedBox.expand(
|
||||||
child: FlowyTextButton(
|
child: FlowyTextButton(
|
||||||
title ?? '',
|
title ?? '',
|
||||||
|
fontWeight: fontWeight,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -53,11 +53,11 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: e8ee051
|
ref: d2d9873
|
||||||
resolved-ref: e8ee051719eded6621ccdc2722f696411c020209
|
resolved-ref: d2d987312d3a667336c7e12c36da7dbbb62d66db
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "3.0.0"
|
version: "3.1.0"
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -121,6 +121,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
barcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: barcode
|
||||||
|
sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.8"
|
||||||
|
bidi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bidi
|
||||||
|
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.10"
|
||||||
bitsdojo_window:
|
bitsdojo_window:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -394,6 +410,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
custom_sliding_segmented_control:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: custom_sliding_segmented_control
|
||||||
|
sha256: "53c3e931c3ae1f696085d1ec70ac8e934da836595a9b7d9b88fdd0fcbf2a5574"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.3"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -578,6 +602,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
flex_color_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flex_color_picker
|
||||||
|
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.1"
|
||||||
|
flex_seed_scheme:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flex_seed_scheme
|
||||||
|
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
flowy_infra:
|
flowy_infra:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -949,6 +989,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
iconsax_flutter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: iconsax_flutter
|
||||||
|
sha256: "95b65699da8ea98f87c5d232f06b0debaaf1ec1332b697e4d90969ec9a93037d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
image_gallery_saver:
|
image_gallery_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1203,7 +1259,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: markdown
|
name: markdown
|
||||||
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
||||||
@ -1410,6 +1466,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
pausable_timer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pausable_timer
|
||||||
|
sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0+3"
|
||||||
|
pdf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pdf
|
||||||
|
sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.11.0"
|
||||||
|
pdf_widget_wrapper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pdf_widget_wrapper
|
||||||
|
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
percent_indicator:
|
percent_indicator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1530,6 +1610,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
printing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: printing
|
||||||
|
sha256: cc4b256a5a89d5345488e3318897b595867f5181b8c5ed6fc63bfa5f2044aec3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.1"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1570,6 +1658,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.2.3"
|
||||||
|
qr:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: qr
|
||||||
|
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
realtime_client:
|
realtime_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2049,6 +2145,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
toastification:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: toastification
|
||||||
|
sha256: "5e751acc2fb5b8d008138dac255d62290fde4e5a24824f29809ac098c3dfe395"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
tuple:
|
tuple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 0.6.2
|
version: 0.6.3
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
flutter: ">=3.22.0"
|
flutter: ">=3.22.0"
|
||||||
@ -66,6 +66,7 @@ dependencies:
|
|||||||
styled_widget: ^0.4.1
|
styled_widget: ^0.4.1
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
flutter_colorpicker: ^1.0.3
|
flutter_colorpicker: ^1.0.3
|
||||||
|
flex_color_picker: ^3.5.1
|
||||||
highlight: ^0.7.0
|
highlight: ^0.7.0
|
||||||
package_info_plus: ^6.0.0
|
package_info_plus: ^6.0.0
|
||||||
url_launcher: ^6.1.11
|
url_launcher: ^6.1.11
|
||||||
@ -141,6 +142,7 @@ dependencies:
|
|||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
isolates: ^3.0.3+8
|
isolates: ^3.0.3+8
|
||||||
markdown_widget: ^2.3.2+6
|
markdown_widget: ^2.3.2+6
|
||||||
|
markdown:
|
||||||
|
|
||||||
# Window Manager for MacOS and Linux
|
# Window Manager for MacOS and Linux
|
||||||
window_manager: ^0.3.9
|
window_manager: ^0.3.9
|
||||||
@ -149,6 +151,8 @@ dependencies:
|
|||||||
bitsdojo_window: ^0.1.6
|
bitsdojo_window: ^0.1.6
|
||||||
|
|
||||||
flutter_highlight: ^0.7.0
|
flutter_highlight: ^0.7.0
|
||||||
|
custom_sliding_segmented_control: ^1.8.3
|
||||||
|
toastification: ^2.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^3.0.1
|
||||||
@ -185,7 +189,7 @@ dependency_overrides:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: "e8ee051"
|
ref: "d2d9873"
|
||||||
|
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
|
135
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -192,7 +192,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-ai-client"
|
name = "appflowy-ai-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -203,6 +203,40 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "appflowy-local-ai"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"appflowy-plugin",
|
||||||
|
"bytes",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "appflowy-plugin"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy_tauri"
|
name = "appflowy_tauri"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@ -772,7 +806,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -821,7 +855,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api-entity"
|
name = "client-api-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -833,7 +867,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-websocket"
|
name = "client-websocket"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -907,7 +941,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -931,7 +965,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -961,7 +995,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -981,7 +1015,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-entity"
|
name = "collab-entity"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -996,7 +1030,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1034,7 +1068,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1073,7 +1107,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-entity"
|
name = "collab-rt-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1098,7 +1132,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-protocol"
|
name = "collab-rt-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1115,7 +1149,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-user"
|
name = "collab-user"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -1341,7 +1375,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"phf 0.8.0",
|
"phf 0.11.2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1452,7 +1486,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -1847,6 +1881,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"allo-isolate",
|
"allo-isolate",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"appflowy-local-ai",
|
||||||
|
"appflowy-plugin",
|
||||||
"bytes",
|
"bytes",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"flowy-chat-pub",
|
"flowy-chat-pub",
|
||||||
@ -1854,7 +1890,6 @@ dependencies = [
|
|||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-sidecar",
|
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"futures",
|
"futures",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
@ -2136,7 +2171,6 @@ dependencies = [
|
|||||||
"fancy-regex 0.11.0",
|
"fancy-regex 0.11.0",
|
||||||
"flowy-codegen",
|
"flowy-codegen",
|
||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-sidecar",
|
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
@ -2172,12 +2206,14 @@ dependencies = [
|
|||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-search-pub",
|
"flowy-search-pub",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
"nanoid",
|
"nanoid",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum_macros 0.21.1",
|
"strum_macros 0.21.1",
|
||||||
@ -2198,6 +2234,7 @@ dependencies = [
|
|||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-folder",
|
"collab-folder",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
|
"serde",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2323,24 +2360,6 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flowy-sidecar"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"lib-infra",
|
|
||||||
"log",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot 0.12.1",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-stream",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flowy-sqlite"
|
name = "flowy-sqlite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2921,7 +2940,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2938,7 +2957,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3370,7 +3389,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -3865,7 +3884,7 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex-automata",
|
"regex-automata 0.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3892,9 +3911,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
@ -4878,7 +4897,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4899,7 +4918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.47",
|
"syn 2.0.47",
|
||||||
@ -5225,13 +5244,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.8.4"
|
version = "1.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.0.2",
|
"aho-corasick 1.0.2",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.2",
|
"regex-automata 0.4.7",
|
||||||
|
"regex-syntax 0.8.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5243,6 +5263,17 @@ dependencies = [
|
|||||||
"regex-syntax 0.6.29",
|
"regex-syntax 0.6.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick 1.0.2",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.8.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.29"
|
version = "0.6.29"
|
||||||
@ -5251,9 +5282,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.2"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rend"
|
name = "rend"
|
||||||
@ -5863,7 +5894,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9884d93aa2805013f36a79c1757174a0b5063065#9884d93aa2805013f36a79c1757174a0b5063065"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a2f92bb#a2f92bb13e7c3d33783359d02235ba4a5058a32f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -8122,9 +8153,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yrs"
|
name = "yrs"
|
||||||
version = "0.18.8"
|
version = "0.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da227d69095141c331d9b60c11496d0a3c6505cd9f8e200898b197219e8e394f"
|
checksum = "71df0198938b69f1eec0d5f19f591c6e4f2f770b0bf16f858428f6d91b8bb280"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
|
@ -29,7 +29,7 @@ tokio = "1.34.0"
|
|||||||
tokio-stream = "0.1.14"
|
tokio-stream = "0.1.14"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||||
yrs = "0.18.8"
|
yrs = "0.19.1"
|
||||||
# Please use the following script to update collab.
|
# Please use the following script to update collab.
|
||||||
# Working directory: frontend
|
# Working directory: frontend
|
||||||
#
|
#
|
||||||
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||||
# ⚠️⚠️⚠️️
|
# ⚠️⚠️⚠️️
|
||||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9884d93aa2805013f36a79c1757174a0b5063065" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a2f92bb" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@ -106,10 +106,26 @@ default = ["custom-protocol"]
|
|||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# Please use the following script to update collab.
|
||||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# Working directory: frontend
|
||||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
#
|
||||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# To update the commit ID, run:
|
||||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# scripts/tool/update_collab_rev.sh new_rev_id
|
||||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
#
|
||||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# To switch to the local path, run:
|
||||||
|
# scripts/tool/update_collab_source.sh
|
||||||
|
# ⚠️⚠️⚠️️
|
||||||
|
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
|
||||||
|
# Working directory: frontend
|
||||||
|
# To update the commit ID, run:
|
||||||
|
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||||
|
# ⚠️⚠️⚠️️
|
||||||
|
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||||
|
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
FROM oven/bun:latest
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y nginx
|
|
||||||
|
|
||||||
RUN bun install cheerio pino axios pino-pretty
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN addgroup --system nginx && \
|
|
||||||
adduser --system --no-create-home --disabled-login --ingroup nginx nginx
|
|
||||||
|
|
||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
|
|
||||||
COPY dist/ /usr/share/nginx/html/
|
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
COPY nginx-signed.crt /etc/ssl/certs/nginx-signed.crt
|
|
||||||
COPY nginx-signed.key /etc/ssl/private/nginx-signed.key
|
|
||||||
|
|
||||||
RUN chown -R nginx:nginx /etc/ssl/certs/nginx-signed.crt /etc/ssl/private/nginx-signed.key
|
|
||||||
|
|
||||||
COPY start.sh /app/start.sh
|
|
||||||
|
|
||||||
RUN chmod +x /app/start.sh
|
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 80 443
|
|
||||||
|
|
||||||
CMD ["/app/start.sh"]
|
|
@ -1,284 +1,163 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
<div align="center">
|
||||||
<h1><code>AppFlowy Web Project</code></h1>
|
<h1>AppFlowy Web</h1>
|
||||||
|
</div>
|
||||||
<div>Welcome to the AppFlowy Web Project, a robust and versatile platform designed to bring the innovative features of
|
<img src="https://img.shields.io/badge/React-v18.2.0-blue"/>
|
||||||
AppFlowy to the web. This project uniquely supports running as a desktop application via Tauri, and offers web
|
<img src="https://img.shields.io/badge/TypeScript-v4.9.5-blue"/>
|
||||||
interfaces powered by WebAssembly (WASM). Dive into an exceptional development experience with high performance and
|
<img src="https://img.shields.io/badge/Nginx-v1.21.6-brightgreen"/>
|
||||||
extensive capabilities.</div>
|
<img src="https://img.shields.io/badge/Bun-latest-black"/>
|
||||||
|
<img src="https://img.shields.io/badge/Docker-v20.10.12-blue"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 🐑 Features
|
## 🌟 Introduction
|
||||||
|
|
||||||
- **Cross-Platform Compatibility**: Seamlessly run on desktop environments with Tauri, and on any web browser through
|
Welcome to the AppFlowy Web project! This project aims to bring the powerful features of AppFlowy to the web. Whether
|
||||||
WASM.
|
you're a developer looking to contribute or a user eager to try out the latest features, this guide will help you get
|
||||||
- **High Performance**: Leverage the speed and efficiency of WebAssembly for your web interfaces.
|
started.
|
||||||
- **Tauri Integration**: Build lightweight, secure, and efficient desktop applications.
|
|
||||||
- **Flexible Development**: Utilize a wide range of AppFlowy's functionalities in your web or desktop projects.
|
|
||||||
|
|
||||||
## 🚀 Getting Started
|
AppFlowy Web is built with the following technologies:
|
||||||
|
|
||||||
### 🛠️ Prerequisites
|
- **React**: A JavaScript library for building user interfaces.
|
||||||
|
- **TypeScript**: A typed superset of JavaScript that compiles to plain JavaScript.
|
||||||
|
- **Bun**: A fast all-in-one JavaScript runtime.
|
||||||
|
- **Nginx**: A high-performance web server.
|
||||||
|
- **Docker**: A platform to develop, ship, and run applications in containers.
|
||||||
|
|
||||||
Before you begin, ensure you have the following installed:
|
### Resource Sharing
|
||||||
|
|
||||||
- Node.js (v14 or later)
|
To maintain consistency across different platforms, the Web project shares i18n translation files and Icons with the
|
||||||
- Rust (latest stable version)
|
Flutter project. This ensures a unified user experience and reduces duplication of effort in maintaining these
|
||||||
- Tauri prerequisites for your operating system
|
resources.
|
||||||
- PNPM (8.5.0)
|
|
||||||
|
|
||||||
### 🏗️ Installation
|
- **i18n Translation Files**: The translation files are shared to provide a consistent localization experience across
|
||||||
|
both Web and Flutter applications. The path to the translation files is `frontend/resources/translations/`.
|
||||||
|
|
||||||
#### Clone the Repository
|
> The translation files are stored in JSON format and contain translations for different languages. The files are
|
||||||
|
named according to the language code (e.g., `en.json` for English, `es.json` for Spanish, etc.).
|
||||||
|
|
||||||
|
- **Icons**: The icon set used in the Web project is the same as the one used in the Flutter project, ensuring visual
|
||||||
|
consistency. The icons are stored in the `frontend/resources/flowy_icons/` directory.
|
||||||
|
|
||||||
|
Let's dive in and get the project up and running! 🚀
|
||||||
|
|
||||||
|
## 🛠 Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Before you begin, make sure you have the following installed on your system:
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) (v18.6.0) 🌳
|
||||||
|
- [pnpm](https://pnpm.io/) (package manager) 📦
|
||||||
|
- [Jest](https://jestjs.io/) (testing framework) 🃏
|
||||||
|
- [Cypress](https://www.cypress.io/) (end-to-end testing) 🧪
|
||||||
|
|
||||||
|
### Clone the Repository
|
||||||
|
|
||||||
|
First, clone the repository to your local machine:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/AppFlowy-IO/AppFlowy
|
git clone https://github.com/AppFlowy-IO/AppFlowy.git
|
||||||
|
cd frontend/appflowy_web_app
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🌐 Install the frontend dependencies:
|
### Install Dependencies
|
||||||
|
|
||||||
|
Install the required dependencies using pnpm:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend/appflowy_web_app
|
## ensure you have pnpm installed, if not run the following command
|
||||||
|
# npm install -g pnpm@8.5.0
|
||||||
|
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🖥️ Desktop Application (Tauri) (Optional)
|
### Configure Environment Variables
|
||||||
|
|
||||||
> **Note**: if you want to run the web app in the browser, skip this step
|
Create a `.env` file in the root of the project and add the following environment variables:
|
||||||
|
|
||||||
- Follow the instructions [here](https://tauri.app/v1/guides/getting-started/prerequisites/) to install Tauri
|
|
||||||
|
|
||||||
##### Windows and Linux Prerequisites
|
|
||||||
|
|
||||||
###### Windows only
|
|
||||||
|
|
||||||
- Install the Duckscript CLI and vcpkg
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install --force duckscript_cli
|
AF_BASE_URL=http://localhost:8080
|
||||||
vcpkg integrate install
|
AF_GOTRUE_URL=http://localhost:9999
|
||||||
|
AF_WS_URL=ws://localhost:8080/ws/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Linux only
|
### Start the Development Server
|
||||||
|
|
||||||
- Install the required dependencies
|
To start the development server, run the following command:
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Get error**: failed to run custom build command for librocksdb-sys v6.11.4
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install clang
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Install Tauri Dependencies
|
|
||||||
|
|
||||||
- Install cargo-make
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install --force cargo-make
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
- Install AppFlowy dev tools
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# install development tools
|
|
||||||
# make sure you are in the root directory of the project
|
|
||||||
cd frontend
|
|
||||||
cargo make appflowy-tauri-deps-tools
|
|
||||||
```
|
|
||||||
|
|
||||||
- Build the service/dependency
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# make sure you are in the root directory of the project
|
|
||||||
cd frontend/appflowy_web_app
|
|
||||||
mkdir dist
|
|
||||||
cd src-tauri
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🚀 Running the Application
|
|
||||||
|
|
||||||
#### 🌐 Web Application
|
|
||||||
|
|
||||||
- Run the web application
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
- Open your browser and navigate to `http://localhost:3000`, You can now interact with the AppFlowy web application
|
|
||||||
|
|
||||||
#### 🖥️ Desktop Application (Tauri)
|
### 🚀 Building for Production(Optional)
|
||||||
|
|
||||||
**Ensure close web application before running the desktop application**
|
if you want to run the production build, use the following commands
|
||||||
|
|
||||||
- Run the desktop application
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run tauri:dev
|
pnpm run build
|
||||||
|
pnpm run start
|
||||||
```
|
```
|
||||||
- The AppFlowy desktop application will open, and you can interact with it
|
|
||||||
|
|
||||||
### 🛠️ Development
|
This will start the application in development mode. Open http://localhost:3000 to view it in the browser.
|
||||||
|
|
||||||
#### How to add or modify i18n keys
|
## 🧪 Running Tests
|
||||||
|
|
||||||
- Modify the i18n files in `frontend/resources/translations/en.json` to add or modify i18n keys
|
### Unit Tests
|
||||||
- Run the following command to update the i18n keys in the application
|
|
||||||
|
We use **Jest** for running unit tests. To run the tests, use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run sync:i18n
|
pnpm run test:unit
|
||||||
```
|
```
|
||||||
|
|
||||||
#### How to modify the theme
|
This will execute all the unit tests in the project and provide a summary of the results. ✅
|
||||||
|
|
||||||
Don't modify the theme file in `frontend/appflowy_web_app/src/styles/variables` directly)
|
### Components Tests
|
||||||
|
|
||||||
- Modify the theme file in `frontend/appflowy_web_app/style-dictionary/tokens/base.json( or dark.json or light.json)` to
|
We use **Cypress** for end-to-end testing. To run the Cypress tests, use the following command:
|
||||||
add or modify theme keys
|
|
||||||
- Run the following command to update the theme in the application
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run css:variables
|
pnpm run cypress:open
|
||||||
```
|
```
|
||||||
|
|
||||||
#### How to add or modify the environment variables
|
This will open the Cypress Test Runner where you can run your end-to-end tests. 🧪
|
||||||
|
|
||||||
- Modify the environment file in `frontend/appflowy_web_app/.env` to add or modify environment variables
|
Alternatively, to run Cypress tests in the headless mode, use:
|
||||||
|
|
||||||
#### How to create symlink for the @appflowyinc/client-api-wasm in local development
|
|
||||||
|
|
||||||
- Run the following command to create a symlink for the @appflowyinc/client-api-wasm
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ensure you are in the frontend/appflowy_web_app directory
|
|
||||||
|
|
||||||
pnpm run link:client-api $source_path $target_path
|
|
||||||
|
|
||||||
# Example
|
|
||||||
# pnpm run link:client-api ../../../AppFlowy-Cloud/libs/client-api-wasm/pkg ./node_modules/@appflowyinc/client-api-wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📝 About the Project
|
|
||||||
|
|
||||||
#### 📁 Directory Structure
|
|
||||||
|
|
||||||
- `frontend/appflowy_web_app`: Contains the web application source code
|
|
||||||
- `frontend/appflowy_web_app/src`: Contains the app entry point and the source code
|
|
||||||
- `frontend/appflowy_web_app/src/components`: Contains the react components
|
|
||||||
- `frontend/appflowy_web_app/src/styles`: Contains the styles for the application
|
|
||||||
- `frontend/appflowy_web_app/src/utils`: Contains the utility functions
|
|
||||||
- `frontend/appflowy_web_app/src/i18n`: Contains the i18n files
|
|
||||||
- `frontend/appflowy_web_app/src/assets`: Contains the assets for the application
|
|
||||||
- `frontend/appflowy_web_app/src/store`: Contains the redux store
|
|
||||||
- `frontend/appflowy_web_app/src/@types`: Contains the typescript types
|
|
||||||
- `frontend/appflowy_web_app/src/applications/services`: Contains the services for the application. In vite.config.ts,
|
|
||||||
we have defined the alias for the services directory for different environments(Tauri/Web)
|
|
||||||
```typescript
|
|
||||||
resolve: {
|
|
||||||
alias: [
|
|
||||||
// ...
|
|
||||||
{
|
|
||||||
find: '$client-services',
|
|
||||||
replacement: !!process.env.TAURI_PLATFORM
|
|
||||||
? `${__dirname}/src/application/services/tauri-services`
|
|
||||||
: `${__dirname}/src/application/services/js-services`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📦 Deployment
|
|
||||||
|
|
||||||
Use the AppFlowy CI/CD pipeline to deploy the application to the test and production environments.
|
|
||||||
|
|
||||||
- Push the changes to the main branch
|
|
||||||
- Deploy Test Environment
|
|
||||||
- Automatically, the test environment will be deployed if merged to the main branch or build/test branch
|
|
||||||
- Deploy Production Environment
|
|
||||||
- Navigate to the Actions tab
|
|
||||||
- Click on the workflow and select the Run workflow
|
|
||||||
- Enter the options
|
|
||||||
- Click on the Run workflow button
|
|
||||||
|
|
||||||
#### 📦 Deployment (Self-Hosted EC2)
|
|
||||||
|
|
||||||
##### Pre-requisites
|
|
||||||
|
|
||||||
Please ensure you have learned about:
|
|
||||||
|
|
||||||
- [Deploy Web application on AWS Cloud using EC2 Instance](https://www.youtube.com/watch?v=gWVIIU1ev0Y)
|
|
||||||
- [How to Install and Use Rsync Command](https://operavps.com/docs/install-rsync-command-in-linux/)
|
|
||||||
- [How to Use ssh-keygen to Generate a New SSH Key?](https://www.ssh.com/academy/ssh/keygen)
|
|
||||||
- [Linux post-installation steps for Docker Engine](https://docs.docker.com/engine/install/linux-postinstall/)
|
|
||||||
- [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
|
||||||
|
|
||||||
And then follow the steps below:
|
|
||||||
|
|
||||||
1. Ensure you have the following installed on your server:
|
|
||||||
- Docker: [Install Docker](https://docs.docker.com/engine/install/)
|
|
||||||
- Rsync: [Install Rsync](https://operavps.com/docs/install-rsync-command-in-linux/)
|
|
||||||
|
|
||||||
2. Create a new user for deploy, and generate an SSH key for the user
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo adduser appflowy(or any name)
|
|
||||||
sudo su - appflowy
|
|
||||||
mkdir ~/.ssh
|
|
||||||
chmod 700 ~/.ssh
|
|
||||||
ssh-keygen -t rsa
|
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
|
||||||
# add the user to the docker group, to run docker commands without sudo
|
|
||||||
sudo usermod -aG docker ${USER}
|
|
||||||
```
|
|
||||||
- visit the `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub` to get the private and public key respectively
|
|
||||||
- add the public key to the `~/.ssh/authorized_keys` file
|
|
||||||
- ensure the private key is kept safe
|
|
||||||
- exit and login back to the server with the new
|
|
||||||
user: `ssh -i your-existing-key.pem ec2-user@your-instance-public-dns`
|
|
||||||
|
|
||||||
3. Clone the AppFlowy repository
|
|
||||||
|
|
||||||
4. Set the following secrets in your
|
|
||||||
repository, have to
|
|
||||||
know [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)
|
|
||||||
|
|
||||||
> Note: Test Environment: prefix the secret with `WEB_TEST_` and Production Environment: prefix the secret with `WEB_`
|
|
||||||
|
|
||||||
> for example, `WEB_TEST_SSH_PRIVATE_KEY` and `WEB_SSH_PRIVATE_KEY`
|
|
||||||
|
|
||||||
- `SSH_PRIVATE_KEY`: The private key generated in step 2: cat ~/.ssh/id_rsa
|
|
||||||
- `REMOTE_HOST`: The host of the server: `your-instance-public-dns` or `your-instance-ip`
|
|
||||||
- `REMOTE_USER`: The user created in step 2: `appflowy`
|
|
||||||
- `SSL_CERTIFICATE`: The SSL certificate for the
|
|
||||||
server - [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
|
||||||
- `SSL_CERTIFICATE_KEY`: The SSL certificate key for the
|
|
||||||
server - [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
|
||||||
|
|
||||||
5. Run the deployment workflow to deploy the application(production or test environment)
|
|
||||||
|
|
||||||
> Note: the test server will **automatically** deploy if merged to the main branch or build/test branch
|
|
||||||
|
|
||||||
### 🧪 Testing
|
|
||||||
|
|
||||||
> We use Cypress for end-to-end testing and component testing - [Cypress](https://www.cypress.io/)
|
|
||||||
|
|
||||||
#### 🧪 End-to-End Testing
|
|
||||||
|
|
||||||
> to be continued
|
|
||||||
|
|
||||||
#### 🧪 Component Testing
|
|
||||||
|
|
||||||
Run the following command to run the component tests
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run test:components
|
pnpm run test:components
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Both commands will provide detailed test results and generate a code coverage report.
|
||||||
|
|
||||||
|
## 🔄 Development Workflow
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
To maintain code quality, we use **ESLint**. To run the linter and fix any linting errors, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Production Deployment
|
||||||
|
|
||||||
|
Our production deployment process is automated using GitHub Actions. The process involves:
|
||||||
|
|
||||||
|
1. **Setting up an AWS EC2 instance**: We use an EC2 instance to host the application.
|
||||||
|
2. **Installing Docker and Docker Compose**: Docker is installed on the AWS instance.
|
||||||
|
3. **Configuring SSH Access**: SSH access is set up with a user and password.
|
||||||
|
4. **Preparing Project Configuration**: We configure `Dockerfile`, `nginx.conf`, and `server.cjs` in the web project.
|
||||||
|
5. **Using GitHub Actions**: We use the easingthemes/ssh-deploy@main action to deploy the project to the remote server.
|
||||||
|
|
||||||
|
The deployment steps include building the Docker image and running the Docker container with the necessary port
|
||||||
|
mappings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t appflowy-web-app .
|
||||||
|
docker rm -f appflowy-web-app || true
|
||||||
|
docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app
|
||||||
|
```
|
||||||
|
|
||||||
|
The Web server runs on Bun. For more details about Bun, please refer to the [Bun documentation](https://bun.sh/).
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
AF_WS_URL=wss://beta.appflowy.cloud/ws/v1
|
|
||||||
AF_BASE_URL=https://beta.appflowy.cloud
|
|
||||||
AF_GOTRUE_URL=https://beta.appflowy.cloud/gotrue
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "9eebea03-3ed5-4298-86b2-a7f77856d48b",
|
|
||||||
"name": "workspace",
|
|
||||||
"icon": "",
|
|
||||||
"owner": {
|
|
||||||
"id": 0,
|
|
||||||
"name": "system"
|
|
||||||
},
|
|
||||||
"type": 0,
|
|
||||||
"workspaceDatabaseId": "375874be-7a4f-4b7c-8b89-1dc9a39838f4"
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"database_id": "037a985f-f369-4c4a-8011-620012850a68",
|
|
||||||
"created_at": "1713429700",
|
|
||||||
"views": [
|
|
||||||
"48c52cf7-bf98-43fa-96ad-b31aade9b071"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "daea6aee-9365-4703-a8e2-a2fa6a07b214",
|
|
||||||
"created_at": "1714449533",
|
|
||||||
"views": [
|
|
||||||
"b6347acb-3174-4f0e-98e9-dcce07e5dbf7"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
|
||||||
"e410747b-5f2f-45a0-b2f7-890ad3001355",
|
|
||||||
"2143e95d-5dcb-4e0f-bb2c-50944e6e019f",
|
|
||||||
"a5566e49-f156-4168-9b2d-17926c5da329",
|
|
||||||
"135615fa-66f7-4451-9b54-d7e99445fca4",
|
|
||||||
"b4e77203-5c8b-48df-bbc5-2e1143eb0e61",
|
|
||||||
"a6af311f-cbc8-42c2-b801-7115619c3776"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
|
||||||
"e97877f5-c365-4025-9e6a-e590c4b19dbb",
|
|
||||||
"f0c59921-04ee-4971-995c-79b7fd8c00e2",
|
|
||||||
"7eb697cd-6a55-40bb-96ac-0d4a3bc924b2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "ee63da2b-aa2a-4d0b-aab0-59008635363a",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"2c1ee95a-1b09-4a1f-8d5e-501bc4861a9d",
|
|
||||||
"91ea7c08-f6b3-4b81-aa1e-d3664686186f"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "e788f014-d0d3-4dfe-81ef-aa1ebb4d6366",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"1b0e322d-4909-4c63-914a-d034fc363097",
|
|
||||||
"350f425b-b671-4e2d-8182-5998a6e62924"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"0ce13415-6cce-4497-94c6-475ad96c249e",
|
|
||||||
"e4c89421-12b2-4d02-863d-20949eec9271"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "ce267d12-3b61-4ebb-bb03-d65272f5f817",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"ee3ae8ce-959a-4df3-8734-40b535ff88e3",
|
|
||||||
"66a6f3bc-c78f-4f74-a09e-08d4717bf1fd",
|
|
||||||
"2bf50c03-f41f-4363-b5b1-101216a6c5cc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"database_id": "87bc006e-c1eb-47fd-9ac6-e39b17956369",
|
|
||||||
"created_at": "0",
|
|
||||||
"views": [
|
|
||||||
"7f233be4-1b4d-46b2-bcfc-f341b8d75267",
|
|
||||||
"a734a068-e73d-4b4b-853c-4daffea389c0"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTI4Mjk2MjAsImlhdCI6MTcxMjgyNjAyMCwic3ViIjoiY2JmZjA2MGEtMTk2ZC00MTVhLWFhODAtNzU5YzAxODg2NDY2IiwiZW1haWwiOiJsdUBhcHBmbG93eS5pbyIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZ29vZ2xlIiwicHJvdmlkZXJzIjpbImdvb2dsZSJdfSwidXNlcl9tZXRhZGF0YSI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTEhabVZBczRTb0ZlVFFuWG5CU2JiNTBBVXF0YktHNWx5MGllVHZCSklYZ1o3UmdRPXM5Ni1jIiwiY3VzdG9tX2NsYWltcyI6eyJoZCI6ImFwcGZsb3d5LmlvIn0sImVtYWlsIjoibHVAYXBwZmxvd3kuaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZnVsbF9uYW1lIjoiTHUgSGUiLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYW1lIjoiTHUgSGUiLCJwaG9uZV92ZXJpZmllZCI6ZmFsc2UsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NMSFptVkFzNFNvRmVUUW5YbkJTYmI1MEFVcXRiS0c1bHkwaWVUdkJKSVhnWjdSZ1E9czk2LWMiLCJwcm92aWRlcl9pZCI6IjEwMTE2OTI1MDgyOTU1NDAyODM4MSIsInN1YiI6IjEwMTE2OTI1MDgyOTU1NDAyODM4MSJ9LCJyb2xlIjoiIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3MTI4MjYwMjB9XSwic2Vzc2lvbl9pZCI6ImJmMzE5OTRlLTQwMTgtNDhjMS05Yzc0LWVmYzkyMGNjOWQ0NSJ9.QeTrRhsnBjBL1GUS3TIWOgU1SPM6RcaWwxZdMVfcFBU",
|
|
||||||
"token_type": "bearer",
|
|
||||||
"expires_in": 3600,
|
|
||||||
"expires_at": 4869016461,
|
|
||||||
"refresh_token": "71vp1jJnSAVluZKaXkhG1A",
|
|
||||||
"user": {
|
|
||||||
"id": "cbff060a-196d-415a-aa80-759c01886466",
|
|
||||||
"aud": "",
|
|
||||||
"role": "",
|
|
||||||
"email": "lu@appflowy.io",
|
|
||||||
"email_confirmed_at": "2024-03-13T10:49:53.165361Z",
|
|
||||||
"phone": "",
|
|
||||||
"confirmed_at": "2024-03-13T10:49:53.165361Z",
|
|
||||||
"last_sign_in_at": "2024-04-11T09:00:20.547468985Z",
|
|
||||||
"app_metadata": {
|
|
||||||
"provider": "google",
|
|
||||||
"providers": [
|
|
||||||
"google"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"user_metadata": {
|
|
||||||
"avatar_url": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
|
||||||
"custom_claims": {
|
|
||||||
"hd": "appflowy.io"
|
|
||||||
},
|
|
||||||
"email": "lu@appflowy.io",
|
|
||||||
"email_verified": true,
|
|
||||||
"full_name": "Lu He",
|
|
||||||
"iss": "https://accounts.google.com",
|
|
||||||
"name": "Lu He",
|
|
||||||
"phone_verified": false,
|
|
||||||
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
|
||||||
"provider_id": "101169250829554028381",
|
|
||||||
"sub": "101169250829554028381"
|
|
||||||
},
|
|
||||||
"identities": [
|
|
||||||
{
|
|
||||||
"identity_id": "e4cf8b69-7f80-42e9-aed2-e25132ad0178",
|
|
||||||
"id": "101169250829554028381",
|
|
||||||
"user_id": "cbff060a-196d-415a-aa80-759c01886466",
|
|
||||||
"identity_data": {
|
|
||||||
"avatar_url": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
|
||||||
"custom_claims": {
|
|
||||||
"hd": "appflowy.io"
|
|
||||||
},
|
|
||||||
"email": "lu@appflowy.io",
|
|
||||||
"email_verified": true,
|
|
||||||
"full_name": "Lu He",
|
|
||||||
"iss": "https://accounts.google.com",
|
|
||||||
"name": "Lu He",
|
|
||||||
"phone_verified": false,
|
|
||||||
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
|
||||||
"provider_id": "101169250829554028381",
|
|
||||||
"sub": "101169250829554028381"
|
|
||||||
},
|
|
||||||
"provider": "google",
|
|
||||||
"last_sign_in_at": "2024-03-13T07:22:43.110504Z",
|
|
||||||
"created_at": "2024-03-13T07:22:43.110543Z",
|
|
||||||
"updated_at": "2024-04-04T06:15:14.03093Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"created_at": "2024-03-13T07:22:43.102586Z",
|
|
||||||
"updated_at": "2024-04-11T09:00:20.551485Z"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"data": {
|
|
||||||
"uid": 304120109071339520,
|
|
||||||
"uuid": "cbff060a-196d-415a-aa80-759c01886466",
|
|
||||||
"email": "lu@appflowy.io",
|
|
||||||
"password": "",
|
|
||||||
"name": "Kilu",
|
|
||||||
"metadata": {
|
|
||||||
"icon_url": "🇽🇰"
|
|
||||||
},
|
|
||||||
"encryption_sign": null,
|
|
||||||
"latest_workspace_id": "fcb503f9-9287-4de4-8de0-ea191e680968",
|
|
||||||
"updated_at": 1710421586
|
|
||||||
},
|
|
||||||
"code": 0,
|
|
||||||
"message": "Operation completed successfully."
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
{"data":{"user_profile":{"uid":304120109071339520,"uuid":"cbff060a-196d-415a-aa80-759c01886466","email":"lu@appflowy.io","password":"","name":"Kilu","metadata":{"icon_url":"🇽🇰"},"encryption_sign":null,"latest_workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","updated_at":1715847453},"visiting_workspace":{"workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","database_storage_id":"375874be-7a4f-4b7c-8b89-1dc9a39838f4","owner_uid":304120109071339520,"owner_name":"Kilu","workspace_type":0,"workspace_name":"Kilu Works","created_at":"2024-03-13T07:23:10.275174Z","icon":"😆"},"workspaces":[{"workspace_id":"81570fa8-8be9-4b2d-9f1c-1ef4f34079a8","database_storage_id":"6c1f1a2c-e8d5-4bc2-917f-495bce862abb","owner_uid":311828434584080384,"owner_name":"Zack Zi Xiang Fu","workspace_type":0,"workspace_name":"My Workspace","created_at":"2024-04-03T13:53:18.295918Z","icon":""},{"workspace_id":"fcb503f9-9287-4de4-8de0-ea191e680968","database_storage_id":"ae1b82a5-2b93-45c7-901a-f9357c544534","owner_uid":276169796100296704,"owner_name":"Annie Anqi Wang","workspace_type":0,"workspace_name":"AppFlowy Test","created_at":"2023-12-27T04:18:36.372013Z","icon":""},{"workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","database_storage_id":"375874be-7a4f-4b7c-8b89-1dc9a39838f4","owner_uid":304120109071339520,"owner_name":"Kilu","workspace_type":0,"workspace_name":"Kilu Works","created_at":"2024-03-13T07:23:10.275174Z","icon":"😆"}]},"code":0,"message":"Operation completed successfully."}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"data": {
|
|
||||||
"is_new": false
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,96 +25,9 @@
|
|||||||
// -- This will overwrite an existing command --
|
// -- This will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
//
|
//
|
||||||
import { YDoc } from '@/application/collab.type';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
import { JSDatabaseService } from '@/application/services/js-services/database.service';
|
|
||||||
import { JSDocumentService } from '@/application/services/js-services/document.service';
|
|
||||||
import { applyYDoc } from '@/application/ydoc/apply';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
|
|
||||||
Cypress.Commands.add('mockAPI', () => {
|
Cypress.Commands.add('mockAPI', () => {
|
||||||
cy.fixture('sign_in_success').then((json) => {
|
// Mock the API
|
||||||
cy.intercept('GET', `/api/user/verify/${json.access_token}`, {
|
|
||||||
fixture: 'verify_token',
|
|
||||||
}).as('verifyToken');
|
|
||||||
cy.intercept('POST', '/gotrue/token?grant_type=password', json).as('loginSuccess');
|
|
||||||
cy.intercept('POST', '/gotrue/token?grant_type=refresh_token', json).as('refreshToken');
|
|
||||||
});
|
|
||||||
cy.intercept('GET', '/api/user/profile', { fixture: 'user' }).as('getUserProfile');
|
|
||||||
cy.intercept('GET', '/api/user/workspace', { fixture: 'user_workspace' }).as('getUserWorkspace');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Example use:
|
export {};
|
||||||
// beforeEach(() => {
|
|
||||||
// cy.mockAPI();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
Cypress.Commands.add('mockCurrentWorkspace', () => {
|
|
||||||
cy.fixture('current_workspace').then((workspace) => {
|
|
||||||
cy.stub(JSDatabaseService.prototype, 'currentWorkspace').resolves(workspace);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
Cypress.Commands.add('mockGetWorkspaceDatabases', () => {
|
|
||||||
cy.fixture('database/databases').then((databases) => {
|
|
||||||
cy.stub(JSDatabaseService.prototype, 'getWorkspaceDatabases').resolves(databases);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
Cypress.Commands.add('mockDatabase', () => {
|
|
||||||
cy.mockCurrentWorkspace();
|
|
||||||
cy.mockGetWorkspaceDatabases();
|
|
||||||
|
|
||||||
const ids = [
|
|
||||||
'4c658817-20db-4f56-b7f9-0637a22dfeb6',
|
|
||||||
'ce267d12-3b61-4ebb-bb03-d65272f5f817',
|
|
||||||
'ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d',
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockOpenDatabase = cy.stub(JSDatabaseService.prototype, 'openDatabase');
|
|
||||||
|
|
||||||
ids.forEach((id) => {
|
|
||||||
cy.fixture(`database/${id}`).then((database) => {
|
|
||||||
cy.fixture(`database/rows/${id}`).then((rows) => {
|
|
||||||
const doc = new Y.Doc();
|
|
||||||
const rootRowsDoc = new Y.Doc();
|
|
||||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
|
||||||
const databaseState = new Uint8Array(database.data.doc_state);
|
|
||||||
|
|
||||||
applyYDoc(doc, databaseState);
|
|
||||||
|
|
||||||
Object.keys(rows).forEach((key) => {
|
|
||||||
const data = rows[key];
|
|
||||||
const rowDoc = new Y.Doc();
|
|
||||||
|
|
||||||
applyYDoc(rowDoc, new Uint8Array(data));
|
|
||||||
rowsFolder.set(key, rowDoc);
|
|
||||||
});
|
|
||||||
mockOpenDatabase.withArgs(id).resolves({
|
|
||||||
databaseDoc: doc,
|
|
||||||
rows: rowsFolder,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
Cypress.Commands.add('mockDocument', (id: string) => {
|
|
||||||
cy.fixture(`document/${id}`).then((subDocument) => {
|
|
||||||
const doc = new Y.Doc();
|
|
||||||
const state = new Uint8Array(subDocument.data.doc_state);
|
|
||||||
|
|
||||||
applyYDoc(doc, state);
|
|
||||||
|
|
||||||
cy.stub(JSDocumentService.prototype, 'openDocument').withArgs(id).resolves(doc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
<title>Components App</title>
|
<title>Components App</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body">
|
<body id="body">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/prism.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
|
||||||
<div data-cy-root></div>
|
<div data-cy-root></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
32
frontend/appflowy_web_app/deploy/Dockerfile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
FROM oven/bun:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y nginx supervisor
|
||||||
|
|
||||||
|
RUN bun install cheerio pino pino-pretty
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
COPY supervisord.conf /app/supervisord.conf
|
||||||
|
|
||||||
|
RUN addgroup --system nginx && \
|
||||||
|
adduser --system --no-create-home --disabled-login --ingroup nginx nginx
|
||||||
|
|
||||||
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
|
COPY dist /usr/share/nginx/html/
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
COPY start.sh /app/start.sh
|
||||||
|
|
||||||
|
RUN chmod +x /app/start.sh
|
||||||
|
RUN chmod +x /app/supervisord.conf
|
||||||
|
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["supervisord", "-c", "/app/supervisord.conf"]
|
28
frontend/appflowy_web_app/deploy/deploy.sh
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "No port number provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORT=$1
|
||||||
|
|
||||||
|
echo "Starting deployment on port $PORT"
|
||||||
|
|
||||||
|
rm -rf deploy
|
||||||
|
|
||||||
|
tar -xzf build-output.tar.gz
|
||||||
|
|
||||||
|
rm -rf build-output.tar.gz
|
||||||
|
|
||||||
|
mv dist deploy/dist
|
||||||
|
|
||||||
|
mv .env deploy/.env
|
||||||
|
|
||||||
|
cd deploy
|
||||||
|
|
||||||
|
docker system prune -f
|
||||||
|
|
||||||
|
docker build -t appflowy-web-app-"$PORT" .
|
||||||
|
|
||||||
|
docker rm -f appflowy-web-app-"$PORT" || true
|
||||||
|
|
||||||
|
docker run -d --env-file .env -p "$PORT":80 --restart always --name appflowy-web-app-"$PORT" appflowy-web-app-"$PORT"
|
@ -40,29 +40,6 @@ http {
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
#server_name appflowy.com *.appflowy.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# Additional server block for HTTPS
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name localhost;
|
|
||||||
#server_name appflowy.com *.appflowy.com;
|
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/certs/nginx-signed.crt;
|
|
||||||
ssl_certificate_key /etc/ssl/private/nginx-signed.key;
|
|
||||||
|
|
||||||
ssl_session_cache shared:SSL:1m;
|
|
||||||
ssl_session_timeout 5m;
|
|
||||||
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:3000;
|
proxy_pass http://localhost:3000;
|
||||||
@ -89,6 +66,18 @@ http {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /og-image.png {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
expires 30d;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /covers/ {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
expires 30d;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
error_page 404 /404.html;
|
error_page 404 /404.html;
|
||||||
location = /404.html {
|
location = /404.html {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
@ -98,5 +87,6 @@ http {
|
|||||||
location = /50x.html {
|
location = /50x.html {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
193
frontend/appflowy_web_app/deploy/server.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import pino from 'pino';
|
||||||
|
import { type CheerioAPI, load } from 'cheerio';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
import { fetch } from 'bun';
|
||||||
|
|
||||||
|
const distDir = path.join(__dirname, 'dist');
|
||||||
|
const indexPath = path.join(distDir, 'index.html');
|
||||||
|
const baseURL = process.env.AF_BASE_URL as string;
|
||||||
|
const defaultSite = 'https://appflowy.io';
|
||||||
|
|
||||||
|
const setOrUpdateMetaTag = ($: CheerioAPI, selector: string, attribute: string, content: string) => {
|
||||||
|
if ($(selector).length === 0) {
|
||||||
|
$('head').append(`<meta ${attribute}="${selector.match(/\[(.*?)\]/)?.[1]}" content="${content}">`);
|
||||||
|
} else {
|
||||||
|
$(selector).attr('content', content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const logger = pino({
|
||||||
|
transport: {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
options: {
|
||||||
|
colorize: true,
|
||||||
|
translateTime: 'SYS:standard',
|
||||||
|
destination: `${__dirname}/pino-logger.log`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
level: 'info',
|
||||||
|
});
|
||||||
|
|
||||||
|
const logRequestTimer = (req: Request) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const pathname = new URL(req.url).pathname;
|
||||||
|
|
||||||
|
logger.info(`Incoming request: ${pathname}`);
|
||||||
|
return () => {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
|
||||||
|
logger.info(`Request for ${pathname} took ${duration}ms`);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMetaData = async (url: string) => {
|
||||||
|
logger.info(`Fetching meta data from ${url}`);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
verbose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error fetching meta data ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createServer = async (req: Request) => {
|
||||||
|
const timer = logRequestTimer(req);
|
||||||
|
const reqUrl = new URL(req.url);
|
||||||
|
const hostname = req.headers.get('host');
|
||||||
|
|
||||||
|
logger.info(`Request URL: ${hostname}${reqUrl.pathname}`);
|
||||||
|
|
||||||
|
const [namespace, publishName] = reqUrl.pathname.slice(1).split('/');
|
||||||
|
|
||||||
|
logger.info(`Namespace: ${namespace}, Publish Name: ${publishName}`);
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
if (namespace === '' || !publishName) {
|
||||||
|
timer();
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers: {
|
||||||
|
Location: defaultSite,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let metaData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
metaData = await fetchMetaData(`${baseURL}/api/workspace/published/${namespace}/${publishName}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error fetching meta data: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlData = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
const $ = load(htmlData);
|
||||||
|
|
||||||
|
const description = 'Write, share, and publish docs quickly on AppFlowy.\nGet started for free.';
|
||||||
|
let title = 'AppFlowy';
|
||||||
|
const url = `https://${hostname}${reqUrl.pathname}`;
|
||||||
|
let image = '/og-image.png';
|
||||||
|
let favicon = '/appflowy.svg';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (metaData && metaData.view) {
|
||||||
|
const view = metaData.view;
|
||||||
|
const emoji = view.icon.value;
|
||||||
|
const titleList = [];
|
||||||
|
|
||||||
|
if (emoji) {
|
||||||
|
const emojiCode = emoji.codePointAt(0).toString(16); // Convert emoji to hex code
|
||||||
|
const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u';
|
||||||
|
|
||||||
|
favicon = `${baseUrl}${emojiCode}.svg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view.name) {
|
||||||
|
titleList.push(view.name);
|
||||||
|
titleList.push('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
titleList.push('AppFlowy');
|
||||||
|
title = titleList.join(' ');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cover = view.extra ? JSON.parse(view.extra)?.cover : null;
|
||||||
|
|
||||||
|
if (cover) {
|
||||||
|
if (['unsplash', 'custom'].includes(cover.type)) {
|
||||||
|
image = cover.value;
|
||||||
|
} else if (cover.type === 'built_in') {
|
||||||
|
image = `/covers/m_cover_image_${cover.value}.png`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error injecting meta data: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('title').text(title);
|
||||||
|
$('link[rel="icon"]').attr('href', favicon);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:title"]', 'property', title);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:description"]', 'property', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:image"]', 'property', image);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:url"]', 'property', url);
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:site_name"]', 'property', 'AppFlowy');
|
||||||
|
setOrUpdateMetaTag($, 'meta[property="og:type"]', 'property', 'website');
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:card"]', 'name', 'summary_large_image');
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:title"]', 'name', title);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:description"]', 'name', description);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:image"]', 'name', image);
|
||||||
|
setOrUpdateMetaTag($, 'meta[name="twitter:site"]', 'name', '@appflowy');
|
||||||
|
|
||||||
|
timer();
|
||||||
|
return new Response($.html(), {
|
||||||
|
headers: { 'Content-Type': 'text/html' },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
timer();
|
||||||
|
logger.error({ message: 'Method not allowed', method: req.method });
|
||||||
|
return new Response('Method not allowed', { status: 405 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const Bun: {
|
||||||
|
serve: (options: { port: number; fetch: typeof createServer; error: (err: Error) => Response }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = () => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
Bun.serve({
|
||||||
|
port: 3000,
|
||||||
|
fetch: createServer,
|
||||||
|
error: (err) => {
|
||||||
|
logger.error(`Internal Server Error: ${err}`);
|
||||||
|
return new Response('Internal Server Error', { status: 500 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.info('Server is running on port 3000');
|
||||||
|
logger.info(`Base URL: ${baseURL}`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
export {};
|
@ -1,10 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
# Start the frontend server
|
|
||||||
bun run server.cjs &
|
|
||||||
|
|
||||||
# Start the nginx server
|
# Start the nginx server
|
||||||
service nginx start
|
service nginx start
|
||||||
|
|
||||||
|
# Start the frontend server
|
||||||
|
bun run server.ts
|
||||||
|
|
||||||
tail -f /dev/null
|
tail -f /dev/null
|
||||||
|
|
9
frontend/appflowy_web_app/deploy/supervisord.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
|
||||||
|
[program:bun]
|
||||||
|
command=sh /app/start.sh
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stderr_logfile=/var/log/bun.err.log
|
||||||
|
stdout_logfile=/var/log/bun.out.log
|
@ -13,7 +13,7 @@
|
|||||||
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
||||||
/>
|
/>
|
||||||
<meta property="og:image"
|
<meta property="og:image"
|
||||||
content="https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png"
|
content="/_next/static/media/og-image.e347bfb5.png"
|
||||||
/>
|
/>
|
||||||
<meta property="og:url" content="https://appflowy.com" />
|
<meta property="og:url" content="https://appflowy.com" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
@ -22,15 +22,17 @@
|
|||||||
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
content="AppFlowy is an AI collaborative workspace where you achieve more without losing control of your data"
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:image"
|
<meta name="twitter:image"
|
||||||
content="https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png"
|
content="/_next/static/media/og-image.e347bfb5.png"
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:site" content="@appflowy" />
|
<meta name="twitter:site" content="@appflowy" />
|
||||||
<meta name="twitter:creator" content="@appflowy" />
|
<meta name="twitter:creator" content="@appflowy" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body id="body">
|
<body id="body">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const userAgent = window.navigator.userAgent.toLowerCase();
|
const userAgent = window.navigator.userAgent.toLowerCase();
|
||||||
@ -62,5 +64,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/prism.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.26.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"coverage": "pnpm run test:unit && pnpm run test:components"
|
"coverage": "pnpm run test:unit && pnpm run test:components"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@appflowyinc/client-api-wasm": "0.0.3",
|
"@appflowyinc/client-api-wasm": "0.1.1",
|
||||||
"@atlaskit/primitives": "^5.5.3",
|
"@atlaskit/primitives": "^5.5.3",
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.1.2",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
@ -43,6 +43,8 @@
|
|||||||
"colorthief": "^2.4.0",
|
"colorthief": "^2.4.0",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"decimal.js": "^10.4.3",
|
"decimal.js": "^10.4.3",
|
||||||
|
"dexie": "^4.0.7",
|
||||||
|
"dexie-react-hooks": "^1.1.7",
|
||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.5.2",
|
||||||
"emoji-regex": "^10.2.1",
|
"emoji-regex": "^10.2.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
@ -56,6 +58,7 @@
|
|||||||
"katex": "^0.16.7",
|
"katex": "^0.16.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
|
"notistack": "^3.0.1",
|
||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"protoc-gen-ts": "0.8.7",
|
"protoc-gen-ts": "0.8.7",
|
||||||
@ -136,6 +139,7 @@
|
|||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"babel-jest": "^29.6.2",
|
"babel-jest": "^29.6.2",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
"cheerio": "1.0.0-rc.12",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.7.2",
|
"cypress": "^13.7.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
@ -145,6 +149,8 @@
|
|||||||
"istanbul-lib-coverage": "^3.2.2",
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
"jest-environment-jsdom": "^29.6.2",
|
"jest-environment-jsdom": "^29.6.2",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
|
"pino": "^9.2.0",
|
||||||
|
"pino-pretty": "^11.2.1",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "2.8.4",
|
"prettier": "2.8.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@appflowyinc/client-api-wasm':
|
'@appflowyinc/client-api-wasm':
|
||||||
specifier: 0.0.3
|
specifier: 0.1.1
|
||||||
version: 0.0.3
|
version: 0.1.1
|
||||||
'@atlaskit/primitives':
|
'@atlaskit/primitives':
|
||||||
specifier: ^5.5.3
|
specifier: ^5.5.3
|
||||||
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
||||||
@ -58,6 +62,12 @@ dependencies:
|
|||||||
decimal.js:
|
decimal.js:
|
||||||
specifier: ^10.4.3
|
specifier: ^10.4.3
|
||||||
version: 10.4.3
|
version: 10.4.3
|
||||||
|
dexie:
|
||||||
|
specifier: ^4.0.7
|
||||||
|
version: 4.0.7
|
||||||
|
dexie-react-hooks:
|
||||||
|
specifier: ^1.1.7
|
||||||
|
version: 1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0)
|
||||||
emoji-mart:
|
emoji-mart:
|
||||||
specifier: ^5.5.2
|
specifier: ^5.5.2
|
||||||
version: 5.6.0
|
version: 5.6.0
|
||||||
@ -97,6 +107,9 @@ dependencies:
|
|||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
|
notistack:
|
||||||
|
specifier: ^3.0.1
|
||||||
|
version: 3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0)
|
||||||
numeral:
|
numeral:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6
|
version: 2.0.6
|
||||||
@ -333,6 +346,9 @@ devDependencies:
|
|||||||
chalk:
|
chalk:
|
||||||
specifier: ^4.1.2
|
specifier: ^4.1.2
|
||||||
version: 4.1.2
|
version: 4.1.2
|
||||||
|
cheerio:
|
||||||
|
specifier: 1.0.0-rc.12
|
||||||
|
version: 1.0.0-rc.12
|
||||||
cross-env:
|
cross-env:
|
||||||
specifier: ^7.0.3
|
specifier: ^7.0.3
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
@ -360,6 +376,12 @@ devDependencies:
|
|||||||
nyc:
|
nyc:
|
||||||
specifier: ^15.1.0
|
specifier: ^15.1.0
|
||||||
version: 15.1.0
|
version: 15.1.0
|
||||||
|
pino:
|
||||||
|
specifier: ^9.2.0
|
||||||
|
version: 9.2.0
|
||||||
|
pino-pretty:
|
||||||
|
specifier: ^11.2.1
|
||||||
|
version: 11.2.1
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.21
|
specifier: ^8.4.21
|
||||||
version: 8.4.21
|
version: 8.4.21
|
||||||
@ -429,8 +451,8 @@ packages:
|
|||||||
'@jridgewell/gen-mapping': 0.3.5
|
'@jridgewell/gen-mapping': 0.3.5
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
/@appflowyinc/client-api-wasm@0.0.3:
|
/@appflowyinc/client-api-wasm@0.1.1:
|
||||||
resolution: {integrity: sha512-ARjLhiDZ8MiZ9egWDbAX9VAdXXS30av+InCPLrS/iqCMYrhuuU9rxS9jQeNEB7jucFrj158gBRusimFN7P/lyw==}
|
resolution: {integrity: sha512-7+/TCmzMi9KrxX3HFLJv9R6ON2AO5xQavV547ii7RZM8+5bZJakuf6+pnyCzOquQX07q3ZYwJCa3MIgDvficaA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
||||||
@ -4580,6 +4602,13 @@ packages:
|
|||||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/abort-controller@3.0.0:
|
||||||
|
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||||
|
engines: {node: '>=6.5'}
|
||||||
|
dependencies:
|
||||||
|
event-target-shim: 5.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/acorn-globals@7.0.1:
|
/acorn-globals@7.0.1:
|
||||||
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
|
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4886,6 +4915,11 @@ packages:
|
|||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/atomic-sleep@1.0.0:
|
||||||
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/autoprefixer@10.4.13(postcss@8.4.21):
|
/autoprefixer@10.4.13(postcss@8.4.21):
|
||||||
resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
|
resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@ -5180,6 +5214,13 @@ packages:
|
|||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/buffer@6.0.3:
|
||||||
|
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/cachedir@2.4.0:
|
/cachedir@2.4.0:
|
||||||
resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==}
|
resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -5289,6 +5330,30 @@ packages:
|
|||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cheerio-select@2.1.0:
|
||||||
|
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||||
|
dependencies:
|
||||||
|
boolbase: 1.0.0
|
||||||
|
css-select: 5.1.0
|
||||||
|
css-what: 6.1.0
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/cheerio@1.0.0-rc.12:
|
||||||
|
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
cheerio-select: 2.1.0
|
||||||
|
dom-serializer: 2.0.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.1.0
|
||||||
|
htmlparser2: 8.0.2
|
||||||
|
parse5: 7.1.2
|
||||||
|
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/chokidar@3.6.0:
|
/chokidar@3.6.0:
|
||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||||
engines: {node: '>= 8.10.0'}
|
engines: {node: '>= 8.10.0'}
|
||||||
@ -5739,6 +5804,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.24.1
|
'@babel/runtime': 7.24.1
|
||||||
|
|
||||||
|
/dateformat@4.6.3:
|
||||||
|
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/dayjs@1.11.10:
|
/dayjs@1.11.10:
|
||||||
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -5860,6 +5929,22 @@ packages:
|
|||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/dexie-react-hooks@1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16'
|
||||||
|
dexie: ^3.2 || ^4.0.1-alpha
|
||||||
|
react: '>=16'
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.66
|
||||||
|
dexie: 4.0.7
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/dexie@4.0.7:
|
||||||
|
resolution: {integrity: sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/didyoumean@1.2.2:
|
/didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6372,6 +6457,11 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/event-target-shim@5.0.1:
|
||||||
|
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/eventemitter2@6.4.7:
|
/eventemitter2@6.4.7:
|
||||||
resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==}
|
resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6455,6 +6545,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
|
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
|
||||||
engines: {'0': node >=0.6.0}
|
engines: {'0': node >=0.6.0}
|
||||||
|
|
||||||
|
/fast-copy@3.0.2:
|
||||||
|
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/fast-deep-equal@3.1.3:
|
/fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
@ -6492,6 +6586,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/fast-redact@3.5.0:
|
||||||
|
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/fast-safe-stringify@2.1.1:
|
||||||
|
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/fastq@1.17.1:
|
/fastq@1.17.1:
|
||||||
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6961,6 +7064,10 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/help-me@5.0.0:
|
||||||
|
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/hoist-non-react-statics@3.3.2:
|
/hoist-non-react-statics@3.3.2:
|
||||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6983,6 +7090,15 @@ packages:
|
|||||||
void-elements: 3.1.0
|
void-elements: 3.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/htmlparser2@8.0.2:
|
||||||
|
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||||
|
dependencies:
|
||||||
|
domelementtype: 2.3.0
|
||||||
|
domhandler: 5.0.3
|
||||||
|
domutils: 3.1.0
|
||||||
|
entities: 4.5.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/http-proxy-agent@5.0.0:
|
/http-proxy-agent@5.0.0:
|
||||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -7881,6 +7997,11 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- ts-node
|
- ts-node
|
||||||
|
|
||||||
|
/joycon@3.1.1:
|
||||||
|
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jpeg-js@0.4.4:
|
/jpeg-js@0.4.4:
|
||||||
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
|
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -8440,6 +8561,21 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/notistack@3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==}
|
||||||
|
engines: {node: '>=12.0.0', npm: '>=6.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
clsx: 1.2.1
|
||||||
|
goober: 2.1.14(csstype@3.1.3)
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- csstype
|
||||||
|
dev: false
|
||||||
|
|
||||||
/npm-run-path@4.0.1:
|
/npm-run-path@4.0.1:
|
||||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -8576,6 +8712,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==}
|
resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/on-exit-leak-free@2.1.2:
|
||||||
|
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/once@1.4.0:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8721,6 +8862,13 @@ packages:
|
|||||||
json-parse-even-better-errors: 2.3.1
|
json-parse-even-better-errors: 2.3.1
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
|
/parse5-htmlparser2-tree-adapter@7.0.0:
|
||||||
|
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
||||||
|
dependencies:
|
||||||
|
domhandler: 5.0.3
|
||||||
|
parse5: 7.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/parse5@7.1.2:
|
/parse5@7.1.2:
|
||||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8810,6 +8958,54 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pino-abstract-transport@1.2.0:
|
||||||
|
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
|
||||||
|
dependencies:
|
||||||
|
readable-stream: 4.5.2
|
||||||
|
split2: 4.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/pino-pretty@11.2.1:
|
||||||
|
resolution: {integrity: sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
colorette: 2.0.20
|
||||||
|
dateformat: 4.6.3
|
||||||
|
fast-copy: 3.0.2
|
||||||
|
fast-safe-stringify: 2.1.1
|
||||||
|
help-me: 5.0.0
|
||||||
|
joycon: 3.1.1
|
||||||
|
minimist: 1.2.8
|
||||||
|
on-exit-leak-free: 2.1.2
|
||||||
|
pino-abstract-transport: 1.2.0
|
||||||
|
pump: 3.0.0
|
||||||
|
readable-stream: 4.5.2
|
||||||
|
secure-json-parse: 2.7.0
|
||||||
|
sonic-boom: 4.0.1
|
||||||
|
strip-json-comments: 3.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/pino-std-serializers@7.0.0:
|
||||||
|
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/pino@9.2.0:
|
||||||
|
resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep: 1.0.0
|
||||||
|
fast-redact: 3.5.0
|
||||||
|
on-exit-leak-free: 2.1.2
|
||||||
|
pino-abstract-transport: 1.2.0
|
||||||
|
pino-std-serializers: 7.0.0
|
||||||
|
process-warning: 3.0.0
|
||||||
|
quick-format-unescaped: 4.0.4
|
||||||
|
real-require: 0.2.0
|
||||||
|
safe-stable-stringify: 2.4.3
|
||||||
|
sonic-boom: 4.0.1
|
||||||
|
thread-stream: 3.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/pirates@4.0.6:
|
/pirates@4.0.6:
|
||||||
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
|
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -9027,6 +9223,10 @@ packages:
|
|||||||
fromentries: 1.3.2
|
fromentries: 1.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/process-warning@3.0.0:
|
||||||
|
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/process@0.11.10:
|
/process@0.11.10:
|
||||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
@ -9115,6 +9315,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
|
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/quick-format-unescaped@4.0.4:
|
||||||
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/quick-lru@5.1.1:
|
/quick-lru@5.1.1:
|
||||||
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -9635,12 +9839,28 @@ packages:
|
|||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/readable-stream@4.5.2:
|
||||||
|
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
buffer: 6.0.3
|
||||||
|
events: 3.3.0
|
||||||
|
process: 0.11.10
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/readdirp@3.6.0:
|
/readdirp@3.6.0:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
/real-require@0.2.0:
|
||||||
|
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||||
|
engines: {node: '>= 12.13.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/redux-thunk@3.1.0(redux@5.0.1):
|
/redux-thunk@3.1.0(redux@5.0.1):
|
||||||
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -9924,6 +10144,11 @@ packages:
|
|||||||
is-regex: 1.1.4
|
is-regex: 1.1.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/safe-stable-stringify@2.4.3:
|
||||||
|
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/safer-buffer@2.1.2:
|
/safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
@ -9973,6 +10198,10 @@ packages:
|
|||||||
compute-scroll-into-view: 3.1.0
|
compute-scroll-into-view: 3.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/secure-json-parse@2.7.0:
|
||||||
|
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/semver@5.7.2:
|
/semver@5.7.2:
|
||||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -10151,6 +10380,12 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/sonic-boom@4.0.1:
|
||||||
|
resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==}
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep: 1.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/source-map-js@1.2.0:
|
/source-map-js@1.2.0:
|
||||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -10194,6 +10429,11 @@ packages:
|
|||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/split2@4.2.0:
|
||||||
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
|
engines: {node: '>= 10.x'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/sprintf-js@1.0.3:
|
/sprintf-js@1.0.3:
|
||||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||||
|
|
||||||
@ -10306,6 +10546,12 @@ packages:
|
|||||||
safe-buffer: 5.1.2
|
safe-buffer: 5.1.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/string_decoder@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/strip-ansi@6.0.1:
|
/strip-ansi@6.0.1:
|
||||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -10499,6 +10745,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/thread-stream@3.1.0:
|
||||||
|
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||||
|
dependencies:
|
||||||
|
real-require: 0.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/throttleit@1.0.1:
|
/throttleit@1.0.1:
|
||||||
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==}
|
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -11414,7 +11666,3 @@ packages:
|
|||||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
@ -1,38 +1,12 @@
|
|||||||
<svg width='100%' height='100%' viewBox='0 0 41 40' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path d="M455.885 297.861C455.85 297.861 455.82 297.886 455.814 297.92C444.323 359.916 394.213 414.254 336.155 444.713C328.858 448.526 326.952 450.629 318.768 451.286H435.513C438.163 451.384 440.805 450.945 443.282 449.998C445.759 449.051 448.019 447.613 449.927 445.772C451.836 443.931 453.353 441.724 454.389 439.283C455.425 436.843 455.958 434.218 455.956 431.566V297.933C455.956 297.893 455.924 297.861 455.885 297.861V297.861Z" fill="#F7931E"/>
|
||||||
d='M39.9564 24.0195C38.8098 30.1683 33.7828 35.5321 28.0061 38.5411C27.3005 38.9336 26.4627 39.1516 25.6689 39.1952H37.9279C39.1185 39.1952 39.9564 38.323 39.9564 37.2328V24.0195Z'
|
<path d="M327.511 447.668C325.125 447.832 322.731 447.832 320.345 447.668H327.511Z" fill="#FFCE00"/>
|
||||||
fill='#F7931E'
|
<path d="M210.239 178.025C208.465 179.504 206.69 180.852 204.882 182.134C175.071 203.103 84.0943 271.073 63.5522 241.821C43.3716 213.095 64.6368 131.617 117.027 92.3728C118.013 91.584 119 90.8609 120.018 90.1707C176.846 50.204 219.377 55.7257 240.05 85.0105C259.245 112.488 237.651 155.61 210.239 178.025Z" fill="#8427E0"/>
|
||||||
/>
|
<path d="M432.62 241.066C404.486 260.787 360.082 237.78 338.061 209.481C337.173 208.33 336.319 207.18 335.497 205.997C314.528 176.088 246.624 85.1106 275.81 64.5356C304.996 43.9606 389.137 66.409 427.46 121.002C428.315 122.218 429.136 123.401 429.925 124.584C467.197 179.67 461.248 220.82 432.62 241.066Z" fill="#00B5FF"/>
|
||||||
<path
|
<path d="M394.954 420.458C393.968 421.225 392.982 421.948 391.996 422.627C335.136 462.594 292.605 457.04 272.03 427.788C252.737 400.311 274.298 357.287 301.611 334.773C303.386 333.294 305.193 331.913 307.001 330.632C336.812 309.695 427.789 241.89 448.331 270.977C468.643 299.67 447.378 381.215 394.954 420.458Z" fill="#FFBD00"/>
|
||||||
d='M15.4381 12.1576C15.2617 12.2884 15.0853 12.4192 14.9089 12.55C11.9103 14.6432 2.82634 21.3589 0.753788 18.4371C-1.27467 15.6026 0.886079 7.57868 6.08952 3.69755C6.17771 3.61033 6.31 3.56672 6.3982 3.4795C12.0867 -0.48885 16.32 0.078058 18.3926 2.95621C20.3328 5.65992 18.1721 9.93353 15.4381 12.1576Z'
|
<path d="M236.205 448.131C206.92 468.673 122.878 446.192 84.5216 391.632L82.2537 388.345C44.7849 333.095 50.701 291.978 79.3943 271.83C107.496 252.11 151.9 275.117 173.921 303.383C174.808 304.534 175.663 305.684 176.517 306.867C197.487 336.711 265.457 427.688 236.205 448.131Z" fill="#E3006D"/>
|
||||||
fill='#8427E0'
|
<path d="M240.905 137.634C230.873 159.981 212.651 177.627 189.993 186.935C146.049 205.439 81.5306 228.939 70.4214 203.369C59.5423 178.225 79.6572 127.018 116.995 92.3754L117.783 91.718C175.761 50.0421 219.114 55.3667 240.05 85.0131C250.403 99.902 248.859 119.458 240.905 137.634Z" fill="#9327FF"/>
|
||||||
/>
|
<path d="M432.62 241.066C417.37 251.78 397.354 249.907 378.718 241.46C356.357 231.053 338.75 212.589 329.417 189.759C311.241 145.75 289.056 83.3019 314.101 72.4885C340.394 61.1164 395.382 83.7621 429.925 124.583C467.197 179.669 461.248 220.819 432.62 241.066Z" fill="#00C8FF"/>
|
||||||
<path
|
<path d="M394.954 420.458L394.297 420.984C336.286 462.726 292.868 457.434 272.03 427.787C261.578 412.899 263.123 393.441 271.077 375.2C281.097 352.845 299.324 335.195 321.989 325.899C365.899 307.361 430.451 283.894 441.56 309.465C452.669 335.036 432.325 385.684 394.954 420.458Z" fill="#FFCE00"/>
|
||||||
d='M33.8715 36.098C33.7833 36.1852 33.6951 36.2288 33.5628 36.316C27.8743 40.2844 23.641 39.7175 21.5684 36.8393C19.6282 34.1356 21.7889 29.862 24.5229 27.638C24.6993 27.5072 24.8757 27.3763 25.0521 27.2455C28.0507 25.1959 37.1347 18.4366 39.1631 21.3584C41.2357 24.1929 39.119 32.2169 33.8715 36.098Z'
|
<path d="M197.914 440.311C171.62 451.65 116.83 429.136 82.2537 388.446C44.7849 333.098 50.701 291.981 79.3943 271.833C94.6119 261.118 114.628 262.959 133.264 271.406C155.591 281.872 173.141 300.384 182.401 323.237C200.609 366.918 222.959 429.432 197.914 440.311Z" fill="#FB006D"/>
|
||||||
fill='#FFBD00'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M17.9954 38.8459C15.085 40.8955 6.70658 38.6715 2.87014 33.264C2.78195 33.1768 2.69376 33.046 2.64966 32.9588C-1.09858 27.5078 -0.481224 23.4086 2.38508 21.4462C5.20728 19.4838 9.61698 21.7515 11.8218 24.586C11.91 24.7168 11.9982 24.804 12.0864 24.9349C14.159 27.8566 20.9499 36.8399 17.9954 38.8459Z'
|
|
||||||
fill='#E3006D'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M15.4385 12.1576C11.3816 13.9455 2.73857 17.6086 1.45976 14.6432C0.357338 12.1576 2.3858 7.09899 6.08994 3.69755C6.17814 3.61033 6.31043 3.56672 6.39862 3.4795C12.0871 -0.48885 16.3204 0.078058 18.393 2.95621C20.3333 5.65992 18.1725 9.93353 15.4385 12.1576Z'
|
|
||||||
fill='#9327FF'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M37.6624 18.3955C34.8402 20.3579 30.4305 18.0903 28.2257 15.2557C28.1375 15.1249 28.0493 15.0377 27.9611 14.9069C25.8444 11.9415 19.0535 2.95819 21.9639 0.952211C24.8743 -1.09738 33.2968 1.12664 37.1333 6.53407C37.2215 6.6649 37.3096 6.75211 37.3978 6.88294C41.102 12.334 40.5287 16.3895 37.6624 18.3955Z'
|
|
||||||
fill='#00B5FF'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M37.6628 18.3934C34.8406 20.3557 30.4309 18.0881 28.2261 15.2536C26.4181 11.1108 22.9344 2.95603 25.8448 1.73499C28.4906 0.601179 33.9587 2.86881 37.4423 6.88077C41.1024 12.3318 40.5291 16.3874 37.6628 18.3934Z'
|
|
||||||
fill='#00C8FF'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M33.8715 36.0986C33.7833 36.1858 33.6951 36.2294 33.5628 36.3166C27.8743 40.285 23.641 39.7181 21.5684 36.8399C19.6282 34.1362 21.7889 29.8626 24.5229 27.6386C28.5799 25.8506 37.2229 22.1875 38.5017 25.1529C39.6482 27.6386 37.6197 32.6971 33.8715 36.0986Z'
|
|
||||||
fill='#FFCE00'
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d='M14.2031 38.061C11.5572 39.1948 6.08922 36.9708 2.64966 32.9588C-1.09858 27.5078 -0.481224 23.4086 2.38508 21.4462C5.20728 19.4838 9.61698 21.7515 11.8218 24.586C13.6298 28.6852 17.1135 36.8399 14.2031 38.061Z'
|
|
||||||
fill='#FB006D'
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_1.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_2.png
Normal file
After Width: | Height: | Size: 731 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_3.png
Normal file
After Width: | Height: | Size: 465 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_4.png
Normal file
After Width: | Height: | Size: 526 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_5.png
Normal file
After Width: | Height: | Size: 293 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_6.png
Normal file
After Width: | Height: | Size: 765 KiB |
Before Width: | Height: | Size: 1.1 MiB |
BIN
frontend/appflowy_web_app/public/og-image.png
Normal file
After Width: | Height: | Size: 189 KiB |
35
frontend/appflowy_web_app/scripts/merge-coverage.cjs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const jestCoverageFile = path.join(__dirname, '../coverage/jest/coverage-final.json');
|
||||||
|
const cypressCoverageFile = path.join(__dirname, '../coverage/cypress/coverage-final.json');
|
||||||
|
const nycOutputDir = path.join(__dirname, '../coverage/.nyc_output');
|
||||||
|
|
||||||
|
// Ensure .nyc_output directory exists
|
||||||
|
if (fs.existsSync(nycOutputDir)) {
|
||||||
|
fs.rmSync(nycOutputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(nycOutputDir, { recursive: true });
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(__dirname, '../coverage/merged'))) {
|
||||||
|
fs.rmSync(path.join(__dirname, '../coverage/merged'), { recursive: true });
|
||||||
|
}
|
||||||
|
// Copy Jest coverage file
|
||||||
|
fs.copyFileSync(jestCoverageFile, path.join(nycOutputDir, 'jest-coverage.json'));
|
||||||
|
// Copy Cypress E2E coverage file
|
||||||
|
fs.copyFileSync(cypressCoverageFile, path.join(nycOutputDir, 'cypress-coverage.json'));
|
||||||
|
|
||||||
|
// Merge coverage files
|
||||||
|
execSync('nyc merge ./coverage/.nyc_output ./coverage/merged/coverage-final.json', { stdio: 'inherit' });
|
||||||
|
|
||||||
|
// Move the merged result to the .nyc_output directory
|
||||||
|
fs.rmSync(nycOutputDir, { recursive: true });
|
||||||
|
fs.mkdirSync(nycOutputDir, { recursive: true });
|
||||||
|
fs.copyFileSync(path.join(__dirname, '../coverage/merged/coverage-final.json'), path.join(nycOutputDir, 'out.json'));
|
||||||
|
|
||||||
|
// Generate final merged report
|
||||||
|
execSync('nyc report --reporter=html --reporter=text-summary --report-dir=coverage/merged --temp-dir=coverage/.nyc_output', { stdio: 'inherit' });
|
||||||
|
console.log(`Merged coverage report written to coverage/merged`);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const pino = require('pino');
|
|
||||||
const cheerio = require('cheerio');
|
|
||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
const distDir = path.join(__dirname, 'dist');
|
|
||||||
const indexPath = path.join(distDir, 'index.html');
|
|
||||||
|
|
||||||
const setOrUpdateMetaTag = ($, selector, attribute, content) => {
|
|
||||||
if ($(selector).length === 0) {
|
|
||||||
$('head').append(`<meta ${attribute}="${selector.match(/\[(.*?)\]/)[1]}" content="${content}">`);
|
|
||||||
} else {
|
|
||||||
$(selector).attr('content', content);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Create a new logger instance
|
|
||||||
const logger = pino({
|
|
||||||
transport: {
|
|
||||||
target: 'pino-pretty',
|
|
||||||
level: 'info',
|
|
||||||
options: {
|
|
||||||
colorize: true,
|
|
||||||
translateTime: 'SYS:standard',
|
|
||||||
destination: `${__dirname}/pino-logger.log`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const logRequestTimer = (req) => {
|
|
||||||
const start = Date.now();
|
|
||||||
const pathname = new URL(req.url).pathname;
|
|
||||||
logger.info(`Incoming request: ${pathname}`);
|
|
||||||
return () => {
|
|
||||||
const duration = Date.now() - start;
|
|
||||||
logger.info(`Request for ${pathname} took ${duration}ms`);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchMetaData = async (url) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching meta data', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createServer = async (req) => {
|
|
||||||
const timer = logRequestTimer(req);
|
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
|
||||||
const pageId = req.url.split('/').pop();
|
|
||||||
let htmlData = fs.readFileSync(indexPath, 'utf8');
|
|
||||||
const $ = cheerio.load(htmlData);
|
|
||||||
if (!pageId) {
|
|
||||||
timer();
|
|
||||||
return new Response($.html(), {
|
|
||||||
headers: { 'Content-Type': 'text/html' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const description = 'Write, share, comment, react, and publish docs quickly and securely on AppFlowy.';
|
|
||||||
let title = 'AppFlowy';
|
|
||||||
const url = 'https://appflowy.com';
|
|
||||||
let image = 'https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png';
|
|
||||||
// Inject meta data into the HTML to support SEO and social sharing
|
|
||||||
// if (metaData) {
|
|
||||||
// title = metaData.title;
|
|
||||||
// image = metaData.image;
|
|
||||||
// }
|
|
||||||
|
|
||||||
$('title').text(title);
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description);
|
|
||||||
setOrUpdateMetaTag($, 'meta[property="og:title"]', 'property', title);
|
|
||||||
setOrUpdateMetaTag($, 'meta[property="og:description"]', 'property', description);
|
|
||||||
setOrUpdateMetaTag($, 'meta[property="og:image"]', 'property', image);
|
|
||||||
setOrUpdateMetaTag($, 'meta[property="og:url"]', 'property', url);
|
|
||||||
setOrUpdateMetaTag($, 'meta[property="og:type"]', 'property', 'article');
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="twitter:card"]', 'name', 'summary_large_image');
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="twitter:title"]', 'name', title);
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="twitter:description"]', 'name', description);
|
|
||||||
setOrUpdateMetaTag($, 'meta[name="twitter:image"]', 'name', image);
|
|
||||||
|
|
||||||
timer();
|
|
||||||
return new Response($.html(), {
|
|
||||||
headers: { 'Content-Type': 'text/html' },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
timer();
|
|
||||||
logger.error({ message: 'Method not allowed', method: req.method });
|
|
||||||
return new Response('Method not allowed', { status: 405 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const start = () => {
|
|
||||||
try {
|
|
||||||
Bun.serve({
|
|
||||||
port: 3000,
|
|
||||||
fetch: createServer,
|
|
||||||
error: (err) => {
|
|
||||||
logger.error(`Internal Server Error: ${err}`);
|
|
||||||
return new Response('Internal Server Error', { status: 500 });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.info(`Server is running on port 3000`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
start();
|
|
62
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
@ -194,6 +194,40 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "appflowy-local-ai"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"appflowy-plugin",
|
||||||
|
"bytes",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "appflowy-plugin"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=0820a0d23f7b813dee505e7e29e88a8561699fe8#0820a0d23f7b813dee505e7e29e88a8561699fe8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy_tauri"
|
name = "appflowy_tauri"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@ -1331,7 +1365,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.10",
|
"itoa 1.0.10",
|
||||||
"phf 0.8.0",
|
"phf 0.11.2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1887,6 +1921,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"allo-isolate",
|
"allo-isolate",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"appflowy-local-ai",
|
||||||
|
"appflowy-plugin",
|
||||||
"bytes",
|
"bytes",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"flowy-chat-pub",
|
"flowy-chat-pub",
|
||||||
@ -1894,7 +1930,6 @@ dependencies = [
|
|||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-sidecar",
|
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"futures",
|
"futures",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
@ -2176,7 +2211,6 @@ dependencies = [
|
|||||||
"fancy-regex 0.11.0",
|
"fancy-regex 0.11.0",
|
||||||
"flowy-codegen",
|
"flowy-codegen",
|
||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-sidecar",
|
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
@ -2363,24 +2397,6 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flowy-sidecar"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"lib-infra",
|
|
||||||
"log",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot 0.12.1",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-stream",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flowy-sqlite"
|
name = "flowy-sqlite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -4962,7 +4978,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4983,7 +4999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.55",
|
"syn 2.0.55",
|
||||||
|
@ -29,7 +29,7 @@ tokio = "1.34.0"
|
|||||||
tokio-stream = "0.1.14"
|
tokio-stream = "0.1.14"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||||
yrs = "0.18.8"
|
yrs = "0.19.1"
|
||||||
# Please use the following script to update collab.
|
# Please use the following script to update collab.
|
||||||
# Working directory: frontend
|
# Working directory: frontend
|
||||||
#
|
#
|
||||||
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||||
# ⚠️⚠️⚠️️
|
# ⚠️⚠️⚠️️
|
||||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9884d93aa2805013f36a79c1757174a0b5063065" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a2f92bb" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@ -107,10 +107,27 @@ default = ["custom-protocol"]
|
|||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# Please use the following script to update collab.
|
||||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# Working directory: frontend
|
||||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
#
|
||||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# To update the commit ID, run:
|
||||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# scripts/tool/update_collab_rev.sh new_rev_id
|
||||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
#
|
||||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }
|
# To switch to the local path, run:
|
||||||
|
# scripts/tool/update_collab_source.sh
|
||||||
|
# ⚠️⚠️⚠️️
|
||||||
|
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||||
|
|
||||||
|
# Working directory: frontend
|
||||||
|
# To update the commit ID, run:
|
||||||
|
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||||
|
# ⚠️⚠️⚠️️
|
||||||
|
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||||
|
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "0820a0d23f7b813dee505e7e29e88a8561699fe8" }
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ export enum BlockType {
|
|||||||
OutlineBlock = 'outline',
|
OutlineBlock = 'outline',
|
||||||
TableBlock = 'table',
|
TableBlock = 'table',
|
||||||
TableCell = 'table/cell',
|
TableCell = 'table/cell',
|
||||||
|
LinkPreview = 'link_preview',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InlineBlockType {
|
export enum InlineBlockType {
|
||||||
@ -46,7 +47,7 @@ export enum AlignType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockData {
|
export interface BlockData {
|
||||||
bg_color?: string;
|
bgColor?: string;
|
||||||
font_color?: string;
|
font_color?: string;
|
||||||
align?: AlignType;
|
align?: AlignType;
|
||||||
}
|
}
|
||||||
@ -79,6 +80,10 @@ export interface MathEquationBlockData extends BlockData {
|
|||||||
formula?: string;
|
formula?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LinkPreviewBlockData extends BlockData {
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export enum ImageType {
|
export enum ImageType {
|
||||||
Local = 0,
|
Local = 0,
|
||||||
Internal = 1,
|
Internal = 1,
|
||||||
@ -111,6 +116,8 @@ export interface TableCellBlockData extends BlockData {
|
|||||||
height: number;
|
height: number;
|
||||||
rowPosition: number;
|
rowPosition: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
rowBackgroundColor: string;
|
||||||
|
colBackgroundColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseNodeData extends BlockData {
|
export interface DatabaseNodeData extends BlockData {
|
||||||
@ -636,3 +643,27 @@ export enum LineHeightLayout {
|
|||||||
normal = 'normal',
|
normal = 'normal',
|
||||||
large = 'large',
|
large = 'large',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ViewMetaIcon {
|
||||||
|
ty: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PublishViewInfo {
|
||||||
|
view_id: string;
|
||||||
|
name: string;
|
||||||
|
icon: ViewMetaIcon | null;
|
||||||
|
extra: string | null;
|
||||||
|
layout: number;
|
||||||
|
created_at: string;
|
||||||
|
created_by: string;
|
||||||
|
last_edited_time: string;
|
||||||
|
last_edited_by: string;
|
||||||
|
child_views: PublishViewInfo[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PublishViewMetaData {
|
||||||
|
view: PublishViewInfo;
|
||||||
|
child_views: PublishViewInfo[];
|
||||||
|
ancestor_views: PublishViewInfo[];
|
||||||
|
}
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
useSortsSelector,
|
useSortsSelector,
|
||||||
} from '../selector';
|
} from '../selector';
|
||||||
import { useDatabaseViewId } from '../context';
|
import { useDatabaseViewId } from '../context';
|
||||||
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
|
||||||
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
||||||
import { withTestingDatabase } from '@/application/database-yjs/__tests__/withTestingData';
|
import { withTestingDatabase } from '@/application/database-yjs/__tests__/withTestingData';
|
||||||
import { expect } from '@jest/globals';
|
import { expect } from '@jest/globals';
|
||||||
@ -31,11 +30,9 @@ const wrapperCreator =
|
|||||||
(viewId: string, doc: YDoc, rowDocMap: Y.Map<YDoc>) =>
|
(viewId: string, doc: YDoc, rowDocMap: Y.Map<YDoc>) =>
|
||||||
({ children }: { children: React.ReactNode }) => {
|
({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<IdProvider objectId={viewId}>
|
|
||||||
<DatabaseContextProvider viewId={viewId} databaseDoc={doc} rowDocMap={rowDocMap} readOnly={true}>
|
<DatabaseContextProvider viewId={viewId} databaseDoc={doc} rowDocMap={rowDocMap} readOnly={true}>
|
||||||
{children}
|
{children}
|
||||||
</DatabaseContextProvider>
|
</DatabaseContextProvider>
|
||||||
</IdProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { YDatabase, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
import { YDatabase, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||||
|
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
@ -9,6 +10,10 @@ export interface DatabaseContextState {
|
|||||||
rowDocMap: Y.Map<YDoc>;
|
rowDocMap: Y.Map<YDoc>;
|
||||||
isDatabaseRowPage?: boolean;
|
isDatabaseRowPage?: boolean;
|
||||||
navigateToRow?: (rowId: string) => void;
|
navigateToRow?: (rowId: string) => void;
|
||||||
|
loadView?: (viewId: string) => Promise<YDoc>;
|
||||||
|
getViewRowsMap?: (viewId: string, rowIds: string[]) => Promise<{ rows: Y.Map<YDoc>; destroy: () => void }>;
|
||||||
|
loadViewMeta?: (viewId: string) => Promise<ViewMeta>;
|
||||||
|
navigateToView?: (viewId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
|
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { FieldId, SortId, YDatabaseField, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||||
FieldId,
|
|
||||||
SortId,
|
|
||||||
YDatabaseField,
|
|
||||||
YDoc,
|
|
||||||
YjsDatabaseKey,
|
|
||||||
YjsEditorKey,
|
|
||||||
YjsFolderKey,
|
|
||||||
} from '@/application/collab.type';
|
|
||||||
import { getCell, metaIdFromRowId, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
|
import { getCell, metaIdFromRowId, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
|
||||||
import {
|
import {
|
||||||
useDatabase,
|
useDatabase,
|
||||||
@ -19,7 +11,6 @@ import {
|
|||||||
import { filterBy, parseFilter } from '@/application/database-yjs/filter';
|
import { filterBy, parseFilter } from '@/application/database-yjs/filter';
|
||||||
import { groupByField } from '@/application/database-yjs/group';
|
import { groupByField } from '@/application/database-yjs/group';
|
||||||
import { sortBy } from '@/application/database-yjs/sort';
|
import { sortBy } from '@/application/database-yjs/sort';
|
||||||
import { useViewsIdSelector } from '@/application/folder-yjs';
|
|
||||||
import { parseYDatabaseCellToCell } from '@/application/database-yjs/cell.parse';
|
import { parseYDatabaseCellToCell } from '@/application/database-yjs/cell.parse';
|
||||||
import { DateTimeCell } from '@/application/database-yjs/cell.type';
|
import { DateTimeCell } from '@/application/database-yjs/cell.type';
|
||||||
import * as dayjs from 'dayjs';
|
import * as dayjs from 'dayjs';
|
||||||
@ -42,9 +33,8 @@ export interface Row {
|
|||||||
|
|
||||||
const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmpty];
|
const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmpty];
|
||||||
|
|
||||||
export function useDatabaseViewsSelector(iidIndex: string) {
|
export function useDatabaseViewsSelector(_iidIndex: string) {
|
||||||
const database = useDatabase();
|
const database = useDatabase();
|
||||||
const { viewsId: visibleViewsId, views: folderViews } = useViewsIdSelector();
|
|
||||||
|
|
||||||
const views = database?.get(YjsDatabaseKey.views);
|
const views = database?.get(YjsDatabaseKey.views);
|
||||||
const [viewIds, setViewIds] = useState<string[]>([]);
|
const [viewIds, setViewIds] = useState<string[]>([]);
|
||||||
@ -65,22 +55,7 @@ export function useDatabaseViewsSelector(iidIndex: string) {
|
|||||||
return Number(viewB.created_at) - Number(viewA.created_at);
|
return Number(viewB.created_at) - Number(viewA.created_at);
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewsId = [];
|
setViewIds(viewsSorted.map(([key]) => key));
|
||||||
|
|
||||||
for (const viewItem of viewsSorted) {
|
|
||||||
const [key] = viewItem;
|
|
||||||
const view = folderViews?.get(key);
|
|
||||||
|
|
||||||
if (
|
|
||||||
visibleViewsId.includes(key) &&
|
|
||||||
view &&
|
|
||||||
(view.get(YjsFolderKey.bid) === iidIndex || view.get(YjsFolderKey.id) === iidIndex)
|
|
||||||
) {
|
|
||||||
viewsId.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setViewIds(viewsId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
observerEvent();
|
observerEvent();
|
||||||
@ -89,7 +64,7 @@ export function useDatabaseViewsSelector(iidIndex: string) {
|
|||||||
return () => {
|
return () => {
|
||||||
views.unobserve(observerEvent);
|
views.unobserve(observerEvent);
|
||||||
};
|
};
|
||||||
}, [visibleViewsId, views, folderViews, iidIndex]);
|
}, [views]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
childViews,
|
childViews,
|
||||||
|
@ -2,6 +2,17 @@ import { YDoc } from '@/application/collab.type';
|
|||||||
import { databasePrefix } from '@/application/constants';
|
import { databasePrefix } from '@/application/constants';
|
||||||
import { IndexeddbPersistence } from 'y-indexeddb';
|
import { IndexeddbPersistence } from 'y-indexeddb';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
import BaseDexie from 'dexie';
|
||||||
|
import { viewMetasSchema, ViewMetasTable } from '@/application/db/tables/view_metas';
|
||||||
|
|
||||||
|
type DexieTables = ViewMetasTable;
|
||||||
|
|
||||||
|
export type Dexie<T = DexieTables> = BaseDexie & T;
|
||||||
|
|
||||||
|
export const db = new BaseDexie(`${databasePrefix}_cache`) as Dexie;
|
||||||
|
const schema = Object.assign({}, viewMetasSchema);
|
||||||
|
|
||||||
|
db.version(1).stores(schema);
|
||||||
|
|
||||||
const openedSet = new Set<string>();
|
const openedSet = new Set<string>();
|
||||||
|
|
||||||
@ -32,10 +43,16 @@ export async function openCollabDB(docName: string): Promise<YDoc> {
|
|||||||
return doc as YDoc;
|
return doc as YDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCollabDBName(id: string, type: string, uuid?: string) {
|
export async function closeCollabDB(docName: string) {
|
||||||
if (!uuid) {
|
const name = `${databasePrefix}_${docName}`;
|
||||||
return `${type}_${id}`;
|
|
||||||
|
if (openedSet.has(name)) {
|
||||||
|
openedSet.delete(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${uuid}_${type}_${id}`;
|
const doc = new Y.Doc();
|
||||||
|
|
||||||
|
const provider = new IndexeddbPersistence(name, doc);
|
||||||
|
|
||||||
|
await provider.destroy();
|
||||||
}
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Table } from 'dexie';
|
||||||
|
import { PublishViewInfo } from '@/application/collab.type';
|
||||||
|
|
||||||
|
export type ViewMeta = {
|
||||||
|
publish_name: string;
|
||||||
|
|
||||||
|
child_views: PublishViewInfo[];
|
||||||
|
ancestor_views: PublishViewInfo[];
|
||||||
|
} & PublishViewInfo;
|
||||||
|
|
||||||
|
export type ViewMetasTable = {
|
||||||
|
view_metas: Table<ViewMeta>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewMetasSchema = {
|
||||||
|
view_metas: 'publish_name',
|
||||||
|
};
|
@ -1,38 +0,0 @@
|
|||||||
import { ViewLayout, YFolder, YjsFolderKey } from '@/application/collab.type';
|
|
||||||
import { createContext, useContext } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
export interface Crumb {
|
|
||||||
viewId: string;
|
|
||||||
rowId?: string;
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FolderContext = createContext<{
|
|
||||||
folder: YFolder | null;
|
|
||||||
onNavigateToView?: (viewId: string) => void;
|
|
||||||
crumbs?: Crumb[];
|
|
||||||
setCrumbs?: React.Dispatch<React.SetStateAction<Crumb[]>>;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
export const useFolderContext = () => {
|
|
||||||
return useContext(FolderContext)?.folder;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useViewLayout = () => {
|
|
||||||
const folder = useFolderContext();
|
|
||||||
const { objectId } = useParams();
|
|
||||||
const views = folder?.get(YjsFolderKey.views);
|
|
||||||
const view = objectId ? views?.get(objectId) : null;
|
|
||||||
|
|
||||||
return Number(view?.get(YjsFolderKey.layout)) as ViewLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useNavigateToView = () => {
|
|
||||||
return useContext(FolderContext)?.onNavigateToView;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useCrumbs = () => {
|
|
||||||
return useContext(FolderContext)?.crumbs;
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
export enum CoverType {
|
|
||||||
NormalColor = 'color',
|
|
||||||
GradientColor = 'gradient',
|
|
||||||
BuildInImage = 'built_in',
|
|
||||||
CustomImage = 'custom',
|
|
||||||
LocalImage = 'local',
|
|
||||||
UpsplashImage = 'unsplash',
|
|
||||||
None = 'none',
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './selector';
|
|
||||||
export * from './context';
|
|
@ -1,70 +0,0 @@
|
|||||||
import { YjsFolderKey, YView } from '@/application/collab.type';
|
|
||||||
import { useFolderContext } from '@/application/folder-yjs/context';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export function useViewsIdSelector() {
|
|
||||||
const folder = useFolderContext();
|
|
||||||
const [viewsId, setViewsId] = useState<string[]>([]);
|
|
||||||
const views = folder?.get(YjsFolderKey.views);
|
|
||||||
const trash = folder?.get(YjsFolderKey.section)?.get(YjsFolderKey.trash);
|
|
||||||
const meta = folder?.get(YjsFolderKey.meta);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!views) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trashUid = trash ? Array.from(trash.keys())[0] : null;
|
|
||||||
const userTrash = trashUid ? trash?.get(trashUid) : null;
|
|
||||||
|
|
||||||
const collectIds = () => {
|
|
||||||
const trashIds = userTrash?.toJSON()?.map((item) => item.id) || [];
|
|
||||||
|
|
||||||
return Array.from(views.keys()).filter((id) => {
|
|
||||||
return !trashIds.includes(id) && id !== meta?.get(YjsFolderKey.current_workspace);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setViewsId(collectIds());
|
|
||||||
const observerEvent = () => setViewsId(collectIds());
|
|
||||||
|
|
||||||
views.observe(observerEvent);
|
|
||||||
userTrash?.observe(observerEvent);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
views.unobserve(observerEvent);
|
|
||||||
userTrash?.unobserve(observerEvent);
|
|
||||||
};
|
|
||||||
}, [views, trash, meta]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
viewsId,
|
|
||||||
views,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useViewSelector(viewId: string) {
|
|
||||||
const folder = useFolderContext();
|
|
||||||
const [clock, setClock] = useState<number>(0);
|
|
||||||
const [view, setView] = useState<YView | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!folder) return;
|
|
||||||
|
|
||||||
const view = folder.get(YjsFolderKey.views)?.get(viewId);
|
|
||||||
|
|
||||||
setView(view || null);
|
|
||||||
const observerEvent = () => setClock((prev) => prev + 1);
|
|
||||||
|
|
||||||
view?.observe(observerEvent);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
view?.unobserve(observerEvent);
|
|
||||||
};
|
|
||||||
}, [folder, viewId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
clock,
|
|
||||||
view,
|
|
||||||
};
|
|
||||||
}
|
|
163
frontend/appflowy_web_app/src/application/publish/context.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { YDoc } from '@/application/collab.type';
|
||||||
|
import { db } from '@/application/db';
|
||||||
|
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||||
|
import { AFConfigContext } from '@/components/app/AppConfig';
|
||||||
|
import { useLiveQuery } from 'dexie-react-hooks';
|
||||||
|
import { createContext, useCallback, useContext, useEffect, useRef } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
|
export interface PublishContextType {
|
||||||
|
namespace: string;
|
||||||
|
publishName: string;
|
||||||
|
viewMeta?: ViewMeta;
|
||||||
|
toView: (viewId: string) => Promise<void>;
|
||||||
|
loadViewMeta: (viewId: string) => Promise<ViewMeta>;
|
||||||
|
getViewRowsMap?: (viewId: string, rowIds: string[]) => Promise<{ rows: Y.Map<YDoc>; destroy: () => void }>;
|
||||||
|
|
||||||
|
loadView: (viewId: string) => Promise<YDoc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PublishContext = createContext<PublishContextType | null>(null);
|
||||||
|
|
||||||
|
export const PublishProvider = ({
|
||||||
|
children,
|
||||||
|
namespace,
|
||||||
|
publishName,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
namespace: string;
|
||||||
|
publishName: string;
|
||||||
|
}) => {
|
||||||
|
const viewMeta = useLiveQuery(async () => {
|
||||||
|
const name = `${namespace}_${publishName}`;
|
||||||
|
|
||||||
|
return db.view_metas.get(name);
|
||||||
|
}, [namespace, publishName]);
|
||||||
|
|
||||||
|
const prevViewMeta = useRef(viewMeta);
|
||||||
|
|
||||||
|
const service = useContext(AFConfigContext)?.service;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const toView = useCallback(
|
||||||
|
async (viewId: string) => {
|
||||||
|
try {
|
||||||
|
const res = await service?.getPublishInfo(viewId);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { namespace, publishName } = res;
|
||||||
|
|
||||||
|
navigate(`/${namespace}/${publishName}`);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[navigate, service]
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadViewMeta = useCallback(
|
||||||
|
async (viewId: string) => {
|
||||||
|
try {
|
||||||
|
const info = await service?.getPublishInfo(viewId);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
throw new Error('View has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { namespace, publishName } = info;
|
||||||
|
|
||||||
|
const res = await service?.getPublishViewMeta(namespace, publishName);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('View meta has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[service]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getViewRowsMap = useCallback(
|
||||||
|
async (viewId: string, rowIds: string[]) => {
|
||||||
|
try {
|
||||||
|
const info = await service?.getPublishInfo(viewId);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
throw new Error('View has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { namespace, publishName } = info;
|
||||||
|
const res = await service?.getPublishDatabaseViewRows(namespace, publishName, rowIds);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('View has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[service]
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadView = useCallback(
|
||||||
|
async (viewId: string) => {
|
||||||
|
try {
|
||||||
|
const res = await service?.getPublishInfo(viewId);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('View has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { namespace, publishName } = res;
|
||||||
|
|
||||||
|
const data = service?.getPublishView(namespace, publishName);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new Error('View has not been published yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[service]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!viewMeta && prevViewMeta.current) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevViewMeta.current = viewMeta;
|
||||||
|
}, [viewMeta]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PublishContext.Provider
|
||||||
|
value={{
|
||||||
|
loadView,
|
||||||
|
viewMeta,
|
||||||
|
getViewRowsMap,
|
||||||
|
loadViewMeta,
|
||||||
|
toView,
|
||||||
|
namespace,
|
||||||
|
publishName,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</PublishContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function usePublishContext() {
|
||||||
|
return useContext(PublishContext);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './context';
|