publishing meta and endless-journal
This commit is contained in:
parent
fac6ecf030
commit
7bf0ab967b
41 changed files with 1168 additions and 27 deletions
|
|
@ -17,6 +17,7 @@
|
|||
"@tiptap/pm": "^3.28.0",
|
||||
"@tiptap/starter-kit": "^3.28.0",
|
||||
"@tiptap/vue-3": "^3.28.0",
|
||||
"fecha": "^4.2.3",
|
||||
"sass": "^1.98.0",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"vue": "^3.5.30",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/public/drops/endless-journal/favicon.png
Normal file
BIN
app/public/drops/endless-journal/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
14
app/public/drops/endless-journal/index.html
Normal file
14
app/public/drops/endless-journal/index.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/drops/endless-journal/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Endless Journal</title>
|
||||
<script type="module" crossorigin src="/drops/endless-journal/assets/index-D22DIc1j.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/drops/endless-journal/assets/index-CjZd22yH.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
124
app/public/drops/endless-journal/meta.json
Normal file
124
app/public/drops/endless-journal/meta.json
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "endless-journal",
|
||||
"title": "Endless Journal",
|
||||
"date": "2026-07-20",
|
||||
"tags": [
|
||||
"Process"
|
||||
],
|
||||
"description": {
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": " Creativity is following the thread of intuition and seeing where it goes. It's about being in the moment and not looking back."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The process of destruction is just as important as the process of creation. Destruction frees the mind and allows it to always stay on the move, constantly reaching new territory."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "As you type, the letters will begin to pile up as a mass of objects. These letters no longer signify the words you wrote but progress in the creative process. There are three fill lines to signify this progress:"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bulletList",
|
||||
"content": [
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Intuition: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This first stage is about finding that intuitive thread and simply holding onto it to see where it takes you."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Chaos: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The second stage is about throwing out every idea your mind relates to that intuitive thread. Nothing is organized yet and that's a good thing. It's ok if you don't feel like you are getting anywhere yet."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Concept: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "By this stage you will hopefully start to take the mass of chaotic ideas and form them into a single core concept. This is the concept you will use to build your creation."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "If a concept still has not shown itself, that's ok, reload and repeat."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"published": true
|
||||
}
|
||||
|
|
@ -42,5 +42,6 @@
|
|||
"date": "2026-07-16",
|
||||
"tags": [
|
||||
"visualization"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[
|
||||
"flowmap",
|
||||
"testing"
|
||||
"endless-journal",
|
||||
"flowmap"
|
||||
]
|
||||
|
|
@ -27,9 +27,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import Btn from '@shared/Btn.vue'
|
||||
import RichText from '@shared/RichText.vue';
|
||||
import { format, parse } from 'fecha'
|
||||
import Btn from '@shared/Btn.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
drop: Object,
|
||||
|
|
@ -51,14 +52,12 @@ const displayTitle = computed(() => {
|
|||
})
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
if (!props.drop.date) return ''
|
||||
if (!props.drop.date) return ''
|
||||
|
||||
const date = new Date(props.drop.date)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
const [year, month, day] = props.drop.date.split('-')
|
||||
const date = new Date(year, month - 1, day)
|
||||
|
||||
return format(date, 'MMMM Do, YYYY')
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
97
bin/buildDrops.js
Normal file
97
bin/buildDrops.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const dropsDir = path.join(__dirname, '../drops')
|
||||
|
||||
/**
|
||||
* Get all published drops by reading their meta.json files
|
||||
*/
|
||||
const getPublishedDrops = () => {
|
||||
const drops = fs
|
||||
.readdirSync(dropsDir, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
const publishedDrops = drops.filter((dropName) => {
|
||||
const metaPath = path.join(dropsDir, dropName, 'public/meta.json')
|
||||
|
||||
if (!fs.existsSync(metaPath)) {
|
||||
console.warn(
|
||||
`Warning: meta.json not found for drop "${dropName}", skipping...`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
|
||||
return meta.published === true
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error reading meta.json for drop "${dropName}":`,
|
||||
err.message,
|
||||
)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return publishedDrops
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a single drop
|
||||
*/
|
||||
const buildDrop = (dropName) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Building drop: ${dropName}`)
|
||||
|
||||
const child = spawn('npm', ['run', 'build', '-w', `drops/${dropName}`], {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
})
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log(`✓ Successfully built: ${dropName}`)
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`Failed to build ${dropName} (exit code ${code})`))
|
||||
}
|
||||
})
|
||||
|
||||
child.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all published drops
|
||||
*/
|
||||
const buildAllPublishedDrops = async () => {
|
||||
const publishedDrops = getPublishedDrops()
|
||||
|
||||
if (publishedDrops.length === 0) {
|
||||
console.log('No published drops found.')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Found ${publishedDrops.length} published drop(s):`, publishedDrops)
|
||||
console.log('Starting builds...\n')
|
||||
|
||||
for (const dropName of publishedDrops) {
|
||||
try {
|
||||
await buildDrop(dropName)
|
||||
} catch (err) {
|
||||
console.error(`Error building ${dropName}:`, err.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✓ All published drops built successfully!')
|
||||
}
|
||||
|
||||
buildAllPublishedDrops().catch((err) => {
|
||||
console.error('Build failed:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -4,14 +4,90 @@ import { fileURLToPath } from 'url'
|
|||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const dropsDir = path.join(__dirname, '../app/public/drops')
|
||||
const dropsSourceDir = path.join(__dirname, '../drops')
|
||||
const dropsPublicDir = path.join(__dirname, '../app/public/drops')
|
||||
const outputPath = path.join(__dirname, '../app/public/manifest.json')
|
||||
|
||||
const dropNames = fs
|
||||
.readdirSync(dropsDir, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
/**
|
||||
* Get all published drops by reading their meta.json files from the source drops directory
|
||||
*/
|
||||
const getPublishedDrops = () => {
|
||||
const drops = fs
|
||||
.readdirSync(dropsSourceDir, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(dropNames, null, 2))
|
||||
const publishedDrops = drops.filter((dropName) => {
|
||||
const metaPath = path.join(dropsSourceDir, dropName, 'public/meta.json')
|
||||
|
||||
console.log(`Manifest created with ${dropNames.length} drops:`, dropNames)
|
||||
if (!fs.existsSync(metaPath)) {
|
||||
console.warn(
|
||||
`Warning: meta.json not found for drop "${dropName}", skipping...`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
|
||||
return meta.published === true
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error reading meta.json for drop "${dropName}":`,
|
||||
err.message,
|
||||
)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return publishedDrops
|
||||
}
|
||||
|
||||
const publishedDrops = getPublishedDrops()
|
||||
|
||||
// Only include drops that are both published and actually built (exist in public/drops)
|
||||
const builtPublishedDrops = publishedDrops.filter((dropName) => {
|
||||
const dropDir = path.join(dropsPublicDir, dropName)
|
||||
return fs.existsSync(dropDir)
|
||||
})
|
||||
|
||||
/**
|
||||
* Sort drops by date (newest first)
|
||||
*/
|
||||
const sortDropsByDate = (dropNames) => {
|
||||
const dropsWithDates = dropNames.map((dropName) => {
|
||||
const metaPath = path.join(dropsPublicDir, dropName, 'meta.json')
|
||||
let date = null
|
||||
|
||||
if (fs.existsSync(metaPath)) {
|
||||
try {
|
||||
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'))
|
||||
date = meta.date || null
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Warning: Could not read date from built meta.json for "${dropName}"`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return { name: dropName, date }
|
||||
})
|
||||
|
||||
// Sort by date descending (newest first), put drops without dates at the end
|
||||
dropsWithDates.sort((a, b) => {
|
||||
if (!a.date && !b.date) return 0
|
||||
if (!a.date) return 1
|
||||
if (!b.date) return -1
|
||||
return new Date(b.date) - new Date(a.date)
|
||||
})
|
||||
|
||||
return dropsWithDates.map((drop) => drop.name)
|
||||
}
|
||||
|
||||
const sortedDrops = sortDropsByDate(builtPublishedDrops)
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(sortedDrops, null, 2))
|
||||
|
||||
console.log(
|
||||
`Manifest created with ${sortedDrops.length} published drop(s) (sorted by date):`,
|
||||
sortedDrops,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -167,6 +167,8 @@ rl.question('Enter drop name: ', async (name) => {
|
|||
console.log('Running npm install...')
|
||||
await runCommand('npm', ['install'])
|
||||
console.log('npm install complete!')
|
||||
|
||||
await runCommand('npm', ['run dev:drops', kebabName])
|
||||
} catch (err) {
|
||||
console.error('Error:', err.message)
|
||||
process.exit(1)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,22 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<!-- Published -->
|
||||
<div class="form-group checkbox-group">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
id="published"
|
||||
v-model="meta.published"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
/>
|
||||
<span class="checkbox-text">
|
||||
<strong>Published</strong>
|
||||
<small>When checked, this drop will be included in the build</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
|
|
@ -124,6 +140,7 @@ const meta = ref({
|
|||
tags: [],
|
||||
links: [],
|
||||
notes: '',
|
||||
published: false,
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
|
|
@ -310,6 +327,48 @@ onMounted(async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--theme-grey);
|
||||
border-radius: rs(10px);
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--sea);
|
||||
}
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 0.125rem;
|
||||
cursor: pointer;
|
||||
accent-color: var(--sea);
|
||||
|
||||
&:checked {
|
||||
background: var(--theme-contrast);
|
||||
}
|
||||
}
|
||||
.checkbox-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
flex: 1;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--theme-grey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
|
|
|||
13
drops/endless-journal/index.html
Normal file
13
drops/endless-journal/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Endless Journal</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
drops/endless-journal/package.json
Normal file
23
drops/endless-journal/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "endless-journal",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"css-font": "^1.2.0",
|
||||
"matter-js": "^0.20.0",
|
||||
"sass": "^1.98.0",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"vue": "^3.5.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
}
|
||||
BIN
drops/endless-journal/public/favicon.png
Normal file
BIN
drops/endless-journal/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
124
drops/endless-journal/public/meta.json
Normal file
124
drops/endless-journal/public/meta.json
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "endless-journal",
|
||||
"title": "Endless Journal",
|
||||
"date": "2026-07-20",
|
||||
"tags": [
|
||||
"Process"
|
||||
],
|
||||
"description": {
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": " Creativity is following the thread of intuition and seeing where it goes. It's about being in the moment and not looking back."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The process of destruction is just as important as the process of creation. Destruction frees the mind and allows it to always stay on the move, constantly reaching new territory."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "As you type, the letters will begin to pile up as a mass of objects. These letters no longer signify the words you wrote but progress in the creative process. There are three fill lines to signify this progress:"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bulletList",
|
||||
"content": [
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Intuition: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This first stage is about finding that intuitive thread and simply holding onto it to see where it takes you."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Chaos: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The second stage is about throwing out every idea your mind relates to that intuitive thread. Nothing is organized yet and that's a good thing. It's ok if you don't feel like you are getting anywhere yet."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "Concept: "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "By this stage you will hopefully start to take the mass of chaotic ideas and form them into a single core concept. This is the concept you will use to build your creation."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "If a concept still has not shown itself, that's ok, reload and repeat."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"published": true
|
||||
}
|
||||
18
drops/endless-journal/src/App.vue
Normal file
18
drops/endless-journal/src/App.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<main class="drop theme-light">
|
||||
<EndlessJournal />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EndlessJournal from './components/EndlessJournal.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
main.drop {
|
||||
background: var(--theme-bg);
|
||||
color: var(--theme-fg);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
120
drops/endless-journal/src/components/EndlessJournal.vue
Normal file
120
drops/endless-journal/src/components/EndlessJournal.vue
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div class="endless-journal">
|
||||
<input
|
||||
v-model="input"
|
||||
class="input"
|
||||
type="text"
|
||||
ref="inputEl"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
|
||||
<div class="cursor" />
|
||||
|
||||
<div class="fill-line one">
|
||||
<h6>Intuition</h6>
|
||||
<div class="line" />
|
||||
</div>
|
||||
<div class="fill-line two">
|
||||
<h6>Chaos</h6>
|
||||
<div class="line" />
|
||||
</div>
|
||||
<div class="fill-line three">
|
||||
<h6>Concept</h6>
|
||||
<div class="line" />
|
||||
</div>
|
||||
|
||||
<physics :input="input" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import Physics from './Physics.vue'
|
||||
import { until } from '@vueuse/core'
|
||||
|
||||
const input = ref()
|
||||
const inputEl = ref()
|
||||
|
||||
onMounted(async () => {
|
||||
await until(inputEl).toBeTruthy()
|
||||
await new Promise((res) => setTimeout(res, 50))
|
||||
inputEl.value.focus()
|
||||
})
|
||||
const onBlur = async () => {
|
||||
await new Promise((res) => setTimeout(res, 50))
|
||||
inputEl.value.focus()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.endless-journal {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.input {
|
||||
border: 1px solid var(--theme-fg);
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
opacity: 0;
|
||||
}
|
||||
.cursor {
|
||||
width: rs(10px);
|
||||
height: rs(100px);
|
||||
background: var(--theme-fg);
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
top: 15vh;
|
||||
z-index: 2;
|
||||
animation: cursor 1s infinite;
|
||||
}
|
||||
|
||||
.fill-line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 var(--layout-margin);
|
||||
z-index: 2;
|
||||
|
||||
h6 {
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-grey);
|
||||
font-weight: 400;
|
||||
}
|
||||
.line {
|
||||
position: absolute;
|
||||
bottom: -1em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--theme-grey);
|
||||
}
|
||||
|
||||
&.one {
|
||||
bottom: 20vh;
|
||||
}
|
||||
&.two {
|
||||
bottom: 40vh;
|
||||
}
|
||||
&.three {
|
||||
bottom: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cursor {
|
||||
5% {
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
opacity: 0;
|
||||
}
|
||||
60% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
95% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
231
drops/endless-journal/src/components/Physics.vue
Normal file
231
drops/endless-journal/src/components/Physics.vue
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
<template>
|
||||
<div class="endless-journal-physics">
|
||||
<canvas width="64" height="64" ref="canvas" hidden />
|
||||
|
||||
<div class="container" ref="container" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Matter from 'matter-js'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { fontAtlas } from '@/libs/fontAtlas'
|
||||
import { useElementBounding } from '@vueuse/core'
|
||||
import { themes } from '@shared/config'
|
||||
|
||||
const props = defineProps({ input: String })
|
||||
|
||||
const container = ref()
|
||||
const canvas = ref()
|
||||
|
||||
const { width, height } = useElementBounding(container)
|
||||
|
||||
const chars = [
|
||||
'!',
|
||||
'?',
|
||||
'"',
|
||||
"'",
|
||||
',',
|
||||
'-',
|
||||
'.',
|
||||
':',
|
||||
';',
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
]
|
||||
const charTextures = ref({})
|
||||
|
||||
const generateCharImages = (atlas) => {
|
||||
const ctx = canvas.value.getContext('2d')
|
||||
|
||||
const step = 64
|
||||
for (let y = 0; y < 8; y++) {
|
||||
for (let x = 0; x < 8; x++) {
|
||||
const index = y * 8 + x
|
||||
if (chars[index]) {
|
||||
const char = chars[index]
|
||||
ctx.clearRect(0, 0, 64, 64)
|
||||
ctx.drawImage(atlas, x * step, y * step, 64, 64, 0, 0, 64, 64)
|
||||
charTextures.value[char] = canvas.value.toDataURL()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matter-js
|
||||
const Bodies = ref()
|
||||
const Composite = ref()
|
||||
const Engine = ref()
|
||||
const engine = ref()
|
||||
onMounted(async () => {
|
||||
const atlas = await fontAtlas({
|
||||
font: {
|
||||
size: '60px',
|
||||
// family: 'Iosevka Charon Mono',
|
||||
family: 'monospace',
|
||||
weight: '700',
|
||||
},
|
||||
step: [64, 64],
|
||||
chars: chars,
|
||||
color: themes.light.fg
|
||||
})
|
||||
generateCharImages(atlas)
|
||||
|
||||
const Render = Matter.Render
|
||||
|
||||
Engine.value = Matter.Engine
|
||||
Bodies.value = Matter.Bodies
|
||||
Composite.value = Matter.Composite
|
||||
|
||||
// create an engine
|
||||
engine.value = Engine.value.create()
|
||||
|
||||
// create a renderer
|
||||
const render = Render.create({
|
||||
element: container.value,
|
||||
engine: engine.value,
|
||||
options: {
|
||||
width: width.value,
|
||||
height: height.value,
|
||||
wireframes: false,
|
||||
background: 'transparent',
|
||||
},
|
||||
})
|
||||
|
||||
const ground = Bodies.value.rectangle(
|
||||
width.value / 2,
|
||||
height.value + 50,
|
||||
width.value,
|
||||
100,
|
||||
{
|
||||
isStatic: true,
|
||||
}
|
||||
)
|
||||
const wallLeft = Bodies.value.rectangle(
|
||||
-50,
|
||||
height.value / 2,
|
||||
100,
|
||||
height.value,
|
||||
{
|
||||
isStatic: true,
|
||||
}
|
||||
)
|
||||
const wallRight = Bodies.value.rectangle(
|
||||
width.value,
|
||||
height.value / 2,
|
||||
1,
|
||||
height.value,
|
||||
{
|
||||
isStatic: true,
|
||||
}
|
||||
)
|
||||
|
||||
// add all of the bodies to the world
|
||||
Composite.value.add(engine.value.world, [ground, wallLeft, wallRight])
|
||||
|
||||
// run the renderer
|
||||
Render.run(render)
|
||||
|
||||
run()
|
||||
})
|
||||
|
||||
const run = () => {
|
||||
const bodies = Composite.value.allBodies(engine.value.world)
|
||||
bodies.forEach((body) => {
|
||||
if (body.speed < 0.05 && !body.isStatic) {
|
||||
Matter.Body.setStatic(body, true)
|
||||
}
|
||||
})
|
||||
|
||||
const delta = 1000 / 60
|
||||
Engine.value.update(engine.value, delta)
|
||||
|
||||
requestAnimationFrame(run)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(newInput, oldInput) => {
|
||||
if (
|
||||
newInput?.length > oldInput?.length ||
|
||||
(newInput.length && !oldInput)
|
||||
) {
|
||||
const char = newInput.substr(newInput.length - 1).toUpperCase()
|
||||
|
||||
if (charTextures.value[char]) {
|
||||
const charBox = Bodies.value.rectangle(
|
||||
width.value / 2,
|
||||
height.value * 0.2,
|
||||
32,
|
||||
32,
|
||||
{
|
||||
render: {
|
||||
sprite: {
|
||||
texture: charTextures.value[char],
|
||||
xScale: 0.5,
|
||||
yScale: 0.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
Matter.Body.setVelocity(charBox, {
|
||||
x: Math.random() * 40 - 20,
|
||||
y: 30,
|
||||
})
|
||||
Composite.value.add(engine.value.world, [charBox])
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.endless-journal-physics {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
drops/endless-journal/src/libs/fontAtlas.js
Normal file
63
drops/endless-journal/src/libs/fontAtlas.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
'use strict'
|
||||
|
||||
import stringifyFont from 'css-font/stringify'
|
||||
const defaultChars = [32, 126]
|
||||
|
||||
export function fontAtlas(options) {
|
||||
options = options || {}
|
||||
|
||||
let shape = options.shape
|
||||
? options.shape
|
||||
: options.canvas
|
||||
? [options.canvas.width, options.canvas.height]
|
||||
: [512, 512]
|
||||
const canvas = options.canvas || document.createElement('canvas')
|
||||
let font = options.font
|
||||
const step =
|
||||
typeof options.step === 'number'
|
||||
? [options.step, options.step]
|
||||
: options.step || [32, 32]
|
||||
let chars = options.chars || defaultChars
|
||||
|
||||
if (font && typeof font !== 'string') font = stringifyFont(font)
|
||||
|
||||
if (!Array.isArray(chars)) {
|
||||
chars = String(chars).split('')
|
||||
} else if (
|
||||
chars.length === 2 &&
|
||||
typeof chars[0] === 'number' &&
|
||||
typeof chars[1] === 'number'
|
||||
) {
|
||||
const newchars = []
|
||||
|
||||
for (let i = chars[0], j = 0; i <= chars[1]; i++) {
|
||||
newchars[j++] = String.fromCharCode(i)
|
||||
}
|
||||
|
||||
chars = newchars
|
||||
}
|
||||
|
||||
shape = shape.slice()
|
||||
canvas.width = shape[0]
|
||||
canvas.height = shape[1]
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
ctx.font = font
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillStyle = options.color || '#000'
|
||||
|
||||
let x = step[0] / 2
|
||||
let y = step[1] / 2
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
ctx.fillText(chars[i], x, y)
|
||||
if ((x += step[0]) > shape[0] - step[0] / 2)
|
||||
(x = step[0] / 2), (y += step[1])
|
||||
}
|
||||
|
||||
return canvas
|
||||
}
|
||||
20
drops/endless-journal/src/libs/math.js
Normal file
20
drops/endless-journal/src/libs/math.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export function clamp(min, input, max) {
|
||||
return Math.max(min, Math.min(input, max));
|
||||
}
|
||||
|
||||
export function mapRange(in_min, in_max, input, out_min, out_max) {
|
||||
return ((input - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
export function lerp(start, end, amt) {
|
||||
return (1 - amt) * start + amt * end;
|
||||
}
|
||||
|
||||
export function truncate(value, decimals) {
|
||||
return parseFloat(value.toFixed(decimals));
|
||||
}
|
||||
|
||||
export function wrapValue(value, lowBound, highBound) {
|
||||
const range = highBound - lowBound;
|
||||
return ((((value - lowBound) % range) + range) % range) + lowBound;
|
||||
}
|
||||
6
drops/endless-journal/src/main.js
Normal file
6
drops/endless-journal/src/main.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import '@shared/styles/shared.scss'
|
||||
import './styles/main.scss'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
1
drops/endless-journal/src/styles/main.scss
Normal file
1
drops/endless-journal/src/styles/main.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
/* Drop specific styles */
|
||||
21
drops/endless-journal/vite.config.js
Normal file
21
drops/endless-journal/vite.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig, mergeConfig } from 'vite'
|
||||
import sharedConfig from '../../shared/vite.config.js'
|
||||
|
||||
export default defineConfig(
|
||||
mergeConfig(sharedConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@shared': fileURLToPath(
|
||||
new URL('../../shared', import.meta.url),
|
||||
),
|
||||
},
|
||||
},
|
||||
base: '/drops/endless-journal/',
|
||||
build: {
|
||||
outDir: '../../app/public/drops/endless-journal',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
|
@ -42,5 +42,6 @@
|
|||
"date": "2026-07-16",
|
||||
"tags": [
|
||||
"visualization"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"date": "2026-07-16",
|
||||
"date": "2026-07-17",
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2",
|
||||
|
|
@ -51,5 +51,6 @@
|
|||
"url": "https://github.com/username/repo"
|
||||
}
|
||||
],
|
||||
"notes": "Any additional notes or thoughts about this drop"
|
||||
}
|
||||
"notes": "Any additional notes or thoughts about this drop",
|
||||
"published": false
|
||||
}
|
||||
|
|
|
|||
112
package-lock.json
generated
112
package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"@tiptap/pm": "^3.28.0",
|
||||
"@tiptap/starter-kit": "^3.28.0",
|
||||
"@tiptap/vue-3": "^3.28.0",
|
||||
"fecha": "^4.2.3",
|
||||
"sass": "^1.98.0",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"vue": "^3.5.30",
|
||||
|
|
@ -47,6 +48,21 @@
|
|||
"vite": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"drops/endless-journal": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"css-font": "^1.2.0",
|
||||
"matter-js": "^0.20.0",
|
||||
"sass": "^1.98.0",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"vue": "^3.5.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"drops/flowmap": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
|
|
@ -1725,6 +1741,59 @@
|
|||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-font": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-font/-/css-font-1.2.0.tgz",
|
||||
"integrity": "sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-font-size-keywords": "^1.0.0",
|
||||
"css-font-stretch-keywords": "^1.0.1",
|
||||
"css-font-style-keywords": "^1.0.1",
|
||||
"css-font-weight-keywords": "^1.0.0",
|
||||
"css-global-keywords": "^1.0.1",
|
||||
"css-system-font-keywords": "^1.0.0",
|
||||
"pick-by-alias": "^1.2.0",
|
||||
"string-split-by": "^1.0.0",
|
||||
"unquote": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-font-size-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-font-stretch-keywords": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz",
|
||||
"integrity": "sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-font-style-keywords": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz",
|
||||
"integrity": "sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-font-weight-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-global-keywords": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz",
|
||||
"integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-system-font-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
|
|
@ -1746,6 +1815,10 @@
|
|||
"integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/endless-journal": {
|
||||
"resolved": "drops/endless-journal",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
|
|
@ -1787,6 +1860,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fecha": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||
|
|
@ -2193,6 +2272,12 @@
|
|||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/matter-js": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz",
|
||||
"integrity": "sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
|
||||
|
|
@ -2265,6 +2350,12 @@
|
|||
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parenthesis": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz",
|
||||
"integrity": "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
|
|
@ -2277,6 +2368,12 @@
|
|||
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pick-by-alias": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz",
|
||||
"integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
@ -2999,6 +3096,15 @@
|
|||
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-split-by": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz",
|
||||
"integrity": "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parenthesis": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
|
|
@ -3188,6 +3294,12 @@
|
|||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/unquote": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
|
||||
"integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/varint": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"create": "node bin/createDrop",
|
||||
"build:drops": "npm run build -w drops && node bin/buildManifest",
|
||||
"build:drops": "node bin/buildDrops && node bin/buildManifest",
|
||||
"dev:drops": "node bin/dev.js",
|
||||
"dev": "npm run build:drops && npm run dev -w app",
|
||||
"build": "npm run build:drops && npm run build -w app"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,17 @@ const editor = useEditor({
|
|||
.rich-text {
|
||||
font-family: inherit;
|
||||
|
||||
.tiptap > * {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
margin: 2em 0 1em;
|
||||
@include label;
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
"title": "$NAME",
|
||||
"date": "2026-07-16",
|
||||
"tags": ["tag1", "tag2", "tag3"],
|
||||
"description": "A brief description of what this drop does"
|
||||
"description": "A brief description of what this drop does",
|
||||
"published": false
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue