@procore/text-editor 0.0.2 → 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.
- package/CHANGELOG.md +26 -0
- package/README.md +1 -22
- package/codemod/__fixtures__/procore.config.ternary.js +18 -0
- package/codemod/__fixtures__/src/components/MultilineFormRichText.tsx +2 -1
- package/codemod/text-editor-migrate.js +313 -105
- package/codemod/text-editor-migrate.test.js +28 -0
- package/dist/TextEditor/TextEditor.d.ts +1 -1
- package/dist/TextEditor/TextEditor.js +81 -36
- package/dist/TextEditor/TextEditor.js.map +1 -1
- package/dist/TextEditor/TextEditor.styles.js +1 -1
- package/dist/TextEditor/TextEditor.types.d.ts +32 -31
- package/dist/TextEditor/TextEditor.types.js.map +1 -1
- package/dist/TextEditor/TextEditorProvider.js +1 -1
- package/dist/TextEditor/TextEditorProvider.js.map +1 -1
- package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js +2 -0
- package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js.map +1 -1
- package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js +2 -1
- package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js.map +1 -1
- package/dist/TextEditor/useTabAsNavigation.js +2 -1
- package/dist/TextEditor/useTabAsNavigation.js.map +1 -1
- package/dist/TextEditor/utils/config.js +5 -3
- package/dist/TextEditor/utils/config.js.map +1 -1
- package/dist/TextEditor/utils/index.d.ts +0 -1
- package/dist/TextEditor/utils/index.js +0 -1
- package/dist/TextEditor/utils/index.js.map +1 -1
- package/dist/TextEditorOutput/TextEditorOutput.styles.js +1 -1
- package/dist/_typedoc/TextEditor/TextEditor.types.json +51 -51
- package/dist/_typedoc/TextEditor/TextEditorProvider.types.json +2 -2
- package/dist/_typedoc/TextEditorOutput/TextEditorOutput.types.json +3 -3
- package/dist/_typedoc/deprecations.json +1 -1
- package/package.json +13 -16
- package/dist/TextEditor/utils/plugins.d.ts +0 -7
- package/dist/TextEditor/utils/plugins.js +0 -184
- package/dist/TextEditor/utils/plugins.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @procore/text-editor
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- New toolbar defaults. Remove `plugins` and `config` props to rely on the TextEditor package's default internal configuration.
|
|
8
|
+
- Add Enter key `stopPropagation` to prevent form submission when pressing Enter in the editor.
|
|
9
|
+
|
|
10
|
+
## 0.0.3
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 4656bce: Default provider TAB key as navigation to remove keyboard trap
|
|
15
|
+
|
|
16
|
+
## 0.0.2
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 7d01b54: Fix some types and remove legacy tinymce no-ops and focus.
|
|
21
|
+
|
|
22
|
+
## 0.0.1
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- f95beb2: Initial release of a separate TextEditor package, extracted from core-react.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ yarn add @procore/text-editor
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### TextEditor Usage
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
16
|
import { TextEditor } from '@procore/text-editor'
|
|
@@ -27,27 +27,6 @@ function MyComponent() {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
### With Custom Configuration
|
|
31
|
-
|
|
32
|
-
```tsx
|
|
33
|
-
import { TextEditor } from '@procore/text-editor'
|
|
34
|
-
|
|
35
|
-
function MyComponent() {
|
|
36
|
-
const [value, setValue] = React.useState('')
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<TextEditor
|
|
40
|
-
value={value}
|
|
41
|
-
onChange={(newValue) => setValue(newValue)}
|
|
42
|
-
plugins={['link', 'table']}
|
|
43
|
-
config={(defaultConfig) => ({
|
|
44
|
-
...defaultConfig,
|
|
45
|
-
// Your custom CKEditor configuration
|
|
46
|
-
})}
|
|
47
|
-
/>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
30
|
|
|
52
31
|
### TextEditorProvider
|
|
53
32
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { coreReactJestConfig } = require('@procore/core-react/jestConfig')
|
|
2
|
+
|
|
3
|
+
module.exports = ({ env }) => ({
|
|
4
|
+
app: {
|
|
5
|
+
webpackOverride(config) {
|
|
6
|
+
return config
|
|
7
|
+
},
|
|
8
|
+
moduleFederation: {
|
|
9
|
+
shared:
|
|
10
|
+
process.env.NODE_ENV === 'production'
|
|
11
|
+
? { react: deps.react, 'react-dom': deps['react-dom'] }
|
|
12
|
+
: {},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
jestOverride: (defaultConfig) => {
|
|
16
|
+
return coreReactJestConfig(defaultConfig)
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Form } from '@procore/core-react'
|
|
1
|
+
import { Form, TextEditor } from '@procore/core-react'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
export const MultilineFormRichText = () => {
|
|
@@ -9,6 +9,7 @@ export const MultilineFormRichText = () => {
|
|
|
9
9
|
label="Description"
|
|
10
10
|
plugins={['table']}
|
|
11
11
|
/>
|
|
12
|
+
<TextEditor value="notes" plugins={['links']} />
|
|
12
13
|
</Form>
|
|
13
14
|
)
|
|
14
15
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 2. Updates Form.RichText usage to include textEditorComponent prop
|
|
10
10
|
* 3. Updates Jest configuration to use textEditorJestConfig
|
|
11
11
|
* 4. Adds ckeditor5 singleton to module federation shared config
|
|
12
|
+
* 5. Remove deprecated plugins prop from TextEditor and Form.RichText components
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
const fs = require('fs')
|
|
@@ -168,6 +169,52 @@ function transformImports(fileContent) {
|
|
|
168
169
|
return { content: newContent, modified, needsTextEditorImport }
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Remove deprecated plugins prop from TextEditor and Form.RichText components
|
|
174
|
+
*/
|
|
175
|
+
function removeDeprecatedProps(fileContent) {
|
|
176
|
+
let modified = false
|
|
177
|
+
let newContent = fileContent
|
|
178
|
+
|
|
179
|
+
// Pattern to match TextEditor components with plugins prop
|
|
180
|
+
const textEditorRegex = /(<TextEditor)([\s\S]*?)(\/>|>)/g
|
|
181
|
+
newContent = newContent.replace(
|
|
182
|
+
textEditorRegex,
|
|
183
|
+
(match, opening, props, closing) => {
|
|
184
|
+
let updatedProps = props
|
|
185
|
+
const hasPlugins = props.includes('plugins=')
|
|
186
|
+
|
|
187
|
+
if (hasPlugins) {
|
|
188
|
+
// Remove plugins prop
|
|
189
|
+
updatedProps = updatedProps.replace(/\s*plugins\s*=\s*\{[^}]*\}/g, '')
|
|
190
|
+
modified = true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return opening + updatedProps + closing
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Pattern to match Form.RichText components with plugins prop
|
|
198
|
+
const formRichTextRegex = /(<Form\.RichText(?:Field)?)([\s\S]*?)(\/>|>)/g
|
|
199
|
+
newContent = newContent.replace(
|
|
200
|
+
formRichTextRegex,
|
|
201
|
+
(match, opening, props, closing) => {
|
|
202
|
+
let updatedProps = props
|
|
203
|
+
const hasPlugins = props.includes('plugins=')
|
|
204
|
+
|
|
205
|
+
if (hasPlugins) {
|
|
206
|
+
// Remove plugins prop
|
|
207
|
+
updatedProps = updatedProps.replace(/\s*plugins\s*=\s*\{[^}]*\}/g, '')
|
|
208
|
+
modified = true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return opening + updatedProps + closing
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return { content: newContent, modified }
|
|
216
|
+
}
|
|
217
|
+
|
|
171
218
|
/**
|
|
172
219
|
* Transform Form.RichText usage
|
|
173
220
|
*/
|
|
@@ -289,9 +336,255 @@ function transformFormRichText(fileContent) {
|
|
|
289
336
|
return { content: newContent, modified }
|
|
290
337
|
}
|
|
291
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Add ckeditor5 singleton configuration to a shared object
|
|
341
|
+
* Returns the modified content and whether it was modified
|
|
342
|
+
*/
|
|
343
|
+
function addSingletonToSharedObject(content, sharedMatch) {
|
|
344
|
+
const insertPosition = sharedMatch.index + sharedMatch[0].length
|
|
345
|
+
|
|
346
|
+
// Detect indentation by looking at the content after `shared: {` or similar
|
|
347
|
+
// Find the first property inside shared to determine proper indentation
|
|
348
|
+
const afterShared = content.substring(insertPosition)
|
|
349
|
+
const firstPropertyMatch = afterShared.match(/\n(\s+)\w+\s*:/)
|
|
350
|
+
|
|
351
|
+
let propertyIndent = ''
|
|
352
|
+
let indentUnit = ' ' // default to 2 spaces
|
|
353
|
+
|
|
354
|
+
if (firstPropertyMatch) {
|
|
355
|
+
// Use the same indentation as the first property inside shared
|
|
356
|
+
propertyIndent = firstPropertyMatch[1]
|
|
357
|
+
// Detect indent unit from the property indent
|
|
358
|
+
const sharedLineMatch = content
|
|
359
|
+
.substring(0, sharedMatch.index)
|
|
360
|
+
.match(/\n(\s+)(?:shared|\?)/)
|
|
361
|
+
if (sharedLineMatch) {
|
|
362
|
+
const sharedIndent = sharedLineMatch[1]
|
|
363
|
+
// indentUnit is the difference between property indent and shared indent
|
|
364
|
+
if (propertyIndent.length > sharedIndent.length) {
|
|
365
|
+
indentUnit = propertyIndent.substring(sharedIndent.length)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
// Fallback: detect from the 'shared' line indentation
|
|
370
|
+
const beforeMatch = content.substring(0, sharedMatch.index)
|
|
371
|
+
const lastNewlineIndex = beforeMatch.lastIndexOf('\n')
|
|
372
|
+
const lineStart =
|
|
373
|
+
lastNewlineIndex >= 0
|
|
374
|
+
? beforeMatch.substring(lastNewlineIndex + 1)
|
|
375
|
+
: beforeMatch
|
|
376
|
+
const baseIndentMatch = lineStart.match(/^(\s*)/)
|
|
377
|
+
const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ''
|
|
378
|
+
|
|
379
|
+
// Determine if we're using tabs or spaces
|
|
380
|
+
const useTabs = baseIndent.includes('\t')
|
|
381
|
+
indentUnit = useTabs ? '\t' : ' '
|
|
382
|
+
propertyIndent = baseIndent + indentUnit
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Build the ckeditor5 configuration
|
|
386
|
+
const ckeditor5Config = `
|
|
387
|
+
${propertyIndent}ckeditor5: {
|
|
388
|
+
${propertyIndent}${indentUnit}requiredVersion: deps['ckeditor5'],
|
|
389
|
+
${propertyIndent}${indentUnit}singleton: true,
|
|
390
|
+
${propertyIndent}},`
|
|
391
|
+
|
|
392
|
+
// Insert the ckeditor5 configuration after the opening brace
|
|
393
|
+
return (
|
|
394
|
+
content.substring(0, insertPosition) +
|
|
395
|
+
ckeditor5Config +
|
|
396
|
+
content.substring(insertPosition)
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Add deps import/require statement to the file if not already present
|
|
402
|
+
*/
|
|
403
|
+
function addDepsImport(content, filePath) {
|
|
404
|
+
// Check if deps variable is already declared
|
|
405
|
+
if (
|
|
406
|
+
content.includes("require('./package.json')") ||
|
|
407
|
+
content.includes("require('./package.json').dependencies") ||
|
|
408
|
+
content.includes("from './package.json'")
|
|
409
|
+
) {
|
|
410
|
+
return content
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Determine if using CommonJS or ES modules
|
|
414
|
+
const isESModule =
|
|
415
|
+
content.includes('export default') ||
|
|
416
|
+
content.includes('export {') ||
|
|
417
|
+
filePath.endsWith('.mjs')
|
|
418
|
+
|
|
419
|
+
if (isESModule) {
|
|
420
|
+
// For ES modules, add import at the top
|
|
421
|
+
const importStatement =
|
|
422
|
+
"import deps from './package.json' with { type: 'json' }\n"
|
|
423
|
+
|
|
424
|
+
// Find the first import or export statement
|
|
425
|
+
const firstImportMatch = content.match(/^(import|export)/m)
|
|
426
|
+
if (firstImportMatch) {
|
|
427
|
+
const insertPos = content.indexOf(firstImportMatch[0])
|
|
428
|
+
return (
|
|
429
|
+
content.substring(0, insertPos) +
|
|
430
|
+
importStatement +
|
|
431
|
+
content.substring(insertPos)
|
|
432
|
+
)
|
|
433
|
+
} else {
|
|
434
|
+
return importStatement + content
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
// For CommonJS, add require at the top
|
|
438
|
+
const requireStatement =
|
|
439
|
+
"const deps = require('./package.json').dependencies\n"
|
|
440
|
+
|
|
441
|
+
// Find the first require or const statement
|
|
442
|
+
const firstRequireMatch = content.match(/^(const|let|var|require)/m)
|
|
443
|
+
if (firstRequireMatch) {
|
|
444
|
+
const insertPos = content.indexOf(firstRequireMatch[0])
|
|
445
|
+
return (
|
|
446
|
+
content.substring(0, insertPos) +
|
|
447
|
+
requireStatement +
|
|
448
|
+
content.substring(insertPos)
|
|
449
|
+
)
|
|
450
|
+
} else {
|
|
451
|
+
return requireStatement + content
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Find the matching closing brace for an opening brace at the given position
|
|
458
|
+
*/
|
|
459
|
+
function findMatchingBrace(content, openBracePos) {
|
|
460
|
+
let depth = 1
|
|
461
|
+
let pos = openBracePos + 1
|
|
462
|
+
|
|
463
|
+
while (pos < content.length && depth > 0) {
|
|
464
|
+
const char = content[pos]
|
|
465
|
+
if (char === '{') {
|
|
466
|
+
depth++
|
|
467
|
+
} else if (char === '}') {
|
|
468
|
+
depth--
|
|
469
|
+
}
|
|
470
|
+
pos++
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return depth === 0 ? pos - 1 : -1
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Check if an object block contains a 'react' key
|
|
478
|
+
*/
|
|
479
|
+
function objectContainsReact(content, openBracePos) {
|
|
480
|
+
const closeBracePos = findMatchingBrace(content, openBracePos)
|
|
481
|
+
if (closeBracePos === -1) return false
|
|
482
|
+
|
|
483
|
+
const objectContent = content.substring(openBracePos, closeBracePos + 1)
|
|
484
|
+
// Check for react as a key (handles react:, 'react':, "react":)
|
|
485
|
+
return /(?:^|[{,\s])react\s*:|['"]react['"]\s*:/.test(objectContent)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Transform module federation with ternary conditional pattern
|
|
490
|
+
* Handles patterns like:
|
|
491
|
+
* shared: process.env.NODE_ENV === 'production'
|
|
492
|
+
* ? { react: deps.react, 'react-dom': deps['react-dom'] }
|
|
493
|
+
* : {},
|
|
494
|
+
* Only adds ckeditor5 to branches that contain 'react'
|
|
495
|
+
*/
|
|
496
|
+
function transformModuleFederationTernary(fileContent, filePath) {
|
|
497
|
+
let newContent = fileContent
|
|
498
|
+
|
|
499
|
+
// Pattern to match ternary conditional for shared - find the opening brace of the consequent
|
|
500
|
+
// Matches: shared: <condition> ? {
|
|
501
|
+
const ternaryRegex =
|
|
502
|
+
/shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*(\{)/g
|
|
503
|
+
|
|
504
|
+
let match
|
|
505
|
+
let offset = 0
|
|
506
|
+
|
|
507
|
+
// Find all ternary patterns and process the consequent (true branch)
|
|
508
|
+
while ((match = ternaryRegex.exec(fileContent)) !== null) {
|
|
509
|
+
const adjustedIndex = match.index + offset
|
|
510
|
+
const bracePos = adjustedIndex + match[0].length - 1 // Position of the opening brace
|
|
511
|
+
|
|
512
|
+
// Only add ckeditor5 if this branch contains 'react'
|
|
513
|
+
if (!objectContainsReact(newContent, bracePos)) {
|
|
514
|
+
continue
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const adjustedMatch = {
|
|
518
|
+
...match,
|
|
519
|
+
index: bracePos,
|
|
520
|
+
0: '{',
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const beforeLength = newContent.length
|
|
524
|
+
newContent = addSingletonToSharedObject(newContent, adjustedMatch)
|
|
525
|
+
offset += newContent.length - beforeLength
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Now find and process the alternate (false branch) - the : { ... } part
|
|
529
|
+
// We need to find the consequent's closing brace first, then find the alternate
|
|
530
|
+
const ternaryRegex2 = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*\{/g
|
|
531
|
+
|
|
532
|
+
let match2
|
|
533
|
+
while ((match2 = ternaryRegex2.exec(newContent)) !== null) {
|
|
534
|
+
// Find the opening brace of the consequent
|
|
535
|
+
const consequentOpenBrace = match2.index + match2[0].length - 1
|
|
536
|
+
|
|
537
|
+
// Find the matching closing brace
|
|
538
|
+
const consequentCloseBrace = findMatchingBrace(
|
|
539
|
+
newContent,
|
|
540
|
+
consequentOpenBrace
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
if (consequentCloseBrace === -1) continue
|
|
544
|
+
|
|
545
|
+
// Find the alternate branch - look for : { after the closing brace
|
|
546
|
+
const afterConsequent = newContent.substring(consequentCloseBrace + 1)
|
|
547
|
+
const alternateMatch = afterConsequent.match(/^\s*:\s*(\{)/)
|
|
548
|
+
|
|
549
|
+
if (alternateMatch) {
|
|
550
|
+
const alternateBracePos =
|
|
551
|
+
consequentCloseBrace +
|
|
552
|
+
1 +
|
|
553
|
+
alternateMatch.index +
|
|
554
|
+
alternateMatch[0].length -
|
|
555
|
+
1
|
|
556
|
+
|
|
557
|
+
// Only add ckeditor5 if this branch contains 'react'
|
|
558
|
+
if (!objectContainsReact(newContent, alternateBracePos)) {
|
|
559
|
+
continue
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const adjustedMatch = {
|
|
563
|
+
index: alternateBracePos,
|
|
564
|
+
0: '{',
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
newContent = addSingletonToSharedObject(newContent, adjustedMatch)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Add deps import if needed only if we made changes
|
|
572
|
+
if (newContent !== fileContent) {
|
|
573
|
+
newContent = addDepsImport(newContent, filePath)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const modified = newContent !== fileContent
|
|
577
|
+
if (modified) {
|
|
578
|
+
stats.moduleFederationUpdated++
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return { content: newContent, modified }
|
|
582
|
+
}
|
|
583
|
+
|
|
292
584
|
/**
|
|
293
585
|
* Transform module federation configuration to add ckeditor5 singleton
|
|
294
586
|
* Handles both hammer.config.* and procore.config.* files
|
|
587
|
+
* Supports both direct object pattern and ternary conditional pattern
|
|
295
588
|
*/
|
|
296
589
|
function transformModuleFederation(fileContent, filePath) {
|
|
297
590
|
let modified = false
|
|
@@ -321,6 +614,12 @@ function transformModuleFederation(fileContent, filePath) {
|
|
|
321
614
|
return { content: newContent, modified }
|
|
322
615
|
}
|
|
323
616
|
|
|
617
|
+
// Check if this is a ternary conditional pattern
|
|
618
|
+
const ternaryPattern = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?/
|
|
619
|
+
if (ternaryPattern.test(newContent)) {
|
|
620
|
+
return transformModuleFederationTernary(newContent, filePath)
|
|
621
|
+
}
|
|
622
|
+
|
|
324
623
|
// Pattern to match shared object in moduleFederation
|
|
325
624
|
// Handles both `shared: {` and `shared:{` patterns
|
|
326
625
|
const sharedObjectRegex = /(shared\s*:\s*\{)/g
|
|
@@ -332,109 +631,8 @@ function transformModuleFederation(fileContent, filePath) {
|
|
|
332
631
|
// Find the match and its position
|
|
333
632
|
const match = sharedObjectRegex.exec(newContent)
|
|
334
633
|
if (match) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// Detect indentation by looking at the content after `shared: {`
|
|
338
|
-
// Find the first property inside shared to determine proper indentation
|
|
339
|
-
const afterShared = newContent.substring(insertPosition)
|
|
340
|
-
const firstPropertyMatch = afterShared.match(/\n(\s+)\w+\s*:/)
|
|
341
|
-
|
|
342
|
-
let propertyIndent = ''
|
|
343
|
-
let indentUnit = ' ' // default to 2 spaces
|
|
344
|
-
|
|
345
|
-
if (firstPropertyMatch) {
|
|
346
|
-
// Use the same indentation as the first property inside shared
|
|
347
|
-
propertyIndent = firstPropertyMatch[1]
|
|
348
|
-
// Detect indent unit from the property indent
|
|
349
|
-
const sharedLineMatch = newContent
|
|
350
|
-
.substring(0, match.index)
|
|
351
|
-
.match(/\n(\s+)shared/)
|
|
352
|
-
if (sharedLineMatch) {
|
|
353
|
-
const sharedIndent = sharedLineMatch[1]
|
|
354
|
-
// indentUnit is the difference between property indent and shared indent
|
|
355
|
-
if (propertyIndent.length > sharedIndent.length) {
|
|
356
|
-
indentUnit = propertyIndent.substring(sharedIndent.length)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
// Fallback: detect from the 'shared' line indentation
|
|
361
|
-
const beforeMatch = newContent.substring(0, match.index)
|
|
362
|
-
const lastNewlineIndex = beforeMatch.lastIndexOf('\n')
|
|
363
|
-
const lineStart =
|
|
364
|
-
lastNewlineIndex >= 0
|
|
365
|
-
? beforeMatch.substring(lastNewlineIndex + 1)
|
|
366
|
-
: beforeMatch
|
|
367
|
-
const baseIndentMatch = lineStart.match(/^(\s*)/)
|
|
368
|
-
const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ''
|
|
369
|
-
|
|
370
|
-
// Determine if we're using tabs or spaces
|
|
371
|
-
const useTabs = baseIndent.includes('\t')
|
|
372
|
-
indentUnit = useTabs ? '\t' : ' '
|
|
373
|
-
propertyIndent = baseIndent + indentUnit
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Build the ckeditor5 configuration
|
|
377
|
-
const ckeditor5Config = `
|
|
378
|
-
${propertyIndent}ckeditor5: {
|
|
379
|
-
${propertyIndent}${indentUnit}requiredVersion: deps['ckeditor5'],
|
|
380
|
-
${propertyIndent}${indentUnit}singleton: true,
|
|
381
|
-
${propertyIndent}},`
|
|
382
|
-
|
|
383
|
-
// Insert the ckeditor5 configuration after `shared: {`
|
|
384
|
-
newContent =
|
|
385
|
-
newContent.substring(0, insertPosition) +
|
|
386
|
-
ckeditor5Config +
|
|
387
|
-
newContent.substring(insertPosition)
|
|
388
|
-
|
|
389
|
-
// Check if deps variable is already declared
|
|
390
|
-
if (
|
|
391
|
-
!newContent.includes("require('./package.json')") &&
|
|
392
|
-
!newContent.includes("require('./package.json').dependencies") &&
|
|
393
|
-
!newContent.includes("from './package.json'")
|
|
394
|
-
) {
|
|
395
|
-
// Add deps declaration at the top of the file
|
|
396
|
-
// Determine if using CommonJS or ES modules
|
|
397
|
-
const isESModule =
|
|
398
|
-
newContent.includes('export default') ||
|
|
399
|
-
newContent.includes('export {') ||
|
|
400
|
-
filePath.endsWith('.mjs')
|
|
401
|
-
|
|
402
|
-
if (isESModule) {
|
|
403
|
-
// For ES modules, add import at the top
|
|
404
|
-
const importStatement =
|
|
405
|
-
"import deps from './package.json' with { type: 'json' }\n"
|
|
406
|
-
|
|
407
|
-
// Find the first import or export statement
|
|
408
|
-
const firstImportMatch = newContent.match(/^(import|export)/m)
|
|
409
|
-
if (firstImportMatch) {
|
|
410
|
-
const insertPos = newContent.indexOf(firstImportMatch[0])
|
|
411
|
-
newContent =
|
|
412
|
-
newContent.substring(0, insertPos) +
|
|
413
|
-
importStatement +
|
|
414
|
-
newContent.substring(insertPos)
|
|
415
|
-
} else {
|
|
416
|
-
newContent = importStatement + newContent
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
// For CommonJS, add require at the top
|
|
420
|
-
const requireStatement =
|
|
421
|
-
"const deps = require('./package.json').dependencies\n"
|
|
422
|
-
|
|
423
|
-
// Find the first require or const statement
|
|
424
|
-
const firstRequireMatch = newContent.match(
|
|
425
|
-
/^(const|let|var|require)/m
|
|
426
|
-
)
|
|
427
|
-
if (firstRequireMatch) {
|
|
428
|
-
const insertPos = newContent.indexOf(firstRequireMatch[0])
|
|
429
|
-
newContent =
|
|
430
|
-
newContent.substring(0, insertPos) +
|
|
431
|
-
requireStatement +
|
|
432
|
-
newContent.substring(insertPos)
|
|
433
|
-
} else {
|
|
434
|
-
newContent = requireStatement + newContent
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
634
|
+
newContent = addSingletonToSharedObject(newContent, match)
|
|
635
|
+
newContent = addDepsImport(newContent, filePath)
|
|
438
636
|
|
|
439
637
|
modified = true
|
|
440
638
|
stats.moduleFederationUpdated++
|
|
@@ -505,7 +703,8 @@ function processFile(filePath) {
|
|
|
505
703
|
// Config file detection
|
|
506
704
|
const isConfigFile =
|
|
507
705
|
/^jest\.(config|setup)\.(js|ts|cjs|mjs)$/.test(fileName) ||
|
|
508
|
-
/\.config\.(js|ts|cjs|mjs)$/.test(fileName)
|
|
706
|
+
/\.config\.(js|ts|cjs|mjs)$/.test(fileName) ||
|
|
707
|
+
/\.config\..*\.(js|ts|cjs|mjs)$/.test(fileName) // Last one for tests like "procore.config.ternay.js"
|
|
509
708
|
|
|
510
709
|
// Process Jest configuration
|
|
511
710
|
if (isConfigFile && content.includes('coreReactJestConfig')) {
|
|
@@ -540,6 +739,13 @@ function processFile(filePath) {
|
|
|
540
739
|
newContent = formResult.content
|
|
541
740
|
wasModified = true
|
|
542
741
|
}
|
|
742
|
+
|
|
743
|
+
// Remove deprecated plugins prop
|
|
744
|
+
const deprecatedPropsResult = removeDeprecatedProps(newContent)
|
|
745
|
+
if (deprecatedPropsResult.modified) {
|
|
746
|
+
newContent = deprecatedPropsResult.content
|
|
747
|
+
wasModified = true
|
|
748
|
+
}
|
|
543
749
|
}
|
|
544
750
|
|
|
545
751
|
// Write back if modified
|
|
@@ -721,7 +927,9 @@ ${colors.bold}Examples:${colors.reset}
|
|
|
721
927
|
|
|
722
928
|
if (stats.filesProcessed > 0) {
|
|
723
929
|
console.log(`${colors.bold}Next steps:${colors.reset}\n`)
|
|
724
|
-
console.log(
|
|
930
|
+
console.log(
|
|
931
|
+
'1. Review the changes made by the codemod. May need to be linted.\n'
|
|
932
|
+
)
|
|
725
933
|
console.log(
|
|
726
934
|
'2. Review previous implementation for additional props and migrate to CKEditor, https://ckeditor.com/docs/ckeditor5/latest/features/index.html\n'
|
|
727
935
|
)
|
|
@@ -129,6 +129,15 @@ describe('text-editor-migrate codemod', () => {
|
|
|
129
129
|
expect(content).not.toContain('textEditorComponent={TextEditor}')
|
|
130
130
|
expect(content).not.toContain('@procore/text-editor')
|
|
131
131
|
})
|
|
132
|
+
|
|
133
|
+
it('should remove deprecated plugins prop from Form.RichText', () => {
|
|
134
|
+
execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
|
|
135
|
+
|
|
136
|
+
const content = readFile('src/components/MultilineFormRichText.tsx')
|
|
137
|
+
|
|
138
|
+
expect(content).not.toContain('plugins=')
|
|
139
|
+
expect(content).toContain('textEditorComponent={TextEditor}')
|
|
140
|
+
})
|
|
132
141
|
})
|
|
133
142
|
|
|
134
143
|
describe('Jest configuration transformations', () => {
|
|
@@ -209,6 +218,25 @@ describe('text-editor-migrate codemod', () => {
|
|
|
209
218
|
expect(matches).toHaveLength(1)
|
|
210
219
|
})
|
|
211
220
|
|
|
221
|
+
it('should add ckeditor5 only to ternary branches that contain react', () => {
|
|
222
|
+
execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
|
|
223
|
+
|
|
224
|
+
const content = readFile('procore.config.ternary.js')
|
|
225
|
+
|
|
226
|
+
// Should have ckeditor5 only in the branch with react (production branch)
|
|
227
|
+
const ckeditor5Matches = content.match(/ckeditor5:/g)
|
|
228
|
+
expect(ckeditor5Matches).toHaveLength(1)
|
|
229
|
+
|
|
230
|
+
// Should have deps import
|
|
231
|
+
expect(content).toContain(
|
|
232
|
+
"const deps = require('./package.json').dependencies"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
// Should have singleton: true only once
|
|
236
|
+
const singletonMatches = content.match(/singleton:\s*true/g)
|
|
237
|
+
expect(singletonMatches).toHaveLength(1)
|
|
238
|
+
})
|
|
239
|
+
|
|
212
240
|
it('should not modify config files without moduleFederation.shared', () => {
|
|
213
241
|
// Create a config file without moduleFederation
|
|
214
242
|
const noModFedConfig = `
|
|
@@ -41,4 +41,4 @@ import type { TextEditorProps } from './TextEditor.types';
|
|
|
41
41
|
* }),
|
|
42
42
|
* })
|
|
43
43
|
*/
|
|
44
|
-
export declare function TextEditor(
|
|
44
|
+
export declare function TextEditor(props: Readonly<TextEditorProps>): React.JSX.Element | null;
|