cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / fileio / html.go
html.go 5.2K
1package fileio23import (4 "context"5 "fmt"6 "html"7 "io"89 "src.rest/cat/012/internal/sheet"10)1112// HTML exports are static pages in 012's look (see htmlstyle.go): the13// control panel's lines above the grid, the grid with its headers and14// charts, and a small script that puts the pointer on a cell clicked or15// moved to with the arrow keys, showing its address in the name box and16// what it holds in the formula bar, as the screen does. The page needs17// nothing beyond itself: fonts, styles, SVG charts and script are in18// the file.1920// HTMLPage is what an HTML page shows: a sheet or range, or one chart.21type HTMLPage struct {22 Title string // the page's title, and the first line's23 Snap *Snapshot // the cells, when Chart is nil24 Chart *SnapChart25}2627// WriteHTML writes p as a page. The note, when there is one, says what28// the page left out.29func WriteHTML(w io.Writer, p HTMLPage) (note string, err error) {30 body, name, where := "", "", ""31 switch {32 case p.Chart != nil:33 body, name, where = HTMLChart(*p.Chart), p.Chart.Data.String(), p.Chart.Type.Title()+" chart of "+p.Chart.Data.String()34 case p.Snap != nil:35 body, note = HTMLGrid(p.Snap)36 where = p.Snap.Name + "!" + p.Snap.Range.String()37 }38 _, err = io.WriteString(w, HTMLDocument(p.Title, HTMLPanel(p.Title, name, where)+body, ""))39 return note, err40}4142// HTMLDocument is a whole page: title, style sheet, body and script,43// with extra added to the script (the MCP view's messages).44func HTMLDocument(title, body, extra string) string {45 return "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">" +46 "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +47 "<meta name=\"generator\" content=\"012\"><title>" + html.EscapeString(title) + "</title>\n<style>\n" +48 HTMLStyle() + "</style></head>\n<body><div class=\"o12\">" + body +49 "</div>\n<script>\n" + htmlScript + extra + "</script></body></html>\n"50}5152// HTMLPanel is the control panel's three lines: the title with the53// mode indicator, the name box (holding name until a cell is pointed54// at) and formula bar, and the context line saying what the page55// shows.56func HTMLPanel(title, name, where string) string {57 return fmt.Sprintf(`<header class="panel"><div><span class="book">%s</span><span class="mode">READY</span></div>`+58 `<div><span class="namebox" id="o12-name">%s</span><span class="input" id="o12-input"></span></div>`+59 `<div class="context" id="o12-context">%s</div></header>`, html.EscapeString(title), html.EscapeString(name), html.EscapeString(where))60}6162// exportHTML writes a sheet or range as a page.63func exportHTML(_ context.Context, name string, snap *Snapshot, _ ExportOptions) (*ExportResult, error) {64 var note string65 err := writeFile(name, func(w io.Writer) error {66 var err error67 note, err = WriteHTML(w, HTMLPage{Title: snap.Name, Snap: snap})68 return err69 })70 if err != nil {71 return nil, err72 }73 res := &ExportResult{Rows: snap.Range.To.Row - snap.Range.From.Row + 1}74 if len(snap.Cells) == 0 {75 res.Rows = 076 }77 if note != "" {78 res.Notes = append(res.Notes, note)79 }80 return res, nil81}8283// ChartSnap is chart i of s with the values it draws, for a page of84// its own.85func ChartSnap(s *sheet.Sheet, i int) (SnapChart, bool) {86 charts := s.Charts()87 if i < 0 || i >= len(charts) {88 return SnapChart{}, false89 }90 return SnapChart{Chart: charts[i], Values: s.ChartData(charts[i])}, true91}9293// htmlScript moves the pointer: a click or an arrow key puts it on a94// cell, whose address goes in the name box, what was typed in it in95// the formula bar, and whose row and column headers light up. It works96// on grids added to the page later, as the MCP view adds them.97const htmlScript = `(function(){98var cur=null;99function col(s){var n=0;for(var i=0;i<s.length;i++)n=n*26+s.charCodeAt(i)-64;return n}100function name(n){var s="";while(n>0){n--;s=String.fromCharCode(65+n%26)+s;n=Math.floor(n/26)}return s}101function split(a){var m=/^([A-Z]+)(\d+)$/.exec(a);return m?[col(m[1]),+m[2]]:null}102function point(td){103 if(cur){cur.classList.remove("ptr")}104 document.querySelectorAll("th.on").forEach(function(t){t.classList.remove("on")});105 cur=td;if(!td)return;106 td.classList.add("ptr");107 var a=td.getAttribute("data-a"),p=split(a),g=td.closest("table");108 var nb=document.getElementById("o12-name"),inp=document.getElementById("o12-input");109 if(nb)nb.textContent=a;110 if(inp)inp.textContent=td.hasAttribute("data-f")?td.getAttribute("data-f"):td.textContent;111 if(p&&g){var c=g.querySelector('th[data-c="'+name(p[0])+'"]'),r=g.querySelector('th[data-r="'+p[1]+'"]');112 if(c)c.classList.add("on");if(r)r.classList.add("on")}113}114document.addEventListener("click",function(e){var td=e.target.closest&&e.target.closest("td[data-a]");if(td)point(td)});115document.addEventListener("keydown",function(e){116 if(!cur)return;var d={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]}[e.key];if(!d)return;117 var p=split(cur.getAttribute("data-a")),g=cur.closest("table");if(!p||!g)return;118 for(var k=1;k<64;k++){var n=g.querySelector('td[data-a="'+name(p[0]+d[0]*k)+(p[1]+d[1]*k)+'"]');119 if(n){point(n);n.scrollIntoView({block:"nearest",inline:"nearest"});e.preventDefault();return}120 if(p[0]+d[0]*k<1||p[1]+d[1]*k<1)return}121});122window.o12point=point;123var first=document.querySelector("td[data-a]");if(first)point(first);124})();125`