Framework Examples¶
The snippets below mirror the patterns used in the demo. Replace the data-loading parts with your own persistence logic.
Vanilla JavaScript¶
<!DOCTYPE html>
<html>
<head>
<script type="module" src="./node_modules/@sciflow/editor-start/dist/bundle/sciflow-editor.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,400,0..1,0" />
</head>
<body>
<sciflow-formatbar for="editor"></sciflow-formatbar>
<sciflow-editor id="editor"></sciflow-editor>
<sciflow-reference-list id="references"></sciflow-reference-list>
<script type="module">
const editor = document.getElementById('editor');
const references = document.getElementById('references');
editor.doc = JSON.parse(localStorage.getItem('draft') ?? '{"type":"doc","content":[]}');
editor.addEventListener('editor-change', (event) => {
localStorage.setItem('draft', JSON.stringify(event.detail.doc));
references.references = event.detail.references ?? [];
});
editor.addEventListener('selection-change', (event) => {
const ids = (event.detail?.citations ?? [])
.map((entry) => entry.id)
.filter((id) => typeof id === 'string');
references.highlight(ids);
});
</script>
</body>
</html>
React¶
import { useEffect, useRef } from 'react';
import '@sciflow/editor-start';
export function Editor() {
const editorRef = useRef<HTMLElement>(null);
const refsRef = useRef<HTMLElement>(null);
useEffect(() => {
const editor = editorRef.current;
const refsEl = refsRef.current as any;
if (!editor) return;
const onChange = (event: Event) => {
const detail = (event as CustomEvent).detail;
refsEl && (refsEl.references = detail.references ?? []);
};
const onSelection = (event: Event) => {
const detail = (event as CustomEvent).detail;
const ids = (detail?.citations ?? [])
.map((entry: { id?: string }) => entry?.id)
.filter((id: unknown): id is string => typeof id === 'string');
refsEl?.highlight?.(ids);
};
editor.addEventListener('editor-change', onChange as EventListener);
editor.addEventListener('selection-change', onSelection as EventListener);
return () => {
editor.removeEventListener('editor-change', onChange as EventListener);
editor.removeEventListener('selection-change', onSelection as EventListener);
};
}, []);
return (
<>
<sciflow-editor ref={editorRef} doc={{ type: 'doc', content: [] }} />
<sciflow-reference-list ref={refsRef} empty-text="No references yet." />
</>
);
}
With Toolbar¶
export function EditorWithToolbar() {
const editorRef = useRef<HTMLElement>(null);
const toolbarRef = useRef<HTMLElement>(null);
const refsRef = useRef<HTMLElement>(null);
useEffect(() => {
const editor = editorRef.current;
const toolbar = toolbarRef.current;
const refsEl = refsRef.current as any;
if (!editor || !toolbar) return;
const handleReady = () => {
(toolbar as any).editor = editor;
};
const handleChange = (event: Event) => {
const detail = (event as CustomEvent).detail;
refsEl && (refsEl.references = detail.references ?? []);
};
const handleSelection = (event: Event) => {
const detail = (event as CustomEvent).detail;
const ids = (detail?.citations ?? [])
.map((entry: { id?: string }) => entry?.id)
.filter((id: unknown): id is string => typeof id === 'string');
refsEl?.highlight?.(ids);
};
editor.addEventListener('editor-ready', handleReady as EventListener);
editor.addEventListener('editor-change', handleChange as EventListener);
editor.addEventListener('selection-change', handleSelection as EventListener);
return () => {
editor.removeEventListener('editor-ready', handleReady as EventListener);
editor.removeEventListener('editor-change', handleChange as EventListener);
editor.removeEventListener('selection-change', handleSelection as EventListener);
};
}, []);
return (
<>
<sciflow-formatbar ref={toolbarRef} />
<sciflow-editor ref={editorRef} />
<sciflow-reference-list ref={refsRef} />
</>
);
}
Vue 3 (Composition API)¶
1) Register the bundle once¶
// src/plugins/sciflow.ts
import '@sciflow/editor-start/dist/bundle/sciflow-editor.js';
let registered = false;
export function registerSciFlowElements(): void {
if (registered) return;
registered = true;
}
export default { install() { registerSciFlowElements(); } };
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import sciFlow from './plugins/sciflow';
createApp(App).use(sciFlow).mount('#app');
2) Tell Vue about the custom elements (and alias the bundle)¶
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'node:path';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) =>
['sciflow-editor', 'sciflow-formatbar', 'sciflow-selection-editor', 'sciflow-reference-list'].includes(tag),
},
},
}),
],
resolve: {
alias: {
'@sciflow/editor-start/dist/bundle/sciflow-editor.js': path.resolve(
__dirname,
'node_modules/@sciflow/editor-start/dist/bundle/sciflow-editor.js',
),
},
},
});
3) Wrap components (optional) and wire events¶
<!-- src/components/SciFlowEditor.vue -->
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
type DocShape = Record<string, unknown> | null;
const props = withDefaults(defineProps<{ doc?: DocShape; editorId?: string; readOnly?: boolean }>(), {
doc: null,
editorId: 'sciflow-editor',
readOnly: false,
});
const emit = defineEmits<{ (e: 'update:doc', doc: DocShape): void; (e: 'editor-change', event: CustomEvent): void }>();
const editorEl = ref<(HTMLElement & { doc?: DocShape }) | null>(null);
const applyDoc = (next: DocShape) => { if (editorEl.value) editorEl.value.doc = next; };
onMounted(() => applyDoc(props.doc));
watch(() => props.doc, applyDoc);
const handleChange = (event: Event) => {
const custom = event as CustomEvent<{ doc?: DocShape; references?: unknown[] }>;
emit('update:doc', custom.detail?.doc ?? null);
emit('editor-change', custom);
};
defineExpose({ editorEl });
</script>
<template>
<sciflow-editor
ref="editorEl"
:id="editorId"
:readonly="readOnly"
v-bind="$attrs"
@editor-change="handleChange"
/>
</template>
<!-- src/components/SciFlowFormatBar.vue -->
<script setup lang="ts">
const props = withDefaults(defineProps<{ forId?: string }>(), { forId: 'sciflow-editor' });
</script>
<template>
<sciflow-formatbar :for="props.forId" v-bind="$attrs" />
</template>
Directly use the sidebar helpers if you want:
<sciflow-selection-editor :for="editorId" class="sidebar-card" />
<sciflow-reference-list ref="referenceList" empty-text="No references yet." />
4) Configure features and keep references in sync¶
import {
citationFeature,
createFigureFeature,
headingFeature,
markFormattingFeature,
} from '@sciflow/editor-start/dist/bundle/sciflow-editor.js';
const DEMO_FEATURES = [
createFigureFeature({ mockServer: false }),
citationFeature,
headingFeature,
markFormattingFeature,
];
const referenceList = ref<HTMLElement & { references?: unknown[]; highlight?: (ids: string[]) => void } | null>(null);
const configureFeatures = async () => {
const el = editorRef.value?.editorEl as any;
if (!el) return;
if (typeof el.configureFeatures === 'function') await el.configureFeatures(DEMO_FEATURES);
else el.features = [...DEMO_FEATURES];
};
const handleEditorChange = (event: CustomEvent<{ doc?: DocShape; references?: unknown[] }>) => {
doc.value = event.detail?.doc ?? null;
referenceList.value && (referenceList.value.references = event.detail?.references ?? []);
};
const handleSelectionChange = (event: CustomEvent<{ citations?: Array<{ id?: string }> }>) => {
const ids = (event.detail?.citations ?? [])
.map((entry) => entry.id)
.filter((id): id is string => Boolean(id));
referenceList.value?.highlight?.(ids);
};
5) TypeScript shims (if needed)¶
// src/shims-sciflow.d.ts
declare module '@sciflow/editor-start/dist/bundle/sciflow-editor.js' {
export const createFigureFeature: (options?: unknown) => unknown;
export const citationFeature: unknown;
export const figureFeature: unknown;
export const headingFeature: unknown;
export const markFormattingFeature: unknown;
const register: unknown;
export default register;
}
Run the sample project in vue/sciflow-vue for a full reference (npm run dev).
Core Library + Sync Strategy¶
import { Editor, type SyncStrategy } from '@sciflow/editor-core';
const sync: SyncStrategy = {
async load(docId) {
const res = await fetch(`/api/docs/${docId}`);
return await res.json();
},
applyExternal(ops) {
console.log('Remote ops', ops);
},
applyLocal(ops) {
void fetch('/api/docs/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ops }),
});
},
};
const editor = await Editor.create({ docId: 'draft-42', sync, initialDoc: { type: 'doc', content: [] } });
editor.mount(document.getElementById('editor-root')!);
Need another framework?
The same hooks work in Svelte, Angular, and Web Components-friendly SSR setups. The only requirement is that the custom element is defined on the client before you render it.