Skip to content

Instantly share code, notes, and snippets.

View kmelve's full-sized avatar
💬
is typing

Knut Melvær kmelve

💬
is typing
View GitHub Profile
@kmelve
kmelve / 00-README.md
Created April 2, 2026 16:08
Em Dash Easter Egg for Sanity + Portable Text — click any em dash to see if it was human-typed or AI-placed

Em Dash Easter Egg

An easter egg for blogs using Sanity and Portable Text: every em dash (—) becomes clickable, revealing whether it was typed by a human or placed by AI.

Human-certified dashes show playful messages like "Certified organic em dash" or "Artisanal punctuation." Uncertified dashes (assumed AI) show messages like "Machine-generated punctuation" or "Algorithmically optimized dash."

How it works

The feature has four layers:

@kmelve
kmelve / gist-bodyPortableText-schema.ts
Created March 17, 2026 20:06
Footnotes in Sanity Portable Text + Next.js — minimal reproducible implementation
// Schema: main body Portable Text with footnote annotation
// This shows how to register footnote as a mark annotation
import { BlockquoteIcon, LaunchIcon } from "@sanity/icons";
import { defineArrayMember, defineField, defineType } from "sanity";
export const bodyPortableText = defineType({
name: "bodyPortableText",
type: "array",
title: "Content",
@kmelve
kmelve / plan.md
Created January 31, 2026 18:31
Reusable Page Blocks in Sanity - Implementation Guide

Reusable Page Blocks in Sanity - Implementation Guide

A pattern for creating once, using everywhere: how to implement reusable content blocks in Sanity that can be shared across multiple pages.


Overview

This pattern allows content editors to create a page block (hero, CTA, feature section, etc.) once as a standalone document, then reference it from multiple pages. Changes to the reusable block automatically propagate everywhere it's used.

@kmelve
kmelve / LinkedInEmbedInput.tsx
Last active September 18, 2024 15:19
LinkedIn Embed Input for Sanity Studio
import React, { useState } from "react"
import { TextInput, Stack } from "@sanity/ui"
import { set, unset } from "sanity"
interface LinkedInEmbed {
_key: string
_type: string
postUrl: string
height: number
}
@kmelve
kmelve / BlockEditor.ts
Created May 24, 2024 11:10
Markdown to Portable Text paste handler
import { toPlainText } from "@portabletext/react"
import { BlockEditor as DefaultBlockEditor } from "sanity"
import { handlePaste } from "~/studio/components/blockEditor/handlePaste"
const wordsPerMinute = 200
export default function BlockEditor(props: any, ref) {
const value = props.value ?? []
const plainText = toPlainText(value)
const characterCount = plainText.length
@kmelve
kmelve / page.tsx
Created May 17, 2024 17:39
Minimal totally-not-ready-for-prod Todo list example using Sanity Live Content API and Next.js Server Actions
import { redirect } from "next/navigation"
import { createClient } from "next-sanity"
const client = createClient({
apiVersion: "vX",
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
token: process.env.SANITY_WRITE_TOKEN,
useCdn: true,
})
@kmelve
kmelve / post.groq
Created June 23, 2023 06:22
How to join referenced category documents for a post with GROQ
// Try it out here: https://groq.dev/iZPFWScPZXANqgFGQgoUH2
*[_type == "post"]{
...,
// join into an array of just the titles
"categoryTitles": categories[]->title,
// join the completedocuments
"categoryDocs": categories[]->,
// join and project specific fields
categories[]->{
import {defineConfig, type Role} from 'sanity'
import {deskTool} from 'sanity/desk'
import {visionTool} from '@sanity/vision'
import {schemaTypes} from './schemas'
export const EDITOR_TYPES = ['post']
const isAdmin = (roles: Role[]) => !roles?.find(({name = ''}) => name === 'administrator')
export default defineConfig({
@kmelve
kmelve / handlePaste.js
Last active October 7, 2022 21:38
Markdown Paste Handling for Sanity Studio v2 using micromark
/* Remember:
sanity install @sanity/code-input
yarn add micromark 
*/
import { micromark } from 'micromark'
import { htmlToBlocks } from '@sanity/block-tools'
export async function handlePaste(input) {
const { event, type, path } = input
const text = event.clipboardData.getData('text/plain')
@kmelve
kmelve / import_json_from_sanity_appsscript.js
Last active September 22, 2022 07:31 — forked from paulgambill/import_json_appsscript.js
Adjusted script for working with the Sanity API
/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();