mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: the name is too long to publish
This commit is contained in:
8
frontend/.vscode/launch.json
vendored
8
frontend/.vscode/launch.json
vendored
@ -116,11 +116,11 @@
|
|||||||
{
|
{
|
||||||
"name": "AF-desktop: Debug Rust",
|
"name": "AF-desktop: Debug Rust",
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "attach",
|
// "request": "attach",
|
||||||
"pid": "${command:pickMyProcess}"
|
// "pid": "${command:pickMyProcess}"
|
||||||
// To launch the application directly, use the following configuration:
|
// To launch the application directly, use the following configuration:
|
||||||
// "request": "launch",
|
"request": "launch",
|
||||||
// "program": "[YOUR_APPLICATION_PATH]",
|
"program": "/Users/lucas.xu/Desktop/development/AppFlowy/frontend/appflowy_flutter/build/macos/Build/Products/Debug/AppFlowy.app",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// https://tauri.app/v1/guides/debugging/vs-code
|
// https://tauri.app/v1/guides/debugging/vs-code
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
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/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';
|
||||||
@ -12,7 +13,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
|
|
||||||
part 'document_share_bloc.freezed.dart';
|
part 'document_share_bloc.freezed.dart';
|
||||||
|
|
||||||
const _url = 'https://test.appflowy.io';
|
const _url = 'https://test.appflowy.com';
|
||||||
|
|
||||||
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
||||||
DocumentShareBloc({
|
DocumentShareBloc({
|
||||||
@ -21,16 +22,30 @@ class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
|||||||
on<DocumentShareEvent>((event, emit) async {
|
on<DocumentShareEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
|
viewListener = ViewListener(viewId: view.id)
|
||||||
|
..start(
|
||||||
|
onViewUpdated: (value) {
|
||||||
|
add(DocumentShareEvent.updateViewName(value.name));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
||||||
publishInfo.fold((s) {
|
publishInfo.fold((s) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
url: '$_url/${s.namespace}/${s.publishName}',
|
url: '$_url/${s.namespace}/${s.publishName}',
|
||||||
|
viewName: view.name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, (f) {
|
}, (f) {
|
||||||
emit(state.copyWith(isPublished: false, url: ''));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPublished: false,
|
||||||
|
url: '',
|
||||||
|
viewName: view.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
share: (type, path) async {
|
share: (type, path) async {
|
||||||
@ -101,14 +116,24 @@ class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
updateViewName: (viewName) async {
|
||||||
|
emit(state.copyWith(viewName: viewName));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
|
late final ViewListener viewListener;
|
||||||
|
|
||||||
late final exporter = DocumentExporter(view);
|
late final exporter = DocumentExporter(view);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await viewListener.stop();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
Future<FlowyResult<ExportDataPB, FlowyError>> _export(
|
Future<FlowyResult<ExportDataPB, FlowyError>> _export(
|
||||||
DocumentShareType type,
|
DocumentShareType type,
|
||||||
String? path,
|
String? path,
|
||||||
@ -181,6 +206,8 @@ class DocumentShareEvent with _$DocumentShareEvent {
|
|||||||
String pageId,
|
String pageId,
|
||||||
) = _Publish;
|
) = _Publish;
|
||||||
const factory DocumentShareEvent.unPublish() = _UnPublish;
|
const factory DocumentShareEvent.unPublish() = _UnPublish;
|
||||||
|
const factory DocumentShareEvent.updateViewName(String name) =
|
||||||
|
_UpdateViewName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -191,11 +218,13 @@ class DocumentShareState with _$DocumentShareState {
|
|||||||
required bool isPublished,
|
required bool isPublished,
|
||||||
FlowyResult<void, FlowyError>? publishResult,
|
FlowyResult<void, FlowyError>? publishResult,
|
||||||
required String url,
|
required String url,
|
||||||
|
required String viewName,
|
||||||
}) = _DocumentShareState;
|
}) = _DocumentShareState;
|
||||||
|
|
||||||
factory DocumentShareState.initial() => const DocumentShareState(
|
factory DocumentShareState.initial() => const DocumentShareState(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isPublished: false,
|
isPublished: false,
|
||||||
url: '',
|
url: '',
|
||||||
|
viewName: '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flowy_infra/uuid.dart';
|
||||||
|
|
||||||
|
final _regExp = RegExp(r'[^\w\-\.@:/]');
|
||||||
|
|
||||||
|
Future<String> generateNameSpace() async {
|
||||||
|
const workspaceName = '';
|
||||||
|
final id = uuid().substring(0, 8);
|
||||||
|
return '$workspaceName$id'.replaceAll(_regExp, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The backend limits the publish name to a maximum of 50 characters.
|
||||||
|
// If the combined length of the ID and the name exceeds 50 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 {
|
||||||
|
final result = '${name.substring(0, min(49 - id.length, name.length))}-$id';
|
||||||
|
return result.replaceAll(_regExp, '-');
|
||||||
|
}
|
@ -1,17 +1,15 @@
|
|||||||
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_share_bloc.dart';
|
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/publish_name_generator.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.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/uuid.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/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';
|
||||||
|
|
||||||
final _regExp = RegExp(r'[^\w\-\.@:/]');
|
|
||||||
|
|
||||||
class PublishTab extends StatelessWidget {
|
class PublishTab extends StatelessWidget {
|
||||||
const PublishTab({super.key});
|
const PublishTab({super.key});
|
||||||
|
|
||||||
@ -40,8 +38,12 @@ class PublishTab extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: _UnPublishWidget(
|
: _UnPublishWidget(
|
||||||
onPublish: () async {
|
onPublish: () async {
|
||||||
final publishName = await _generatePublishName(context);
|
final id = context.read<DocumentShareBloc>().view.id;
|
||||||
final nameSpace = await _generateNameSpace();
|
final publishName = await generatePublishName(
|
||||||
|
id,
|
||||||
|
state.viewName,
|
||||||
|
);
|
||||||
|
final nameSpace = await generateNameSpace();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.read<DocumentShareBloc>().add(
|
context.read<DocumentShareBloc>().add(
|
||||||
DocumentShareEvent.publish(nameSpace, publishName),
|
DocumentShareEvent.publish(nameSpace, publishName),
|
||||||
@ -52,18 +54,6 @@ class PublishTab extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _generateNameSpace() async {
|
|
||||||
const workspaceName = '';
|
|
||||||
final id = uuid().substring(0, 8);
|
|
||||||
return '$workspaceName$id'.replaceAll(_regExp, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _generatePublishName(BuildContext context) async {
|
|
||||||
final publishName = context.read<DocumentShareBloc>().view.name;
|
|
||||||
final id = uuid().substring(0, 8);
|
|
||||||
return '$publishName$id'.replaceAll(_regExp, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PublishedWidget extends StatefulWidget {
|
class _PublishedWidget extends StatefulWidget {
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
use crate::entities::ViewPB;
|
use crate::entities::ViewPB;
|
||||||
use flowy_folder_pub::entities::PublishViewInfo;
|
use flowy_folder_pub::entities::PublishViewInfo;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
fn replace_invalid_url_chars(input: &str) -> String {
|
fn replace_invalid_url_chars(input: &str) -> String {
|
||||||
let re = Regex::new(r"[^\w-]").unwrap();
|
let regex = Regex::new(r"[^\w-]").unwrap();
|
||||||
|
regex.replace_all(input, "-").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
let replaced = re.replace_all(input, "_").to_string();
|
|
||||||
if replaced.len() > 20 {
|
|
||||||
replaced[..20].to_string()
|
|
||||||
} else {
|
|
||||||
replaced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn generate_publish_name(id: &str, name: &str) -> String {
|
pub fn generate_publish_name(id: &str, name: &str) -> String {
|
||||||
|
let id_len = id.len();
|
||||||
let name = replace_invalid_url_chars(name);
|
let name = replace_invalid_url_chars(name);
|
||||||
format!("{}-{}", name, id)
|
let name_len = name.len();
|
||||||
|
// The backend limits the publish name to a maximum of 50 characters.
|
||||||
|
// If the combined length of the ID and the name exceeds 50 characters,
|
||||||
|
// we will truncate the name to ensure the final result is within the limit.
|
||||||
|
// The name should only contain alphanumeric characters and hyphens.
|
||||||
|
let result = format!("{}-{}", &name[..std::cmp::min(49 - id_len, name_len)], id);
|
||||||
|
trace!("generate_publish_name: {}", result);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_pb_to_publish_view(view: &ViewPB) -> PublishViewInfo {
|
pub fn view_pb_to_publish_view(view: &ViewPB) -> PublishViewInfo {
|
||||||
|
Reference in New Issue
Block a user