mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
Extend prepare-seed.js to extract hashtags from item text fields and generate tags.json automatically during the seeding process. This ensures hashtag click filtering works correctly in the application. Changes: - Extract all #hashtag patterns from items.json text fields - Generate tags.json with unique tags and auto-assigned colors - Preserve existing tag colors when re-running the script - Add tags.json to .gitignore (auto-generated file) The script runs as part of seed.sh, so CI/CD pipelines and local development will automatically have matching tags for all hashtags used in seed data items.
234 lines
6.2 KiB
JavaScript
234 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Prepares seed data for development environment.
|
|
*
|
|
* This script performs two main tasks:
|
|
*
|
|
* 1. Updates event dates relative to the current date:
|
|
* - Modifies items.json in-place, updating all items with layer "layer-events"
|
|
* - Ensures events are visible in the app (end dates in the future)
|
|
*
|
|
* 2. Extracts hashtags from items and generates tags.json:
|
|
* - Parses all #hashtag patterns from text fields in items.json
|
|
* - Preserves existing tag colors from tags.json if present
|
|
* - Generates new colors for new hashtags
|
|
* - Ensures hashtag filtering works in the application
|
|
*
|
|
* Date strategy:
|
|
* - Event 1 (item-event-1): Long-running, started 30 days ago, ends in 365 days
|
|
* - Event 2 (item-event-2): Upcoming single-day event in 7 days
|
|
* - Event 3 (item-event-3): Ongoing event, started yesterday, ends tomorrow
|
|
* - Event 4 (item-event-4): Upcoming multi-day conference in 30 days
|
|
*/
|
|
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
const seedDir = path.join(__dirname, 'directus-config/development/seed')
|
|
const seedPath = path.join(seedDir, 'items.json')
|
|
const tagsPath = path.join(seedDir, 'tags.json')
|
|
|
|
function addDays(date, days) {
|
|
const result = new Date(date)
|
|
result.setDate(result.getDate() + days)
|
|
return result
|
|
}
|
|
|
|
function addHours(date, hours) {
|
|
const result = new Date(date)
|
|
result.setHours(result.getHours() + hours)
|
|
return result
|
|
}
|
|
|
|
function formatDateTime(date) {
|
|
return date.toISOString().slice(0, 19)
|
|
}
|
|
|
|
/**
|
|
* Color palette for auto-generated tag colors.
|
|
* Uses modern, accessible colors with good contrast.
|
|
*/
|
|
const TAG_COLORS = [
|
|
'#22C55E', // green
|
|
'#3B82F6', // blue
|
|
'#EAB308', // yellow
|
|
'#F97316', // orange
|
|
'#EC4899', // pink
|
|
'#8B5CF6', // purple
|
|
'#06B6D4', // cyan
|
|
'#14B8A6', // teal
|
|
'#F43F5E', // rose
|
|
'#84CC16', // lime
|
|
'#A855F7', // violet
|
|
'#0EA5E9', // sky
|
|
'#10B981', // emerald
|
|
'#EF4444', // red
|
|
'#64748B', // slate
|
|
]
|
|
|
|
/**
|
|
* Extract all unique hashtags from items.json text fields
|
|
*/
|
|
function extractHashtags(items) {
|
|
const hashtags = new Set()
|
|
const hashtagRegex = /#([a-zA-Z0-9_-]+)/g
|
|
|
|
for (const item of items) {
|
|
if (item.text) {
|
|
let match
|
|
while ((match = hashtagRegex.exec(item.text)) !== null) {
|
|
hashtags.add(match[1].toLowerCase())
|
|
}
|
|
}
|
|
}
|
|
|
|
return Array.from(hashtags).sort()
|
|
}
|
|
|
|
/**
|
|
* Load existing tags.json to preserve colors
|
|
*/
|
|
function loadExistingTags() {
|
|
try {
|
|
if (fs.existsSync(tagsPath)) {
|
|
const content = fs.readFileSync(tagsPath, 'utf8')
|
|
const tagsData = JSON.parse(content)
|
|
const colorMap = {}
|
|
for (const tag of tagsData.data || []) {
|
|
colorMap[tag.name.toLowerCase()] = tag.color
|
|
}
|
|
return colorMap
|
|
}
|
|
} catch (error) {
|
|
console.warn(' Warning: Could not read existing tags.json:', error.message)
|
|
}
|
|
return {}
|
|
}
|
|
|
|
/**
|
|
* Generate tags.json from extracted hashtags
|
|
*/
|
|
function generateTagsSeed(hashtags, existingColors) {
|
|
const data = hashtags.map((name, index) => ({
|
|
_sync_id: `tag-${name}`,
|
|
name: name,
|
|
color: existingColors[name] || TAG_COLORS[index % TAG_COLORS.length],
|
|
}))
|
|
|
|
return {
|
|
collection: 'tags',
|
|
meta: {
|
|
insert_order: 0,
|
|
create: true,
|
|
update: true,
|
|
delete: true,
|
|
preserve_ids: false,
|
|
ignore_on_update: [],
|
|
},
|
|
data: data,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate dynamic dates for each event based on current time
|
|
*/
|
|
function getEventDates(syncId, now) {
|
|
const dateConfigs = {
|
|
// "Some Event" - long-running, started in past, ends far in future
|
|
'item-event-1': {
|
|
start: addDays(now, -30),
|
|
end: addDays(now, 365)
|
|
},
|
|
// "Tech Meetup Munich" - upcoming single-day event (the one used in search tests)
|
|
'item-event-2': {
|
|
start: addHours(addDays(now, 7), 18),
|
|
end: addHours(addDays(now, 7), 21)
|
|
},
|
|
// "Sustainability Workshop NYC" - ongoing event (started yesterday, ends tomorrow)
|
|
'item-event-3': {
|
|
start: addHours(addDays(now, -1), 14),
|
|
end: addHours(addDays(now, 1), 17)
|
|
},
|
|
// "Open Source Conference" - upcoming multi-day conference
|
|
'item-event-4': {
|
|
start: addHours(addDays(now, 30), 9),
|
|
end: addHours(addDays(now, 32), 18)
|
|
}
|
|
}
|
|
|
|
return dateConfigs[syncId] || null
|
|
}
|
|
|
|
/**
|
|
* Update event dates in items.json
|
|
*/
|
|
function updateEventDates(seedData) {
|
|
const now = new Date()
|
|
let updatedCount = 0
|
|
|
|
console.log('Updating event dates:')
|
|
for (const item of seedData.data) {
|
|
if (item.layer === 'layer-events' && item._sync_id) {
|
|
const dates = getEventDates(item._sync_id, now)
|
|
if (dates) {
|
|
item.start = formatDateTime(dates.start)
|
|
item.end = formatDateTime(dates.end)
|
|
console.log(` ${item._sync_id} (${item.name}):`)
|
|
console.log(` start: ${item.start}`)
|
|
console.log(` end: ${item.end}`)
|
|
updatedCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(` Updated ${updatedCount} event(s) with dynamic dates.\n`)
|
|
}
|
|
|
|
/**
|
|
* Generate tags.json from hashtags in items.json
|
|
*/
|
|
function updateTagsSeed(seedData) {
|
|
console.log('Generating tags from hashtags:')
|
|
|
|
// Extract hashtags from items
|
|
const hashtags = extractHashtags(seedData.data)
|
|
console.log(` Found ${hashtags.length} unique hashtag(s): ${hashtags.join(', ')}`)
|
|
|
|
// Load existing colors to preserve them
|
|
const existingColors = loadExistingTags()
|
|
const preservedCount = Object.keys(existingColors).filter((name) => hashtags.includes(name)).length
|
|
if (preservedCount > 0) {
|
|
console.log(` Preserving colors for ${preservedCount} existing tag(s)`)
|
|
}
|
|
|
|
// Generate and write tags.json
|
|
const tagsSeed = generateTagsSeed(hashtags, existingColors)
|
|
fs.writeFileSync(tagsPath, JSON.stringify(tagsSeed, null, 4))
|
|
console.log(` Written ${hashtags.length} tag(s) to tags.json\n`)
|
|
}
|
|
|
|
/**
|
|
* Main entry point
|
|
*/
|
|
function prepareSeedData() {
|
|
console.log('Preparing seed data...\n')
|
|
|
|
// Read the current items.json
|
|
const content = fs.readFileSync(seedPath, 'utf8')
|
|
const seedData = JSON.parse(content)
|
|
|
|
// 1. Update event dates
|
|
updateEventDates(seedData)
|
|
|
|
// Write back to items.json (with updated dates)
|
|
fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4))
|
|
|
|
// 2. Generate tags.json from hashtags
|
|
updateTagsSeed(seedData)
|
|
|
|
console.log('Done!')
|
|
}
|
|
|
|
prepareSeedData()
|