diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index dea8e87712..68f8eada78 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -10,6 +10,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 709df1cb97..e1e7b949f1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -3,7 +3,7 @@ part of 'cell_service.dart'; typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; typedef GridDateCellContext = _GridCellContext; -typedef GridURLCellContext = _GridCellContext; +typedef GridURLCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index c223347715..92caedc4e9 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -58,7 +58,11 @@ class GridCellDataLoader extends IGridCellDataLoader { return fut.then( (result) => result.fold((Cell cell) { try { - return parser.parserData(cell.data); + if (cell.data.isEmpty) { + return null; + } else { + return parser.parserData(cell.data); + } } catch (e, s) { Log.error('$parser parser cellData failed, $e'); Log.error('Stack trace \n $s'); @@ -105,9 +109,6 @@ class StringCellDataParser implements ICellDataParser { class DateCellDataParser implements ICellDataParser { @override DateCellData? parserData(List data) { - if (data.isEmpty) { - return null; - } return DateCellData.fromBuffer(data); } } @@ -115,19 +116,13 @@ class DateCellDataParser implements ICellDataParser { class SelectOptionCellDataParser implements ICellDataParser { @override SelectOptionCellData? parserData(List data) { - if (data.isEmpty) { - return null; - } return SelectOptionCellData.fromBuffer(data); } } -class URLCellDataParser implements ICellDataParser { +class URLCellDataParser implements ICellDataParser { @override - Cell? parserData(List data) { - if (data.isEmpty) { - return null; - } - return Cell.fromBuffer(data); + URLCellData? parserData(List data) { + return URLCellData.fromBuffer(data); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart index 8b4245540a..04284b16f7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -23,7 +23,7 @@ class URLCellBloc extends Bloc { emit(state.copyWith(content: text)); }, didReceiveCellUpdate: (cellData) { - emit(state.copyWith(content: cellData.content)); + emit(state.copyWith(content: cellData.content, url: cellData.url)); }, ); }, @@ -54,7 +54,7 @@ class URLCellBloc extends Bloc { @freezed class URLCellEvent with _$URLCellEvent { const factory URLCellEvent.initial() = _InitialCell; - const factory URLCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; + const factory URLCellEvent.didReceiveCellUpdate(URLCellData cell) = _DidReceiveCellUpdate; const factory URLCellEvent.updateText(String text) = _UpdateText; } @@ -67,6 +67,9 @@ class URLCellState with _$URLCellState { factory URLCellState.initial(GridURLCellContext context) { final cellData = context.getCellData(); - return URLCellState(content: cellData?.content ?? "", url: ""); + return URLCellState( + content: cellData?.content ?? "", + url: cellData?.url ?? "", + ); } } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 23fe8ed62b..59ca0e3dec 100755 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -928,6 +928,7 @@ dependencies = [ "dart-notify", "dashmap", "diesel", + "fancy-regex", "flowy-database", "flowy-derive", "flowy-error", @@ -953,7 +954,6 @@ dependencies = [ "strum_macros", "tokio", "tracing", - "url", ] [[package]] diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 2bde4a3f36..a4677d727f 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" indexmap = {version = "1.8.1", features = ["serde"]} -url = { version = "2"} +fancy-regex = "0.10.0" [dev-dependencies] flowy-test = { path = "../flowy-test" } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 25a16a0515..c96166272a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -627,7 +627,7 @@ mod tests { field_meta: &FieldMeta, ) -> String { type_option - .decode_cell_data(encoded_data, &FieldType::DateTime, &field_meta) + .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta) .unwrap() .parse::() .unwrap() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs index 1e857e9fd3..26efd34acc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs @@ -537,7 +537,7 @@ mod tests { let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); + assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option @@ -580,12 +580,12 @@ mod tests { cell_data, &type_option, &field_meta, - vec![google_option.clone(), facebook_option.clone()], + vec![google_option.clone(), facebook_option], ); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]); + assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option]); // Invalid option id let cell_data = type_option @@ -612,7 +612,7 @@ mod tests { assert_eq!( expected, type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) .unwrap() .parse::() .unwrap() @@ -629,7 +629,7 @@ mod tests { assert_eq!( expected, type_option - .decode_cell_data(cell_data, &field_meta.field_type, &field_meta) + .decode_cell_data(cell_data, &field_meta.field_type, field_meta) .unwrap() .parse::() .unwrap() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs index 5ca29ee87d..6cef992fbd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -1,14 +1,16 @@ use crate::impl_type_option; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; +use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; use bytes::Bytes; +use fancy_regex::Regex; use flowy_derive::ProtoBuf; -use flowy_error::{FlowyError, FlowyResult}; +use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; - +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Default)] pub struct URLTypeOptionBuilder(URLTypeOption); @@ -32,7 +34,7 @@ pub struct URLTypeOption { } impl_type_option!(URLTypeOption, FieldType::URL); -impl CellDataOperation for URLTypeOption { +impl CellDataOperation, String> for URLTypeOption { fn decode_cell_data( &self, encoded_data: T, @@ -40,14 +42,13 @@ impl CellDataOperation for URLTypeOption { _field_meta: &FieldMeta, ) -> FlowyResult where - T: Into, + T: Into>, { if !decoded_field_type.is_url() { return Ok(DecodedCellData::default()); } - - let cell_data = encoded_data.into(); - Ok(DecodedCellData::from_content(cell_data)) + let cell_data = encoded_data.into().try_into_inner()?; + DecodedCellData::try_from_bytes(cell_data) } fn apply_changeset(&self, changeset: C, _cell_meta: Option) -> Result @@ -55,7 +56,16 @@ impl CellDataOperation for URLTypeOption { C: Into, { let changeset = changeset.into(); - Ok(changeset.to_string()) + let mut cell_data = URLCellData { + url: "".to_string(), + content: changeset.to_string(), + }; + + if let Ok(Some(m)) = URL_REGEX.find(&changeset) { + cell_data.url = m.as_str().to_string(); + } + + cell_data.to_json() } } @@ -68,34 +78,97 @@ pub struct URLCellData { pub content: String, } +impl URLCellData { + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), + } + } + + fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } +} + +impl FromStr for URLCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + serde_json::from_str::(s).map_err(internal_error) + } +} + +lazy_static! { + static ref URL_REGEX: Regex = Regex::new( + "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" + ) + .unwrap(); +} + #[cfg(test)] mod tests { use crate::services::field::FieldBuilder; - use crate::services::field::URLTypeOption; - use crate::services::row::CellDataOperation; + use crate::services::field::{URLCellData, URLTypeOption}; + use crate::services::row::{CellDataOperation, EncodedCellData}; use flowy_grid_data_model::entities::{FieldMeta, FieldType}; #[test] - fn url_type_option_format_test() { + fn url_type_option_test_no_url() { let type_option = URLTypeOption::default(); let field_type = FieldType::URL; let field_meta = FieldBuilder::from_field_type(&field_type).build(); - assert_equal(&type_option, "123", "123", &field_type, &field_meta); + assert_changeset(&type_option, "123", &field_type, &field_meta, "123", ""); } - fn assert_equal( - type_option: &URLTypeOption, - cell_data: &str, - expected_str: &str, - field_type: &FieldType, - field_meta: &FieldMeta, - ) { - assert_eq!( - type_option - .decode_cell_data(cell_data, field_type, field_meta) - .unwrap() - .content, - expected_str.to_owned() + #[test] + fn url_type_option_test_contains_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_meta = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset( + &type_option, + "AppFlowy website - https://www.appflowy.io", + &field_type, + &field_meta, + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io", + ); + + assert_changeset( + &type_option, + "AppFlowy website appflowy.io", + &field_type, + &field_meta, + "AppFlowy website appflowy.io", + "appflowy.io", ); } + + fn assert_changeset( + type_option: &URLTypeOption, + cell_data: &str, + field_type: &FieldType, + field_meta: &FieldMeta, + expected: &str, + expected_url: &str, + ) { + let encoded_data = type_option.apply_changeset(cell_data, None).unwrap(); + let decode_cell_data = decode_cell_data(encoded_data, type_option, field_meta, field_type); + assert_eq!(expected.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &URLTypeOption, + field_meta: &FieldMeta, + field_type: &FieldType, + ) -> URLCellData { + type_option + .decode_cell_data(encoded_data, field_type, field_meta) + .unwrap() + .parse::() + .unwrap() + } }