sekkei-preview 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from 'vue'
3
+ import MilkdownEditor from './MilkdownEditor.vue'
4
+ import { useFileApi } from '../composables/use-file-api'
5
+
6
+ const props = defineProps<{
7
+ filePath: string
8
+ }>()
9
+
10
+ const emit = defineEmits<{
11
+ change: []
12
+ }>()
13
+
14
+ const content = ref('')
15
+ const loading = ref(true)
16
+ const error = ref('')
17
+ const editorRef = ref<InstanceType<typeof MilkdownEditor> | null>(null)
18
+ const currentMarkdown = ref('')
19
+
20
+ const { read, save } = useFileApi()
21
+
22
+ onMounted(async () => {
23
+ try {
24
+ const result = await read(props.filePath)
25
+ content.value = result.content
26
+ currentMarkdown.value = result.content
27
+ } catch (err) {
28
+ error.value = (err as Error).message
29
+ } finally {
30
+ loading.value = false
31
+ }
32
+ })
33
+
34
+ function onEditorChange(markdown: string) {
35
+ currentMarkdown.value = markdown
36
+ emit('change')
37
+ }
38
+
39
+ async function saveContent(): Promise<void> {
40
+ const markdown = editorRef.value?.getMarkdown() ?? currentMarkdown.value
41
+ await save(props.filePath, markdown)
42
+ }
43
+
44
+ function getMarkdown(): string {
45
+ return editorRef.value?.getMarkdown() ?? currentMarkdown.value
46
+ }
47
+
48
+ defineExpose({ save: saveContent, getMarkdown })
49
+ </script>
50
+
51
+ <template>
52
+ <div class="sekkei-editor-wrapper">
53
+ <div v-if="loading" class="editor-loading">
54
+ <span>Loading editor...</span>
55
+ </div>
56
+ <div v-else-if="error" class="editor-error">
57
+ <span>Failed to load: {{ error }}</span>
58
+ </div>
59
+ <ClientOnly v-else>
60
+ <template #fallback>
61
+ <div class="editor-loading">Loading editor...</div>
62
+ </template>
63
+ <MilkdownEditor
64
+ ref="editorRef"
65
+ :initial-value="content"
66
+ @change="onEditorChange"
67
+ />
68
+ </ClientOnly>
69
+ </div>
70
+ </template>
71
+
72
+ <style scoped>
73
+ .sekkei-editor-wrapper {
74
+ min-height: 400px;
75
+ background: var(--vp-c-bg, #fff);
76
+ border: 1px solid var(--vp-c-divider, #e2e8f0);
77
+ border-radius: 8px;
78
+ overflow: hidden;
79
+ }
80
+
81
+ .editor-loading,
82
+ .editor-error {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ min-height: 400px;
87
+ color: var(--vp-c-text-2, #666);
88
+ font-size: 0.9rem;
89
+ }
90
+
91
+ .editor-error {
92
+ color: var(--vp-c-danger-1, #e53e3e);
93
+ }
94
+ </style>
@@ -0,0 +1,55 @@
1
+ import { ref, computed, provide, inject, type Ref, type InjectionKey } from 'vue'
2
+
3
+ export type EditState = 'view' | 'loading' | 'editing' | 'saving'
4
+
5
+ interface EditModeContext {
6
+ state: Ref<EditState>
7
+ isDirty: Ref<boolean>
8
+ isEditing: Ref<boolean>
9
+ startEdit: () => void
10
+ cancelEdit: () => void
11
+ setSaving: () => void
12
+ setView: () => void
13
+ setEditing: () => void
14
+ markDirty: () => void
15
+ clearDirty: () => void
16
+ }
17
+
18
+ const EDIT_MODE_KEY: InjectionKey<EditModeContext> = Symbol('sekkei-edit-mode')
19
+
20
+ /**
21
+ * Provide edit mode state in Layout component.
22
+ */
23
+ export function provideEditMode(): EditModeContext {
24
+ const state = ref<EditState>('view')
25
+ const isDirty = ref(false)
26
+
27
+ const isEditing = computed(() =>
28
+ state.value === 'editing' || state.value === 'loading' || state.value === 'saving'
29
+ )
30
+
31
+ const ctx: EditModeContext = {
32
+ state,
33
+ isDirty,
34
+ isEditing,
35
+ startEdit: () => { state.value = 'loading' },
36
+ cancelEdit: () => { state.value = 'view'; isDirty.value = false },
37
+ setSaving: () => { state.value = 'saving' },
38
+ setView: () => { state.value = 'view'; isDirty.value = false },
39
+ setEditing: () => { state.value = 'editing' },
40
+ markDirty: () => { isDirty.value = true },
41
+ clearDirty: () => { isDirty.value = false },
42
+ }
43
+
44
+ provide(EDIT_MODE_KEY, ctx)
45
+ return ctx
46
+ }
47
+
48
+ /**
49
+ * Inject edit mode state in child components.
50
+ */
51
+ export function useEditMode(): EditModeContext {
52
+ const ctx = inject(EDIT_MODE_KEY)
53
+ if (!ctx) throw new Error('useEditMode() called outside of provideEditMode()')
54
+ return ctx
55
+ }
@@ -0,0 +1,46 @@
1
+ interface ReadResult {
2
+ content: string
3
+ frontmatter: string
4
+ path: string
5
+ }
6
+
7
+ interface ListResult {
8
+ files: Array<{ path: string; title: string }>
9
+ }
10
+
11
+ /**
12
+ * Composable for interacting with the sekkei file API endpoints.
13
+ */
14
+ export function useFileApi() {
15
+ async function read(path: string): Promise<ReadResult> {
16
+ const res = await fetch(`/__api/read?path=${encodeURIComponent(path)}`)
17
+ if (!res.ok) {
18
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }))
19
+ throw new Error(err.error ?? `Failed to read: ${res.status}`)
20
+ }
21
+ return res.json()
22
+ }
23
+
24
+ async function save(path: string, content: string): Promise<void> {
25
+ const res = await fetch('/__api/save', {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify({ path, content }),
29
+ })
30
+ if (!res.ok) {
31
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }))
32
+ throw new Error(err.error ?? `Failed to save: ${res.status}`)
33
+ }
34
+ }
35
+
36
+ async function list(): Promise<ListResult> {
37
+ const res = await fetch('/__api/list')
38
+ if (!res.ok) {
39
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }))
40
+ throw new Error(err.error ?? `Failed to list: ${res.status}`)
41
+ }
42
+ return res.json()
43
+ }
44
+
45
+ return { read, save, list }
46
+ }
package/theme/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import DefaultTheme from 'vitepress/theme'
2
+ import type { Theme } from 'vitepress'
3
+ import Layout from './components/Layout.vue'
4
+ import MilkdownWrapper from './components/MilkdownWrapper.vue'
5
+ import './styles/custom.css'
6
+ import './styles/editor.css'
7
+
8
+ const theme: Theme = {
9
+ extends: DefaultTheme,
10
+ Layout,
11
+ enhanceApp({ app }) {
12
+ app.component('MilkdownWrapper', MilkdownWrapper)
13
+ },
14
+ }
15
+
16
+ export default theme
@@ -0,0 +1,85 @@
1
+ /* Sekkei Docs — Japanese typography + spec table styling */
2
+
3
+ :root {
4
+ --vp-font-family-base: 'Noto Sans JP', 'Hiragino Kaku Gothic ProN', 'Yu Gothic',
5
+ 'Meiryo', sans-serif;
6
+ --vp-font-family-mono: 'JetBrains Mono', 'Source Code Pro', 'Menlo', monospace;
7
+ --vp-layout-max-width: 1440px;
8
+ }
9
+
10
+ /* JP-friendly line height */
11
+ .vp-doc {
12
+ line-height: 1.8;
13
+ }
14
+
15
+ .vp-doc p,
16
+ .vp-doc li {
17
+ line-height: 1.8;
18
+ }
19
+
20
+ /* Spec tables — visible borders, comfortable padding */
21
+ .vp-doc table {
22
+ width: 100%;
23
+ border-collapse: collapse;
24
+ margin: 1rem 0;
25
+ }
26
+
27
+ .vp-doc th,
28
+ .vp-doc td {
29
+ border: 1px solid var(--vp-c-divider);
30
+ padding: 0.6rem 0.8rem;
31
+ text-align: left;
32
+ }
33
+
34
+ .vp-doc th {
35
+ background-color: var(--vp-c-bg-soft);
36
+ font-weight: 600;
37
+ }
38
+
39
+ .vp-doc tr:hover td {
40
+ background-color: var(--vp-c-bg-soft);
41
+ }
42
+
43
+ /* Wider content area for tables */
44
+ .VPDoc .content-container {
45
+ max-width: 960px;
46
+ }
47
+
48
+ @media (min-width: 1440px) {
49
+ .VPDoc .content-container {
50
+ max-width: 1100px;
51
+ }
52
+ }
53
+
54
+ /* Headings — slightly tighter for JP */
55
+ .vp-doc h1 {
56
+ font-size: 1.8rem;
57
+ font-weight: 700;
58
+ letter-spacing: -0.01em;
59
+ }
60
+
61
+ .vp-doc h2 {
62
+ font-size: 1.4rem;
63
+ font-weight: 600;
64
+ border-top: 1px solid var(--vp-c-divider);
65
+ padding-top: 1.2rem;
66
+ margin-top: 2rem;
67
+ }
68
+
69
+ .vp-doc h3 {
70
+ font-size: 1.15rem;
71
+ font-weight: 600;
72
+ }
73
+
74
+ /* Code blocks */
75
+ .vp-doc div[class*='language-'] {
76
+ margin: 1rem 0;
77
+ border-radius: 6px;
78
+ }
79
+
80
+ /* Frontmatter ID badges (REQ-xxx, F-xxx, etc.) */
81
+ .vp-doc code {
82
+ font-size: 0.85em;
83
+ padding: 0.15em 0.35em;
84
+ border-radius: 4px;
85
+ }
@@ -0,0 +1,31 @@
1
+ /* Editor mode styles */
2
+
3
+ /* Smooth transition for editor mount */
4
+ .sekkei-editor-container {
5
+ animation: sekkei-fade-in 0.2s ease-in;
6
+ }
7
+
8
+ @keyframes sekkei-fade-in {
9
+ from { opacity: 0; transform: translateY(4px); }
10
+ to { opacity: 1; transform: translateY(0); }
11
+ }
12
+
13
+ /* Ensure editor takes full width */
14
+ .sekkei-editor-wrapper {
15
+ width: 100%;
16
+ }
17
+
18
+ /* Milkdown editor theme adjustments for VitePress */
19
+ .sekkei-editor-wrapper .milkdown {
20
+ background: var(--vp-c-bg, #fff);
21
+ color: var(--vp-c-text-1);
22
+ font-family: var(--vp-font-family-base);
23
+ line-height: 1.8;
24
+ }
25
+
26
+ /* Editor focus indicator */
27
+ .sekkei-editor-wrapper .milkdown:focus-within {
28
+ outline: 2px solid var(--vp-c-brand-1, #3451b2);
29
+ outline-offset: -2px;
30
+ border-radius: 8px;
31
+ }