@cloudcannon/editable-regions 0.0.19 → 0.0.20-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/integrations/hugo/browser/entry.js +13 -0
- package/integrations/hugo/browser/errors.ts +41 -0
- package/integrations/hugo/browser/index.ts +344 -0
- package/integrations/hugo/browser/logger.ts +42 -0
- package/integrations/hugo/browser/wasm_exec.js +575 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-config-files.html +14 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-dep-template-files.html +66 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-files-with-extension.html +27 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-template-files.html +51 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/load-deps.html +5 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/normalize-extensions.html +9 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/resources.html +87 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions/wasm-url.html +28 -0
- package/integrations/hugo/hugo-module/layouts/partials/editable-regions.html +6 -0
- package/integrations/hugo/renderer/build.sh +24 -0
- package/integrations/hugo/renderer/go.mod +100 -0
- package/integrations/hugo/renderer/go.sum +241 -0
- package/integrations/hugo/renderer/main.go +412 -0
- package/integrations/liquid/globals.mjs +7 -1
- package/package.json +31 -19
- package/types/hugo.d.ts +69 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"log"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
"syscall/js"
|
|
12
|
+
_ "time/tzdata"
|
|
13
|
+
|
|
14
|
+
"github.com/fsnotify/fsnotify"
|
|
15
|
+
"github.com/gohugoio/hugo/common/hmaps"
|
|
16
|
+
"github.com/gohugoio/hugo/config"
|
|
17
|
+
"github.com/gohugoio/hugo/config/allconfig"
|
|
18
|
+
"github.com/gohugoio/hugo/deps"
|
|
19
|
+
"github.com/gohugoio/hugo/hugofs"
|
|
20
|
+
"github.com/gohugoio/hugo/hugolib"
|
|
21
|
+
"github.com/gohugoio/hugo/parser/metadecoders"
|
|
22
|
+
"github.com/gohugoio/hugo/resources/page"
|
|
23
|
+
"github.com/spf13/afero"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// dispatchView is the view renderBatch executes for each batch: the batch's
|
|
27
|
+
// requests arrive as template data (keyed by render id), and each request's
|
|
28
|
+
// partial renders with `page` bound through the execution context, keying
|
|
29
|
+
// every result with its request id for the browser to demultiplex.
|
|
30
|
+
const dispatchView = `{{- with .cc_requests -}}
|
|
31
|
+
{{- /* cc_requests is a MAP keyed by render id, prepared on the Go side with
|
|
32
|
+
Hugo's params preparation: it recurses into nested maps (not arrays),
|
|
33
|
+
keeping case-insensitive Params semantics for props. Range order is
|
|
34
|
+
key-sorted; the browser demuxes by id, not position. */ -}}
|
|
35
|
+
{{- range $id, $req := . -}}
|
|
36
|
+
{{- $partial := $req.partial -}}
|
|
37
|
+
{{- $found := templates.Exists (printf "partials/%s" $partial) -}}
|
|
38
|
+
{{- $found = or $found (templates.Exists (printf "partials/%s.html" $partial)) -}}
|
|
39
|
+
{{- $found = or $found (templates.Exists (printf "partials/%s.htm" $partial)) -}}
|
|
40
|
+
{{- if not $found -}}
|
|
41
|
+
<div data-cc-render="{{ $id }}"><cc-missing-partial data-name="{{ $partial }}"></cc-missing-partial></div>
|
|
42
|
+
{{- else -}}
|
|
43
|
+
<div data-cc-render="{{ $id }}">{{- $result := try (partial $partial $req.props) -}}
|
|
44
|
+
{{- if $result.Err -}}
|
|
45
|
+
<cc-failed-partial data-name="{{ $partial }}" data-message="{{ $result.Err }}"></cc-failed-partial>
|
|
46
|
+
{{- else -}}
|
|
47
|
+
{{- $result.Value -}}
|
|
48
|
+
{{- end -}}</div>
|
|
49
|
+
{{- end -}}
|
|
50
|
+
{{- end -}}
|
|
51
|
+
{{- end -}}`
|
|
52
|
+
|
|
53
|
+
const dispatchViewPath = "/_default/__cc-dispatch.html"
|
|
54
|
+
|
|
55
|
+
type editorSiteBuilder struct {
|
|
56
|
+
Cfg *allconfig.Configs
|
|
57
|
+
Afs afero.Fs
|
|
58
|
+
Fs *hugofs.Fs
|
|
59
|
+
Sites *hugolib.HugoSites
|
|
60
|
+
changedFiles []string
|
|
61
|
+
removedFiles []string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func editorFlags() config.Provider {
|
|
65
|
+
flags := config.New()
|
|
66
|
+
flags.Set("disableKinds", []string{"taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404"})
|
|
67
|
+
flags.Set("cascade", []interface{}{
|
|
68
|
+
map[string]interface{}{
|
|
69
|
+
"build": map[string]interface{}{"render": "link"},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
return flags
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func (builder *editorSiteBuilder) loadConfig() error {
|
|
76
|
+
env := "production"
|
|
77
|
+
if contents, err := builder.readFile("cc-env"); err == nil {
|
|
78
|
+
env = strings.TrimSpace(contents)
|
|
79
|
+
}
|
|
80
|
+
cfg, err := allconfig.LoadConfig(allconfig.ConfigSourceDescriptor{
|
|
81
|
+
Fs: builder.Afs,
|
|
82
|
+
Flags: editorFlags(),
|
|
83
|
+
ConfigDir: "config",
|
|
84
|
+
Environment: env,
|
|
85
|
+
// Only mirrored modules (theme, vendored) can resolve in the in-memory fs;
|
|
86
|
+
// skip imports whose replacement points at the repo on disk.
|
|
87
|
+
IgnoreModuleDoesNotExist: true,
|
|
88
|
+
})
|
|
89
|
+
if err != nil {
|
|
90
|
+
return err
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Rebuilds run through Hugo's incremental change-event pipeline, so enable
|
|
94
|
+
// Running/Watch and propagate to per-language configs (read by the `hugo.*`
|
|
95
|
+
// template namespace).
|
|
96
|
+
cfg.Base.WorkingDir = ""
|
|
97
|
+
cfg.Base.Internal.Running = true
|
|
98
|
+
cfg.Base.Internal.Watch = true
|
|
99
|
+
|
|
100
|
+
for _, languageConfig := range cfg.LanguageConfigMap {
|
|
101
|
+
languageConfig.Internal.Running = true
|
|
102
|
+
languageConfig.Internal.Watch = true
|
|
103
|
+
languageConfig.Build.BuildStats.Enable = false
|
|
104
|
+
if languageConfig.Params == nil {
|
|
105
|
+
languageConfig.Params = hmaps.Params{}
|
|
106
|
+
}
|
|
107
|
+
languageConfig.Params["env_client"] = true
|
|
108
|
+
}
|
|
109
|
+
builder.Cfg = cfg
|
|
110
|
+
builder.Fs = hugofs.NewFrom(builder.Afs, cfg.GetFirstLanguageConfig().BaseConfig())
|
|
111
|
+
|
|
112
|
+
return nil
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func (builder *editorSiteBuilder) createSites() error {
|
|
116
|
+
if err := builder.loadConfig(); err != nil {
|
|
117
|
+
return fmt.Errorf("failed to load config: %w", err)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
builder.Fs.PublishDir = hugofs.NewCreateCountingFs(builder.Fs.PublishDir)
|
|
121
|
+
|
|
122
|
+
sites, err := hugolib.NewHugoSites(deps.DepsCfg{
|
|
123
|
+
Fs: builder.Fs,
|
|
124
|
+
Configs: builder.Cfg,
|
|
125
|
+
})
|
|
126
|
+
if err != nil {
|
|
127
|
+
return fmt.Errorf("failed to create sites: %w", err)
|
|
128
|
+
}
|
|
129
|
+
builder.Sites = sites
|
|
130
|
+
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func (builder *editorSiteBuilder) build() error {
|
|
135
|
+
var events []fsnotify.Event
|
|
136
|
+
if builder.Sites == nil {
|
|
137
|
+
if err := builder.createSites(); err != nil {
|
|
138
|
+
return err
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
events = builder.changeEvents()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
err := builder.Sites.Build(hugolib.BuildCfg{NoBuildLock: true}, events...)
|
|
145
|
+
if err != nil {
|
|
146
|
+
return err
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
builder.changedFiles = nil
|
|
150
|
+
builder.removedFiles = nil
|
|
151
|
+
|
|
152
|
+
if n := builder.Sites.NumLogErrors(); n > 0 {
|
|
153
|
+
err = fmt.Errorf("logged %d errors", n)
|
|
154
|
+
}
|
|
155
|
+
return err
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// buildIfDirty runs the initial build when the site doesn't exist yet, then
|
|
159
|
+
// an incremental build only when files changed since the last build, so
|
|
160
|
+
// renders with nothing pending skip the build entirely.
|
|
161
|
+
func (builder *editorSiteBuilder) buildIfDirty() error {
|
|
162
|
+
if builder.Sites == nil {
|
|
163
|
+
return builder.build()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if len(builder.changedFiles) == 0 && len(builder.removedFiles) == 0 {
|
|
167
|
+
return nil
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if err := builder.build(); err != nil {
|
|
171
|
+
return fmt.Errorf("build after pending content changes: %w", err)
|
|
172
|
+
}
|
|
173
|
+
return nil
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func (builder *editorSiteBuilder) writeFile(filename, content string) {
|
|
177
|
+
filename = normalizeMemfsPath(filename)
|
|
178
|
+
if err := afero.WriteFile(builder.Afs, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
|
|
179
|
+
fmt.Println(fmt.Sprintf("Failed to write file: %s", err))
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
builder.changedFiles = append(builder.changedFiles, filename)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func (builder *editorSiteBuilder) removeFile(filename string) {
|
|
187
|
+
filename = normalizeMemfsPath(filename)
|
|
188
|
+
if err := builder.Afs.Remove(filename); err != nil {
|
|
189
|
+
fmt.Println(fmt.Sprintf("Failed to remove file: %s", err))
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Publish-dir files aren't Hugo source files, so their removal shouldn't
|
|
194
|
+
// be fed back in as a change event.
|
|
195
|
+
if !strings.HasPrefix(filepath.ToSlash(filename), "public/") {
|
|
196
|
+
builder.removedFiles = append(builder.removedFiles, filename)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func (builder *editorSiteBuilder) readFile(filename string) (string, error) {
|
|
201
|
+
filename = normalizeMemfsPath(filename)
|
|
202
|
+
b, err := afero.ReadFile(builder.Afs, filepath.Clean(filename))
|
|
203
|
+
if err != nil {
|
|
204
|
+
return "", err
|
|
205
|
+
}
|
|
206
|
+
return string(b), nil
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func (builder *editorSiteBuilder) changeEvents() []fsnotify.Event {
|
|
210
|
+
var events []fsnotify.Event
|
|
211
|
+
|
|
212
|
+
for _, v := range builder.changedFiles {
|
|
213
|
+
events = append(events, fsnotify.Event{
|
|
214
|
+
Name: v,
|
|
215
|
+
Op: fsnotify.Write,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for _, v := range builder.removedFiles {
|
|
220
|
+
events = append(events, fsnotify.Event{
|
|
221
|
+
Name: v,
|
|
222
|
+
Op: fsnotify.Remove,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return events
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// normalizeMemfsPath maps the browser's site-root-relative source paths
|
|
230
|
+
// ("/content/blog/one.md") to the in-memory fs's slash-less relative form,
|
|
231
|
+
// which Hugo's contentDir/dataDir/layoutDir also use.
|
|
232
|
+
func normalizeMemfsPath(p string) string {
|
|
233
|
+
return strings.TrimPrefix(filepath.ToSlash(p), "/")
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var builder editorSiteBuilder
|
|
237
|
+
|
|
238
|
+
func main() {
|
|
239
|
+
builder = editorSiteBuilder{Afs: afero.NewMemMapFs()}
|
|
240
|
+
|
|
241
|
+
log.SetOutput(io.Discard)
|
|
242
|
+
|
|
243
|
+
c := make(chan struct{}, 0)
|
|
244
|
+
js.Global().Set("writeHugoFiles", js.FuncOf(writeHugoFiles))
|
|
245
|
+
js.Global().Set("removeHugoFiles", js.FuncOf(removeHugoFiles))
|
|
246
|
+
js.Global().Set("readHugoFiles", js.FuncOf(readHugoFiles))
|
|
247
|
+
js.Global().Set("initHugoEditorSite", js.FuncOf(initHugoEditorSite))
|
|
248
|
+
js.Global().Set("renderHugoPartials", js.FuncOf(renderHugoPartials))
|
|
249
|
+
<-c
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func errorValue(format string, args ...interface{}) js.Value {
|
|
253
|
+
return js.ValueOf(map[string]interface{}{
|
|
254
|
+
"error": fmt.Sprintf(format, args...),
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func writeHugoFiles(this js.Value, args []js.Value) interface{} {
|
|
259
|
+
var writeFiles map[string]string
|
|
260
|
+
if err := json.Unmarshal([]byte(args[0].String()), &writeFiles); err != nil {
|
|
261
|
+
return errorValue("bad writeHugoFiles payload: %s", err)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for fileName, fileContents := range writeFiles {
|
|
265
|
+
builder.writeFile(fileName, fileContents)
|
|
266
|
+
}
|
|
267
|
+
return nil
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func removeHugoFiles(this js.Value, args []js.Value) interface{} {
|
|
271
|
+
var removeFiles []string
|
|
272
|
+
if err := json.Unmarshal([]byte(args[0].String()), &removeFiles); err != nil {
|
|
273
|
+
return errorValue("bad removeHugoFiles payload: %s", err)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for _, fileName := range removeFiles {
|
|
277
|
+
builder.removeFile(fileName)
|
|
278
|
+
}
|
|
279
|
+
return nil
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
func readHugoFiles(this js.Value, args []js.Value) interface{} {
|
|
283
|
+
var readFiles []string
|
|
284
|
+
if err := json.Unmarshal([]byte(args[0].String()), &readFiles); err != nil {
|
|
285
|
+
return errorValue("bad readHugoFiles payload: %s", err)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fileContents := make(map[string]interface{})
|
|
289
|
+
for _, fileName := range readFiles {
|
|
290
|
+
contents, err := builder.readFile(fileName)
|
|
291
|
+
if err != nil {
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
fileContents[fileName] = contents
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return js.ValueOf(fileContents)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
func initHugoEditorSite(this js.Value, args []js.Value) interface{} {
|
|
301
|
+
if err := builder.loadConfig(); err != nil {
|
|
302
|
+
return errorValue("failed to load config: %s", err)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
builder.writeFile(filepath.Join(
|
|
306
|
+
builder.Cfg.Base.LayoutDir, "_default", "__cc-dispatch.html"), dispatchView)
|
|
307
|
+
return nil
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// renderRequests is the browser's queued batch: the one render target every
|
|
311
|
+
// request shares, plus the requests to render against it.
|
|
312
|
+
type renderRequests struct {
|
|
313
|
+
Target string `json:"target"`
|
|
314
|
+
Requests []renderRequest `json:"requests"`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type renderRequest struct {
|
|
318
|
+
ID string `json:"id"`
|
|
319
|
+
Partial string `json:"partial"`
|
|
320
|
+
Props json.RawMessage `json:"props"`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
func (builder *editorSiteBuilder) targetPage(target string) page.Page {
|
|
324
|
+
target = normalizeMemfsPath(target)
|
|
325
|
+
if target != "" && builder.Sites != nil {
|
|
326
|
+
for _, s := range builder.Sites.Sites {
|
|
327
|
+
for _, p := range s.Pages() {
|
|
328
|
+
f := p.File()
|
|
329
|
+
if f == nil {
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
if filepath.Join(builder.Cfg.Base.ContentDir, f.Path()) != target {
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
return p
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return nil
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func renderHugoPartials(this js.Value, args []js.Value) interface{} {
|
|
343
|
+
var payload renderRequests
|
|
344
|
+
if err := json.Unmarshal([]byte(args[0].String()), &payload); err != nil {
|
|
345
|
+
return errorValue("bad renderHugoPartials payload: %s", err)
|
|
346
|
+
}
|
|
347
|
+
reqs := payload.Requests
|
|
348
|
+
if len(reqs) == 0 {
|
|
349
|
+
return errorValue("renderHugoPartials requires at least one request")
|
|
350
|
+
}
|
|
351
|
+
if err := builder.buildIfDirty(); err != nil {
|
|
352
|
+
return errorValue("editor site build failed: %s", err)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for _, req := range reqs {
|
|
356
|
+
if req.Partial == "" {
|
|
357
|
+
return errorValue("renderHugoPartials requires a \"partial\" name on every request")
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
html, err := builder.renderBatch(payload.Target, reqs)
|
|
362
|
+
if err != nil {
|
|
363
|
+
return errorValue("%s", err)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return js.ValueOf(map[string]interface{}{
|
|
367
|
+
"html": html,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
func (builder *editorSiteBuilder) renderBatch(target string, reqs []renderRequest) (string, error) {
|
|
372
|
+
requests := make(map[string]interface{}, len(reqs))
|
|
373
|
+
for _, req := range reqs {
|
|
374
|
+
var props interface{}
|
|
375
|
+
if len(req.Props) > 0 {
|
|
376
|
+
if err := json.Unmarshal(req.Props, &props); err != nil {
|
|
377
|
+
return "", fmt.Errorf("bad props for %s: %s", req.Partial, err)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
requests[req.ID] = map[string]interface{}{
|
|
381
|
+
"partial": req.Partial,
|
|
382
|
+
"props": props,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Decode the batch through Hugo's YAML decoder so props decode with identical types
|
|
386
|
+
encoded, err := json.Marshal(requests)
|
|
387
|
+
if err != nil {
|
|
388
|
+
return "", fmt.Errorf("failed to encode render batch: %s", err)
|
|
389
|
+
}
|
|
390
|
+
decoded, err := metadecoders.Decoder{}.UnmarshalToMap(encoded, metadecoders.YAML)
|
|
391
|
+
if err != nil {
|
|
392
|
+
return "", fmt.Errorf("failed to decode render batch: %s", err)
|
|
393
|
+
}
|
|
394
|
+
hmaps.PrepareParams(decoded)
|
|
395
|
+
|
|
396
|
+
renderTarget := builder.targetPage(target)
|
|
397
|
+
if renderTarget == nil {
|
|
398
|
+
renderTarget = page.NopPage
|
|
399
|
+
}
|
|
400
|
+
store := builder.Sites.GetTemplateStore()
|
|
401
|
+
ctx := store.PrepareTopLevelRenderCtx(context.Background(), renderTarget)
|
|
402
|
+
tmpl := store.LookupByPath(dispatchViewPath)
|
|
403
|
+
if tmpl == nil {
|
|
404
|
+
return "", fmt.Errorf("dispatch view %q not found", dispatchViewPath)
|
|
405
|
+
}
|
|
406
|
+
var out strings.Builder
|
|
407
|
+
if err := store.ExecuteWithContext(ctx, tmpl, &out,
|
|
408
|
+
map[string]interface{}{"cc_requests": decoded}); err != nil {
|
|
409
|
+
return "", fmt.Errorf("failed to render the dispatch view for %q: %w", target, err)
|
|
410
|
+
}
|
|
411
|
+
return out.String(), nil
|
|
412
|
+
}
|
|
@@ -136,7 +136,13 @@ async function materialiseFile(file) {
|
|
|
136
136
|
*/
|
|
137
137
|
export async function buildPageData() {
|
|
138
138
|
await apiLoadedPromise;
|
|
139
|
-
|
|
139
|
+
let file;
|
|
140
|
+
try {
|
|
141
|
+
file = CloudCannon?.currentFile?.();
|
|
142
|
+
} catch {
|
|
143
|
+
// No current file (page with no associated source).
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
140
146
|
if (!file) return {};
|
|
141
147
|
const inputPath = file.path;
|
|
142
148
|
const data = (await file.data.get()) ?? {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcannon/editable-regions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Visual Editing for the CloudCannon CMS.",
|
|
6
6
|
"keywords": [
|
|
@@ -25,13 +25,19 @@
|
|
|
25
25
|
"typecheck:watch": "tsc --noEmit --watch",
|
|
26
26
|
"lint-autofix": "biome check --fix",
|
|
27
27
|
"lint": "biome check",
|
|
28
|
+
"build:hugo": "bash integrations/hugo/renderer/build.sh",
|
|
28
29
|
"test": "vitest run",
|
|
29
30
|
"test:watch": "vitest",
|
|
30
31
|
"test:update-snapshots": "vitest run -u",
|
|
31
|
-
"test:build-fixtures": "npm run test:build-astro-fixture && npm run test:build-eleventy-fixture && npm run test:build-eleventy-plugin-config",
|
|
32
|
+
"test:build-fixtures": "npm run test:build-astro-fixture && npm run test:build-eleventy-fixture && npm run test:build-eleventy-plugin-config && npm run test:build-hugo-fixture && npm run test:build-hugo-custom-dirs && npm run test:build-hugo-config-options && npm run test:build-hugo-templates-overrides",
|
|
33
|
+
"test:build-hugo-custom-dirs": "npm --prefix test/unit/_fixtures/hugo-custom-dirs run build",
|
|
34
|
+
"test:build-hugo-config-options": "npm --prefix test/unit/_fixtures/hugo-config-options run build",
|
|
35
|
+
"test:build-hugo-templates-overrides": "npm --prefix test/unit/_fixtures/hugo-templates-overrides run build",
|
|
36
|
+
"test:build-hugo-fixtures": "npm run test:build-hugo-fixture && npm run test:build-hugo-custom-dirs && npm run test:build-hugo-config-options && npm run test:build-hugo-templates-overrides",
|
|
32
37
|
"test:build-astro-fixture": "npm --prefix test/unit/_fixtures/astro install --silent && npm --prefix test/unit/_fixtures/astro run build",
|
|
33
38
|
"test:build-eleventy-fixture": "npm --prefix test/unit/_fixtures/eleventy install --silent && npm --prefix test/unit/_fixtures/eleventy run build",
|
|
34
|
-
"test:build-eleventy-plugin-config": "npm --prefix test/unit/_fixtures/eleventy-plugin-config install --silent && npm --prefix test/unit/_fixtures/eleventy-plugin-config run build"
|
|
39
|
+
"test:build-eleventy-plugin-config": "npm --prefix test/unit/_fixtures/eleventy-plugin-config install --silent && npm --prefix test/unit/_fixtures/eleventy-plugin-config run build",
|
|
40
|
+
"test:build-hugo-fixture": "npm --prefix test/unit/_fixtures/hugo run build"
|
|
35
41
|
},
|
|
36
42
|
"files": [
|
|
37
43
|
"components",
|
|
@@ -39,7 +45,9 @@
|
|
|
39
45
|
"integrations",
|
|
40
46
|
"nodes",
|
|
41
47
|
"styles",
|
|
42
|
-
"types"
|
|
48
|
+
"types",
|
|
49
|
+
"!integrations/hugo/renderer/hugo_renderer.wasm",
|
|
50
|
+
"!integrations/hugo/hugo-module/assets/_cloudcannon"
|
|
43
51
|
],
|
|
44
52
|
"exports": {
|
|
45
53
|
"./*": null,
|
|
@@ -87,36 +95,40 @@
|
|
|
87
95
|
}
|
|
88
96
|
},
|
|
89
97
|
"./eleventy/browser": "./integrations/eleventy/browser/index.mjs",
|
|
98
|
+
"./hugo/browser": {
|
|
99
|
+
"types": "./types/hugo.d.ts",
|
|
100
|
+
"default": "./integrations/hugo/browser/index.ts"
|
|
101
|
+
},
|
|
90
102
|
"./internal/components": "./components/index.js",
|
|
91
103
|
"./internal/styles": "./styles/index.js"
|
|
92
104
|
},
|
|
93
105
|
"devDependencies": {
|
|
94
|
-
"@astrojs/react": "6.0.
|
|
106
|
+
"@astrojs/react": "6.0.2",
|
|
95
107
|
"@astrojs/svelte": "9.0.1",
|
|
96
|
-
"@astrojs/vue": "7.0.
|
|
97
|
-
"@biomejs/biome": "2.5.
|
|
98
|
-
"@cloudcannon/visual-editor-api": "0.0.
|
|
108
|
+
"@astrojs/vue": "7.0.2",
|
|
109
|
+
"@biomejs/biome": "2.5.8",
|
|
110
|
+
"@cloudcannon/visual-editor-api": "0.0.20",
|
|
99
111
|
"@sindresorhus/slugify": "3.0.0",
|
|
100
|
-
"@sveltejs/vite-plugin-svelte": "7.
|
|
112
|
+
"@sveltejs/vite-plugin-svelte": "7.3.0",
|
|
101
113
|
"@types/js-beautify": "1.14.3",
|
|
102
|
-
"@types/node": "26.
|
|
103
|
-
"@types/react": "19.2.
|
|
104
|
-
"@types/react-dom": "19.2.
|
|
114
|
+
"@types/node": "26.2.0",
|
|
115
|
+
"@types/react": "19.2.18",
|
|
116
|
+
"@types/react-dom": "19.2.4",
|
|
105
117
|
"@vitejs/plugin-vue": "6.0.8",
|
|
106
|
-
"astro": "7.
|
|
107
|
-
"happy-dom": "20.11.
|
|
118
|
+
"astro": "7.2.2",
|
|
119
|
+
"happy-dom": "20.11.2",
|
|
108
120
|
"js-beautify": "2.0.3",
|
|
109
|
-
"liquidjs": "10.
|
|
121
|
+
"liquidjs": "10.29.0",
|
|
110
122
|
"react": "19.2.8",
|
|
111
123
|
"react-dom": "19.2.8",
|
|
112
124
|
"slugify": "1.6.9",
|
|
113
|
-
"svelte": "5.56.
|
|
125
|
+
"svelte": "5.56.9",
|
|
114
126
|
"typescript": "6.0.3",
|
|
115
127
|
"vitest": "4.1.10",
|
|
116
|
-
"vue": "3.5.
|
|
117
|
-
"vue-tsc": "3.3.
|
|
128
|
+
"vue": "3.5.41",
|
|
129
|
+
"vue-tsc": "3.3.10"
|
|
118
130
|
},
|
|
119
131
|
"dependencies": {
|
|
120
|
-
"esbuild": "0.28.
|
|
132
|
+
"esbuild": "0.28.2"
|
|
121
133
|
}
|
|
122
134
|
}
|
package/types/hugo.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CloudCannonVisualEditorAPIRouter,
|
|
3
|
+
CloudCannonVisualEditorAPIV0,
|
|
4
|
+
CloudCannonVisualEditorAPIV1,
|
|
5
|
+
} from "@cloudcannon/visual-editor-api";
|
|
6
|
+
|
|
7
|
+
declare module "@cloudcannon/editable-regions/hugo/browser" {
|
|
8
|
+
/**
|
|
9
|
+
* Boots Hugo live editing from the `window.cc_hugo*` globals emitted by
|
|
10
|
+
* the Hugo module's snapshot prelude. Installs the component proxy
|
|
11
|
+
* immediately; the WASM renderer loads once the CloudCannon Visual Editor
|
|
12
|
+
* API appears.
|
|
13
|
+
*/
|
|
14
|
+
export function initHugoLiveEditing(): void;
|
|
15
|
+
|
|
16
|
+
/** Starts (or returns the in-flight start of) the WASM renderer. */
|
|
17
|
+
export function ensureEngine(): Promise<void>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Wraps `window.cc_components` in a Proxy manufacturing a renderer for
|
|
21
|
+
* any component name on demand; partial existence is decided by the Hugo
|
|
22
|
+
* renderer at render time. Called by `initHugoLiveEditing`.
|
|
23
|
+
*/
|
|
24
|
+
export function initComponentProxy(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare global {
|
|
28
|
+
/** Snapshot metadata emitted onto `window.cc_hugo` by the module's prelude. */
|
|
29
|
+
interface HugoRuntimeMeta {
|
|
30
|
+
generator?: string;
|
|
31
|
+
wasmUrl?: string;
|
|
32
|
+
verbose?: boolean;
|
|
33
|
+
env?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Result from the editor-site mutation entry points. */
|
|
37
|
+
interface HugoEditorResult {
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Result from `renderHugoPartials`. */
|
|
42
|
+
interface HugoRenderResult {
|
|
43
|
+
html?: string;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The Hugo runtime's `window` doubles as the CloudCannon Visual Editor
|
|
49
|
+
* window and carries the snapshot globals emitted by the module's prelude.
|
|
50
|
+
*/
|
|
51
|
+
interface Window {
|
|
52
|
+
/** CloudCannon's versioned API router (present inside the Visual Editor). */
|
|
53
|
+
CloudCannonAPI?: CloudCannonVisualEditorAPIRouter;
|
|
54
|
+
/** The installed v0/v1 CloudCannon API for this page. */
|
|
55
|
+
CloudCannon?: CloudCannonVisualEditorAPIV0 | CloudCannonVisualEditorAPIV1;
|
|
56
|
+
/** Emitter metadata: generator, wasmUrl, verbose, env. */
|
|
57
|
+
cc_hugo?: HugoRuntimeMeta;
|
|
58
|
+
/** Template/config snapshot keyed by physical path. */
|
|
59
|
+
cc_hugo_files?: Record<string, string>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The Hugo WASM renderer exposes these functions on `globalThis` once it
|
|
63
|
+
// boots; the runtime calls them directly after the engine is ready.
|
|
64
|
+
function writeHugoFiles(json: string): HugoEditorResult | null;
|
|
65
|
+
function removeHugoFiles(json: string): HugoEditorResult | null;
|
|
66
|
+
function readHugoFiles(json: string): Record<string, string>;
|
|
67
|
+
function initHugoEditorSite(): HugoEditorResult | null;
|
|
68
|
+
function renderHugoPartials(json: string): HugoRenderResult | null;
|
|
69
|
+
}
|