@grapecity-software/dsh-spreadjs-editor 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,212 @@
1
+ /* dsh-spreadjs-editor panel styles */
2
+
3
+ .dsh-spreadjs-panel {
4
+ display: flex;
5
+ flex-direction: column;
6
+ width: 100%;
7
+ height: 100%;
8
+ min-height: 0;
9
+ overflow: hidden;
10
+ background: var(--dsw-bg, #ffffff);
11
+ color: var(--dsw-fg, #1f2329);
12
+ }
13
+
14
+ .dsh-spreadjs-header {
15
+ display: flex;
16
+ align-items: center;
17
+ flex-wrap: wrap;
18
+ gap: 10px;
19
+ row-gap: 8px;
20
+ padding: 10px 14px;
21
+ border-bottom: 1px solid var(--dsw-border, #d5d8de);
22
+ background: var(--dsw-bg-subtle, #f7f8fa);
23
+ flex: 0 0 auto;
24
+ min-width: 0;
25
+ }
26
+
27
+ .dsh-spreadjs-title {
28
+ font-size: 14px;
29
+ font-weight: 600;
30
+ margin: 0;
31
+ min-width: 0;
32
+ overflow: hidden;
33
+ text-overflow: ellipsis;
34
+ white-space: nowrap;
35
+ }
36
+
37
+ .dsh-spreadjs-spacer {
38
+ flex: 1 1 auto;
39
+ }
40
+
41
+ .dsh-spreadjs-btn {
42
+ appearance: none;
43
+ border: 1px solid var(--dsw-border, #c9cdd4);
44
+ background: var(--dsw-bg, #ffffff);
45
+ color: inherit;
46
+ border-radius: 6px;
47
+ padding: 5px 12px;
48
+ font-size: 12px;
49
+ cursor: pointer;
50
+ white-space: nowrap;
51
+ }
52
+
53
+ .dsh-spreadjs-btn:hover:not(:disabled) {
54
+ background: var(--dsw-bg-hover, #eef0f3);
55
+ }
56
+
57
+ .dsh-spreadjs-btn:disabled {
58
+ opacity: 0.45;
59
+ cursor: default;
60
+ }
61
+
62
+ .dsh-spreadjs-btn-primary {
63
+ background: #3b6ef6;
64
+ border-color: #3b6ef6;
65
+ color: #fff;
66
+ }
67
+
68
+ .dsh-spreadjs-btn-primary:hover:not(:disabled) {
69
+ background: #2f5ce0;
70
+ }
71
+
72
+ .dsh-spreadjs-export-select {
73
+ max-width: 110px;
74
+ padding: 4px 6px;
75
+ font-size: 12px;
76
+ border: 1px solid var(--dsw-border, #c9cdd4);
77
+ border-radius: 6px;
78
+ background: var(--dsw-bg, #ffffff);
79
+ color: inherit;
80
+ }
81
+
82
+ .dsh-spreadjs-editor {
83
+ flex: 1 1 auto;
84
+ min-width: 0;
85
+ min-height: 0;
86
+ display: flex;
87
+ flex-direction: column;
88
+ }
89
+
90
+ .dsh-spreadjs-host {
91
+ flex: 1 1 auto;
92
+ min-height: 0;
93
+ position: relative;
94
+ display: flex;
95
+ flex-direction: column;
96
+ }
97
+
98
+ .dsh-spreadjs-host > div {
99
+ flex: 1 1 auto;
100
+ min-height: 0;
101
+ width: 100%;
102
+ }
103
+
104
+ .dsh-spreadjs-empty {
105
+ position: absolute;
106
+ inset: 0;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ color: var(--dsw-fg-muted, #8a919c);
111
+ font-size: 13px;
112
+ background: var(--dsw-bg, #ffffff);
113
+ }
114
+
115
+ .dsh-spreadjs-loading {
116
+ position: absolute;
117
+ inset: 0;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ background: rgba(255, 255, 255, 0.72);
122
+ color: #4b5563;
123
+ font-size: 13px;
124
+ z-index: 5;
125
+ }
126
+
127
+ .dsh-spreadjs-error {
128
+ position: absolute;
129
+ inset: 0;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ color: #c0392b;
134
+ font-size: 13px;
135
+ padding: 24px;
136
+ text-align: center;
137
+ z-index: 5;
138
+ background: var(--dsw-bg, #ffffff);
139
+ }
140
+
141
+ .dsh-spreadjs-statusbar {
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 8px;
145
+ min-height: 28px;
146
+ padding: 4px 12px;
147
+ border-top: 1px solid var(--dsw-border, #d5d8de);
148
+ background: var(--dsw-bg-subtle, #fafbfc);
149
+ color: var(--dsw-fg-muted, #6b7280);
150
+ font-size: 12px;
151
+ flex: 0 0 auto;
152
+ }
153
+
154
+ .dsh-spreadjs-status-error {
155
+ color: #c0392b;
156
+ }
157
+
158
+ .dsh-spreadjs-status-busy {
159
+ color: #1a56db;
160
+ }
161
+
162
+ /* Follow the OS light/dark preference. The Designer chrome is themed through
163
+ GC.Spread.Sheets.Designer.setTheme(); these rules are a fallback for the
164
+ Designer container and the spreadsheet canvas so it stays readable in dark. */
165
+ @media (prefers-color-scheme: dark) {
166
+ .dsh-spreadjs-panel {
167
+ color-scheme: dark;
168
+ }
169
+
170
+ .dsh-spreadjs-header {
171
+ background: #1e2227;
172
+ border-color: #343a42;
173
+ }
174
+
175
+ .dsh-spreadjs-btn {
176
+ background: #262b32;
177
+ border-color: #3a414a;
178
+ color: #e8eaed;
179
+ }
180
+
181
+ .dsh-spreadjs-btn:hover:not(:disabled) {
182
+ background: #2c3138;
183
+ }
184
+
185
+ .dsh-spreadjs-export-select {
186
+ background: #262b32;
187
+ border-color: #3a414a;
188
+ color: #e8eaed;
189
+ }
190
+
191
+ .dsh-spreadjs-loading {
192
+ background: rgba(20, 22, 26, 0.72);
193
+ color: #c7ccd3;
194
+ }
195
+
196
+ .dsh-spreadjs-error {
197
+ background: #171a1f;
198
+ color: #f0a0a0;
199
+ }
200
+
201
+ .dsh-spreadjs-host,
202
+ .dsh-spreadjs-host .gc-designer-container,
203
+ .dsh-spreadjs-host .spread-sheets,
204
+ .dsh-spreadjs-host .sheet-container,
205
+ .dsh-spreadjs-host .rowHeader,
206
+ .dsh-spreadjs-host .colHeader,
207
+ .dsh-spreadjs-host .hscrollbar,
208
+ .dsh-spreadjs-host .vscrollbar {
209
+ background-color: #1e2227;
210
+ color: #e8eaed;
211
+ }
212
+ }
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * dsh-spreadjs-editor — node half.
3
+ *
4
+ * Registers the `/spreadjs` prefix route on the harness web server. The
5
+ * better-sidebar browser half reads and writes files through ui-all's session
6
+ * routes; this host half only supplies the SpreadJS license/config handshake.
7
+ *
8
+ * Security posture: the server binds to 127.0.0.1 by default and this plugin
9
+ * adds no external exposure of its own.
10
+ */
11
+
12
+ import type { IncomingMessage, ServerResponse } from 'node:http'
13
+ import type { Context } from '@deepseek-ai/cordis'
14
+
15
+ export const name = 'dsh-spreadjs-editor'
16
+
17
+ /** Required service: the web server whose route table this plugin extends. */
18
+ export const inject = ['webServer']
19
+
20
+ /** Plugin configuration (patch layer). */
21
+ export interface Config {
22
+ /** SpreadJS license key; empty runs the evaluation build. @default '' */
23
+ licenseKey?: string
24
+ }
25
+
26
+ function sendJson(res: ServerResponse, status: number, body: unknown): void {
27
+ const data = JSON.stringify(body)
28
+ res.writeHead(status, {
29
+ 'Content-Type': 'application/json; charset=utf-8',
30
+ 'Content-Length': Buffer.byteLength(data),
31
+ 'Cache-Control': 'no-store',
32
+ })
33
+ res.end(data)
34
+ }
35
+
36
+ function readPathname(req: IncomingMessage): string {
37
+ return new URL(req.url ?? '/', 'http://localhost').pathname
38
+ }
39
+
40
+ export function apply(ctx: Context, config: Config = {}): void {
41
+ const webServer = ctx.webServer
42
+
43
+ const disposer = webServer.register({
44
+ kind: 'prefix',
45
+ path: '/spreadjs',
46
+ handler: (req: IncomingMessage, res: ServerResponse): void => {
47
+ try {
48
+ const pathname = readPathname(req)
49
+ if (pathname === '/spreadjs/api/health') {
50
+ sendJson(res, 200, { ok: true })
51
+ return
52
+ }
53
+ if (pathname === '/spreadjs/api/config') {
54
+ sendJson(res, 200, { licenseKey: config.licenseKey ?? '' })
55
+ return
56
+ }
57
+ sendJson(res, 404, { error: 'unknown endpoint' })
58
+ } catch (error) {
59
+ const message = error instanceof Error ? error.message : String(error)
60
+ if (!res.headersSent) sendJson(res, 500, { error: message })
61
+ else res.destroy()
62
+ }
63
+ },
64
+ })
65
+
66
+ ctx.effect(() => disposer, 'dsh-spreadjs-editor: /spreadjs routes')
67
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "noImplicitOverride": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "verbatimModuleSyntax": true,
17
+ "allowImportingTsExtensions": true,
18
+ "noEmit": true,
19
+ "types": ["node"]
20
+ },
21
+ "include": ["src", "tests"]
22
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * tsdown build for dsh-spreadjs-editor.
3
+ *
4
+ * Two artifacts (aligned with the harness client bundle preset):
5
+ * - lib/index.js — node half (ESM). Runs on the harness host: registers the
6
+ * /spreadjs file bridge on ctx.webServer. No runtime dependencies beyond
7
+ * node builtins (dsh services are injected, not imported).
8
+ * - lib/client.js — browser half (CJS closure factory). Loaded by the
9
+ * client-modules system through window.__ModuleLoader__.load({id, factory}).
10
+ * React resolves from the module table and stays external; SpreadJS and
11
+ * the stylesheets are inlined into the bundle.
12
+ */
13
+ import { readFile } from 'node:fs/promises'
14
+ import type { UserConfig } from 'tsdown'
15
+
16
+ const PKG_ID = 'dsh-spreadjs-editor'
17
+
18
+ /** Module-table specifiers the browser bundle resolves at runtime. */
19
+ const CLIENT_EXTERNALS = new Set([
20
+ 'react',
21
+ 'react/jsx-runtime',
22
+ 'react-dom',
23
+ 'react-dom/client',
24
+ '@deepseek-ai/cordis',
25
+ ])
26
+
27
+ /** Inline any `*.css` import as a raw string module (injected by styles.ts). */
28
+ function cssAsText() {
29
+ const PREFIX = '\0dsh-spreadjs-css:'
30
+ const SUFFIX = '.mjs'
31
+ return {
32
+ name: 'dsh-spreadjs-css-as-text',
33
+ async resolveId(this: any, source: string, importer: string | undefined) {
34
+ if (!source.endsWith('.css') || importer === undefined) return null
35
+ const resolved = await this.resolve(source, importer, { skipSelf: true })
36
+ if (resolved === null) return null
37
+ const fileId = resolved.id
38
+ this.addWatchFile(fileId)
39
+ // Virtual id must NOT end in `.css` or tsdown's css-guard intercepts it.
40
+ return PREFIX + fileId + SUFFIX
41
+ },
42
+ async load(this: any, id: string) {
43
+ if (!id.startsWith(PREFIX)) return null
44
+ const fileId = id.slice(PREFIX.length, -SUFFIX.length)
45
+ const source = await readFile(fileId, 'utf8')
46
+ return `export default ${JSON.stringify(source)}`
47
+ },
48
+ }
49
+ }
50
+
51
+ const nodeHalf: UserConfig = {
52
+ name: PKG_ID,
53
+ entry: { index: 'src/index.ts' },
54
+ outDir: 'lib',
55
+ format: ['esm'],
56
+ platform: 'node',
57
+ target: 'es2022',
58
+ fixedExtension: false,
59
+ dts: false,
60
+ clean: true,
61
+ sourcemap: true,
62
+ deps: {
63
+ // Relative sources inline; node: builtins stay external; nothing else
64
+ // (the node half imports no bare runtime deps — dsh services are injected).
65
+ neverBundle: specifier => specifier.startsWith('node:'),
66
+ alwaysBundle: specifier => specifier.startsWith('.'),
67
+ },
68
+ }
69
+
70
+ const clientHalf: UserConfig = {
71
+ name: `${PKG_ID}/client`,
72
+ entry: { client: 'src/client/index.ts' },
73
+ outDir: 'lib',
74
+ format: 'cjs',
75
+ platform: 'browser',
76
+ target: 'es2022',
77
+ fixedExtension: false,
78
+ dts: false,
79
+ clean: false,
80
+ sourcemap: true,
81
+ deps: {
82
+ // Requested module-table rows stay imports; everything else inlines.
83
+ neverBundle: specifier => CLIENT_EXTERNALS.has(specifier),
84
+ alwaysBundle: specifier => !CLIENT_EXTERNALS.has(specifier),
85
+ },
86
+ // Dual-mode libs resolve their browser flavor; inline deps that read
87
+ // process.env.NODE_ENV / import.meta.env.MODE need the substitutions.
88
+ inputOptions: {
89
+ resolve: {
90
+ conditionNames: [
91
+ (process.env.NODE_ENV ?? 'production') === 'development' ? 'development' : 'production',
92
+ 'browser', 'import', 'module', 'default',
93
+ ],
94
+ },
95
+ },
96
+ define: {
97
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'production'),
98
+ 'import.meta.env.MODE': JSON.stringify(process.env.NODE_ENV ?? 'production'),
99
+ 'import.meta.env': JSON.stringify({ MODE: process.env.NODE_ENV ?? 'production' }),
100
+ },
101
+ plugins: [cssAsText()],
102
+ outputOptions: {
103
+ entryFileNames: 'client.js',
104
+ banner: `window.__ModuleLoader__.load({ id: ${JSON.stringify(PKG_ID)}, factory: (require) => {`,
105
+ footer: 'return module.exports; } });',
106
+ intro: 'var module = { exports: {} }; var exports = module.exports;',
107
+ },
108
+ }
109
+
110
+ export default [nodeHalf, clientHalf]