Skip to content

Commit

Permalink
feat/blog-post-scripts (#823)
Browse files Browse the repository at this point in the history
* feat: enable scripts to run in blog posts

* fix: use onMounted to ensure code is always executed on client with the DOM available

* feat: add mounted and unmounted scripts

* remove .nvmrc

* docs: expand on hint in DatoCMS

* setup concurrency limits when building site

---------

Co-authored-by: Jurgen Beliën <[email protected]>
  • Loading branch information
WesselSmit and jurgenbelien authored Jan 16, 2025
1 parent 1fde16d commit 33be5c2
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 2 deletions.
77 changes: 77 additions & 0 deletions migrations/1725011361_addScriptFieldsToBlogPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Client, SimpleSchemaTypes } from "@datocms/cli/lib/cma-client-node";

export default async function (client: Client) {
console.log("Creating new fields/fieldsets");

console.log(
'Create fieldset "\uD83E\uDE84 Scripts" in model "Blog post" (`blog_post`)'
);
await client.fieldsets.create("38241", {
id: "IKdvIfTYR2aXiSu_uTucYg",
title: "\uD83E\uDE84 Scripts",
hint: "Javascript script that will be executed on the client side of the rendered page. Can be used to make your blog posts more interactive through the use of Web Components etc. The scripts will be wrapped in IIFEs.\n\nRemember that Web Components should have unique names, so consider adding a suffix to the class and the tag.",
collapsible: true,
start_collapsed: true,
});

console.log(
'Create Multiple-paragraph text field "OnMounted Script" (`on_mounted_script`) in model "Blog post" (`blog_post`)'
);
await client.fields.create("38241", {
id: "Z0w2kERXQumhXvr5nEwuRQ",
label: "OnMounted Script",
field_type: "text",
api_key: "on_mounted_script",
hint: "Will be called after the component has mounted, so you have access to the DOM. If you are using web-components, make sure to check if custom element is already defined in the CustomElementRegistry before defining a new web-component (unregistering is not possible, so this is necessary to prevent runtime errors).",
appearance: {
addons: [],
editor: "textarea",
parameters: { placeholder: null },
type: "textarea",
},
default_value: "",
fieldset: { id: "IKdvIfTYR2aXiSu_uTucYg", type: "fieldset" },
});

console.log(
'Create Multiple-paragraph text field "OnUnmounted Script" (`on_unmounted_script`) in model "Blog post" (`blog_post`)'
);
await client.fields.create("38241", {
id: "dtwrtWI0S5u_hKSBk-k6TQ",
label: "OnUnmounted Script",
field_type: "text",
api_key: "on_unmounted_script",
hint: "Should be used to clean up artefacts from your `OnMounted` script.",
appearance: {
addons: [],
editor: "textarea",
parameters: { placeholder: null },
type: "textarea",
},
default_value: "",
fieldset: { id: "IKdvIfTYR2aXiSu_uTucYg", type: "fieldset" },
});

console.log("Update existing fields/fieldsets");

console.log(
'Update Multiple-paragraph text field "OnMounted Script" (`on_mounted_script`) in model "Blog post" (`blog_post`)'
);
await client.fields.update("Z0w2kERXQumhXvr5nEwuRQ", { position: 0 });

console.log(
'Update Multiple-paragraph text field "OnUnmounted Script" (`on_unmounted_script`) in model "Blog post" (`blog_post`)'
);
await client.fields.update("dtwrtWI0S5u_hKSBk-k6TQ", { position: 1 });

console.log("Add new translations");

await client.items.create({
item_type: { type: 'item_type', id: '55443' },
key: 'blog_post_noscript',
value: {
en: 'Normally some javascript would be used to improve the user experience of the page. Since you have javascript disabled, it is possible that some functionalities will not work.',
nl: 'Normaal gesproken wordt er javascript gebruikt om de gebruikerservaring van de pagina te verbeteren. Omdat je javascript hebt uitgeschakeld, is het mogelijk dat sommige functies niet werken.',
}
});
}
4 changes: 3 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export default defineNuxtConfig({
prerender: {
crawlLinks: false,
// need at least one route to trigger the 'prerender:routes' hook
routes: [`/${defaultLanguage}/`]
routes: [`/${defaultLanguage}/`],
concurrency: 35, // stay below 40 to avoid rate limiting
interval: 1000, // use 1 second interval to avoid rate limiting
},
routeRules: {
'/': { prerender: false },
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"datocms-listen": "^0.1.14",
"datocms-structured-text-utils": "^2.0.4",
"dayjs": "^1.11.10",
"nanoid": "^5.0.7",
"nuxt": "^3.5.3",
"nuxt-icons": "^3.2.1",
"prismjs": "^1.29.0",
Expand Down
56 changes: 56 additions & 0 deletions src/components/custom-script/custom-script.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<div class="custom-script">
<!-- we show a <noscript> element if the user has javascript disabled, this is necessary because some functionality might be broken due to javascript being disabled and its important that users are notified why it isn't working -->
<!-- this needs to be v-html instead of your typical {{ someValue }} interpolation since that would not be to be evaluated by Vue -->
<noscript
v-html="$t('blog_post_noscript')"
class="rich-text body-big"
/>
</div>
</template>

<script lang="ts" setup>
import { nanoid } from 'nanoid';
const props = defineProps<{
mountScript: string;
unmountScript?: string;
}>()
const onMountScriptId = nanoid()
const onUnmountScriptId = nanoid()
onMounted(() => {
runScriptInBrowser(props.mountScript, onMountScriptId);
});
onUnmounted(() => {
const mountScript = document.getElementById(onMountScriptId);
mountScript?.remove();
if (props.unmountScript) {
const unmountScript = runScriptInBrowser(props.unmountScript, onUnmountScriptId);
unmountScript?.remove();
}
});
function runScriptInBrowser(code: string, id: string) {
const script = document.createElement('script');
script.innerHTML = `(function() { ${code} })()`;
script.setAttribute('id', id);
document.body.appendChild(script);
return script;
}
</script>

<style>
.custom-script {
margin-block-start: var(--spacing-medium);
padding: var(--spacing-larger);
background: var(--base2);
}
</style>
2 changes: 1 addition & 1 deletion src/constants.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const datocmsEnvironment = 'section-video-deploy';
export const datocmsEnvironment = 'blog-post-scripts';
export const mastodonUrl = 'https://fosstodon.org/@devoorhoede';
2 changes: 2 additions & 0 deletions src/pages/[language]/blog/[slug]/index.query.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ fragment page on BlogPostRecord {
...blogPostListItem
}
}
onMountedScript
onUnmountedScript
}

fragment blogPostListItem on BlogPostRecord {
Expand Down
6 changes: 6 additions & 0 deletions src/pages/[language]/blog/[slug]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@
</div>
</template>

<custom-script
v-if="data.page.onMountedScript"
:mount-script="data.page.onMountedScript"
:unmount-script="data.page.onUnmountedScript"
/>

<section v-if="relatedBlogPosts.length">
<h2 class="h3 page-blog-post__related-blog-posts-title">
{{ $t('related_blog_posts') }}
Expand Down

0 comments on commit 33be5c2

Please sign in to comment.