mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 15:16:28 +08:00
287 lines
8.3 KiB
Rust
287 lines
8.3 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use y_octo::{Any, Map, Value};
|
|
|
|
use super::{text_content, text_content_for_summary};
|
|
use crate::doc_parser::{
|
|
blocksuite::{DocContext, collect_child_ids, get_string},
|
|
markdown::{
|
|
DeltaToMdOptions, InlineReferencePayload, delta_value_to_inline_markdown, extract_inline_references_from_value,
|
|
text_to_inline_markdown,
|
|
},
|
|
table::{MarkdownTableOptions, render_markdown_table},
|
|
value::{any_as_string, value_to_string},
|
|
};
|
|
|
|
pub(super) struct DatabaseTable {
|
|
pub(super) columns: Vec<DatabaseColumn>,
|
|
pub(super) rows: Vec<Vec<String>>,
|
|
}
|
|
|
|
pub(super) struct DatabaseOption {
|
|
id: Option<String>,
|
|
value: Option<String>,
|
|
color: Option<String>,
|
|
}
|
|
|
|
pub(super) struct DatabaseColumn {
|
|
pub(super) id: String,
|
|
pub(super) name: Option<String>,
|
|
pub(super) col_type: String,
|
|
pub(super) options: Vec<DatabaseOption>,
|
|
}
|
|
|
|
pub(super) fn build_database_table(
|
|
block: &Map,
|
|
context: &DocContext,
|
|
md_options: &DeltaToMdOptions,
|
|
) -> Option<DatabaseTable> {
|
|
let columns = parse_database_columns(block)?;
|
|
let cells_map = block.get("prop:cells").and_then(|v| v.to_map())?;
|
|
let child_ids = collect_child_ids(block);
|
|
|
|
let mut rows = Vec::new();
|
|
for child_id in child_ids {
|
|
let row_cells = cells_map.get(&child_id).and_then(|v| v.to_map());
|
|
let mut row = Vec::new();
|
|
|
|
for column in columns.iter() {
|
|
let mut cell_text = String::new();
|
|
if column.col_type == "title" {
|
|
if let Some(child_block) = context.block_pool.get(&child_id) {
|
|
if let Some(text_md) = text_to_inline_markdown(child_block, "prop:text", md_options) {
|
|
cell_text = text_md;
|
|
} else if let Some((text, _)) = text_content(child_block, "prop:text") {
|
|
cell_text = text;
|
|
} else if let Some((text, _)) = text_content_for_summary(child_block, "prop:text") {
|
|
cell_text = text;
|
|
}
|
|
}
|
|
} else if let Some(row_cells) = &row_cells
|
|
&& let Some(cell_val) = row_cells.get(&column.id).and_then(|v| v.to_map())
|
|
&& let Some(value) = cell_val.get("value")
|
|
{
|
|
if let Some(text_md) = delta_value_to_inline_markdown(&value, md_options) {
|
|
cell_text = text_md;
|
|
} else {
|
|
cell_text = format_cell_value(&value, column);
|
|
}
|
|
}
|
|
|
|
row.push(cell_text);
|
|
}
|
|
if row.iter().any(|cell| !cell.is_empty()) {
|
|
rows.push(row);
|
|
}
|
|
}
|
|
|
|
Some(DatabaseTable { columns, rows })
|
|
}
|
|
|
|
pub(super) fn database_table_markdown(table: DatabaseTable) -> Option<String> {
|
|
let mut rows = Vec::with_capacity(table.rows.len() + 1);
|
|
let header = table
|
|
.columns
|
|
.iter()
|
|
.map(|column| column.name.as_deref().unwrap_or_default().to_string())
|
|
.collect::<Vec<_>>();
|
|
rows.push(header);
|
|
rows.extend(table.rows);
|
|
|
|
let options = MarkdownTableOptions::new(false, "<br />", true);
|
|
render_markdown_table(&rows, options)
|
|
}
|
|
|
|
pub(super) fn database_summary_text(block: &Map, context: &DocContext) -> Option<String> {
|
|
let md_options = DeltaToMdOptions::new(None);
|
|
let table = build_database_table(block, context, &md_options)?;
|
|
let mut summary = String::new();
|
|
|
|
if let Some(title) = get_string(block, "prop:title")
|
|
&& !title.is_empty()
|
|
{
|
|
summary.push_str(&title);
|
|
summary.push('|');
|
|
}
|
|
|
|
for column in table.columns.iter() {
|
|
if let Some(name) = column.name.as_ref()
|
|
&& !name.is_empty()
|
|
{
|
|
summary.push_str(name);
|
|
summary.push('|');
|
|
}
|
|
for option in column.options.iter() {
|
|
if let Some(value) = option.value.as_ref()
|
|
&& !value.is_empty()
|
|
{
|
|
summary.push_str(value);
|
|
summary.push('|');
|
|
}
|
|
}
|
|
}
|
|
|
|
for row in table.rows.iter() {
|
|
for cell_text in row.iter() {
|
|
if !cell_text.is_empty() {
|
|
summary.push_str(cell_text);
|
|
summary.push('|');
|
|
}
|
|
}
|
|
}
|
|
|
|
if summary.is_empty() { None } else { Some(summary) }
|
|
}
|
|
|
|
pub(super) fn gather_database_texts(block: &Map) -> (Vec<String>, Option<String>) {
|
|
let mut texts = Vec::new();
|
|
let database_title = get_string(block, "prop:title");
|
|
if let Some(title) = &database_title {
|
|
texts.push(title.clone());
|
|
}
|
|
|
|
if let Some(columns) = parse_database_columns(block) {
|
|
for column in columns.iter() {
|
|
if let Some(name) = column.name.as_ref() {
|
|
texts.push(name.clone());
|
|
}
|
|
for option in column.options.iter() {
|
|
if let Some(value) = option.value.as_ref() {
|
|
texts.push(value.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
(texts, database_title)
|
|
}
|
|
|
|
pub(super) fn collect_database_cell_references(block: &Map) -> Vec<InlineReferencePayload> {
|
|
let cells_map = match block.get("prop:cells").and_then(|value| value.to_map()) {
|
|
Some(map) => map,
|
|
None => return Vec::new(),
|
|
};
|
|
|
|
let mut refs = Vec::new();
|
|
let mut seen: HashSet<(String, String)> = HashSet::new();
|
|
|
|
for row in cells_map.values() {
|
|
let Some(row_map) = row.to_map() else {
|
|
continue;
|
|
};
|
|
for cell in row_map.values() {
|
|
let Some(cell_map) = cell.to_map() else {
|
|
continue;
|
|
};
|
|
let Some(value) = cell_map.get("value") else {
|
|
continue;
|
|
};
|
|
for reference in extract_inline_references_from_value(&value) {
|
|
let key = (reference.doc_id.clone(), reference.payload.clone());
|
|
if seen.insert(key) {
|
|
refs.push(reference);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
refs
|
|
}
|
|
|
|
fn parse_database_columns(block: &Map) -> Option<Vec<DatabaseColumn>> {
|
|
let columns = block.get("prop:columns").and_then(|value| value.to_array())?;
|
|
let mut parsed = Vec::new();
|
|
for column_value in columns.iter() {
|
|
if let Some(column) = column_value.to_map() {
|
|
let id = column
|
|
.get("id")
|
|
.and_then(|value| value_to_string(&value))
|
|
.unwrap_or_default();
|
|
let name = column.get("name").and_then(|value| value_to_string(&value));
|
|
let col_type = column
|
|
.get("type")
|
|
.and_then(|value| value_to_string(&value))
|
|
.unwrap_or_default();
|
|
let options = parse_database_options(&column);
|
|
parsed.push(DatabaseColumn {
|
|
id,
|
|
name,
|
|
col_type,
|
|
options,
|
|
});
|
|
}
|
|
}
|
|
Some(parsed)
|
|
}
|
|
|
|
fn parse_database_options(column: &Map) -> Vec<DatabaseOption> {
|
|
let Some(data) = column.get("data").and_then(|value| value.to_map()) else {
|
|
return Vec::new();
|
|
};
|
|
let Some(options) = data.get("options").and_then(|value| value.to_array()) else {
|
|
return Vec::new();
|
|
};
|
|
|
|
let mut parsed = Vec::new();
|
|
for option_value in options.iter() {
|
|
if let Some(option) = option_value.to_map() {
|
|
parsed.push(DatabaseOption {
|
|
id: option.get("id").and_then(|value| value_to_string(&value)),
|
|
value: option.get("value").and_then(|value| value_to_string(&value)),
|
|
color: option.get("color").and_then(|value| value_to_string(&value)),
|
|
});
|
|
}
|
|
}
|
|
parsed
|
|
}
|
|
|
|
fn format_option_tag(option: &DatabaseOption) -> String {
|
|
let id = option.id.as_deref().unwrap_or_default();
|
|
let value = option.value.as_deref().unwrap_or_default();
|
|
let color = option.color.as_deref().unwrap_or_default();
|
|
|
|
format!("<span data-affine-option data-value=\"{id}\" data-option-color=\"{color}\">{value}</span>")
|
|
}
|
|
|
|
fn format_cell_value(value: &Value, column: &DatabaseColumn) -> String {
|
|
match column.col_type.as_str() {
|
|
"select" => {
|
|
let id = match value {
|
|
Value::Any(any) => any_as_string(any).map(str::to_string),
|
|
Value::Text(text) => Some(text.to_string()),
|
|
_ => None,
|
|
};
|
|
if let Some(id) = id {
|
|
for option in column.options.iter() {
|
|
if option.id.as_deref() == Some(id.as_str()) {
|
|
return format_option_tag(option);
|
|
}
|
|
}
|
|
}
|
|
String::new()
|
|
}
|
|
"multi-select" => {
|
|
let ids: Vec<String> = match value {
|
|
Value::Any(Any::Array(ids)) => ids.iter().filter_map(any_as_string).map(str::to_string).collect(),
|
|
Value::Array(array) => array.iter().filter_map(|id_val| value_to_string(&id_val)).collect(),
|
|
_ => Vec::new(),
|
|
};
|
|
|
|
if ids.is_empty() {
|
|
return String::new();
|
|
}
|
|
|
|
let mut selected = Vec::new();
|
|
for id in ids.iter() {
|
|
for option in column.options.iter() {
|
|
if option.id.as_deref() == Some(id.as_str()) {
|
|
selected.push(format_option_tag(option));
|
|
}
|
|
}
|
|
}
|
|
selected.join("")
|
|
}
|
|
_ => value_to_string(value).unwrap_or_default(),
|
|
}
|
|
}
|