chore: return URLCellData & parse url from cell content

This commit is contained in:
appflowy 2022-05-29 10:56:34 +08:00
parent 9844c02cbc
commit ae0f71b5ee
9 changed files with 124 additions and 52 deletions

View File

@ -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/cell_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.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/selection_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';

View File

@ -3,7 +3,7 @@ part of 'cell_service.dart';
typedef GridCellContext = _GridCellContext<String, String>; typedef GridCellContext = _GridCellContext<String, String>;
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>; typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>; typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
typedef GridURLCellContext = _GridCellContext<Cell, String>; typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
class GridCellContextBuilder { class GridCellContextBuilder {
final GridCellCache _cellCache; final GridCellCache _cellCache;

View File

@ -58,7 +58,11 @@ class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
return fut.then( return fut.then(
(result) => result.fold((Cell cell) { (result) => result.fold((Cell cell) {
try { try {
return parser.parserData(cell.data); if (cell.data.isEmpty) {
return null;
} else {
return parser.parserData(cell.data);
}
} catch (e, s) { } catch (e, s) {
Log.error('$parser parser cellData failed, $e'); Log.error('$parser parser cellData failed, $e');
Log.error('Stack trace \n $s'); Log.error('Stack trace \n $s');
@ -105,9 +109,6 @@ class StringCellDataParser implements ICellDataParser<String> {
class DateCellDataParser implements ICellDataParser<DateCellData> { class DateCellDataParser implements ICellDataParser<DateCellData> {
@override @override
DateCellData? parserData(List<int> data) { DateCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return DateCellData.fromBuffer(data); return DateCellData.fromBuffer(data);
} }
} }
@ -115,19 +116,13 @@ class DateCellDataParser implements ICellDataParser<DateCellData> {
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> { class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
@override @override
SelectOptionCellData? parserData(List<int> data) { SelectOptionCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return SelectOptionCellData.fromBuffer(data); return SelectOptionCellData.fromBuffer(data);
} }
} }
class URLCellDataParser implements ICellDataParser<Cell> { class URLCellDataParser implements ICellDataParser<URLCellData> {
@override @override
Cell? parserData(List<int> data) { URLCellData? parserData(List<int> data) {
if (data.isEmpty) { return URLCellData.fromBuffer(data);
return null;
}
return Cell.fromBuffer(data);
} }
} }

View File

@ -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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -23,7 +23,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
emit(state.copyWith(content: text)); emit(state.copyWith(content: text));
}, },
didReceiveCellUpdate: (cellData) { 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<URLCellEvent, URLCellState> {
@freezed @freezed
class URLCellEvent with _$URLCellEvent { class URLCellEvent with _$URLCellEvent {
const factory URLCellEvent.initial() = _InitialCell; 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; const factory URLCellEvent.updateText(String text) = _UpdateText;
} }
@ -67,6 +67,9 @@ class URLCellState with _$URLCellState {
factory URLCellState.initial(GridURLCellContext context) { factory URLCellState.initial(GridURLCellContext context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return URLCellState(content: cellData?.content ?? "", url: ""); return URLCellState(
content: cellData?.content ?? "",
url: cellData?.url ?? "",
);
} }
} }

View File

@ -928,6 +928,7 @@ dependencies = [
"dart-notify", "dart-notify",
"dashmap", "dashmap",
"diesel", "diesel",
"fancy-regex",
"flowy-database", "flowy-database",
"flowy-derive", "flowy-derive",
"flowy-error", "flowy-error",
@ -953,7 +954,6 @@ dependencies = [
"strum_macros", "strum_macros",
"tokio", "tokio",
"tracing", "tracing",
"url",
] ]
[[package]] [[package]]

View File

@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = {version = "1.0"} serde_json = {version = "1.0"}
serde_repr = "0.1" serde_repr = "0.1"
indexmap = {version = "1.8.1", features = ["serde"]} indexmap = {version = "1.8.1", features = ["serde"]}
url = { version = "2"} fancy-regex = "0.10.0"
[dev-dependencies] [dev-dependencies]
flowy-test = { path = "../flowy-test" } flowy-test = { path = "../flowy-test" }

View File

@ -627,7 +627,7 @@ mod tests {
field_meta: &FieldMeta, field_meta: &FieldMeta,
) -> String { ) -> String {
type_option type_option
.decode_cell_data(encoded_data, &FieldType::DateTime, &field_meta) .decode_cell_data(encoded_data, &FieldType::DateTime, field_meta)
.unwrap() .unwrap()
.parse::<DateCellData>() .parse::<DateCellData>()
.unwrap() .unwrap()

View File

@ -537,7 +537,7 @@ mod tests {
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); 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 // Invalid option id
let cell_data = type_option let cell_data = type_option
@ -580,12 +580,12 @@ mod tests {
cell_data, cell_data,
&type_option, &type_option,
&field_meta, &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 data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
let cell_data = type_option.apply_changeset(data, None).unwrap(); 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 // Invalid option id
let cell_data = type_option let cell_data = type_option
@ -612,7 +612,7 @@ mod tests {
assert_eq!( assert_eq!(
expected, expected,
type_option 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() .unwrap()
.parse::<SelectOptionCellData>() .parse::<SelectOptionCellData>()
.unwrap() .unwrap()
@ -629,7 +629,7 @@ mod tests {
assert_eq!( assert_eq!(
expected, expected,
type_option 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() .unwrap()
.parse::<SelectOptionCellData>() .parse::<SelectOptionCellData>()
.unwrap() .unwrap()

View File

@ -1,14 +1,16 @@
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; 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 bytes::Bytes;
use fancy_regex::Regex;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{internal_error, FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
}; };
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Default)] #[derive(Default)]
pub struct URLTypeOptionBuilder(URLTypeOption); pub struct URLTypeOptionBuilder(URLTypeOption);
@ -32,7 +34,7 @@ pub struct URLTypeOption {
} }
impl_type_option!(URLTypeOption, FieldType::URL); impl_type_option!(URLTypeOption, FieldType::URL);
impl CellDataOperation<String, String> for URLTypeOption { impl CellDataOperation<EncodedCellData<URLCellData>, String> for URLTypeOption {
fn decode_cell_data<T>( fn decode_cell_data<T>(
&self, &self,
encoded_data: T, encoded_data: T,
@ -40,14 +42,13 @@ impl CellDataOperation<String, String> for URLTypeOption {
_field_meta: &FieldMeta, _field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData> ) -> FlowyResult<DecodedCellData>
where where
T: Into<String>, T: Into<EncodedCellData<URLCellData>>,
{ {
if !decoded_field_type.is_url() { if !decoded_field_type.is_url() {
return Ok(DecodedCellData::default()); return Ok(DecodedCellData::default());
} }
let cell_data = encoded_data.into().try_into_inner()?;
let cell_data = encoded_data.into(); DecodedCellData::try_from_bytes(cell_data)
Ok(DecodedCellData::from_content(cell_data))
} }
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError> fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
@ -55,7 +56,16 @@ impl CellDataOperation<String, String> for URLTypeOption {
C: Into<CellContentChangeset>, C: Into<CellContentChangeset>,
{ {
let changeset = changeset.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, pub content: String,
} }
impl URLCellData {
pub fn new(s: &str) -> Self {
Self {
url: "".to_string(),
content: s.to_string(),
}
}
fn to_json(&self) -> FlowyResult<String> {
serde_json::to_string(self).map_err(internal_error)
}
}
impl FromStr for URLCellData {
type Err = FlowyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str::<URLCellData>(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)] #[cfg(test)]
mod tests { mod tests {
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::field::URLTypeOption; use crate::services::field::{URLCellData, URLTypeOption};
use crate::services::row::CellDataOperation; use crate::services::row::{CellDataOperation, EncodedCellData};
use flowy_grid_data_model::entities::{FieldMeta, FieldType}; use flowy_grid_data_model::entities::{FieldMeta, FieldType};
#[test] #[test]
fn url_type_option_format_test() { fn url_type_option_test_no_url() {
let type_option = URLTypeOption::default(); let type_option = URLTypeOption::default();
let field_type = FieldType::URL; let field_type = FieldType::URL;
let field_meta = FieldBuilder::from_field_type(&field_type).build(); 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( #[test]
type_option: &URLTypeOption, fn url_type_option_test_contains_url() {
cell_data: &str, let type_option = URLTypeOption::default();
expected_str: &str, let field_type = FieldType::URL;
field_type: &FieldType, let field_meta = FieldBuilder::from_field_type(&field_type).build();
field_meta: &FieldMeta, assert_changeset(
) { &type_option,
assert_eq!( "AppFlowy website - https://www.appflowy.io",
type_option &field_type,
.decode_cell_data(cell_data, field_type, field_meta) &field_meta,
.unwrap() "AppFlowy website - https://www.appflowy.io",
.content, "https://www.appflowy.io",
expected_str.to_owned() );
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<T: Into<EncodedCellData<URLCellData>>>(
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::<URLCellData>()
.unwrap()
}
} }