Added Typst Spaces and Internal Packages

This commit is contained in:
2026-05-31 18:03:04 -04:00
parent c1fdb5a1b7
commit bbd7be86a5
26 changed files with 2873 additions and 114 deletions
+29
View File
@@ -20,6 +20,35 @@ export async function compileTypst(text: string, document_id?: string): Promise<
return await res.json();
}
export async function compileSpace(space_id: string, files: Record<string, string>): Promise<CompileResponse> {
const res = await fetch('/api/compile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ space_id, files }),
});
return await res.json();
}
export function exportSpace(space_id: string, files: Record<string, string>, format: 'pdf' | 'png' | 'svg', title: string = 'document') {
return fetch(`/api/export/${format}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ space_id, files }),
})
.then((res) => {
if (!res.ok) throw new Error('Export failed');
return res.blob();
})
.then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title}.${format}`;
a.click();
URL.revokeObjectURL(url);
});
}
export function exportTypst(text: string, format: 'pdf' | 'png' | 'svg', title: string = 'document', document_id?: string) {
const form = document.createElement('form');
form.method = 'POST';
+114
View File
@@ -0,0 +1,114 @@
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { get } from 'svelte/store';
import { userStore } from './auth';
import { connectionStatus, connectedUsers } from './store';
import type { AwarenessUser } from './store';
export interface OpenFile {
fileId: string;
path: string;
doc: Y.Doc;
text: Y.Text;
provider: WebsocketProvider;
}
const userColors = [
'#30bced', '#6eeb83', '#ffbc42', '#ecd444', '#ee6352',
'#9ac2c9', '#8acb88', '#1be7ff', '#ff0054', '#9e0059'
];
const open = new Map<string, OpenFile>();
let spaceId: string | null = null;
const TEXT_NAME = 'typst';
export function setSpace(id: string) {
spaceId = id;
}
export function openFile(fileId: string, path: string): OpenFile {
const existing = open.get(fileId);
if (existing) return existing;
if (!spaceId) throw new Error('Space not set');
const doc = new Y.Doc();
const text = doc.getText(TEXT_NAME);
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
connectionStatus.set('connecting');
const provider = new WebsocketProvider(`${protocol}//${host}/yjs`, `space:${spaceId}:${fileId}`, doc);
const user = get(userStore);
const color = userColors[Math.floor(Math.random() * userColors.length)];
provider.awareness.setLocalStateField('user', {
name: user?.username || 'Anonymous',
color,
colorLight: color + '33'
});
provider.on('status', (event: { status: string }) => {
connectionStatus.set(event.status);
});
provider.awareness.on('change', () => {
const states = provider.awareness.getStates();
const localId = provider.awareness.clientID;
const uniqueUsers = new Map<string, AwarenessUser>();
states.forEach((state, clientId) => {
if (state.user) {
const isLocal = clientId === localId;
const userObj = { clientId, ...state.user, isLocal };
if (isLocal) {
uniqueUsers.set(state.user.name, userObj);
} else if (!uniqueUsers.has(state.user.name) || !uniqueUsers.get(state.user.name)!.isLocal) {
uniqueUsers.set(state.user.name, userObj);
}
}
});
connectedUsers.set(Array.from(uniqueUsers.values()));
});
const entry: OpenFile = { fileId, path, doc, text, provider };
open.set(fileId, entry);
return entry;
}
export function getOpenFile(fileId: string): OpenFile | undefined {
return open.get(fileId);
}
export function renameOpenFile(fileId: string, path: string) {
const entry = open.get(fileId);
if (entry) entry.path = path;
}
export function closeFile(fileId: string) {
const entry = open.get(fileId);
if (entry) {
entry.provider.disconnect();
entry.provider.destroy();
entry.doc.destroy();
open.delete(fileId);
}
}
export function getAllText(): Record<string, string> {
const result: Record<string, string> = {};
for (const entry of open.values()) {
result[entry.path] = entry.text.toString();
}
return result;
}
export function cleanupSpace() {
for (const fileId of Array.from(open.keys())) {
closeFile(fileId);
}
spaceId = null;
connectionStatus.set('disconnected');
connectedUsers.set([]);
}