Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apple/Sources/Logseq/LGPlatformViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum AssetPresentationPolicy {
static func kind(assetType: String?, localPath: String?) -> AssetPresentationKind {
let normalizedType = (assetType ?? pathExtension(localPath)).lowercased()
if normalizedType.hasPrefix("image/")
|| ["jpg", "jpeg", "png", "gif", "heic", "webp"].contains(normalizedType) {
|| ["jpg", "jpeg", "png", "gif", "heic", "heif", "webp", "avif", "bmp", "wbmp"].contains(normalizedType) {
return .image
}
if normalizedType.hasPrefix("audio/")
Expand Down
19 changes: 14 additions & 5 deletions shared/src/logseq/core/result_view.ml
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,20 @@ let column_json ?(include_candidates = true) ?(include_values = true) db rows p
"filterValues", `List filter_values;
"valuesDeferred", `Bool (not include_values);
"choices", choices_json db p.choices]
let row_json ~compact_block db encode properties = function
let row_json ~compact_block ~include_asset db encode properties = function
| Query_block.Cells cells -> `Assoc ["cells", `List (List.map (fun cell -> `String cell) cells)]
| Query_block.Block block -> let id = eid db block.uuid in
let encoded = if compact_block then `Assoc [
"uuid", `String block.uuid; "title", `String block.title; "pageId", `String block.page_id;
"displayTitle", `String (Markup.plain_text (Markup.parse block.references block.tags block.title))]
let encoded = if compact_block then
let asset_fields =
if include_asset && block.is_asset then
let field key = Option.fold ~none:[] ~some:(fun value -> [key, `String value]) in
("isAsset", `Bool true) :: (
field "assetType" block.asset_type @ field "assetChecksum" block.asset_checksum
@ field "localPath" block.local_path)
else [] in
`Assoc (["uuid", `String block.uuid; "title", `String block.title; "pageId", `String block.page_id;
"displayTitle", `String (Markup.plain_text (Markup.parse block.references block.tags block.title))]
@ asset_fields)
else encode block in
`Assoc ["block", encoded; "properties", `List (List.map (fun p ->
let editor = if p.closed then
Expand Down Expand Up @@ -375,7 +383,8 @@ let render_rows_cached ?cache ~reuse_prepared ?(input = "") ?(include_values = t
"rowsDeferred", `Bool (window <> None); "input", `String input;
"columns", prepared.columns;
"properties", prepared.catalog;
"rows", `List (List.map (row_json ~compact_block:(typ <> "list") db encode_block prepared.properties) rows)])
"rows", `List (List.map (row_json ~compact_block:(typ <> "list") ~include_asset:(typ = "gallery")
db encode_block prepared.properties) rows)])
with error -> `Assoc (fields @ ["error", `String (match error with Invalid_argument message -> message | _ -> Printexc.to_string error);
"rows", `List []; "columns", `List []]) in
`Assoc ["ownerUuid", `String owner; "featureType", `String feature; "views", `List (List.map view_json views)]
Expand Down
41 changes: 33 additions & 8 deletions shared/src/logseq/view_query.ml
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,28 @@ let value_sheet owner feature view model_source send item property close =
payload owner feature view.id "editCell" (("blockUuid", `String (Option.value ~default:"" item.uuid)) :: fields)))))
model_source property close

let gallery_image (row : Model.outline_row) =
if not row.is_asset then None else
let asset_type = View_base.outliner_row_asset_type row in
let path = View_base.outliner_row_local_path row in
let format =
if asset_type <> "" then asset_type else
let extension = Filename.extension (if path = "" then row.row_title else path) in
if extension = "" then "" else String.sub extension 1 (String.length extension - 1) in
let normalized = String.lowercase_ascii format in
if String.starts_with ~prefix:"image/" normalized
|| List.mem normalized ["jpg"; "jpeg"; "png"; "gif"; "heic"; "heif"; "webp"; "avif"; "bmp"; "wbmp"]
then Some (fun context parent ->
let node = Lui_ui.extension context "outliner-block-content" in
attach context parent node;
List.iter (fun (key, value) -> Lui_ui.extension_property context node key (Lui_protocol.StringValue value))
["block-id", row.row_uuid; "title", row.row_title; "asset-type", format; "local-path", path;
"markup-json", "[]"; "youtube-target-url", ""];
Lui_ui.extension_property context node "is-asset" (Lui_protocol.BoolValue true);
Lui_ui.extension_property context node "is-completed" (Lui_protocol.BoolValue false);
node)
else None

let table_column_width column = if jfield "attr" column = `String "block/title" then 260 else 170

let table_column_window columns (offset, width) =
Expand Down Expand Up @@ -932,14 +954,17 @@ let collection_json ?(fill_height = false) ~render_block model_source parsed sen
(Signal.map2 (fun view item -> view, item) view_source item_source) column_window))) row_context parent in
let gallery_card item_source : t =
fun card_context parent ->
(dyn (fun (view, item) -> column ~gap:10 ~padding:16 ~background:"secondary" ~border_color:"border" ~border_width:1 ~corner_radius:12
~accessibility_identifier:("view.card." ^ view.id ^ "." ^ Option.value ~default:"" item.uuid) [
button ~text:item.title ~text_alignment:`start ~variant:`ghost ~min_height:44
~on_press:(fun _ -> Option.iter (fun uuid -> ignore (send (View_base.request_node_action (Signal.sample model_source) uuid))) item.uuid) [];
column ~gap:8 (choices view |> List.filter (fun col -> jfield "attr" col <> `String "block/title")
|> List.map (fun col -> let property = cell view item (jtext (jfield "attr" col)) in
row ~gap:12 ~main:`space_between [text ~value:(property_title (Signal.sample model_source) col) ~foreground:"muted-foreground" ~style_class:"caption" [];
text ~value:(jtext (jfield "text" property)) ~style_class:"caption" []]))])
(dyn (fun (view, item) ->
let image = Option.bind item.block (fun block -> gallery_image block.row) in
column ~gap:10 ~padding:16 ~background:"secondary" ~border_color:"border" ~border_width:1 ~corner_radius:12
~accessibility_identifier:("view.card." ^ view.id ^ "." ^ Option.value ~default:"" item.uuid)
(Option.to_list image @ [
button ~text:item.title ~text_alignment:`start ~variant:`ghost ~min_height:44
~on_press:(fun _ -> Option.iter (fun uuid -> ignore (send (View_base.request_node_action (Signal.sample model_source) uuid))) item.uuid) [];
column ~gap:8 (choices view |> List.filter (fun col -> jfield "attr" col <> `String "block/title")
|> List.map (fun col -> let property = cell view item (jtext (jfield "attr" col)) in
row ~gap:12 ~main:`space_between [text ~value:(property_title (Signal.sample model_source) col) ~foreground:"muted-foreground" ~style_class:"caption" [];
text ~value:(jtext (jfield "text" property)) ~style_class:"caption" []]))]))
(Signal.own_signal card_context.Lui_ui.ui_scope (Signal.map2 (fun view item -> view, item) view_source item_source))) card_context parent in
let item_key item = match item.uuid with
| Some uuid -> "block:" ^ uuid | None -> "tuple:" ^ string_of_int item.index in
Expand Down
41 changes: 41 additions & 0 deletions shared/test/logseq/result_view_test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,46 @@ let native_view_tabs_search_table_and_gallery () =
check (find ("outliner.block." ^ alpha) <> -1))))
[App_test.ios_backend; App_test.kotlin_backend]

let gallery_cards_render_image_assets () =
let db = Ds.db_with [
put alpha "block/title" (Ds.String "Photo.jpg");
put alpha "block/tags" (Ds.Ref_to (Ds.Ident "logseq.class/Asset"));
put alpha "logseq.property.asset/type" (Ds.String "jpeg");
put alpha "logseq.property.asset/checksum" (Ds.String "photo-checksum");
put beta "block/title" (Ds.String "Document.pdf");
put beta "block/tags" (Ds.Ref_to (ref_uuid Q.project));
put beta "block/tags" (Ds.Ref_to (Ds.Ident "logseq.class/Asset"));
put beta "logseq.property.asset/type" (Ds.String "pdf");
put gallery_view "logseq.property/view-for" (Ds.Ref_to (ref_uuid Q.project));
put gallery_view "logseq.property.view/feature-type" (Ds.Keyword "class-objects");
] (fixture ()) in
let session, _ = make_session ~configured:true db in
let path = "Assets/Photo.jpg" in
Cache_model.cache_local_asset (Session.state session).model alpha "Photo.jpg" "jpeg" 123
"photo-checksum" path 1 None;
let response = Rpc_test.dispatch_json session "openNode" (Json.to_string (`Assoc ["uuid", `String Q.project])) in
check_eq (member "ok" response) (`Bool true);
List.iter (fun backend ->
Q.with_ui backend (Json.to_string response) (fun app ->
App_test.send app (Model.RequestAppNode Q.project);
let projection = match Response_snapshot.decode_json_response response with
| Ok value -> value | Error message -> fail message in
App_test.send app (Model.ApplyCoreSnapshot projection); App_test.flush app;
let find = find_visible session app in
let runtime = Lui_app.runtime app in
let card = find ("view.card." ^ gallery_view ^ "." ^ alpha) in
check ~msg:"the tagged photo is already a Gallery result" (card <> -1);
let preview = App_test.descendant_with_extension runtime app card "outliner-block-content" in
check ~msg:"the Gallery card mounts the native photo preview" (preview <> -1);
check_eq (App_test.extension_property app preview "asset-type")
(Some (Lui_protocol.StringValue "jpeg"));
check_eq (App_test.extension_property app preview "local-path")
(Some (Lui_protocol.StringValue path));
Comment on lines +532 to +537

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Preview test skips image decoding

The fixture never creates the JPEG. The host assertions verify extension properties, not a decoded image or the tap-to-open flow.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

let pdf = find ("view.card." ^ gallery_view ^ "." ^ beta) in
check ~msg:"the PDF remains a result" (pdf <> -1);
check_eq (App_test.descendant_with_extension runtime app pdf "outliner-block-content") (-1)))
[App_test.ios_backend; App_test.kotlin_backend]

let native_view_settings_and_local_errors () =
List.iter (fun backend -> with_runtime (fun session _ _ ->
Q.with_ui backend (Session.snapshot_visible session) (fun app ->
Expand Down Expand Up @@ -1571,6 +1611,7 @@ let cases = [
case "saved filters preserve nested UUID sets and nullable bounds" settings_preserve_nested_match_values;
case "many numeric and scalar checkbox and text edits preserve types" many_and_scalar_cells_preserve_types;
case "table edits assign owner values with validation and conflict checks" editable_cells_use_owner_values_and_conflicts;
case "Gallery cards show tagged photos" gallery_cards_render_image_assets;
case "both native hosts expose tabs search table editing and gallery" native_view_tabs_search_table_and_gallery;
case "native settings expose filters sorting and local action errors" native_view_settings_and_local_errors;
case "native sorting survives Done and subsequent core refreshes" native_sort_settings_survive_save_and_refresh;
Expand Down
Loading