Added Word Count

This commit is contained in:
2026-04-08 14:26:50 +00:00
parent 43df9fb760
commit c588867625
9 changed files with 174 additions and 31 deletions
+60
View File
@@ -0,0 +1,60 @@
<script lang="ts">
import { connectionStatus, documentStatsStore } from '../ts/store';
let showStatsModal = $state(false);
function toggleModal() {
showStatsModal = !showStatsModal;
}
</script>
<div class="h-8 border-t border-[var(--theme-border)] bg-[var(--theme-bg)] flex items-center justify-between px-4 text-xs text-[var(--theme-text)] select-none z-[60] relative">
<div class="flex items-center gap-4">
<div class="flex items-center gap-1.5 font-medium {
$connectionStatus === 'connected' ? 'text-emerald-600 dark:text-emerald-400' : 'text-amber-600 dark:text-amber-400'
}">
<div class="w-1.5 h-1.5 rounded-full {$connectionStatus === 'connected' ? 'bg-emerald-500 shadow-[0_0_4px_rgba(16,185,129,0.4)]' : 'bg-amber-500 animate-pulse'}"></div>
{$connectionStatus === 'connected' ? 'Document synced' : 'Connecting...'}
</div>
{#if $documentStatsStore}
<button class="hover:bg-gray-100 dark:hover:bg-white/10 px-2 py-0.5 rounded transition-colors flex items-center gap-1 cursor-pointer" onclick={toggleModal} aria-label="Word count statistics">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-70"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
{$documentStatsStore.words} words
</button>
{/if}
</div>
</div>
{#if showStatsModal}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 bg-black/20 dark:bg-black/40 z-[100] flex items-center justify-center backdrop-blur-sm" onclick={toggleModal}>
<div class="bg-[var(--theme-bg)] text-[var(--theme-text)] border border-[var(--theme-border)] rounded-xl shadow-xl w-80 overflow-hidden" onclick={e => e.stopPropagation()}>
<div class="px-4 py-3 border-b border-[var(--theme-border)] flex items-center justify-between">
<h3 class="font-semibold text-sm">Word count</h3>
<button onclick={toggleModal} aria-label="Close" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="p-4 flex flex-col gap-3 text-sm">
<div class="flex justify-between items-center pb-2 border-b border-[var(--theme-border)] border-dashed">
<span class="opacity-70">Pages</span>
<span class="font-medium">{$documentStatsStore?.pages || 0}</span>
</div>
<div class="flex justify-between items-center pb-2 border-b border-[var(--theme-border)] border-dashed">
<span class="opacity-70">Words</span>
<span class="font-medium">{$documentStatsStore?.words || 0}</span>
</div>
<div class="flex justify-between items-center pb-2 border-b border-[var(--theme-border)] border-dashed">
<span class="opacity-70">Characters</span>
<span class="font-medium">{$documentStatsStore?.characters || 0}</span>
</div>
<div class="flex justify-between items-center">
<span class="opacity-70">Characters excluding spaces</span>
<span class="font-medium">{$documentStatsStore?.characters_excluding_spaces || 0}</span>
</div>
</div>
</div>
</div>
{/if}
+2 -8
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { exportTypst } from '../ts/typst-api';
import { text } from '../ts/yjs-setup';
import { connectionStatus, connectedUsers, themeStore, darkModeStore, editorViewStore, documentZoomStore, commentsSidebarOpen, versionHistoryOpen, triggerLspReconnect } from '../ts/store';
import { connectionStatus, connectedUsers, themeStore, darkModeStore, editorViewStore, documentZoomStore, commentsSidebarOpen, versionHistoryOpen, triggerLspReconnect, documentStatsStore } from '../ts/store';
import { themes } from '../ts/themes';
import { goto } from '$app/navigation';
import ShareModal from './ShareModal.svelte';
@@ -414,13 +414,7 @@
{title}
</h1>
<div class="flex items-center gap-1.5 ml-2 px-2 py-0.5 rounded-full text-[11px] font-medium {
$connectionStatus === 'connected' ? 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/20 dark:text-emerald-400 dark:border-emerald-800/30' :
'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800/30'
} border">
<div class="w-1.5 h-1.5 rounded-full {$connectionStatus === 'connected' ? 'bg-emerald-500 shadow-[0_0_4px_rgba(16,185,129,0.4)]' : 'bg-amber-500 animate-pulse'}"></div>
{$connectionStatus === 'connected' ? 'Synced' : 'Connecting...'}
</div>
<!-- Sync status removed from here and moved to Footer -->
</div>
+1
View File
@@ -10,6 +10,7 @@ export const commentsSidebarOpen = writable(false);
export const versionHistoryOpen = writable(false);
export const commentReference = writable('');
export const editorErrors = writable<Diagnostic[]>([]);
export const documentStatsStore = writable<{pages: number; words: number; characters: number; characters_excluding_spaces: number} | null>(null);
export const triggerLspReconnect = writable(0);
export interface AwarenessUser {
+1
View File
@@ -8,6 +8,7 @@ export interface Diagnostic {
export interface CompileResponse {
svgs: string[] | null;
errors: Diagnostic[] | null;
stats?: { pages: number; words: number; characters: number; characters_excluding_spaces: number };
}
export async function compileTypst(text: string, document_id?: string): Promise<CompileResponse> {
+7 -1
View File
@@ -3,12 +3,13 @@
import Editor from '$lib/components/Editor.svelte';
import Preview from '$lib/components/Preview.svelte';
import Toolbar from '$lib/components/Toolbar.svelte';
import DocFooter from '$lib/components/DocFooter.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import { text, initYjs, cleanupYjs } from '$lib/ts/yjs-setup';
import { compileTypst } from '$lib/ts/typst-api';
import type { Diagnostic } from '$lib/ts/typst-api';
import { page } from '$app/stores';
import { commentsSidebarOpen, commentReference, editorViewStore, editorErrors } from '$lib/ts/store';
import { commentsSidebarOpen, commentReference, editorViewStore, editorErrors, documentStatsStore } from '$lib/ts/store';
let svgs = $state<string[]>([]);
let errors = $state<Diagnostic[]>([]);
@@ -57,6 +58,9 @@
const docId = $page.params.id;
compileTypst(content, docId)
.then((res) => {
if (res.stats) {
$documentStatsStore = res.stats;
}
if (res.svgs) {
svgs = res.svgs;
errors = [];
@@ -133,6 +137,8 @@
<ErrorBanner {errors} />
</div>
</main>
<DocFooter />
</div>
<!-- Custom Context Menu for Editor -->