浏览代码

chore: return URLCellData & parse url from cell content

appflowy 2 年之前
父节点
当前提交
ae0f71b5ee

+ 1 - 0
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';

+ 1 - 1
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<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;

+ 8 - 13
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart

@@ -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);
   }
 }

+ 7 - 4
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<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 ?? "",
+    );
   }
 }

+ 1 - 1
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]]

+ 1 - 1
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" }

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

+ 5 - 5
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::<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()

+ 95 - 22
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<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(
+    #[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,
-        expected_str: &str,
         field_type: &FieldType,
         field_meta: &FieldMeta,
+        expected: &str,
+        expected_url: &str,
     ) {
-        assert_eq!(
-            type_option
-                .decode_cell_data(cell_data, field_type, field_meta)
-                .unwrap()
-                .content,
-            expected_str.to_owned()
-        );
+        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()
     }
 }