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/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';

View File

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

View File

@ -58,7 +58,11 @@ class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
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<String> {
class DateCellDataParser implements ICellDataParser<DateCellData> {
@override
DateCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return DateCellData.fromBuffer(data);
}
}
@ -115,19 +116,13 @@ class DateCellDataParser implements ICellDataParser<DateCellData> {
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
@override
SelectOptionCellData? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return SelectOptionCellData.fromBuffer(data);
}
}
class URLCellDataParser implements ICellDataParser<Cell> {
class URLCellDataParser implements ICellDataParser<URLCellData> {
@override
Cell? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return Cell.fromBuffer(data);
URLCellData? parserData(List<int> data) {
return URLCellData.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:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
@ -23,7 +23,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
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<URLCellEvent, URLCellState> {
@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 ?? "",
);
}
}

View File

@ -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]]

View File

@ -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" }

View File

@ -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::<DateCellData>()
.unwrap()

View File

@ -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::<SelectOptionCellData>()
.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::<SelectOptionCellData>()
.unwrap()

View File

@ -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<String, String> for URLTypeOption {
impl CellDataOperation<EncodedCellData<URLCellData>, String> for URLTypeOption {
fn decode_cell_data<T>(
&self,
encoded_data: T,
@ -40,14 +42,13 @@ impl CellDataOperation<String, String> for URLTypeOption {
_field_meta: &FieldMeta,
) -> FlowyResult<DecodedCellData>
where
T: Into<String>,
T: Into<EncodedCellData<URLCellData>>,
{
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<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
@ -55,7 +56,16 @@ impl CellDataOperation<String, String> for URLTypeOption {
C: Into<CellContentChangeset>,
{
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<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)]
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<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()
}
}