Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7309fc1
feat (backend): pagination in list resource ids by tags apis
aavshr Nov 13, 2025
f093af1
chore: use list resources by tags paginated APIs in sffs and resource…
aavshr Nov 13, 2025
4fa894e
chore: use pagination load APIs in SurfLoader component
aavshr Nov 14, 2025
40cdc01
feat: search input in notebook contents view
aavshr Nov 10, 2025
cd14cdd
feat: category views in notebook contents
aavshr Nov 10, 2025
7913906
chore: refactor for loading and no results snippet and only show add …
aavshr Nov 10, 2025
aaf4f0b
chore: responsive notebook main layout and content max heights
aavshr Nov 12, 2025
df47fc0
chore: also allow library to be collapsed
aavshr Nov 12, 2025
823529c
chore: move Drafts notebook init to notebooks list and have no result…
aavshr Nov 12, 2025
262fac1
chore: remove collapsable library state from notebook contents view
aavshr Nov 13, 2025
54b86f1
chore: use scroll to load behavior with pagination in NotebookContent…
aavshr Nov 14, 2025
bf91eb7
chore: deprecate include annotations param in search resources API
aavshr Nov 14, 2025
6822530
chore: remove unneeded onMount hook on NotebookContents view
aavshr Nov 15, 2025
6fa3eb7
fix (backend): use updated_at along with id for pagination
aavshr Nov 17, 2025
522a687
fix (backend): fix pagination query for list resource ids by tags
aavshr Nov 17, 2025
83f873b
chore: refactor and use single API and loader component for listing r…
aavshr Nov 18, 2025
4c48919
fix: fix context menu check for notebook cover & add prop for showing…
aavshr Nov 18, 2025
16cca46
chore: remove animations in NotebookContents view
aavshr Nov 18, 2025
225bad5
fix: readd removed NotebookLoader file but use SurfLoader in notebook…
aavshr Nov 18, 2025
36fe1ea
chore (backend): also use space id filter in other resource id list a…
aavshr Nov 18, 2025
e0cae2c
fix: use loading snippet for search as well as do not filter on resou…
aavshr Nov 18, 2025
3f06c3e
chore: use differet header for notebook view and root view for notebo…
aavshr Nov 18, 2025
77d052c
chore (backend): fix latest stable clippy warnings
aavshr Nov 18, 2025
1b4dd2a
chore: remove resources provider as teleype provider
aavshr Nov 18, 2025
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
1,051 changes: 526 additions & 525 deletions app/src/renderer/Resource/components/notebook/NotebookContents.svelte

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
type OpenTarget,
SpaceEntryOrigin
} from '@deta/types'
import { NotebookLoader, SurfLoader, SourceCard } from '@deta/ui'
import { SurfLoader, SourceCard } from '@deta/ui'
import { type Notebook } from '@deta/services/notebooks'
import { type Resource, getResourceCtxItems } from '@deta/services/resources'
import {
Expand Down Expand Up @@ -252,7 +252,7 @@

<MaskedScroll --padding={'0.5rem 0.5rem 0rem 0.5rem'}>
<SurfLoader
excludeWithinSpaces
{notebookId}
tags={[SearchResourceTags.ResourceType(ResourceTypes.DOCUMENT_SPACE_NOTE, 'eq')]}
search={{
query,
Expand Down Expand Up @@ -280,7 +280,7 @@
</SurfLoader>

<SurfLoader
excludeWithinSpaces
{notebookId}
tags={[SearchResourceTags.ResourceType(ResourceTypes.DOCUMENT_SPACE_NOTE, 'ne')]}
search={{
query,
Expand Down Expand Up @@ -457,6 +457,7 @@
{/if}

<SurfLoader
{notebookId}
tags={[SearchResourceTags.ResourceType(ResourceTypes.DOCUMENT_SPACE_NOTE, 'eq')]}
search={{
query,
Expand Down Expand Up @@ -487,6 +488,7 @@
</SurfLoader>

<SurfLoader
{notebookId}
tags={[SearchResourceTags.ResourceType(ResourceTypes.DOCUMENT_SPACE_NOTE, 'ne')]}
search={{
query,
Expand Down Expand Up @@ -540,15 +542,14 @@
</SurfLoader>
</MaskedScroll>
{:else}
<NotebookLoader
<SurfLoader
{notebookId}
search={{
query,
parameters: {
semanticSearch: false
}
}}
fetchContents
>
{#snippet children([notebook, searchResult, searching])}
<header class="px pt">
Expand Down Expand Up @@ -661,7 +662,7 @@
{/if}
</MaskedScroll>
{/snippet}
</NotebookLoader>
</SurfLoader>
{/if}
</aside>

Expand Down
51 changes: 21 additions & 30 deletions app/src/renderer/Resource/layouts/NotebookLayout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,35 @@
transition: transform 123ms ease-out;
}
}

.notebook {
position: relative;
overflow-x: hidden;

//.bg {
// content: '';
// position: absolute;
// inset: -4px;
// background:
// linear-gradient(rgba(255, 255, 255, 0.65), rgba(255, 255, 255, 1)),
// url('https://i.imgur.com/7XbyivJ.png');
// background-repeat: no-repeat;
// background-size: cover;
// background-position: 50% 30%;
// //background: #fff;
// z-index: -1;
// filter: blur(2px);
// pointer-events: none;
//}

height: 100vh;
width: 100%;
display: flex;
flex-direction: column;

padding-inline: 1.5rem;
padding-block: 4.5rem;
// default for laptops (13-15")
padding-inline: 2rem;
padding-block: 2rem;

// larger laptops and small external monitors (15-24")
@media screen and (min-width: 1440px) {
padding-inline: 3rem;
padding-block: 3rem;
}

// large external monitors (27"+)
@media screen and (min-width: 1920px) {
padding-inline: 4rem;
padding-block: 4rem;
}

//&::before {
// content: '';
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// height: 2rem;
// z-index: 0;
// pointer-events: none;
// background: linear-gradient(to bottom, var(--page-gradient-color), transparent);
//}
// ultra-wide or very large displays
@media screen and (min-width: 2560px) {
padding-inline: 5rem;
padding-block: 5rem;
}
}
</style>
5 changes: 3 additions & 2 deletions packages/backend/src/api/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ pub enum ResourceMessage {
RemoveResources(Vec<String>),
RemoveResourcesByTags(Vec<ResourceTagFilter>),
RecoverResource(String),
ListResourcesByTags(Vec<ResourceTagFilter>),
ListResourcesByTagsNoSpace(Vec<ResourceTagFilter>),
// Last Param is space filter where
// None = no space filter, Some("") = no space, Some(id) = specific space
ListResourcesByTags(Vec<ResourceTagFilter>, PaginationParams, Option<String>),
ListAllResourcesAndSpaces(Vec<ResourceTagFilter>),
SearchResources(SearchResourcesParams),
UpdateResource(Resource),
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,23 @@ pub fn register_exported_functions(cx: &mut ModuleContext) -> NeonResult<()> {
kv::register_exported_functions(cx)?;
Ok(())
}

pub fn parse_json_argument<T: serde::de::DeserializeOwned>(
cx: &mut FunctionContext,
arg_index: usize,
arg_name: &str,
) -> Result<T, String> {
let json_string = cx
.argument_opt(arg_index)
.and_then(|arg| arg.downcast::<JsString, FunctionContext>(cx).ok())
.map(|js_string| js_string.value(cx));

match json_string
.map(|json_str| serde_json::from_str(&json_str))
.transpose()
{
Ok(Some(value)) => Ok(value),
Ok(None) => Err(format!("{} must be provided", arg_name)),
Err(err) => Err(err.to_string()),
}
}
95 changes: 36 additions & 59 deletions packages/backend/src/api/store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::store::models::SearchResourcesParams;
use crate::{api::message::*, store::models, worker::tunnel::WorkerTunnel};
use super::{message::*, parse_json_argument};
use crate::{store::models, worker::tunnel::WorkerTunnel};
use neon::prelude::*;
use neon::types::JsDate;

Expand All @@ -18,10 +18,6 @@ pub fn register_exported_functions(cx: &mut ModuleContext) -> NeonResult<()> {
"js__store_list_resources_by_tags",
js_list_resources_by_tags,
)?;
cx.export_function(
"js__store_list_resources_by_tags_no_space",
js_list_resources_by_tags_no_space,
)?;
cx.export_function(
"js__store_list_all_resources_and_spaces",
js_list_all_resources_and_spaces,
Expand Down Expand Up @@ -469,47 +465,32 @@ fn js_recover_resource(mut cx: FunctionContext) -> JsResult<JsPromise> {
fn js_list_resources_by_tags(mut cx: FunctionContext) -> JsResult<JsPromise> {
let tunnel = cx.argument::<JsBox<WorkerTunnel>>(0)?;

let resource_tags_json = cx
.argument_opt(1)
.and_then(|arg| arg.downcast::<JsString, FunctionContext>(&mut cx).ok())
.map(|js_string| js_string.value(&mut cx));
let resource_tags: Vec<models::ResourceTagFilter> = match resource_tags_json
.map(|json_str| serde_json::from_str(&json_str))
.transpose()
{
Ok(Some(tags)) => tags,
Ok(None) => return cx.throw_error("Resource tags must be provided"),
Err(err) => return cx.throw_error(err.to_string()),
};

let (deferred, promise) = cx.promise();
tunnel.worker_send_js(
WorkerMessage::ResourceMessage(ResourceMessage::ListResourcesByTags(resource_tags)),
deferred,
);
let resource_tags: Vec<models::ResourceTagFilter> =
match parse_json_argument(&mut cx, 1, "Resource tags") {
Ok(tags) => tags,
Err(err) => return cx.throw_error(err),
};

Ok(promise)
}

fn js_list_resources_by_tags_no_space(mut cx: FunctionContext) -> JsResult<JsPromise> {
let tunnel = cx.argument::<JsBox<WorkerTunnel>>(0)?;
let pagination_params: models::PaginationParams =
match parse_json_argument(&mut cx, 2, "Pagination parameters") {
Ok(params) => params,
Err(err) => return cx.throw_error(err),
};

let resource_tags_json = cx
.argument_opt(1)
.and_then(|arg| arg.downcast::<JsString, FunctionContext>(&mut cx).ok())
.map(|js_string| js_string.value(&mut cx));
let resource_tags: Vec<models::ResourceTagFilter> = match resource_tags_json
.map(|json_str| serde_json::from_str(&json_str))
.transpose()
{
Ok(Some(tags)) => tags,
Ok(None) => return cx.throw_error("Resource tags must be provided"),
Err(err) => return cx.throw_error(err.to_string()),
};
// None = no space filter, Some("") = no space, Some(id) = specific space
let space_id = cx.argument_opt(3).and_then(|arg| {
arg.downcast::<JsString, FunctionContext>(&mut cx)
.ok()
.map(|js_string| js_string.value(&mut cx))
});

let (deferred, promise) = cx.promise();
tunnel.worker_send_js(
WorkerMessage::ResourceMessage(ResourceMessage::ListResourcesByTagsNoSpace(resource_tags)),
WorkerMessage::ResourceMessage(ResourceMessage::ListResourcesByTags(
resource_tags,
pagination_params,
space_id,
)),
deferred,
);

Expand Down Expand Up @@ -572,35 +553,31 @@ fn js_search_resources(mut cx: FunctionContext) -> JsResult<JsPromise> {
.ok()
.map(|js_number| js_number.value(&mut cx) as i64)
});
let include_annotations = cx.argument_opt(6).and_then(|arg| {
arg.downcast::<JsBoolean, FunctionContext>(&mut cx)
.ok()
.map(|js_boolean| js_boolean.value(&mut cx))
});
let space_id = cx.argument_opt(7).and_then(|arg| {
let space_id = cx.argument_opt(6).and_then(|arg| {
arg.downcast::<JsString, FunctionContext>(&mut cx)
.ok()
.map(|js_string| js_string.value(&mut cx))
});

let keyword_limit = cx.argument_opt(8).and_then(|arg| {
let keyword_limit = cx.argument_opt(7).and_then(|arg| {
arg.downcast::<JsNumber, FunctionContext>(&mut cx)
.ok()
.map(|js_number| js_number.value(&mut cx) as i64)
});

let (deferred, promise) = cx.promise();
tunnel.worker_send_js(
WorkerMessage::ResourceMessage(ResourceMessage::SearchResources(SearchResourcesParams {
query,
resource_tag_filters,
semantic_search_enabled,
embeddings_distance_threshold,
embeddings_limit,
include_annotations,
space_id,
keyword_limit,
})),
WorkerMessage::ResourceMessage(ResourceMessage::SearchResources(
models::SearchResourcesParams {
query,
resource_tag_filters,
semantic_search_enabled,
embeddings_distance_threshold,
embeddings_limit,
space_id,
keyword_limit,
},
)),
deferred,
);

Expand Down
42 changes: 36 additions & 6 deletions packages/backend/src/store/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::str::FromStr;
use std::string::ToString;
use strum_macros::EnumString;

use crate::{BackendError, BackendResult};

pub fn default_horizon_tint() -> String {
"hsl(275, 40%, 80%)".to_owned()
}
Expand Down Expand Up @@ -533,7 +535,9 @@ pub struct PostProcessingJob {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
#[derive(Default)]
pub enum ResourceProcessingState {
#[default]
Pending,
Started,
Failed { message: String },
Expand All @@ -556,11 +560,6 @@ impl FromSql for ResourceProcessingState {
}
}

impl Default for ResourceProcessingState {
fn default() -> Self {
Self::Pending
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LegacyResourceTextContent {
Expand Down Expand Up @@ -951,7 +950,6 @@ pub struct SearchResourcesParams {
pub semantic_search_enabled: Option<bool>,
pub embeddings_distance_threshold: Option<f32>,
pub embeddings_limit: Option<i64>,
pub include_annotations: Option<bool>,
pub space_id: Option<String>,
pub keyword_limit: Option<i64>,
}
Expand Down Expand Up @@ -1000,3 +998,35 @@ pub struct App {
pub icon: Option<String>,
pub meta: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PaginationParams {
pub limit: usize,
pub cursor: Option<String>,
}

pub struct PaginationCursor {}

// TODO: allowing different cursor formats?
impl PaginationCursor {
pub fn encode_date_id(datetime: &str, id: &str) -> String {
format!("{}|{}", datetime, id)
}

pub fn decode_date_id(cursor: &str) -> BackendResult<(String, String)> {
let parts: Vec<&str> = cursor.split('|').collect();
if parts.len() != 2 {
return Err(BackendError::GenericError(
"Invalid cursor format".to_string(),
));
}
Ok((parts[0].to_string(), parts[1].to_string()))
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PaginatedResult<T> {
pub items: Vec<T>,
pub next_cursor: Option<String>,
pub has_more: bool,
}
Loading
Loading