Skip to content

Commit

Permalink
feat(hugo): provide site string functions in the visual editor
Browse files Browse the repository at this point in the history
`{{ site.BaseURL }}`, `{{ site.Title }}`, and `{{ site.Copyright }}`
will now work in the visual editor, with the correct values
from your Hugo build.
  • Loading branch information
bglw committed Mar 25, 2022
1 parent 798e7ed commit a71e1ea
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 63 deletions.
2 changes: 1 addition & 1 deletion hugo/v2/core/bookshop_bindings.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Expects one argument, a string to use as the data binding in CloudCannon
*/}}

{{- (printf `<!--bookshop-live meta(version: "2.6.1")-->`) | safeHTML }}
{{- (printf `<!--bookshop-live meta(version: "2.6.1" baseurl: "%s" copyright: "%s" title: "%s")-->` site.BaseURL site.Copyright site.Title) | safeHTML }}
{{ $is_string := eq "string" (printf "%T" .) -}}

{{- if $is_string -}}
Expand Down
2 changes: 1 addition & 1 deletion javascript-modules/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@
"engines": {
"node": ">=14.16"
}
}
}
18 changes: 11 additions & 7 deletions javascript-modules/engines/eleventy-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class Engine {
this.plugins.push(unbind, slug, loopContext);

this.meta = {};
this.info = {};
this.plugins.push(urlFilterBuilder(this.meta));

this.initializeLiquid();
Expand Down Expand Up @@ -101,19 +102,23 @@ export class Engine {
return false;
}

injectInfo(props, info = {}, meta = {}) {
injectInfo(props) {
return {
...(info.collections || {}),
...(info.data || {}),
...(this.info.collections || {}),
...(this.info.data || {}),
...props,
};
}

async updateMeta(meta = {}) {
async storeMeta(meta = {}) {
this.meta.pathPrefix = meta.pathPrefix ? await this.eval(meta.pathPrefix) : undefined;
}

async render(target, name, props, globals, cloudcannonInfo, meta, logger) {
async storeInfo(info = {}) {
this.info = info;
}

async render(target, name, props, globals, logger) {
let source = this.getComponent(name);
// TODO: Remove the below check and update the live comments to denote shared
if (!source) source = this.getShared(name);
Expand All @@ -125,8 +130,7 @@ export class Engine {
source = translateLiquid(source);
logger?.log?.(`Rewritten the template for ${name}`);
if (!globals || typeof globals !== "object") globals = {};
props = this.injectInfo({ ...globals, ...props }, cloudcannonInfo, meta);
await this.updateMeta(meta);
props = this.injectInfo({ ...globals, ...props });
logger?.log?.(`Rendered ${name}`);
target.innerHTML = await this.liquid.parseAndRender(source || "", props);
}
Expand Down
7 changes: 7 additions & 0 deletions javascript-modules/engines/hugo-engine/hugo-renderer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func main() {
js.Global().Set("renderHugo", js.FuncOf(renderHugo))
js.Global().Set("loadHugoBookshopPartials", js.FuncOf(loadHugoBookshopPartials))
js.Global().Set("loadHugoBookshopData", js.FuncOf(loadHugoBookshopData))
js.Global().Set("loadHugoBookshopMeta", js.FuncOf(loadHugoBookshopMeta))
<-c
}

Expand All @@ -33,3 +34,9 @@ func loadHugoBookshopData(this js.Value, args []js.Value) interface{} {

return library.LoadHugoBookshopData(bookshopData)
}

func loadHugoBookshopMeta(this js.Value, args []js.Value) interface{} {
bookshopMeta := args[0].String()

return library.LoadHugoBookshopMeta(bookshopMeta)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,24 @@ func LoadHugoBookshopData(data string) interface{} {
func RetrieveHugoBookshopData() *bookshopData {
return &bookshopDataStorage
}

var bookshopMetaStorage map[string]interface{}

// loadHugoBookshopMeta takes any data that hugo/bookshop
// injected into the template and stashes it all away
// on this side of the wasm boundary, for the site
// function to use.
func LoadHugoBookshopMeta(data string) interface{} {
err := json.Unmarshal([]byte(data), &bookshopMetaStorage)
if err != nil {
buf := bytes.NewBufferString(fmt.Sprintf("bad json unmarshal: %s", err.Error()))
return buf.String()
}

buf := bytes.NewBufferString("loaded Bookshop site data")
return buf.String()
}

func RetrieveHugoBookshopMeta() map[string]interface{} {
return bookshopMetaStorage
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,24 @@ func (ns *Namespace) Data() (map[string]interface{}, error) {

return data.Data, nil
}

// site.Title should have been stashed inside a Bookshop meta comment
func (ns *Namespace) Title() (interface{}, error) {
meta := library.RetrieveHugoBookshopMeta()

return meta["title"], nil
}

// site.Copyright should have been stashed inside a Bookshop meta comment
func (ns *Namespace) Copyright() (interface{}, error) {
meta := library.RetrieveHugoBookshopMeta()

return meta["copyright"], nil
}

// site.BaseURL should have been stashed inside a Bookshop meta comment
func (ns *Namespace) BaseURL() (interface{}, error) {
meta := library.RetrieveHugoBookshopMeta()

return meta["baseurl"], nil
}
18 changes: 15 additions & 3 deletions javascript-modules/engines/hugo-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,25 @@ export class Engine {
};
}

async render(target, name, props, globals, cloudcannonInfo, meta, logger) {
async storeMeta(meta = {}) {
while (!window.loadHugoBookshopMeta) {
await sleep(100);
};
window.loadHugoBookshopMeta(JSON.stringify(meta));
}

async storeInfo(info = {}) {
while (!window.loadHugoBookshopData) {
await sleep(100);
};
window.loadHugoBookshopData(JSON.stringify(info));
}

async render(target, name, props, globals, logger) {
while (!window.renderHugo) {
logger?.log?.(`Waiting for the Hugo WASM to be available...`);
await sleep(100);
};
logger?.log?.(`Moving cloudcannonInfo across the WASM boundary`);
window.loadHugoBookshopData(JSON.stringify(cloudcannonInfo));

let source = this.getComponent(name);
// TODO: Remove the below check and update the live comments to denote shared
Expand Down
23 changes: 14 additions & 9 deletions javascript-modules/engines/jekyll-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class Engine {
this.plugins.push(jsonify, slugify, unbind, emulateJekyll, local, liquidHighlight, loop_context, markdownify);

this.meta = {};
this.info = {};
this.plugins.push(relativeUrlFilterBuilder(this.meta));

this.initializeLiquid();
Expand Down Expand Up @@ -119,23 +120,28 @@ export class Engine {
};
}

injectInfo(props, info = {}, meta = {}) {
injectInfo(props) {
return {
site: {
...(info.collections || {}),
data: (info.data || {}),
baseurl: meta.baseurl || "",
title: meta.title || "",
...(this.info.collections || {}),
data: (this.info.data || {}),
baseurl: this.meta.baseurl || "",
title: this.meta.title || "",
},
...props,
};
}

async updateMeta(meta = {}) {
async storeMeta(meta = {}) {
this.meta.baseurl = meta.baseurl ? await this.eval(meta.baseurl) : undefined;
this.meta.title = meta.title ? await this.eval(meta.title) : undefined;
}

async render(target, name, props, globals, cloudcannonInfo, meta, logger) {
async storeInfo(info = {}) {
this.info = info;
}

async render(target, name, props, globals, logger) {
let source = this.getComponent(name);
// TODO: Remove the below check and update the live comments to denote shared
if (!source) source = this.getShared(name);
Expand All @@ -147,8 +153,7 @@ export class Engine {
source = translateLiquid(source, {});
logger?.log?.(`Rewritten the template for ${name}`);
if (!globals || typeof globals !== "object") globals = {};
props = this.injectInfo({ ...globals, include: props }, cloudcannonInfo, meta);
await this.updateMeta(meta);
props = this.injectInfo({ ...globals, include: props });
logger?.log?.(`Rendered ${name}`);
target.innerHTML = await this.liquid.parseAndRender(source || "", props);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Feature: Hugo Bookshop CloudCannon Integration
And stdout should contain "Total in"
And site/public/index.html should contain the text:
"""
<!--bookshop-live meta(version: "[version]")-->
<!--bookshop-live meta(version: "[version]" baseurl: "https://bookshop.build/" copyright: "🎉" title: "Hugo Bookshop Cucumber")-->
<!--bookshop-live name(__bookshop__subsequent) params(.: .Params)-->
<!--bookshop-live name(page)-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,6 @@ Feature: Hugo Bookshop CloudCannon Live Editing Site Data
go.mod from starters/hugo/site.go.mod
config.toml from starters/hugo/site.config.toml
"""

Scenario: Bookshop live renders website data
Given a site/cloudcannon.config.yml file containing:
"""
data_config: true
"""
Given a site/data/cat.yml file containing:
"""
name: Cheeka
"""
* a component-lib/components/cat/cat.hugo.html file containing:
"""
<h1>{{ if .show }}{{ site.Data.cat.name }}{{ end }}</h1>
"""
* [front_matter]:
"""
show: false
Expand All @@ -43,10 +29,24 @@ Feature: Hugo Bookshop CloudCannon Live Editing Site Data
<html>
<body>
{{ partial "bookshop_bindings" `(dict "show" .Params.show)` }}
{{ partial "bookshop" (slice "cat" (dict "show" .Params.show)) }}
{{ partial "bookshop" (slice "block" (dict "show" .Params.show)) }}
</body>
</html>
"""

Scenario: Bookshop live renders website data
Given a site/cloudcannon.config.yml file containing:
"""
data_config: true
"""
Given a site/data/cat.yml file containing:
"""
name: Cheeka
"""
* a component-lib/components/block/block.hugo.html file containing:
"""
<h1>{{ if .show }}{{ site.Data.cat.name }}{{ end }}</h1>
"""
* 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
Expand Down Expand Up @@ -74,32 +74,32 @@ Feature: Hugo Bookshop CloudCannon Live Editing Site Data
useful: yes
messy: yes
"""
* a component-lib/components/cat/cat.hugo.html file containing:
* a component-lib/components/block/block.hugo.html file containing:
"""
{{ if .show }}
{{ range site.Data.cats }}
<p>{{ .name }} — {{ .useful }}/{{ .messy }}</p>
{{ end }}
{{ end }}
"""
* [front_matter]:
"""
show: false
"""
* a site/content/_index.md file containing:
* 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
---
[front_matter]
---
show: true
"""
* a site/layouts/index.html file containing:
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector p:nth-of-type(1) should contain "Cheeka — no/no"
* 🌐 The selector p:nth-of-type(2) should contain "Smudge — yes/yes"

Scenario: Bookshop live renders special website config
Given a component-lib/components/block/block.hugo.html file containing:
"""
<html>
<body>
{{ partial "bookshop_bindings" `(dict "show" .Params.show)` }}
{{ partial "bookshop" (slice "cat" (dict "show" .Params.show)) }}
</body>
</html>
{{ if .show }}
<h1>{{ site.BaseURL }}</h1>
<h2>{{ site.Copyright }}</h2>
<h3>{{ site.Title }}</h3>
{{ end }}
"""
* 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
Expand All @@ -108,5 +108,6 @@ Feature: Hugo Bookshop CloudCannon Live Editing Site Data
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector p:nth-of-type(1) should contain "Cheeka — no/no"
* 🌐 The selector p:nth-of-type(2) should contain "Smudge — yes/yes"
* 🌐 The selector h1 should contain "https://bookshop.build/"
* 🌐 The selector h2 should contain "🎉"
* 🌐 The selector h3 should contain "Hugo Bookshop Cucumber"
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ Feature: Jekyll Bookshop CloudCannon Live Editing Site Data
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "/documentation"
* 🌐 The selector h2 should contain "/documentation/home"
* 🌐 The selector h3 should contain "My Site"
* 🌐 The selector h3 should contain "My Site"
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
baseURL = "http://example.org/"
baseURL = "https://bookshop.build/"
languageCode = "en-us"
title = "Hugo Bookshop Starter"
title = "Hugo Bookshop Cucumber"
copyright = "🎉"

[module]
replacements = "bookshop.test/components -> ../../component-lib,github.com/cloudcannon/bookshop/hugo/v2 -> ../../../../../../hugo/v2/"
Expand Down
6 changes: 3 additions & 3 deletions javascript-modules/live/lib/app/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ const evaluateTemplate = async (liveInstance, documentNode, parentPathStack, tem
);
}
}

await liveInstance.storeMeta(meta);
}

for (const [name, identifier] of parseParams(liveTag?.context)) {
Expand Down Expand Up @@ -233,7 +235,6 @@ const evaluateTemplate = async (liveInstance, documentNode, parentPathStack, tem
params,
stashedNodes,
depth: stack.length - 1,
meta,
});
stashedParams = [];
stashedNodes = [];
Expand All @@ -260,7 +261,7 @@ export const renderComponentUpdates = async (liveInstance, documentNode, logger)
const vDom = document.implementation.createHTMLDocument();
const updates = []; // Rendered elements and their DOM locations

const templateBlockHandler = async ({ startNode, endNode, name, scope, pathStack, depth, stashedNodes, meta }, logger) => {
const templateBlockHandler = async ({ startNode, endNode, name, scope, pathStack, depth, stashedNodes }, logger) => {
// We only need to render the outermost component
logger?.log?.(`Received a template block to render for ${name}`);
if (depth) {
Expand All @@ -282,7 +283,6 @@ export const renderComponentUpdates = async (liveInstance, documentNode, logger)
await liveInstance.renderElement(
name,
scope,
meta,
output,
logger?.nested?.(),
)
Expand Down
Loading

0 comments on commit a71e1ea

Please sign in to comment.