oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1
2{{/* The status page refreshes itself ONLY while a run is active, and it
3 is a meta refresh because every affordance here works scriptless.
4 This is also the honest mechanism: the import is a goroutine that
5 lives as long as the process, and the platform sleeps an idle app,
6 so an open page is what keeps it awake. The copy below says so
7 rather than implying a background worker memoria does not have. */}}
8{{define "head"}}{{if .Run}}{{if .Run.Running}}<meta http-equiv="refresh" content="5">{{end}}{{end}}{{end}}
9
10{{define "content"}}
11
12<h1>Notion</h1>
13
14{{if .Flash}}<div rst-callout rst-tone="{{.FlashKind | tone}}"{{if eq .FlashKind "danger"}} role="alert"{{end}}><div rst-callout-body><p>{{.Flash}}</p></div></div>{{end}}
15
16{{if not .Configured}}
17
18 <p rst-lede>
19 Memoria imports the Notion pages you share with an integration, and
20 everything nested underneath them.
21 </p>
22
23 {{/* Info: the untoned callout (runTone says why there is no "info"). */}}
24 <div rst-callout>
25 <div rst-callout-body><p>
26 <strong>What you connect, Memoria keeps.</strong> Once a page is
27 imported, it stays in Memoria and stays readable by everyone here —
28 even if you later archive it in Notion, unshare it, or revoke this
29 token. Removing it is something you do in Memoria.
30 </p></div>
31 </div>
32
33 <section rst-section>
34 <h2>Connect</h2>
35 <ol rst-steps>
36 <li>In Notion, create an <strong>internal integration</strong> with
37 read-only capabilities and no user information.</li>
38 <li>Share the pages you want Memoria to hold with that integration.
39 Sharing a page is what publishes it. Memoria imports what the
40 integration can see and nothing else.</li>
41 <li>Paste the integration token below.</li>
42 </ol>
43
44 <form method="post" action="/connectors/notion" rst-stack>
45 <label for="token">Integration token</label>
46 <input type="password" id="token" name="token" autocomplete="off" required>
47 <p rst-muted>Checked with Notion before it is saved. It is stored sealed and is never shown again.</p>
48 <button rst-btn="primary">Connect</button>
49 </form>
50 </section>
51
52{{else}}
53
54 <section rst-section>
55 <h2>Connected</h2>
56 <p rst-muted>
57 {{if .Verified}}Token last checked {{.Verified}}.{{else}}Token stored.{{end}}
58 </p>
59
60 {{if .CountsKnown}}
61 <p>{{.Imported}} of {{.Pages}} known page{{if ne .Pages 1}}s{{end}} imported.</p>
62 {{else}}
63 <p>The page counts could not be read, so this says nothing about how much has been imported.</p>
64 {{end}}
65
66 <form method="post" action="/connectors/notion/sync" rst-inline>
67 <button rst-btn="primary"{{if .Run}}{{if .Run.Running}} disabled{{end}}{{end}}>
68 {{if .Run}}{{if .Run.Running}}Importing…{{else}}Import now{{end}}{{else}}Import now{{end}}
69 </button>
70 </form>
71 </section>
72
73 {{if .Run}}
74 <section rst-section>
75 <h2>Last import</h2>
76 {{/* A callout toned by the run's state (spec §4, runTone): a run in
77 progress or finished cleanly is info (untoned), one that finished
78 with pages it could not read, stopped, or failed is a warning. It used to be a
79 plain paragraph, so "some pages could not be read" read like
80 "finished". */}}
81 <div rst-callout{{with .Run.Tone}} rst-tone="{{.}}"{{end}}><div rst-callout-body><p>{{.Run.Message}}</p></div></div>
82
83 <dl rst-facts>
84 <dt>Started</dt><dd>{{.Run.Started}}</dd>
85 {{if .Run.Finished}}<dt>Finished</dt><dd>{{.Run.Finished}}</dd>{{end}}
86 <dt>Top-level pages shared with the integration</dt><dd>{{.Run.Roots}}</dd>
87 <dt>Pages seen</dt>
88 <dd>
89 {{if .Run.TotalKnown}}
90 {{.Run.Total}}
91 {{else}}
92 {{.Run.Seen}} so far — the total is not known until the import finishes
93 {{end}}
94 </dd>
95 <dt>Imported</dt><dd>{{.Run.Fetched}}</dd>
96 <dt>Already up to date</dt><dd>{{.Run.Skipped}}</dd>
97 <dt>Empty pages</dt><dd>{{.Run.Empty}}</dd>
98 <dt>Could not be read</dt>
99 <dd>
100 {{.Run.Failed}}
101 {{if .Run.Failed}}<span rst-muted>— counted here rather than left out of the totals</span>{{end}}
102 </dd>
103 {{if .Run.Unread}}
104 <dt>Blocks Memoria does not read</dt>
105 <dd>{{.Run.Unread}} <span rst-muted>— a page that came in thin may be one of these</span></dd>
106 {{end}}
107 </dl>
108 </section>
109 {{end}}
110
111 <section rst-section>
112 <h2>Replace the token</h2>
113 <p rst-muted>
114 Rotating a token replaces the stored one. The old token is not kept.
115 Nothing already imported is affected.
116 </p>
117 <form method="post" action="/connectors/notion" rst-stack>
118 <label for="token">New integration token</label>
119 {{/* Never carries a value. The stored token is not rendered back
120 here or anywhere — not even masked, because a masked secret is
121 still a secret in the DOM. TestTokenIsNeverRendered checks
122 this branch specifically: an earlier version of that gate only
123 looked at the not-yet-connected page, which has no token field
124 at all, so it passed while proving nothing. */}}
125 <input type="password" id="token" name="token" autocomplete="off" required>
126 <button rst-btn="quiet">Replace</button>
127 </form>
128 </section>
129
130 <section rst-section>
131 <h2>Disconnect</h2>
132 <p>
133 This removes the token and stops future imports. <strong>It deletes
134 nothing Memoria has already imported</strong> — those pages stay
135 here until someone deletes them in Memoria.
136 </p>
137 <form method="post" action="/connectors/notion/disconnect" rst-inline>
138 <button rst-btn="danger">Disconnect Notion</button>
139 </form>
140 </section>
141
142{{end}}
143
144{{end}}
145