| 1 | package sheets |
| 2 | |
| 3 | import "net/http" |
| 4 | |
| 5 | // The import page. |
| 6 | // |
| 7 | // Separate from handlers_import.go, which holds the POST that does the |
| 8 | // work, for the dullest possible reason: together they were 305 lines |
| 9 | // and the file cap is 300 (AGENTS.md / Mechanical rules, enforced by |
| 10 | // TestFileLineCap). The split falls in a sensible place anyway — this |
| 11 | // file is the screen, that one is the upload. |
| 12 | |
| 13 | // importPageView is the import page's payload. |
| 14 | type importPageView struct { |
| 15 | // Error is a refusal to show above the form — one of the refusal |
| 16 | // constants in handlers_import.go, never a wrapped error, because |
| 17 | // everything shown here is read by somebody deciding what to do |
| 18 | // differently. |
| 19 | Error string |
| 20 | } |
| 21 | |
| 22 | // handleImportPage draws the import form on a page of its own. |
| 23 | // |
| 24 | // Import used to be a second form on the sheet list, below the form |
| 25 | // that made a blank sheet. Both came off that page: a list of sheets |
| 26 | // should be a list of sheets, and the verbs moved into the chrome |
| 27 | // around it. |
| 28 | // |
| 29 | // A page rather than an item inside the File menu, which is where this |
| 30 | // nearly went. Three reasons, and the third is the one that decided it: |
| 31 | // a dropdown is a poor container for a file picker plus three limits; a |
| 32 | // hidden input behind a menu-shaped label never submits itself, so that |
| 33 | // menu item would have needed script to import anything at all, and |
| 34 | // import is meant to work with scripting off; and the File menu exists |
| 35 | // only on a sheet page, so somebody with no sheets yet — exactly the |
| 36 | // person most likely to be importing one — would have had no way in. |
| 37 | func (a *App) handleImportPage(w http.ResponseWriter, r *http.Request) { |
| 38 | // Behind requireSession already; this is the same belt-and-braces |
| 39 | // actor check the sibling handlers make, and it is what makes the |
| 40 | // handler safe to call from a test that does not mount the group. |
| 41 | if _, ok := a.actor(r); !ok { |
| 42 | a.forbidden(w, r) |
| 43 | return |
| 44 | } |
| 45 | a.renderImportPage(w, r, http.StatusOK, "") |
| 46 | } |
| 47 | |
| 48 | // renderImportPage draws that form, optionally carrying a refusal and |
| 49 | // the status that goes with it. |
| 50 | // |
| 51 | // A refused import re-renders this page rather than redirecting to it, |
| 52 | // so the refusal survives without a flash: the person lands back on the |
| 53 | // form they posted from, with the reason above it. No session storage, |
| 54 | // no cookie, no redirect, and it works with scripting off by |
| 55 | // construction. That reasoning arrived here from renderSheetList along |
| 56 | // with the form itself. |
| 57 | func (a *App) renderImportPage(w http.ResponseWriter, r *http.Request, status int, refusal string) { |
| 58 | a.render(w, r, "import", titled("Import a spreadsheet"), status, |
| 59 | importPageView{Error: refusal}) |
| 60 | } |
| 61 | |