|
|
|
|
|
const storeKey = 'clarity1_entries_v1'; |
|
|
const chatKey = 'clarity1_chat_v1'; |
|
|
|
|
|
const els = { |
|
|
sidebar: document.getElementById('sidebar'), |
|
|
sidebarToggle: document.getElementById('sidebarToggle'), |
|
|
searchInput: document.getElementById('searchInput'), |
|
|
composeBtn: document.getElementById('composeBtn'), |
|
|
composeModal: document.getElementById('composeModal'), |
|
|
composeForm: document.getElementById('composeForm'), |
|
|
entryTitle: document.getElementById('entryTitle'), |
|
|
entryMood: document.getElementById('entryMood'), |
|
|
entryContent: document.getElementById('entryContent'), |
|
|
entryTags: document.getElementById('entryTags'), |
|
|
aiAssistBtn: document.getElementById('aiAssistBtn'), |
|
|
closeCompose: document.getElementById('closeCompose'), |
|
|
|
|
|
entriesGrid: document.getElementById('entriesGrid'), |
|
|
entriesList: document.getElementById('entriesList'), |
|
|
viewModeGrid: document.getElementById('viewModeGrid'), |
|
|
viewModeList: document.getElementById('viewModeList'), |
|
|
|
|
|
viewJournal: document.getElementById('view-journal'), |
|
|
viewPrompts: document.getElementById('view-prompts'), |
|
|
viewChat: document.getElementById('view-chat'), |
|
|
viewAnalytics: document.getElementById('view-analytics'), |
|
|
viewSettings: document.getElementById('view-settings'), |
|
|
promptsGrid: document.getElementById('promptsGrid'), |
|
|
|
|
|
chatMessages: document.getElementById('chatMessages'), |
|
|
chatForm: document.getElementById('chatForm'), |
|
|
chatInput: document.getElementById('chatInput'), |
|
|
|
|
|
statTotal: document.getElementById('statTotal'), |
|
|
statWeek: document.getElementById('statWeek'), |
|
|
statStreak: document.getElementById('statStreak'), |
|
|
|
|
|
entryModal: document.getElementById('entryModal'), |
|
|
entryModalTitle: document.getElementById('entryModalTitle'), |
|
|
entryModalDate: document.getElementById('entryModalDate'), |
|
|
entryModalMood: document.getElementById('entryModalMood'), |
|
|
entryModalTags: document.getElementById('entryModalTags'), |
|
|
entryModalContent: document.getElementById('entryModalContent'), |
|
|
deleteEntryBtn: document.getElementById('deleteEntryBtn'), |
|
|
closeEntry: document.getElementById('closeEntry'), |
|
|
}; |
|
|
|
|
|
let state = { |
|
|
entries: [], |
|
|
selectedEntryId: null, |
|
|
view: 'journal', |
|
|
mode: 'grid', |
|
|
chat: [], |
|
|
}; |
|
|
|
|
|
const promptsCatalog = [ |
|
|
{ title: 'Values Check', body: 'What matters most to me this week?', tags: ['reflection', 'values'] }, |
|
|
{ title: 'Peak Moment', body: 'Describe a high point today and why it resonated.', tags: ['gratitude'] }, |
|
|
{ title: 'Friction Map', body: 'What’s one friction point I can reduce tomorrow?', tags: ['action'] }, |
|
|
{ title: 'Anchor Habit', body: 'If I had to pick one habit to do daily, what is it?', tags: ['habit'] }, |
|
|
{ title: 'Perspective Shift', body: 'What assumption am I ready to challenge?', tags: ['growth'] }, |
|
|
{ title: 'Energy Audit', body: 'When do I feel most alive?', tags: ['energy'] }, |
|
|
]; |
|
|
|
|
|
function loadEntries() { |
|
|
try { |
|
|
const data = JSON.parse(localStorage.getItem(storeKey) || '[]'); |
|
|
if (Array.isArray(data)) state.entries = data; |
|
|
} catch (e) { |
|
|
console.warn('Failed to load entries', e); |
|
|
state.entries = []; |
|
|
} |
|
|
} |
|
|
|
|
|
function saveEntries() { |
|
|
localStorage.setItem(storeKey, JSON.stringify(state.entries)); |
|
|
} |
|
|
|
|
|
function loadChat() { |
|
|
try { |
|
|
const data = JSON.parse(localStorage.getItem(chatKey) || '[]'); |
|
|
if (Array.isArray(data)) state.chat = data; |
|
|
} catch { |
|
|
state.chat = []; |
|
|
} |
|
|
} |
|
|
|
|
|
function saveChat() { |
|
|
localStorage.setItem(chatKey, JSON.stringify(state.chat)); |
|
|
} |
|
|
|
|
|
function uid() { |
|
|
return Math.random().toString(36).slice(2) + Date.now().toString(36); |
|
|
} |
|
|
|
|
|
function formatDate(ts) { |
|
|
const d = new Date(ts); |
|
|
return d.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); |
|
|
} |
|
|
|
|
|
function renderNav(active) { |
|
|
document.querySelectorAll('.nav-item').forEach(btn => { |
|
|
btn.classList.toggle('bg-primary', btn.dataset.nav === active); |
|
|
btn.classList.toggle('text-onPrimary', btn.dataset.nav === active); |
|
|
}); |
|
|
} |
|
|
|
|
|
function showView(name) { |
|
|
state.view = name; |
|
|
els.viewJournal.classList.toggle('hidden', name !== 'journal'); |
|
|
els.viewPrompts.classList.toggle('hidden', name !== 'prompts'); |
|
|
els.viewChat.classList.toggle('hidden', name !== 'chat'); |
|
|
els.viewAnalytics.classList.toggle('hidden', name !== 'analytics'); |
|
|
els.viewSettings.classList.toggle('hidden', name !== 'settings'); |
|
|
renderNav(name); |
|
|
|
|
|
if (name === 'journal') { |
|
|
renderEntries(); |
|
|
updateAnalytics(); |
|
|
} |
|
|
if (name === 'prompts') { |
|
|
renderPrompts(); |
|
|
} |
|
|
if (name === 'chat') { |
|
|
renderChat(); |
|
|
} |
|
|
if (name === 'analytics') { |
|
|
updateAnalytics(); |
|
|
} |
|
|
} |
|
|
|
|
|
function entryCard(e) { |
|
|
const tags = (e.tags || []).slice(0, 3).map(t => `#${t}`).join(' '); |
|
|
return ` |
|
|
<article class="card animate-fadeUp overflow-hidden"> |
|
|
<div class="p-4 space-y-3"> |
|
|
<div class="flex items-start justify-between gap-2"> |
|
|
<h3 class="text-lg font-semibold text-onSurface">${escapeHtml(e.title || 'Untitled')}</h3> |
|
|
<span class="rounded-full bg-surface-container-high px-2.5 py-1 text-xs text-onSurfaceVariant">${e.mood}</span> |
|
|
</div> |
|
|
<p class="text-sm text-onSurfaceVariant line-clamp-3">${escapeHtml((e.content || '').slice(0, 200))}</p> |
|
|
<div class="mt-2 flex items-center justify-between"> |
|
|
<span class="text-xs text-onSurfaceVariant">${formatDate(e.createdAt)}</span> |
|
|
<div class="flex items-center gap-2 text-onSurfaceVariant"> |
|
|
<span class="text-xs">${tags}</span> |
|
|
<button data-view="${e.id}" class="inline-flex h-8 w-8 items-center justify-center rounded-lg hover:bg-surface-variant text-onSurfaceVariant hover:text-onSurface"> |
|
|
<i data-feather="eye" class="h-4 w-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</article> |
|
|
`; |
|
|
} |
|
|
|
|
|
function entryRow(e) { |
|
|
const tags = (e.tags || []).map(t => `#${t}`).join(' '); |
|
|
return ` |
|
|
<div class="card p-4"> |
|
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> |
|
|
<div class="min-w-0"> |
|
|
<div class="flex items-center gap-2"> |
|
|
<h3 class="truncate text-lg font-semibold text-onSurface">${escapeHtml(e.title || 'Untitled')}</h3> |
|
|
<span class="rounded-full bg-surface-container-high px-2.5 py-1 text-xs text-onSurfaceVariant">${e.mood}</span> |
|
|
</div> |
|
|
<p class="truncate text-sm text-onSurfaceVariant">${escapeHtml(e.content || '')}</p> |
|
|
</div> |
|
|
<div class="flex items-center gap-3"> |
|
|
<span class="text-xs text-onSurfaceVariant">${formatDate(e.createdAt)}</span> |
|
|
<span class="text-xs text-onSurfaceVariant">${tags}</span> |
|
|
<button data-view="${e.id}" class="inline-flex h-8 w-8 items-center justify-center rounded-lg hover:bg-surface-variant text-onSurfaceVariant hover:text-onSurface"> |
|
|
<i data-feather="eye" class="h-4 w-4"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
function renderEntries() { |
|
|
const q = (els.searchInput.value || '').toLowerCase().trim(); |
|
|
const list = state.entries |
|
|
.slice() |
|
|
.sort((a, b) => b.createdAt - a.createdAt) |
|
|
.filter(e => { |
|
|
if (!q) return true; |
|
|
return ( |
|
|
(e.title || '').toLowerCase().includes(q) || |
|
|
(e.content || '').toLowerCase().includes(q) || |
|
|
(e.tags || []).some(t => t.toLowerCase().includes(q)) |
|
|
); |
|
|
}); |
|
|
|
|
|
if (state.mode === 'grid') { |
|
|
els.entriesGrid.classList.remove('hidden'); |
|
|
els.entriesList.classList.add('hidden'); |
|
|
els.entriesGrid.innerHTML = list.map(entryCard).join('') || ` |
|
|
<div class="card p-10 text-center text-onSurfaceVariant"> |
|
|
<i data-feather="inbox" class="mx-auto h-6 w-6 mb-2"></i> |
|
|
No entries yet. Create your first one! |
|
|
</div>`; |
|
|
} else { |
|
|
els.entriesGrid.classList.add('hidden'); |
|
|
els.entriesList.classList.remove('hidden'); |
|
|
els.entriesList.innerHTML = list.map(entryRow).join('') || ` |
|
|
<div class="card p-10 text-center text-onSurfaceVariant"> |
|
|
<i data-feather="inbox" class="mx-auto h-6 w-6 mb-2"></i> |
|
|
No entries yet. Create your first one! |
|
|
</div>`; |
|
|
} |
|
|
|
|
|
|
|
|
if (window.feather) feather.replace(); |
|
|
|
|
|
document.querySelectorAll('[data-view]').forEach(btn => { |
|
|
btn.addEventListener('click', () => openEntry(btn.getAttribute('data-view'))); |
|
|
}); |
|
|
} |
|
|
|
|
|
function renderPrompts() { |
|
|
els.promptsGrid.innerHTML = promptsCatalog.map(p => ` |
|
|
<div class="card p-5"> |
|
|
<h3 class="text-lg font-semibold text-onSurface">${p.title}</h3> |
|
|
<p class="mt-2 text-sm text-onSurfaceVariant">${p.body}</p> |
|
|
<div class="mt-3 flex items-center justify-between"> |
|
|
<div class="text-xs text-onSurfaceVariant">${(p.tags || []).map(t => `#${t}`).join(' ')}</div> |
|
|
<button data-use-prompt="${encodeURIComponent(JSON.stringify(p))}" class="inline-flex items-center gap-2 rounded-xl bg-primary px-3 py-1.5 text-sm font-semibold text-onPrimary shadow-md3 hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-surface"> |
|
|
<i data-feather="arrow-right" class="h-4 w-4"></i> Use |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`).join(''); |
|
|
if (window.feather) feather.replace(); |
|
|
|
|
|
document.querySelectorAll('[data-use-prompt]').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
const p = JSON.parse(decodeURIComponent(btn.getAttribute('data-use-prompt'))); |
|
|
openCompose({ preset: p }); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
function openCompose({ preset } = {}) { |
|
|
if (preset) { |
|
|
els.entryTitle.value = preset.title || ''; |
|
|
els.entryContent.value = `${preset.body}\n\n`; |
|
|
els.entryTags.value = (preset.tags || []).join(', '); |
|
|
} else { |
|
|
els.composeForm.reset(); |
|
|
} |
|
|
els.composeModal.classList.remove('hidden'); |
|
|
els.composeModal.classList.add('flex'); |
|
|
} |
|
|
|
|
|
function closeCompose() { |
|
|
els.composeModal.classList.add('hidden'); |
|
|
els.composeModal.classList.remove('flex'); |
|
|
} |
|
|
|
|
|
function openEntry(id) { |
|
|
const entry = state.entries.find(e => e.id === id); |
|
|
if (!entry) return; |
|
|
state.selectedEntryId = id; |
|
|
els.entryModalTitle.textContent = entry.title || 'Untitled'; |
|
|
els.entryModalDate.textContent = formatDate(entry.createdAt); |
|
|
els.entryModalMood.textContent = entry.mood; |
|
|
els.entryModalTags.textContent = (entry.tags || []).map(t => `#${t}`).join(' '); |
|
|
els.entryModalContent.innerHTML = markdownToHtml(entry.content || ''); |
|
|
els.entryModal.classList.remove('hidden'); |
|
|
els.entryModal.classList.add('flex'); |
|
|
} |
|
|
|
|
|
function closeEntry() { |
|
|
els.entryModal.classList.add('hidden'); |
|
|
els.entryModal.classList.remove('flex'); |
|
|
state.selectedEntryId = null; |
|
|
} |
|
|
|
|
|
function renderChat() { |
|
|
els.chatMessages.innerHTML = state.chat.map(m => ` |
|
|
<div class="flex items-start gap-3 ${m.role === 'assistant' ? '' : 'justify-end'}"> |
|
|
${m.role === 'assistant' ? ` |
|
|
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-secondary text-onSecondary"> |
|
|
<i data-feather="cpu" class="h-4 w-4"></i> |
|
|
</div> |
|
|
` : ` |
|
|
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary text-onPrimary"> |
|
|
<i data-feather="user" class="h-4 w-4"></i> |
|
|
</div> |
|
|
`} |
|
|
<div class="max-w-[85%] rounded-2xl px-4 py-2.5 ${m.role === 'assistant' ? 'bg-surface-container-high text-onSurface' : 'bg-primary text-onPrimary'}"> |
|
|
<div class="text-sm whitespace-pre-wrap">${escapeHtml(m.content)}</div> |
|
|
</div> |
|
|
</div> |
|
|
`).join('') || ` |
|
|
<div class="text-center text-onSurfaceVariant"> |
|
|
<i data-feather="message-square" class="mx-auto h-6 w-6"></i> |
|
|
Ask Clarity1 to reflect on your journal. |
|
|
</div> |
|
|
`; |
|
|
if (window.feather) feather.replace(); |
|
|
els.chatMessages.scrollTop = els.chatMessages.scrollHeight; |
|
|
} |
|
|
|
|
|
function aiAssistCompose(content, mood, tags) { |
|
|
|
|
|
const base = content.trim(); |
|
|
const suggestions = [ |
|
|
'You might expand this with a concrete next step.', |
|
|
'Try adding an example that illustrates your point.', |
|
|
'Consider noting one thing you’re grateful for related to this.', |
|
|
]; |
|
|
const pick = () => suggestions[Math.floor(Math.random() * suggestions.length)]; |
|
|
return ` |
|
|
Here’s a polished version of your entry: |
|
|
|
|
|
${base} |
|
|
|
|
|
Suggestions: |
|
|
- ${pick()} |
|
|
- ${pick()} |
|
|
- ${pick()} |
|
|
|
|
|
Mood matched: ${mood}. Tags suggested: ${(tags || []).slice(0, 4).join(', ') || 'journal, reflection'} |
|
|
`.trim(); |
|
|
} |
|
|
|
|
|
function aiChatRespond(message) { |
|
|
const m = message.trim(); |
|
|
if (!m) return 'Please enter a message.'; |
|
|
if (/summar/i.test(m)) { |
|
|
return summarizeEntries(); |
|
|
} |
|
|
if (/prompt/i.test(m)) { |
|
|
return 'Here are three prompts to help you reflect:\n1) What energized me today?\n2) What did I avoid, and why?\n3) What assumption am I willing to revisit?'; |
|
|
} |
|
|
if (/procrastinat/i.test(m)) { |
|
|
return 'A quick approach:\n- Name the task in one sentence.\n- Identify the tiniest next action.\n- Timebox 10 minutes and start.'; |
|
|
} |
|
|
if (/action|next|steps/i.test(m)) { |
|
|
return 'Turn your draft into steps:\n1) Define the outcome.\n2) List the smallest actionable task.\n3) Assign a time window.'; |
|
|
} |
|
|
if (/last week|week/i.test(m)) { |
|
|
return summarizeEntries(); |
|
|
} |
|
|
return localFallback(m); |
|
|
} |
|
|
|
|
|
function localFallback(msg) { |
|
|
const rules = [ |
|
|
{ k: /hello|hi/i, r: 'Hello! Ask me to summarize your week, generate prompts, or refine a draft.' }, |
|
|
{ k: /help/i, r: 'Try: “Summarize my entries from last week”, “Give me three prompts”, or “Turn my draft into steps”.' }, |
|
|
]; |
|
|
for (const { k, r } of rules) if (k.test(msg)) return r; |
|
|
return `I heard: “${msg}”. I can help summarize your entries or turn thoughts into steps.`; |
|
|
} |
|
|
|
|
|
function summarizeEntries() { |
|
|
const total = state.entries.length; |
|
|
if (!total) return 'No entries to summarize yet.'; |
|
|
const last7 = state.entries.filter(e => Date.now() - e.createdAt <= 7 * 86400000); |
|
|
const words = state.entries.reduce((acc, e) => acc + (e.content || '').split(/\s+/).filter(Boolean).length, 0); |
|
|
const moods = state.entries.reduce((acc, e) => { |
|
|
const m = (e.mood || '').split(' ')[0]; |
|
|
acc[m] = (acc[m] || 0) + 1; |
|
|
return acc; |
|
|
}, {}); |
|
|
const moodTop = Object.entries(moods).sort((a,b) => b[1]-a[1])[0]?.[0] || 'Unknown'; |
|
|
|
|
|
return `Summary: |
|
|
- ${total} total entries |
|
|
- ${last7.length} entries in the last 7 days |
|
|
- Common mood: ${moodTop} |
|
|
- Estimated total words: ${words}`; |
|
|
} |
|
|
|
|
|
function updateAnalytics() { |
|
|
const total = state.entries.length; |
|
|
const week = state.entries.filter(e => Date.now() - e.createdAt <= 7 * 86400000).length; |
|
|
|
|
|
|
|
|
const days = new Set(state.entries.map(e => new Date(e.createdAt).toDateString())); |
|
|
let streak = 0; |
|
|
let cursor = new Date(); |
|
|
while (days.has(cursor.toDateString())) { |
|
|
streak += 1; |
|
|
cursor.setDate(cursor.getDate() - 1); |
|
|
} |
|
|
|
|
|
els.statTotal.textContent = total; |
|
|
els.statWeek.textContent = week; |
|
|
els.statStreak.textContent = streak; |
|
|
} |
|
|
|
|
|
function escapeHtml(s) { |
|
|
return (s || '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); |
|
|
} |
|
|
|
|
|
function markdownToHtml(s) { |
|
|
|
|
|
return escapeHtml(s) |
|
|
.replace(/^### (.*$)/gim, '<h3 class="text-base font-semibold mt-4">$1</h3>') |
|
|
.replace(/^## (.*$)/gim, '<h2 class="text-lg font-semibold mt-4">$1</h2>') |
|
|
.replace(/^# (.*$)/gim, '<h1 class="text-xl font-semibold mt-4">$1</h1>') |
|
|
.replace(/\*\*(.*?)\*\*/gim, '<strong class="font-semibold">$1</strong>') |
|
|
.replace(/\*(.*?)\*/gim, '<em class="italic">$1</em>') |
|
|
.replace(/\n$/gim, '<br />') |
|
|
.replace(/\n/gim, '<br />'); |
|
|
} |
|
|
|
|
|
|
|
|
function setTheme(t) { |
|
|
document.documentElement.classList.toggle('dark', t === 'dark'); |
|
|
localStorage.setItem('clarity1_theme', t); |
|
|
} |
|
|
function initTheme() { |
|
|
const saved = localStorage.getItem('clarity1_theme'); |
|
|
if (saved) { |
|
|
setTheme(saved); |
|
|
} else { |
|
|
setTheme('dark'); |
|
|
} |
|
|
} |
|
|
|
|
|
function bindEvents() { |
|
|
els.sidebarToggle.addEventListener('click', () => { |
|
|
els.sidebar.classList.toggle('hidden'); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.nav-item').forEach(btn => { |
|
|
btn.addEventListener('click', () => showView(btn.dataset.nav)); |
|
|
}); |
|
|
|
|
|
els.searchInput.addEventListener('input', renderEntries); |
|
|
els.viewModeGrid.addEventListener('click', () => { |
|
|
state.mode = 'grid'; |
|
|
renderEntries(); |
|
|
}); |
|
|
els.viewModeList.addEventListener('click', () => { |
|
|
state.mode = 'list'; |
|
|
renderEntries(); |
|
|
}); |
|
|
|
|
|
els.composeBtn.addEventListener('click', () => openCompose()); |
|
|
els.closeCompose.addEventListener('click', closeCompose); |
|
|
|
|
|
els.composeForm.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
const entry = { |
|
|
id: uid(), |
|
|
title: els.entryTitle.value.trim(), |
|
|
mood: els.entryMood.value, |
|
|
content: els.entryContent.value, |
|
|
tags: els.entryTags.value.split(',').map(s => s.trim()).filter(Boolean), |
|
|
createdAt: Date.now(), |
|
|
}; |
|
|
state.entries.unshift(entry); |
|
|
saveEntries(); |
|
|
closeCompose(); |
|
|
renderEntries(); |
|
|
updateAnalytics(); |
|
|
}); |
|
|
|
|
|
els.aiAssistBtn.addEventListener('click', () => { |
|
|
const suggestion = aiAssistCompose( |
|
|
els.entryContent.value, |
|
|
els.entryMood.value, |
|
|
els.entryTags.value.split(',').map(s => s.trim()).filter(Boolean) |
|
|
); |
|
|
els.entryContent.value = suggestion; |
|
|
}); |
|
|
|
|
|
els.chatForm.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
const text = els.chatInput.value.trim(); |
|
|
if (!text) return; |
|
|
state.chat.push({ role: 'user', content: text }); |
|
|
els.chatInput.value = ''; |
|
|
saveChat(); |
|
|
renderChat(); |
|
|
|
|
|
setTimeout(() => { |
|
|
const reply = aiChatRespond(text); |
|
|
state.chat.push({ role: 'assistant', content: reply }); |
|
|
saveChat(); |
|
|
renderChat(); |
|
|
}, 300); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.chat-example').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
els.chatInput.value = btn.textContent.trim(); |
|
|
els.chatForm.dispatchEvent(new Event('submit')); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.theme-toggle').forEach(btn => { |
|
|
btn.addEventListener('click', () => setTheme(btn.dataset.theme)); |
|
|
}); |
|
|
|
|
|
els.deleteEntryBtn.addEventListener('click', () => { |
|
|
if (!state.selectedEntryId) return; |
|
|
if (confirm('Delete this entry?')) { |
|
|
state.entries = state.entries.filter(e => e.id !== state.selectedEntryId); |
|
|
saveEntries(); |
|
|
closeEntry(); |
|
|
renderEntries(); |
|
|
updateAnalytics(); |
|
|
} |
|
|
}); |
|
|
|
|
|
els.closeEntry.addEventListener('click', closeEntry); |
|
|
} |
|
|
|
|
|
function boot() { |
|
|
initTheme(); |
|
|
loadEntries(); |
|
|
loadChat(); |
|
|
bindEvents(); |
|
|
renderNav('journal'); |
|
|
renderEntries(); |
|
|
renderPrompts(); |
|
|
renderChat(); |
|
|
updateAnalytics(); |
|
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', boot); |