diff --git a/base/src/model.rs b/base/src/model.rs index 7ceb536..9846157 100644 --- a/base/src/model.rs +++ b/base/src/model.rs @@ -1931,16 +1931,32 @@ impl Model { } /// Returns markup representation of the given `sheet`. - pub fn get_sheet_markup(&self, sheet: u32) -> Result { - let worksheet = self.workbook.worksheet(sheet)?; - let dimension = worksheet.dimension(); + pub fn get_sheet_markup( + &self, + sheet: u32, + start_row: i32, + start_column: i32, + width: i32, + height: i32, + ) -> Result { + let mut table: Vec> = Vec::new(); + if start_row < 1 || start_column < 1 { + return Err("Start row and column must be positive".to_string()); + } + if start_row + height >= LAST_ROW || start_column + width >= LAST_COLUMN { + return Err("Start row and column exceed the maximum allowed".to_string()); + } + if height <= 0 || width <= 0 { + return Err("Height must be positive and width must be positive".to_string()); + } - let mut rows = Vec::new(); + // a mutable vector to store the column widths of length `width + 1` + let mut column_widths: Vec = vec![0.0; (width + 1) as usize]; - for row in 1..(dimension.max_row + 1) { + for row in start_row..(start_row + height + 1) { let mut row_markup: Vec = Vec::new(); - for column in 1..(dimension.max_column + 1) { + for column in start_column..(start_column + width + 1) { let mut cell_markup = match self.get_cell_formula(sheet, row, column)? { Some(formula) => formula, None => self.get_formatted_cell_value(sheet, row, column)?, @@ -1949,12 +1965,34 @@ impl Model { if style.font.b { cell_markup = format!("**{cell_markup}**") } + column_widths[(column - start_column) as usize] = + column_widths[(column - start_column) as usize].max(cell_markup.len() as f64); row_markup.push(cell_markup); } - rows.push(row_markup.join("|")); + table.push(row_markup); } + let mut rows = Vec::new(); + for (j, row) in table.iter().enumerate() { + if j == 1 { + let mut row_markup = String::new(); + for i in 0..(width + 1) { + row_markup.push('|'); + let wide = column_widths[i as usize] as usize; + row_markup.push_str(&"-".repeat(wide)); + } + rows.push(row_markup); + } + let mut row_markup = String::new(); + for (i, cell) in row.iter().enumerate() { + row_markup.push('|'); + let wide = column_widths[i] as usize; + // Add padding to the cell content + row_markup.push_str(&format!("{: Result { + self.model + .get_sheet_markup(sheet, row_start, column_start, row_end, column_end) + } + /// Undoes last change if any, places the change in the redo list and evaluates the model if needed /// /// See also: diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 5767a2e..6f93185 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -672,4 +672,18 @@ impl Model { .delete_defined_name(name, scope) .map_err(|e| to_js_error(e.to_string())) } + + #[wasm_bindgen(js_name = "getSheetMarkup")] + pub fn get_sheet_markup( + &self, + sheet: u32, + start_row: i32, + start_column: i32, + end_row: i32, + end_column: i32, + ) -> Result { + self.model + .get_sheet_markup(sheet, start_row, start_column, end_row, end_column) + .map_err(to_js_error) + } } diff --git a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx index 8c26e98..8b0f393 100644 --- a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx +++ b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx @@ -40,6 +40,7 @@ import { ArrowMiddleFromLine, DecimalPlacesDecreaseIcon, DecimalPlacesIncreaseIcon, + Markdown, } from "../../icons"; import { theme } from "../../theme"; import BorderPicker from "../BorderPicker/BorderPicker"; @@ -74,6 +75,7 @@ type ToolbarProperties = { onClearFormatting: () => void; onIncreaseFontSize: (delta: number) => void; onDownloadPNG: () => void; + onCopyMarkdown: () => void; fillColor: string; fontColor: string; fontSize: number; @@ -429,6 +431,17 @@ function Toolbar(properties: ToolbarProperties) { > + { + properties.onCopyMarkdown(); + }} + disabled={!canEdit} + title={t("toolbar.selected_markdown")} + > + + { onIncreaseFontSize={(delta: number) => { onIncreaseFontSize(delta); }} + onCopyMarkdown={async () => { + const { + sheet, + range: [rowStart, columnStart, rowEnd, columnEnd], + } = model.getSelectedView(); + const row = Math.min(rowStart, rowEnd); + const column = Math.min(columnStart, columnEnd); + const width = Math.abs(columnEnd - columnStart) + 1; + const height = Math.abs(rowEnd - rowStart) + 1; + const markdown = model.getSheetMarkup( + sheet, + row, + column, + width, + height, + ); + // Copy to clipboard + // NB: This will not work in non secure contexts or in iframes (i.e storybook) + await navigator.clipboard.writeText(markdown); + }} onDownloadPNG={() => { // creates a new canvas element in the visible part of the the selected area const worksheetCanvas = worksheetRef.current?.getCanvas(); diff --git a/webapp/IronCalc/src/icons/index.ts b/webapp/IronCalc/src/icons/index.ts index 10773a3..2ef15f2 100644 --- a/webapp/IronCalc/src/icons/index.ts +++ b/webapp/IronCalc/src/icons/index.ts @@ -19,6 +19,7 @@ import InsertColumnLeftIcon from "./insert-column-left.svg?react"; import InsertColumnRightIcon from "./insert-column-right.svg?react"; import InsertRowAboveIcon from "./insert-row-above.svg?react"; import InsertRowBelow from "./insert-row-below.svg?react"; +import Markdown from "./markdown.svg?react"; import IronCalcIcon from "./ironcalc_icon.svg?react"; import IronCalcLogo from "./orange+black.svg?react"; @@ -48,4 +49,5 @@ export { IronCalcIcon, IronCalcLogo, Fx, + Markdown, }; diff --git a/webapp/IronCalc/src/icons/markdown.svg b/webapp/IronCalc/src/icons/markdown.svg new file mode 100644 index 0000000..dd93309 --- /dev/null +++ b/webapp/IronCalc/src/icons/markdown.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/webapp/IronCalc/src/locale/en_us.json b/webapp/IronCalc/src/locale/en_us.json index db69a3a..1db9fcd 100644 --- a/webapp/IronCalc/src/locale/en_us.json +++ b/webapp/IronCalc/src/locale/en_us.json @@ -26,6 +26,7 @@ "vertical_align_middle": " Align middle", "vertical_align_top": "Align top", "selected_png": "Export Selected area as PNG", + "selected_markdown": "Export Selected area as Markdown", "wrap_text": "Wrap text", "format_menu": { "auto": "Auto",