@remyxjs/react 1.0.0-beta
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.
- package/README.md +2158 -0
- package/create/index.js +367 -0
- package/create/templates/jsx/index.html +12 -0
- package/create/templates/jsx/src/App.jsx +25 -0
- package/create/templates/jsx/src/main.jsx +9 -0
- package/create/templates/jsx/vite.config.js +6 -0
- package/create/templates/typescript/index.html +12 -0
- package/create/templates/typescript/src/App.tsx +25 -0
- package/create/templates/typescript/src/main.tsx +9 -0
- package/create/templates/typescript/src/vite-env.d.ts +1 -0
- package/create/templates/typescript/tsconfig.json +21 -0
- package/create/templates/typescript/vite.config.ts +6 -0
- package/dist/AttachmentModal--6T-vYJt.js +21 -0
- package/dist/AttachmentModal--6T-vYJt.js.map +1 -0
- package/dist/AttachmentModal-D-uxbXvb.cjs +2 -0
- package/dist/AttachmentModal-D-uxbXvb.cjs.map +1 -0
- package/dist/CommandPalette-DXWyTGOU.js +19 -0
- package/dist/CommandPalette-DXWyTGOU.js.map +1 -0
- package/dist/CommandPalette-t9J8JGNJ.cjs +2 -0
- package/dist/CommandPalette-t9J8JGNJ.cjs.map +1 -0
- package/dist/ContextMenu-B4K3Zbfp.cjs +2 -0
- package/dist/ContextMenu-B4K3Zbfp.cjs.map +1 -0
- package/dist/ContextMenu-D8wNSMc3.js +2 -0
- package/dist/ContextMenu-D8wNSMc3.js.map +1 -0
- package/dist/EmbedModal-Bh2Tcow9.cjs +2 -0
- package/dist/EmbedModal-Bh2Tcow9.cjs.map +1 -0
- package/dist/EmbedModal-cxE4maD2.js +11 -0
- package/dist/EmbedModal-cxE4maD2.js.map +1 -0
- package/dist/ExportModal-Cf1uE4r_.js +13 -0
- package/dist/ExportModal-Cf1uE4r_.js.map +1 -0
- package/dist/ExportModal-DwQVsrZE.cjs +2 -0
- package/dist/ExportModal-DwQVsrZE.cjs.map +1 -0
- package/dist/FindReplaceModal-DYL_2z8U.cjs +2 -0
- package/dist/FindReplaceModal-DYL_2z8U.cjs.map +1 -0
- package/dist/FindReplaceModal-Dgt_MrWb.js +17 -0
- package/dist/FindReplaceModal-Dgt_MrWb.js.map +1 -0
- package/dist/ImageModal-D39ywxqI.cjs +2 -0
- package/dist/ImageModal-D39ywxqI.cjs.map +1 -0
- package/dist/ImageModal-DqScpPrc.js +23 -0
- package/dist/ImageModal-DqScpPrc.js.map +1 -0
- package/dist/ImportDocumentModal-BKqMxO3z.js +22 -0
- package/dist/ImportDocumentModal-BKqMxO3z.js.map +1 -0
- package/dist/ImportDocumentModal-Bev9hp_J.cjs +2 -0
- package/dist/ImportDocumentModal-Bev9hp_J.cjs.map +1 -0
- package/dist/LinkModal-B-igSa-g.cjs +2 -0
- package/dist/LinkModal-B-igSa-g.cjs.map +1 -0
- package/dist/LinkModal-k9IeDtAb.js +15 -0
- package/dist/LinkModal-k9IeDtAb.js.map +1 -0
- package/dist/MenuBar-B-ZAX9rH.cjs +2 -0
- package/dist/MenuBar-B-ZAX9rH.cjs.map +1 -0
- package/dist/MenuBar-DWxJNHmb.js +12 -0
- package/dist/MenuBar-DWxJNHmb.js.map +1 -0
- package/dist/ModalOverlay-BDsGgv3_.cjs +2 -0
- package/dist/ModalOverlay-BDsGgv3_.cjs.map +1 -0
- package/dist/ModalOverlay-CLvRNHmp.js +6 -0
- package/dist/ModalOverlay-CLvRNHmp.js.map +1 -0
- package/dist/SourceModal-BNI_i4iW.cjs +2 -0
- package/dist/SourceModal-BNI_i4iW.cjs.map +1 -0
- package/dist/SourceModal-MdTGK3Uf.js +12 -0
- package/dist/SourceModal-MdTGK3Uf.js.map +1 -0
- package/dist/TablePickerModal-DYODWEA1.js +17 -0
- package/dist/TablePickerModal-DYODWEA1.js.map +1 -0
- package/dist/TablePickerModal-Do1QyoyM.cjs +2 -0
- package/dist/TablePickerModal-Do1QyoyM.cjs.map +1 -0
- package/dist/index-C720tbJA.js +359 -0
- package/dist/index-C720tbJA.js.map +1 -0
- package/dist/index-Dc63uIP0.cjs +2 -0
- package/dist/index-Dc63uIP0.cjs.map +1 -0
- package/dist/remyx-react.cjs +2 -0
- package/dist/remyx-react.cjs.map +1 -0
- package/dist/remyx-react.css +1 -0
- package/dist/remyx-react.js +2 -0
- package/dist/remyx-react.js.map +1 -0
- package/package.json +69 -0
package/create/index.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { stdin, stdout, argv, exit, cwd } from 'node:process'
|
|
4
|
+
import { createInterface } from 'node:readline'
|
|
5
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
6
|
+
import { resolve, join } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
10
|
+
|
|
11
|
+
// ── Colors (no dependencies) ────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const bold = (s) => `\x1b[1m${s}\x1b[22m`
|
|
14
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[39m`
|
|
15
|
+
const green = (s) => `\x1b[32m${s}\x1b[39m`
|
|
16
|
+
const dim = (s) => `\x1b[2m${s}\x1b[22m`
|
|
17
|
+
const red = (s) => `\x1b[31m${s}\x1b[39m`
|
|
18
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[39m`
|
|
19
|
+
|
|
20
|
+
// ── Readline prompt ─────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function createPrompt() {
|
|
23
|
+
const rl = createInterface({ input: stdin, output: stdout })
|
|
24
|
+
const ask = (question) =>
|
|
25
|
+
new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())))
|
|
26
|
+
const close = () => rl.close()
|
|
27
|
+
return { ask, close }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Plugin catalog ──────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const PLUGIN_CATALOG = [
|
|
33
|
+
// Core (selected by default)
|
|
34
|
+
{ name: 'TablePlugin', group: 'Core', desc: 'Sortable columns, formulas, cell formatting', defaultOn: true },
|
|
35
|
+
{ name: 'SyntaxHighlightPlugin', group: 'Core', desc: 'Code block highlighting (11 languages)', defaultOn: true },
|
|
36
|
+
|
|
37
|
+
// Content
|
|
38
|
+
{ name: 'CommentsPlugin', group: 'Content', desc: 'Inline comment threads, @mentions', defaultOn: false },
|
|
39
|
+
{ name: 'CalloutPlugin', group: 'Content', desc: 'Alert blocks (info, warning, error, success)', defaultOn: false },
|
|
40
|
+
{ name: 'TemplatePlugin', group: 'Content', desc: 'Merge tags, conditionals, template library', defaultOn: false },
|
|
41
|
+
{ name: 'MathPlugin', group: 'Content', desc: 'LaTeX math equations, symbol palette', defaultOn: false },
|
|
42
|
+
|
|
43
|
+
// Editing
|
|
44
|
+
{ name: 'KeyboardPlugin', group: 'Editing', desc: 'Vim/Emacs modes, multi-cursor, auto-pair', defaultOn: false },
|
|
45
|
+
{ name: 'DragDropPlugin', group: 'Editing', desc: 'Block reorder, drag & drop, drop zones', defaultOn: false },
|
|
46
|
+
{ name: 'LinkPlugin', group: 'Editing', desc: 'Link previews, broken link detection, bookmarks', defaultOn: false },
|
|
47
|
+
|
|
48
|
+
// Analysis
|
|
49
|
+
{ name: 'AnalyticsPlugin', group: 'Analysis', desc: 'Readability scores, reading time, SEO hints', defaultOn: false },
|
|
50
|
+
{ name: 'SpellcheckPlugin', group: 'Analysis', desc: 'Grammar & spell checking, writing-style presets', defaultOn: false },
|
|
51
|
+
{ name: 'TocPlugin', group: 'Analysis', desc: 'Auto-generated table of contents', defaultOn: false },
|
|
52
|
+
|
|
53
|
+
// Collaboration
|
|
54
|
+
{ name: 'CollaborationPlugin', group: 'Collaboration', desc: 'Real-time co-editing, live cursors', defaultOn: false },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
// ── Version (read from package.json) ────────────────────────────
|
|
58
|
+
|
|
59
|
+
const __pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))
|
|
60
|
+
|
|
61
|
+
// ── Main ────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
const projectArg = argv[2]
|
|
65
|
+
|
|
66
|
+
console.log()
|
|
67
|
+
console.log(bold(cyan(' create-remyx-app')) + dim(' v' + __pkg.version))
|
|
68
|
+
console.log()
|
|
69
|
+
|
|
70
|
+
const { ask, close } = createPrompt()
|
|
71
|
+
|
|
72
|
+
// 1. Project name
|
|
73
|
+
let projectName = projectArg
|
|
74
|
+
if (!projectName) {
|
|
75
|
+
projectName = await ask(bold(' Project name: ') + dim('(remyx-app) '))
|
|
76
|
+
if (!projectName) projectName = 'remyx-app'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. Language
|
|
80
|
+
console.log()
|
|
81
|
+
console.log(bold(' Language:'))
|
|
82
|
+
console.log(` ${cyan('1.')} JavaScript ${dim('(JSX)')}`)
|
|
83
|
+
console.log(` ${cyan('2.')} TypeScript ${dim('(TSX)')}`)
|
|
84
|
+
console.log()
|
|
85
|
+
const langChoice = await ask(bold(' Choose (1 or 2): ') + dim('(1) '))
|
|
86
|
+
const useTypeScript = langChoice === '2'
|
|
87
|
+
const variant = useTypeScript ? 'typescript' : 'jsx'
|
|
88
|
+
|
|
89
|
+
// 3. Theme
|
|
90
|
+
console.log()
|
|
91
|
+
console.log(bold(' Theme:'))
|
|
92
|
+
console.log(` ${cyan('1.')} Light ${dim('— clean white (default)')}`)
|
|
93
|
+
console.log(` ${cyan('2.')} Dark ${dim('— neutral dark')}`)
|
|
94
|
+
console.log(` ${cyan('3.')} Ocean ${dim('— deep blue')}`)
|
|
95
|
+
console.log(` ${cyan('4.')} Forest ${dim('— green earth-tone')}`)
|
|
96
|
+
console.log(` ${cyan('5.')} Sunset ${dim('— warm orange / amber')}`)
|
|
97
|
+
console.log(` ${cyan('6.')} Rose ${dim('— soft pink')}`)
|
|
98
|
+
console.log()
|
|
99
|
+
const themeChoice = await ask(bold(' Choose (1-6): ') + dim('(1) '))
|
|
100
|
+
const themeNames = ['light', 'dark', 'ocean', 'forest', 'sunset', 'rose']
|
|
101
|
+
const themeIndex = parseInt(themeChoice, 10)
|
|
102
|
+
const selectedTheme = themeNames[(themeIndex >= 1 && themeIndex <= 6) ? themeIndex - 1 : 0]
|
|
103
|
+
|
|
104
|
+
// 4. Install heavy optional deps?
|
|
105
|
+
console.log()
|
|
106
|
+
console.log(bold(' Optional features:'))
|
|
107
|
+
console.log(` PDF & DOCX import requires ${dim('pdfjs-dist')} and ${dim('mammoth')} (~5 MB)`)
|
|
108
|
+
console.log()
|
|
109
|
+
const includeDocsRaw = await ask(bold(' Include PDF/DOCX import? (y/N): ') + dim('(N) '))
|
|
110
|
+
const includeDocs = includeDocsRaw.toLowerCase() === 'y' || includeDocsRaw.toLowerCase() === 'yes'
|
|
111
|
+
|
|
112
|
+
// 5. Plugin selection
|
|
113
|
+
console.log()
|
|
114
|
+
console.log(bold(' Plugins:'))
|
|
115
|
+
console.log(dim(' Choose which plugins to include. Unselected plugins won\'t add to your bundle.'))
|
|
116
|
+
console.log(dim(' WordCountPlugin, AutolinkPlugin, and PlaceholderPlugin are always included.'))
|
|
117
|
+
console.log()
|
|
118
|
+
|
|
119
|
+
let currentGroup = ''
|
|
120
|
+
for (let i = 0; i < PLUGIN_CATALOG.length; i++) {
|
|
121
|
+
const plugin = PLUGIN_CATALOG[i]
|
|
122
|
+
if (plugin.group !== currentGroup) {
|
|
123
|
+
currentGroup = plugin.group
|
|
124
|
+
const groupLabel = currentGroup === 'Core' ? `${currentGroup} ${dim('(selected by default)')}` : currentGroup
|
|
125
|
+
console.log(` ${bold(groupLabel)}`)
|
|
126
|
+
}
|
|
127
|
+
const marker = plugin.defaultOn ? green('◉') : dim('◯')
|
|
128
|
+
const num = cyan(`${i + 1}.`.padEnd(4))
|
|
129
|
+
console.log(` ${marker} ${num}${plugin.name.padEnd(24)} ${dim('— ' + plugin.desc)}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log()
|
|
133
|
+
console.log(dim(' Enter numbers separated by commas to toggle selection.'))
|
|
134
|
+
console.log(dim(' Press Enter to keep defaults, or type "all" for everything, "none" for none.'))
|
|
135
|
+
console.log()
|
|
136
|
+
|
|
137
|
+
const pluginInput = await ask(bold(' Plugins (e.g. 1,2,5,7): ') + dim('(defaults) '))
|
|
138
|
+
|
|
139
|
+
// Parse plugin selection
|
|
140
|
+
const selectedPlugins = resolvePluginSelection(pluginInput)
|
|
141
|
+
|
|
142
|
+
close()
|
|
143
|
+
|
|
144
|
+
// ── Validate project name ──────────────────────────────────
|
|
145
|
+
|
|
146
|
+
// Prevent path traversal: reject names containing ../, absolute paths, or OS-reserved chars
|
|
147
|
+
if (
|
|
148
|
+
/\.\.[\\/]/.test(projectName) || // directory traversal
|
|
149
|
+
/^[/\\]/.test(projectName) || // absolute path
|
|
150
|
+
/^[A-Za-z]:/.test(projectName) || // Windows drive letter
|
|
151
|
+
/[\x00-\x1f<>:"|?*]/.test(projectName) // null bytes & OS-reserved chars
|
|
152
|
+
) {
|
|
153
|
+
console.error(red(`\n Error: Invalid project name "${projectName}" — must not contain path traversal or special characters.\n`))
|
|
154
|
+
exit(1)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Scaffold ────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
const targetDir = resolve(cwd(), projectName)
|
|
160
|
+
|
|
161
|
+
// Ensure resolved path stays within the working directory
|
|
162
|
+
const cwdResolved = resolve(cwd())
|
|
163
|
+
if (!targetDir.startsWith(cwdResolved)) {
|
|
164
|
+
console.error(red(`\n Error: Project directory would be created outside the current working directory.\n`))
|
|
165
|
+
exit(1)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log()
|
|
169
|
+
console.log(` ${bold('Scaffolding')} ${cyan(projectName)} ...`)
|
|
170
|
+
console.log()
|
|
171
|
+
|
|
172
|
+
// Copy template (inject selected theme and plugins into App source)
|
|
173
|
+
const templateDir = join(__dirname, 'templates', variant)
|
|
174
|
+
copyDir(templateDir, targetDir, { theme: selectedTheme, plugins: selectedPlugins })
|
|
175
|
+
|
|
176
|
+
// Write package.json
|
|
177
|
+
const pkg = buildPackageJson(projectName, useTypeScript, includeDocs)
|
|
178
|
+
writeFileSync(join(targetDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n')
|
|
179
|
+
|
|
180
|
+
// Write .gitignore (can't include in template as npm strips them)
|
|
181
|
+
writeFileSync(
|
|
182
|
+
join(targetDir, '.gitignore'),
|
|
183
|
+
['node_modules', 'dist', '.DS_Store', '*.local', ''].join('\n'),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// ── Done ──────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
console.log(green(' Done!') + ' Created project in ' + dim(targetDir))
|
|
189
|
+
console.log()
|
|
190
|
+
console.log(' Next steps:')
|
|
191
|
+
console.log()
|
|
192
|
+
console.log(` ${cyan('cd')} ${projectName}`)
|
|
193
|
+
console.log(` ${cyan('npm install')}`)
|
|
194
|
+
console.log(` ${cyan('npm run dev')}`)
|
|
195
|
+
console.log()
|
|
196
|
+
|
|
197
|
+
if (selectedPlugins.length > 0) {
|
|
198
|
+
console.log(dim(` Plugins: ${selectedPlugins.join(', ')}`))
|
|
199
|
+
} else {
|
|
200
|
+
console.log(dim(` Plugins: none (only built-in defaults)`))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (useTypeScript) {
|
|
204
|
+
console.log(dim(` TypeScript types included via @remyxjs/react (ships .d.ts declarations)`))
|
|
205
|
+
} else {
|
|
206
|
+
console.log(dim(` Tip: switch to TypeScript later by renaming .jsx -> .tsx and adding tsconfig.json`))
|
|
207
|
+
}
|
|
208
|
+
console.log(dim(` Theme: ${selectedTheme} — change anytime via the theme prop`))
|
|
209
|
+
console.log()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Plugin selection resolver ────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
function resolvePluginSelection(input) {
|
|
215
|
+
const lowerInput = input.toLowerCase()
|
|
216
|
+
|
|
217
|
+
// "all" → select everything
|
|
218
|
+
if (lowerInput === 'all') {
|
|
219
|
+
return PLUGIN_CATALOG.map(p => p.name)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// "none" → select nothing
|
|
223
|
+
if (lowerInput === 'none') {
|
|
224
|
+
return []
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Empty input → keep defaults
|
|
228
|
+
if (!input) {
|
|
229
|
+
return PLUGIN_CATALOG.filter(p => p.defaultOn).map(p => p.name)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Parse comma-separated numbers
|
|
233
|
+
const nums = input.split(',')
|
|
234
|
+
.map(s => parseInt(s.trim(), 10))
|
|
235
|
+
.filter(n => !isNaN(n) && n >= 1 && n <= PLUGIN_CATALOG.length)
|
|
236
|
+
|
|
237
|
+
if (nums.length === 0) {
|
|
238
|
+
// Invalid input → fall back to defaults
|
|
239
|
+
return PLUGIN_CATALOG.filter(p => p.defaultOn).map(p => p.name)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Start with defaults, then toggle the specified numbers
|
|
243
|
+
const selected = new Set(
|
|
244
|
+
PLUGIN_CATALOG.filter(p => p.defaultOn).map(p => p.name)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
for (const num of nums) {
|
|
248
|
+
const plugin = PLUGIN_CATALOG[num - 1]
|
|
249
|
+
if (selected.has(plugin.name)) {
|
|
250
|
+
selected.delete(plugin.name) // toggle off
|
|
251
|
+
} else {
|
|
252
|
+
selected.add(plugin.name) // toggle on
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return Array.from(selected)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
function buildPackageJson(name, useTypeScript, includeDocs) {
|
|
262
|
+
// Read versions from this package's own package.json to avoid hardcoding
|
|
263
|
+
const ownPeer = __pkg.peerDependencies || {}
|
|
264
|
+
const ownDev = __pkg.devDependencies || {}
|
|
265
|
+
const coreVersion = ownDev['@remyxjs/core'] || ownPeer['@remyxjs/core'] || __pkg.version
|
|
266
|
+
const reactVersion = ownDev['react'] || ownPeer['react'] || '>=18.0.0'
|
|
267
|
+
const reactDomVersion = ownDev['react-dom'] || ownPeer['react-dom'] || '>=18.0.0'
|
|
268
|
+
const viteVersion = ownDev['vite'] || '>=7.0.0'
|
|
269
|
+
const pluginReactVersion = ownDev['@vitejs/plugin-react'] || '>=5.0.0'
|
|
270
|
+
|
|
271
|
+
const pkg = {
|
|
272
|
+
name,
|
|
273
|
+
private: true,
|
|
274
|
+
version: '0.0.0',
|
|
275
|
+
type: 'module',
|
|
276
|
+
scripts: {
|
|
277
|
+
dev: 'vite',
|
|
278
|
+
build: 'vite build',
|
|
279
|
+
preview: 'vite preview',
|
|
280
|
+
},
|
|
281
|
+
dependencies: {
|
|
282
|
+
'@remyxjs/core': '^' + coreVersion,
|
|
283
|
+
'@remyxjs/react': '^' + coreVersion,
|
|
284
|
+
react: '^' + reactVersion,
|
|
285
|
+
'react-dom': '^' + reactDomVersion,
|
|
286
|
+
},
|
|
287
|
+
devDependencies: {
|
|
288
|
+
'@vitejs/plugin-react': '^' + pluginReactVersion,
|
|
289
|
+
vite: '^' + viteVersion,
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (useTypeScript) {
|
|
294
|
+
pkg.devDependencies['typescript'] = '^5.8.0'
|
|
295
|
+
pkg.devDependencies['@types/react'] = '^19.0.0'
|
|
296
|
+
pkg.devDependencies['@types/react-dom'] = '^19.0.0'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (includeDocs) {
|
|
300
|
+
// These are peer/optional deps of @remyxjs/core for document import
|
|
301
|
+
pkg.dependencies['pdfjs-dist'] = '^5.0.0'
|
|
302
|
+
pkg.dependencies['mammoth'] = '^1.11.0'
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Sort devDependencies
|
|
306
|
+
pkg.devDependencies = Object.fromEntries(
|
|
307
|
+
Object.entries(pkg.devDependencies).sort(([a], [b]) => a.localeCompare(b)),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return pkg
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function copyDir(src, dest, opts = {}) {
|
|
314
|
+
mkdirSync(dest, { recursive: true })
|
|
315
|
+
for (const entry of readdirSync(src)) {
|
|
316
|
+
const srcPath = join(src, entry)
|
|
317
|
+
const destPath = join(dest, entry)
|
|
318
|
+
try {
|
|
319
|
+
if (statSync(srcPath).isDirectory()) {
|
|
320
|
+
copyDir(srcPath, destPath, opts)
|
|
321
|
+
} else if (/\.(jsx|tsx)$/.test(entry)) {
|
|
322
|
+
// Inject theme and plugins into React source files
|
|
323
|
+
let content = readFileSync(srcPath, 'utf-8')
|
|
324
|
+
|
|
325
|
+
// Theme injection
|
|
326
|
+
if (opts.theme && opts.theme !== 'light') {
|
|
327
|
+
content = content.replace(/theme="light"(?=[\s/>])/g, `theme="${opts.theme}"`)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Plugin injection
|
|
331
|
+
content = injectPlugins(content, opts.plugins || [])
|
|
332
|
+
|
|
333
|
+
writeFileSync(destPath, content)
|
|
334
|
+
} else {
|
|
335
|
+
writeFileSync(destPath, readFileSync(srcPath))
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
throw new Error(`Failed to copy "${srcPath}" to "${destPath}": ${err.message}`)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function injectPlugins(content, plugins) {
|
|
344
|
+
if (plugins.length > 0) {
|
|
345
|
+
// Generate import line
|
|
346
|
+
const importLine = `import { ${plugins.join(', ')} } from '@remyxjs/core'`
|
|
347
|
+
content = content.replace('/* __PLUGIN_IMPORTS__ */', importLine)
|
|
348
|
+
|
|
349
|
+
// Generate plugins prop
|
|
350
|
+
const pluginCalls = plugins.map(p => `${p}()`).join(', ')
|
|
351
|
+
content = content.replace(
|
|
352
|
+
'/* __PLUGIN_ARRAY__ */',
|
|
353
|
+
`plugins={[${pluginCalls}]}`
|
|
354
|
+
)
|
|
355
|
+
} else {
|
|
356
|
+
// No plugins selected — remove markers cleanly
|
|
357
|
+
content = content.replace('/* __PLUGIN_IMPORTS__ */\n', '')
|
|
358
|
+
content = content.replace('\n /* __PLUGIN_ARRAY__ */', '')
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return content
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
main().catch((err) => {
|
|
365
|
+
console.error(red(`\n Error: ${err.message}\n`))
|
|
366
|
+
exit(1)
|
|
367
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Remyx Editor</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { RemyxEditor } from '@remyxjs/react'
|
|
3
|
+
/* __PLUGIN_IMPORTS__ */
|
|
4
|
+
import '@remyxjs/core/style.css'
|
|
5
|
+
import '@remyxjs/react/style.css'
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
const [content, setContent] = useState('')
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ maxWidth: 900, margin: '2rem auto', padding: '0 1rem' }}>
|
|
12
|
+
<h1>Remyx Editor</h1>
|
|
13
|
+
<RemyxEditor
|
|
14
|
+
value={content}
|
|
15
|
+
onChange={setContent}
|
|
16
|
+
placeholder="Start typing..."
|
|
17
|
+
height={400}
|
|
18
|
+
theme="light"
|
|
19
|
+
/* __PLUGIN_ARRAY__ */
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default App
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Remyx Editor</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { RemyxEditor } from '@remyxjs/react'
|
|
3
|
+
/* __PLUGIN_IMPORTS__ */
|
|
4
|
+
import '@remyxjs/core/style.css'
|
|
5
|
+
import '@remyxjs/react/style.css'
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
const [content, setContent] = useState<string>('')
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ maxWidth: 900, margin: '2rem auto', padding: '0 1rem' }}>
|
|
12
|
+
<h1>Remyx Editor</h1>
|
|
13
|
+
<RemyxEditor
|
|
14
|
+
value={content}
|
|
15
|
+
onChange={setContent}
|
|
16
|
+
placeholder="Start typing..."
|
|
17
|
+
height={400}
|
|
18
|
+
theme="light"
|
|
19
|
+
/* __PLUGIN_ARRAY__ */
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default App
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedIndexedAccess": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import{jsxs as e,jsx as a,Fragment as r}from"react/jsx-runtime";import{useState as l,useRef as t}from"react";import{P as n}from"./index-C720tbJA.js";import{M as o}from"./ModalOverlay-CLvRNHmp.js";const i=/^\s*(javascript|vbscript|data\s*:\s*text\/html)\s*:/i;function m({open:n,onClose:m,engine:s}){const[d,c]=l("url"),[p,u]=l(""),[h,x]=l(""),[b,f]=l(null),[N,v]=l(!1),[y,g]=l(""),C=t(null),F=!!s?.options?.uploadHandler,U=()=>{m(),u(""),x(""),f(null),v(!1),g(""),c("url")};/* @__PURE__ */
|
|
2
|
+
return e(o,{title:"Attach File",open:n,onClose:U,children:[
|
|
3
|
+
/* @__PURE__ */e("div",{className:"rmx-tabs",children:[
|
|
4
|
+
/* @__PURE__ */a("button",{type:"button",className:"rmx-tab "+("url"===d?"rmx-active":""),onClick:()=>c("url"),children:"URL"}),
|
|
5
|
+
/* @__PURE__ */a("button",{type:"button",className:"rmx-tab "+("upload"===d?"rmx-active":""),onClick:()=>c("upload"),children:"Upload"})]}),"upload"===d&&/* @__PURE__ */a("div",{className:"rmx-upload-area",children:F?/* @__PURE__ */e(r,{children:[
|
|
6
|
+
/* @__PURE__ */a("input",{ref:C,type:"file",onChange:e=>{const a=e.target.files[0];a&&(x(a.name),f(a.size),s.options.uploadHandler&&(v(!0),g(""),s.options.uploadHandler(a).then((e=>{u(e),c("url"),v(!1)})).catch((e=>{g(e.message||"File upload failed. Please try again."),v(!1)}))))},style:{display:"none"}}),
|
|
7
|
+
/* @__PURE__ */a("button",{type:"button",className:"rmx-btn rmx-btn-upload",onClick:()=>C.current?.click(),disabled:N,children:N?/* @__PURE__ */e(r,{children:[
|
|
8
|
+
/* @__PURE__ */a("span",{className:"rmx-spinner","aria-hidden":"true"})," Uploading…"]}):"Choose File"}),
|
|
9
|
+
/* @__PURE__ */a("p",{className:"rmx-upload-hint",children:"or drag and drop a file into the editor"})]}):/* @__PURE__ */e("p",{className:"rmx-upload-hint",children:["File upload is not available. Provide an ",
|
|
10
|
+
/* @__PURE__ */a("code",{children:"uploadHandler"})," prop to enable uploads, or use the URL tab to link to a file directly."]})}),y&&/* @__PURE__ */a("div",{className:"rmx-form-group rmx-form-error",children:y}),
|
|
11
|
+
/* @__PURE__ */e("form",{onSubmit:e=>{if(e.preventDefault(),!p.trim())return;let a;try{a=decodeURIComponent(p.trim())}catch{a=p.trim()}i.test(a)||(s.executeCommand("insertAttachment",{url:p,filename:h||"file",filesize:b}),U())},className:"rmx-modal-form",children:["url"===d&&/* @__PURE__ */e("div",{className:"rmx-form-group",children:[
|
|
12
|
+
/* @__PURE__ */a("label",{className:"rmx-form-label",htmlFor:"rmx-attachment-url",children:"File URL"}),
|
|
13
|
+
/* @__PURE__ */a("input",{id:"rmx-attachment-url",type:"url",className:"rmx-form-input",value:p,onChange:e=>u(e.target.value),placeholder:"https://example.com/document.pdf",required:!0,autoFocus:!0})]}),
|
|
14
|
+
/* @__PURE__ */e("div",{className:"rmx-form-group",children:[
|
|
15
|
+
/* @__PURE__ */a("label",{className:"rmx-form-label",htmlFor:"rmx-attachment-name",children:"Display Name"}),
|
|
16
|
+
/* @__PURE__ */a("input",{id:"rmx-attachment-name",type:"text",className:"rmx-form-input",value:h,onChange:e=>x(e.target.value),placeholder:"document.pdf"})]}),
|
|
17
|
+
/* @__PURE__ */e("div",{className:"rmx-modal-actions",children:[
|
|
18
|
+
/* @__PURE__ */a("button",{type:"button",className:"rmx-btn",onClick:U,children:"Cancel"}),
|
|
19
|
+
/* @__PURE__ */a("button",{type:"submit",className:"rmx-btn rmx-btn-primary",disabled:!p.trim()||N,children:N?/* @__PURE__ */e(r,{children:[
|
|
20
|
+
/* @__PURE__ */a("span",{className:"rmx-spinner","aria-hidden":"true"})," Uploading…"]}):"Insert"})]})]})]})}m.propTypes={open:n.bool.isRequired,onClose:n.func.isRequired,engine:n.object};export{m as AttachmentModal};
|
|
21
|
+
//# sourceMappingURL=AttachmentModal--6T-vYJt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachmentModal--6T-vYJt.js","sources":["../src/components/Modals/AttachmentModal.jsx"],"sourcesContent":["import { useState, useRef } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\n\nconst DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\nexport function AttachmentModal({ open, onClose, engine }) {\n const [tab, setTab] = useState('url')\n const [url, setUrl] = useState('')\n const [filename, setFilename] = useState('')\n const [filesize, setFilesize] = useState(null)\n const [uploading, setUploading] = useState(false)\n const [error, setError] = useState('')\n const fileInputRef = useRef(null)\n\n const hasUploadHandler = !!engine?.options?.uploadHandler\n\n const handleSubmit = (e) => {\n e.preventDefault()\n if (!url.trim()) return\n // Decode percent-encoding before validation to prevent bypasses like java%73cript:\n let decoded\n try {\n decoded = decodeURIComponent(url.trim())\n } catch {\n decoded = url.trim()\n }\n if (DANGEROUS_PROTOCOL.test(decoded)) return\n engine.executeCommand('insertAttachment', {\n url,\n filename: filename || 'file',\n filesize,\n })\n handleClose()\n }\n\n const handleClose = () => {\n onClose()\n setUrl('')\n setFilename('')\n setFilesize(null)\n setUploading(false)\n setError('')\n setTab('url')\n }\n\n const handleFileChange = (e) => {\n const file = e.target.files[0]\n if (!file) return\n\n setFilename(file.name)\n setFilesize(file.size)\n\n if (engine.options.uploadHandler) {\n setUploading(true)\n setError('')\n engine.options.uploadHandler(file)\n .then((resultUrl) => {\n setUrl(resultUrl)\n setTab('url')\n setUploading(false)\n })\n .catch((err) => {\n console.error('File upload failed:', err)\n setError(err.message || 'File upload failed. Please try again.')\n setUploading(false)\n })\n }\n }\n\n return (\n <ModalOverlay title=\"Attach File\" open={open} onClose={handleClose}>\n <div className=\"rmx-tabs\">\n <button\n type=\"button\"\n className={`rmx-tab ${tab === 'url' ? 'rmx-active' : ''}`}\n onClick={() => setTab('url')}\n >\n URL\n </button>\n <button\n type=\"button\"\n className={`rmx-tab ${tab === 'upload' ? 'rmx-active' : ''}`}\n onClick={() => setTab('upload')}\n >\n Upload\n </button>\n </div>\n\n {tab === 'upload' && (\n <div className=\"rmx-upload-area\">\n {hasUploadHandler ? (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n <button\n type=\"button\"\n className=\"rmx-btn rmx-btn-upload\"\n onClick={() => fileInputRef.current?.click()}\n disabled={uploading}\n >\n {uploading ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Uploading\\u2026'}\n </>\n ) : 'Choose File'}\n </button>\n <p className=\"rmx-upload-hint\">or drag and drop a file into the editor</p>\n </>\n ) : (\n <p className=\"rmx-upload-hint\">\n File upload is not available. Provide an <code>uploadHandler</code> prop to enable uploads, or use the URL tab to link to a file directly.\n </p>\n )}\n </div>\n )}\n\n {error && (\n <div className=\"rmx-form-group rmx-form-error\">\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"rmx-modal-form\">\n {tab === 'url' && (\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-attachment-url\">File URL</label>\n <input\n id=\"rmx-attachment-url\"\n type=\"url\"\n className=\"rmx-form-input\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://example.com/document.pdf\"\n required\n autoFocus\n />\n </div>\n )}\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-attachment-name\">Display Name</label>\n <input\n id=\"rmx-attachment-name\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={filename}\n onChange={(e) => setFilename(e.target.value)}\n placeholder=\"document.pdf\"\n />\n </div>\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={handleClose}>Cancel</button>\n <button type=\"submit\" className=\"rmx-btn rmx-btn-primary\" disabled={!url.trim() || uploading}>\n {uploading ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Uploading\\u2026'}\n </>\n ) : 'Insert'}\n </button>\n </div>\n </form>\n </ModalOverlay>\n )\n}\n\nAttachmentModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["DANGEROUS_PROTOCOL","AttachmentModal","open","onClose","engine","tab","setTab","useState","url","setUrl","filename","setFilename","filesize","setFilesize","uploading","setUploading","error","setError","fileInputRef","useRef","hasUploadHandler","options","uploadHandler","handleClose","ModalOverlay","title","children","jsxs","className","jsx","type","onClick","Fragment","ref","onChange","e","file","target","files","name","size","then","resultUrl","catch","err","message","style","display","current","click","disabled","onSubmit","preventDefault","trim","decoded","decodeURIComponent","test","executeCommand","htmlFor","id","value","placeholder","required","autoFocus","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("react"),t=require("./index-Dc63uIP0.cjs"),r=require("./ModalOverlay-BDsGgv3_.cjs"),l=/^\s*(javascript|vbscript|data\s*:\s*text\/html)\s*:/i;function s({open:t,onClose:s,engine:n}){const[o,i]=a.useState("url"),[m,d]=a.useState(""),[c,u]=a.useState(""),[p,x]=a.useState(null),[h,b]=a.useState(!1),[j,f]=a.useState(""),y=a.useRef(null),g=!!n?.options?.uploadHandler,N=()=>{s(),d(""),u(""),x(null),b(!1),f(""),i("url")};return e.jsxs(r.ModalOverlay,{title:"Attach File",open:t,onClose:N,children:[e.jsxs("div",{className:"rmx-tabs",children:[e.jsx("button",{type:"button",className:"rmx-tab "+("url"===o?"rmx-active":""),onClick:()=>i("url"),children:"URL"}),e.jsx("button",{type:"button",className:"rmx-tab "+("upload"===o?"rmx-active":""),onClick:()=>i("upload"),children:"Upload"})]}),"upload"===o&&e.jsx("div",{className:"rmx-upload-area",children:g?e.jsxs(e.Fragment,{children:[e.jsx("input",{ref:y,type:"file",onChange:e=>{const a=e.target.files[0];a&&(u(a.name),x(a.size),n.options.uploadHandler&&(b(!0),f(""),n.options.uploadHandler(a).then((e=>{d(e),i("url"),b(!1)})).catch((e=>{f(e.message||"File upload failed. Please try again."),b(!1)}))))},style:{display:"none"}}),e.jsx("button",{type:"button",className:"rmx-btn rmx-btn-upload",onClick:()=>y.current?.click(),disabled:h,children:h?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"rmx-spinner","aria-hidden":"true"})," Uploading…"]}):"Choose File"}),e.jsx("p",{className:"rmx-upload-hint",children:"or drag and drop a file into the editor"})]}):e.jsxs("p",{className:"rmx-upload-hint",children:["File upload is not available. Provide an ",e.jsx("code",{children:"uploadHandler"})," prop to enable uploads, or use the URL tab to link to a file directly."]})}),j&&e.jsx("div",{className:"rmx-form-group rmx-form-error",children:j}),e.jsxs("form",{onSubmit:e=>{if(e.preventDefault(),!m.trim())return;let a;try{a=decodeURIComponent(m.trim())}catch{a=m.trim()}l.test(a)||(n.executeCommand("insertAttachment",{url:m,filename:c||"file",filesize:p}),N())},className:"rmx-modal-form",children:["url"===o&&e.jsxs("div",{className:"rmx-form-group",children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-attachment-url",children:"File URL"}),e.jsx("input",{id:"rmx-attachment-url",type:"url",className:"rmx-form-input",value:m,onChange:e=>d(e.target.value),placeholder:"https://example.com/document.pdf",required:!0,autoFocus:!0})]}),e.jsxs("div",{className:"rmx-form-group",children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-attachment-name",children:"Display Name"}),e.jsx("input",{id:"rmx-attachment-name",type:"text",className:"rmx-form-input",value:c,onChange:e=>u(e.target.value),placeholder:"document.pdf"})]}),e.jsxs("div",{className:"rmx-modal-actions",children:[e.jsx("button",{type:"button",className:"rmx-btn",onClick:N,children:"Cancel"}),e.jsx("button",{type:"submit",className:"rmx-btn rmx-btn-primary",disabled:!m.trim()||h,children:h?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"rmx-spinner","aria-hidden":"true"})," Uploading…"]}):"Insert"})]})]})]})}s.propTypes={open:t.PropTypes.bool.isRequired,onClose:t.PropTypes.func.isRequired,engine:t.PropTypes.object},exports.AttachmentModal=s;
|
|
2
|
+
//# sourceMappingURL=AttachmentModal-D-uxbXvb.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachmentModal-D-uxbXvb.cjs","sources":["../src/components/Modals/AttachmentModal.jsx"],"sourcesContent":["import { useState, useRef } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\n\nconst DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\nexport function AttachmentModal({ open, onClose, engine }) {\n const [tab, setTab] = useState('url')\n const [url, setUrl] = useState('')\n const [filename, setFilename] = useState('')\n const [filesize, setFilesize] = useState(null)\n const [uploading, setUploading] = useState(false)\n const [error, setError] = useState('')\n const fileInputRef = useRef(null)\n\n const hasUploadHandler = !!engine?.options?.uploadHandler\n\n const handleSubmit = (e) => {\n e.preventDefault()\n if (!url.trim()) return\n // Decode percent-encoding before validation to prevent bypasses like java%73cript:\n let decoded\n try {\n decoded = decodeURIComponent(url.trim())\n } catch {\n decoded = url.trim()\n }\n if (DANGEROUS_PROTOCOL.test(decoded)) return\n engine.executeCommand('insertAttachment', {\n url,\n filename: filename || 'file',\n filesize,\n })\n handleClose()\n }\n\n const handleClose = () => {\n onClose()\n setUrl('')\n setFilename('')\n setFilesize(null)\n setUploading(false)\n setError('')\n setTab('url')\n }\n\n const handleFileChange = (e) => {\n const file = e.target.files[0]\n if (!file) return\n\n setFilename(file.name)\n setFilesize(file.size)\n\n if (engine.options.uploadHandler) {\n setUploading(true)\n setError('')\n engine.options.uploadHandler(file)\n .then((resultUrl) => {\n setUrl(resultUrl)\n setTab('url')\n setUploading(false)\n })\n .catch((err) => {\n console.error('File upload failed:', err)\n setError(err.message || 'File upload failed. Please try again.')\n setUploading(false)\n })\n }\n }\n\n return (\n <ModalOverlay title=\"Attach File\" open={open} onClose={handleClose}>\n <div className=\"rmx-tabs\">\n <button\n type=\"button\"\n className={`rmx-tab ${tab === 'url' ? 'rmx-active' : ''}`}\n onClick={() => setTab('url')}\n >\n URL\n </button>\n <button\n type=\"button\"\n className={`rmx-tab ${tab === 'upload' ? 'rmx-active' : ''}`}\n onClick={() => setTab('upload')}\n >\n Upload\n </button>\n </div>\n\n {tab === 'upload' && (\n <div className=\"rmx-upload-area\">\n {hasUploadHandler ? (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n onChange={handleFileChange}\n style={{ display: 'none' }}\n />\n <button\n type=\"button\"\n className=\"rmx-btn rmx-btn-upload\"\n onClick={() => fileInputRef.current?.click()}\n disabled={uploading}\n >\n {uploading ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Uploading\\u2026'}\n </>\n ) : 'Choose File'}\n </button>\n <p className=\"rmx-upload-hint\">or drag and drop a file into the editor</p>\n </>\n ) : (\n <p className=\"rmx-upload-hint\">\n File upload is not available. Provide an <code>uploadHandler</code> prop to enable uploads, or use the URL tab to link to a file directly.\n </p>\n )}\n </div>\n )}\n\n {error && (\n <div className=\"rmx-form-group rmx-form-error\">\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"rmx-modal-form\">\n {tab === 'url' && (\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-attachment-url\">File URL</label>\n <input\n id=\"rmx-attachment-url\"\n type=\"url\"\n className=\"rmx-form-input\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://example.com/document.pdf\"\n required\n autoFocus\n />\n </div>\n )}\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-attachment-name\">Display Name</label>\n <input\n id=\"rmx-attachment-name\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={filename}\n onChange={(e) => setFilename(e.target.value)}\n placeholder=\"document.pdf\"\n />\n </div>\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={handleClose}>Cancel</button>\n <button type=\"submit\" className=\"rmx-btn rmx-btn-primary\" disabled={!url.trim() || uploading}>\n {uploading ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Uploading\\u2026'}\n </>\n ) : 'Insert'}\n </button>\n </div>\n </form>\n </ModalOverlay>\n )\n}\n\nAttachmentModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["DANGEROUS_PROTOCOL","AttachmentModal","open","onClose","engine","tab","setTab","useState","url","setUrl","filename","setFilename","filesize","setFilesize","uploading","setUploading","error","setError","fileInputRef","useRef","hasUploadHandler","options","uploadHandler","handleClose","ModalOverlay","title","children","jsxs","className","jsx","type","onClick","Fragment","ref","onChange","e","file","target","files","name","size","then","resultUrl","catch","err","message","style","display","current","click","disabled","onSubmit","preventDefault","trim","decoded","decodeURIComponent","test","executeCommand","htmlFor","id","value","placeholder","required","autoFocus","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import a,{useRef as n,useState as r,useMemo as o,useEffect as c,useCallback as l}from"react";import{filterSlashItems as m,recordRecentCommand as i,getCustomCommandItems as d,SLASH_COMMAND_ITEMS as s,TOOLTIP_MAP as p,SHORTCUT_MAP as u,MODAL_COMMANDS as h,getShortcutLabel as x}from"@remyxjs/core";const v=a.memo((function({open:a,onClose:v,engine:g,onOpenModal:f,slashCommandItems:y}){const b=n(null),N=n(null),[k,w]=r(""),[D,C]=r(0),A=o((()=>function(e,t){const a=t||s,n=d(),r=[...a];for(const l of n)r.some((e=>e.id===l.id))||r.push(l);const o=new Set(r.map((e=>e.id))),c=[];if(e){const t=e.commands.getAll();for(const e of t){if(o.has(e))continue;if("undo"===e||"redo"===e||"removeFormat"===e)continue;const t=p[e];if(!t)continue;const a=u[e],n=e in h;c.push({id:e,label:t,description:"",icon:a?x(e).charAt(0):"•",keywords:[e],category:"Commands",shortcutLabel:a?x(e):null,action:n?(t,a)=>a?.(h[e]):t=>t.executeCommand(e)}),o.add(e)}}return[...r.map((e=>({...e,shortcutLabel:u[e.id]?x(e.id):null}))),...c]}(g,y)),[g,y]),L=o((()=>m(A,k)),[A,k]);c((()=>{a&&(w(""),C(0),requestAnimationFrame((()=>b.current?.focus())))}),[a]),c((()=>{C(0)}),[k]),c((()=>{if(!N.current)return;const e=N.current.querySelector(".rmx-command-palette-item.rmx-active");e&&e.scrollIntoView({block:"nearest"})}),[D]);const q=l((e=>{v(),i(e.id),requestAnimationFrame((()=>{e.action(g,f)}))}),[g,v,f]),E=l((e=>"Escape"===e.key?(e.preventDefault(),void v()):"ArrowDown"===e.key?(e.preventDefault(),void C((e=>0===L.length?0:(e+1)%L.length))):"ArrowUp"===e.key?(e.preventDefault(),void C((e=>0===L.length?0:(e-1+L.length)%L.length))):void("Enter"===e.key&&(e.preventDefault(),L.length>0&&D<L.length&&q(L[D])))),[L,D,q,v]),F=l((e=>{"Tab"===e.key&&e.preventDefault()}),[]);if(!a)return null;const M=[];let T=null;for(let e=0;e<L.length;e++){const t=L[e];t.category!==T&&(M.push({category:t.category,items:[]}),T=t.category),M[M.length-1].items.push({item:t,index:e})}/* @__PURE__ */
|
|
2
|
+
return e("div",{className:"rmx-command-palette-overlay",onMouseDown:e=>{e.target===e.currentTarget&&(e.preventDefault(),v())},onKeyDown:F,children:/* @__PURE__ */t("div",{className:"rmx-command-palette",role:"dialog","aria-label":"Command palette",children:[
|
|
3
|
+
/* @__PURE__ */t("div",{className:"rmx-command-palette-input-wrap",children:[
|
|
4
|
+
/* @__PURE__ */e("span",{className:"rmx-command-palette-search-icon","aria-hidden":"true",children:"/"}),
|
|
5
|
+
/* @__PURE__ */e("input",{ref:b,className:"rmx-command-palette-input",type:"text",placeholder:"Type a command...",value:k,onChange:e=>w(e.target.value),onKeyDown:E,"aria-activedescendant":L[D]?`rmx-cmd-item-${L[D].id}`:void 0,role:"combobox","aria-expanded":"true","aria-controls":"rmx-command-palette-list","aria-autocomplete":"list"})]}),
|
|
6
|
+
/* @__PURE__ */e("div",{ref:N,className:"rmx-command-palette-list",id:"rmx-command-palette-list",role:"listbox",children:0===L.length?/* @__PURE__ */e("div",{className:"rmx-command-palette-empty",children:"No matching commands"}):M.map((a=>/* @__PURE__ */t("div",{children:[
|
|
7
|
+
/* @__PURE__ */e("div",{className:"rmx-command-palette-category",children:a.category}),a.items.map((({item:a,index:n})=>/* @__PURE__ */t("div",{id:`rmx-cmd-item-${a.id}`,className:"rmx-command-palette-item"+(n===D?" rmx-active":""),role:"option","aria-selected":n===D,onMouseEnter:()=>C(n),onClick:()=>q(a),children:[
|
|
8
|
+
/* @__PURE__ */e("div",{className:"rmx-command-palette-item-icon",children:a.icon}),
|
|
9
|
+
/* @__PURE__ */t("div",{className:"rmx-command-palette-item-text",children:[
|
|
10
|
+
/* @__PURE__ */e("div",{className:"rmx-command-palette-item-label",children:a.label}),a.description&&/* @__PURE__ */e("div",{className:"rmx-command-palette-item-desc",children:a.description})]}),a.shortcutLabel&&/* @__PURE__ */e("span",{className:"rmx-command-palette-shortcut",children:a.shortcutLabel})]},a.id)))]},a.category)))}),
|
|
11
|
+
/* @__PURE__ */t("div",{className:"rmx-command-palette-hint",children:[
|
|
12
|
+
/* @__PURE__ */t("span",{children:[
|
|
13
|
+
/* @__PURE__ */e("kbd",{children:"↑"}),
|
|
14
|
+
/* @__PURE__ */e("kbd",{children:"↓"})," navigate"]}),
|
|
15
|
+
/* @__PURE__ */t("span",{children:[
|
|
16
|
+
/* @__PURE__ */e("kbd",{children:"↵"})," select"]}),
|
|
17
|
+
/* @__PURE__ */t("span",{children:[
|
|
18
|
+
/* @__PURE__ */e("kbd",{children:"esc"})," close"]})]})]})})}));export{v as CommandPalette};
|
|
19
|
+
//# sourceMappingURL=CommandPalette-DXWyTGOU.js.map
|