Просмотр исходного кода

Integrated spreadsheet system into markdown

main
Rocketsoup 1 год назад
Родитель
Сommit
c85a73c15a
3 измененных файлов: 77 добавлений и 53 удалений
  1. 1
    0
      js/markdown.js
  2. 62
    52
      js/spreadsheet.js
  3. 14
    1
      markdownjs.html

+ 1
- 0
js/markdown.js Просмотреть файл

226
 	 * @returns {string} escaped HTML
226
 	 * @returns {string} escaped HTML
227
 	 */
227
 	 */
228
 	static escapeHTML(str, encodeNewlinesAsBreaks=false) {
228
 	static escapeHTML(str, encodeNewlinesAsBreaks=false) {
229
+		if (typeof str !== 'string') return '';
229
 		var html = str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
230
 		var html = str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
230
 		if (encodeNewlinesAsBreaks) {
231
 		if (encodeNewlinesAsBreaks) {
231
 			html = html.replace(/\n/g, "<br>\n");
232
 			html = html.replace(/\n/g, "<br>\n");

+ 62
- 52
js/spreadsheet.js Просмотреть файл

2686
 	outputValueAt(address) {
2686
 	outputValueAt(address) {
2687
 		return this.cellAt(address)?.outputValue;
2687
 		return this.cellAt(address)?.outputValue;
2688
 	}
2688
 	}
2689
-
2690
-	applyToTable() {
2691
-		for (const column of this.cells) {
2692
-			for (const cell of column) {
2693
-				cell.applyToBlock();
2694
-			}
2695
-		}
2696
-	}
2697
-
2698
-	/**
2699
-	 * @param {MDTableBlock} tableBlock
2700
-	 * @param {MDState} state
2701
-	 * @returns {SpreadsheetGrid}
2702
-	 */
2703
-	static fromTableBlock(tableBlock, state) {
2704
-		var columnCount = tableBlock.headerRow.cells.length;
2705
-		for (const row of tableBlock.bodyRows) {
2706
-			columnCount = Math.max(columnCount, row.cells.length);
2707
-		}
2708
-		const rowCount = tableBlock.bodyRows.length;
2709
-		const grid = new SpreadsheetGrid(columnCount, rowCount);
2710
-		for (var c = 0; c < columnCount; c++) {
2711
-			for (var r = 0; r < rowCount; r++) {
2712
-				const cellBlock = tableBlock.bodyRows[r].cells[c];
2713
-				if (cellBlock === undefined) continue;
2714
-				const cell = grid.cells[c][r];
2715
-				cell.block = cellBlock;
2716
-				cell.originalValue = CellValue.fromCellString(cellBlock.content.toPlaintext(state));
2717
-			}
2718
-		}
2719
-		return grid;
2720
-	}
2721
 }
2689
 }
2722
 
2690
 
2723
 class SpreadsheetCell {
2691
 class SpreadsheetCell {
2724
 	/**
2692
 	/**
2725
-	 * @type {MDTableCellBlock|null}
2726
-	 */
2727
-	block = null;
2728
-
2729
-	/**
2730
 	 * @type {CellValue}
2693
 	 * @type {CellValue}
2731
 	 */
2694
 	 */
2732
 	originalValue = CellValue.BLANK;
2695
 	originalValue = CellValue.BLANK;
2746
 	 * @type {CellValue|null}
2709
 	 * @type {CellValue|null}
2747
 	 */
2710
 	 */
2748
 	get resolvedValue() { return this.outputValue ?? this.originalValue; }
2711
 	get resolvedValue() { return this.outputValue ?? this.originalValue; }
2749
-
2750
-	applyToBlock() {
2751
-		if (this.block === null) return;
2752
-		const resolvedValue = this.outputValue ?? this.originalValue;
2753
-		const num = resolvedValue.numericValue;
2754
-		if (num !== null) {
2755
-			this.block.attributes['data-numeric-value'] = `${num}`;
2756
-		}
2757
-		if (this.outputValue === null) return;
2758
-		this.block.content = new MDInlineBlock(new MDTextSpan(this.outputValue.formattedValue));
2759
-		this.block.cssClasses.push('calculated', `type-${this.outputValue.type}`);
2760
-	}
2761
 }
2712
 }
2762
 
2713
 
2763
 class SpreadsheetBlockReader extends MDBlockReader {
2714
 class SpreadsheetBlockReader extends MDBlockReader {
2768
 	postProcess(state, blocks) {
2719
 	postProcess(state, blocks) {
2769
 		for (const block of blocks) {
2720
 		for (const block of blocks) {
2770
 			if (block instanceof MDTableBlock) {
2721
 			if (block instanceof MDTableBlock) {
2771
-				this.#processTable(tableBlock, state);
2722
+				this.#processTable(block, state);
2772
 			}
2723
 			}
2773
 		}
2724
 		}
2774
 	}
2725
 	}
2778
 	 * @param {MDState} state
2729
 	 * @param {MDState} state
2779
 	 */
2730
 	 */
2780
 	#processTable(tableBlock, state) {
2731
 	#processTable(tableBlock, state) {
2781
-		const grid = SpreadsheetGrid.fromTableBlock(tableBlock, state);
2782
-		// TODO
2732
+		// Measure table
2733
+		const rowCount = tableBlock.bodyRows.length;
2734
+		var columnCount = 0;
2735
+		for (const row of tableBlock.bodyRows) {
2736
+			columnCount = Math.max(columnCount, row.cells.length);
2737
+		}
2738
+
2739
+		// Create and populate grid
2740
+		const grid = new SpreadsheetGrid(columnCount, rowCount);
2741
+		for (var c = 0; c < columnCount; c++) {
2742
+			for (var r = 0; r < rowCount; r++) {
2743
+				const cellBlock = tableBlock.bodyRows[r].cells[c];
2744
+				if (cellBlock === undefined) continue;
2745
+				const cellText = cellBlock.toPlaintext(state);
2746
+				const gridCell = grid.cells[c][r];
2747
+				gridCell.originalValue = CellValue.fromCellString(cellText);
2748
+			}
2749
+		}
2750
+
2751
+		// Calculate
2752
+		const expressions = new CellExpressionSet(grid);
2753
+		expressions.calculateCells();
2754
+
2755
+		// See if anything was calculated. If not, don't mess with table.
2756
+		var isCalculated = false;
2757
+		for (var c = 0; c < columnCount && !isCalculated; c++) {
2758
+			for (var r = 0; r < rowCount; r++) {
2759
+				if (grid.cells[c][r].isCalculated) {
2760
+					isCalculated = true;
2761
+					break;
2762
+				}
2763
+			}
2764
+		}
2765
+		if (!isCalculated) return;
2766
+
2767
+		// Copy results back to table
2768
+		for (var c = 0; c < columnCount; c++) {
2769
+			for (var r = 0; r < rowCount; r++) {
2770
+				const cellBlock = tableBlock.bodyRows[r].cells[c];
2771
+				if (cellBlock === undefined) continue;
2772
+				const gridCell = grid.cells[c][r];
2773
+				const gridValue = gridCell.outputValue;
2774
+				const cellText = gridValue.formattedValue;
2775
+				cellBlock.content = new MDInlineBlock(new MDTextSpan(cellText));
2776
+				if (gridCell.isCalculated) {
2777
+					cellBlock.cssClasses.push('calculated');
2778
+				}
2779
+				cellBlock.cssClasses.push(`spreadsheet-type-${gridValue.type}`);
2780
+				if (gridValue.type == CellValue.TYPE_ERROR) {
2781
+					cellBlock.attributes['title'] = gridValue.value;
2782
+				}
2783
+				const gridNumber = gridValue.numericValue();
2784
+				if (gridNumber !== null) {
2785
+					cellBlock.attributes['data-numeric-value'] = `${gridNumber}`;
2786
+				}
2787
+				const gridString = gridValue.stringValue(false);
2788
+				if (gridString !== null) {
2789
+					cellBlock.attributes['data-string-value'] = gridString;
2790
+				}
2791
+			}
2792
+		}
2783
 	}
2793
 	}
2784
 }
2794
 }

+ 14
- 1
markdownjs.html Просмотреть файл

12
 				--color-editor-text: white;
12
 				--color-editor-text: white;
13
 				--color-table-header: #ddd;
13
 				--color-table-header: #ddd;
14
 				--color-table-border: black;
14
 				--color-table-border: black;
15
+				--color-calculated-background: #eee;
15
 			}
16
 			}
16
 			:root, body {
17
 			:root, body {
17
 				width: 100%;
18
 				width: 100%;
53
 				background-color: var(--color-editor-background);
54
 				background-color: var(--color-editor-background);
54
 				color: var(--color-editor-text);
55
 				color: var(--color-editor-text);
55
 			}
56
 			}
57
+			.calculated {
58
+				background-color: var(--color-calculated-background);
59
+				font-style: italic;
60
+			}
61
+			.spreadsheet-type-number,
62
+			.spreadsheet-type-currency,
63
+			.spreadsheet-type-percent {
64
+				text-align: right;
65
+			}
56
 			table {
66
 			table {
57
 				border-collapse: collapse;
67
 				border-collapse: collapse;
58
 			}
68
 			}
69
 					--color-text: white;
79
 					--color-text: white;
70
 					--color-table-header: #333;
80
 					--color-table-header: #333;
71
 					--color-table-border: #ccc;
81
 					--color-table-border: #ccc;
82
+					--color-calculated-background: #444;
72
 				}
83
 				}
73
 			}
84
 			}
74
 		</style>
85
 		</style>
75
 		<script src="js/markdown.js"></script>
86
 		<script src="js/markdown.js"></script>
87
+		<script src="js/spreadsheet.js"></script>
76
 		<script>
88
 		<script>
89
+			var parser = new Markdown([ ...Markdown.allBlockReaders, new SpreadsheetBlockReader() ], Markdown.allInlineReaders);
77
 			function onDocumentLoad() {
90
 			function onDocumentLoad() {
78
 				document.getElementById('markdowninput').addEventListener('input', onMarkdownChange);
91
 				document.getElementById('markdowninput').addEventListener('input', onMarkdownChange);
79
 				setTimeout(onMarkdownChange, 0);
92
 				setTimeout(onMarkdownChange, 0);
80
 			}
93
 			}
81
 			function onMarkdownChange() {
94
 			function onMarkdownChange() {
82
 				let markdown = document.getElementById('markdowninput').value;
95
 				let markdown = document.getElementById('markdowninput').value;
83
-				let html = Markdown.completeParser.toHTML(markdown);
96
+				let html = parser.toHTML(markdown);
84
 				document.getElementById('preview').innerHTML = html;
97
 				document.getElementById('preview').innerHTML = html;
85
 			}
98
 			}
86
 
99
 

Загрузка…
Отмена
Сохранить