@oamm/textor 1.0.7 → 1.0.8

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 CHANGED
@@ -341,6 +341,8 @@ The .textor/config.json file allows full control over the tool's behavior.
341
341
  "indexFile": "index.astro"
342
342
  },
343
343
  "importAliases": {
344
+ "layouts": "@/layouts",
345
+ "features": "@/features"
344
346
  },
345
347
  "naming": {
346
348
  "routeExtension": ".astro",
@@ -459,6 +461,31 @@ The .textor/config.json file allows full control over the tool's behavior.
459
461
  ```
460
462
  *Supported formatting tools: prettier, biome, none.*
461
463
 
464
+ ### 7. Layout Parameters
465
+
466
+ You can pass parameters to your layout component by defining `layoutProps` in `.textor/config.json`. These props support variable substitution.
467
+
468
+ ```json
469
+ {
470
+ "features": {
471
+ "layout": "AppLayout",
472
+ "layoutProps": {
473
+ "title": "{{componentName}}",
474
+ "description": "Description for {{componentName}}"
475
+ }
476
+ }
477
+ }
478
+ ```
479
+
480
+ You can also override these props via the CLI using the `--prop` flag:
481
+ ```bash
482
+ pnpm textor add-section /users users/roles --prop title="Custom Title" --prop breadcrumbs='{[{ label: "Users" }]}'
483
+ ```
484
+
485
+ Properties that start and end with curly braces `{}` are passed as JavaScript expressions, others as strings.
486
+
487
+ ---
488
+
462
489
  ## 📝 Template Overrides
463
490
 
464
491
  You can customize the code generated by Textor by providing your own templates. Textor looks for override files in the `.textor/templates/` directory at your project root.
@@ -473,7 +500,7 @@ You can customize the code generated by Textor by providing your own templates.
473
500
 
474
501
  | Template Name | File to create in `.textor/templates/` | Available Variables |
475
502
  | :--- | :--- | :--- |
476
- | **Route** | `route.astro` | `{{layoutName}}`, `{{layoutImportPath}}`, `{{featureImportPath}}`, `{{featureComponentName}}` |
503
+ | **Route** | `route.astro` | `{{layoutName}}`, `{{layoutImportPath}}`, `{{featureImportPath}}`, `{{featureComponentName}}`, plus any `layoutProps` |
477
504
  | **Feature** | `feature.astro` or `feature.tsx` | `{{componentName}}`, `{{scriptImportPath}}` |
478
505
  | **Component** | `component.astro` or `component.tsx` | `{{componentName}}` |
479
506
  | **Hook** | `hook.ts` | `{{componentName}}`, `{{hookName}}` |
@@ -42,6 +42,7 @@ const CURRENT_CONFIG_VERSION = 2;
42
42
  * @property {boolean} features.createScriptsDir
43
43
  * @property {string} features.scriptsIndexFile
44
44
  * @property {string} features.layout
45
+ * @property {Object} features.layoutProps
45
46
  * @property {Object} components
46
47
  * @property {boolean} components.createSubComponentsDir
47
48
  * @property {boolean} components.createContext
@@ -108,7 +109,8 @@ const DEFAULT_CONFIG = {
108
109
  createReadme: false,
109
110
  createStories: false,
110
111
  createIndex: false,
111
- layout: 'Main'
112
+ layout: 'Main',
113
+ layoutProps: {}
112
114
  },
113
115
  components: {
114
116
  framework: 'react',
@@ -1020,27 +1022,6 @@ async function formatFiles(filePaths, tool) {
1020
1022
  }
1021
1023
  }
1022
1024
 
1023
- var filesystem = /*#__PURE__*/Object.freeze({
1024
- __proto__: null,
1025
- calculateHash: calculateHash,
1026
- cleanupEmptyDirs: cleanupEmptyDirs,
1027
- ensureDir: ensureDir,
1028
- ensureNotExists: ensureNotExists,
1029
- formatFiles: formatFiles,
1030
- getSignature: getSignature,
1031
- inferKind: inferKind,
1032
- isEmptyDir: isEmptyDir,
1033
- isTextorGenerated: isTextorGenerated,
1034
- safeDelete: safeDelete,
1035
- safeDeleteDir: safeDeleteDir,
1036
- safeMove: safeMove,
1037
- scanDirectory: scanDirectory,
1038
- secureJoin: secureJoin,
1039
- updateSignature: updateSignature,
1040
- verifyFileIntegrity: verifyFileIntegrity,
1041
- writeFileWithSignature: writeFileWithSignature
1042
- });
1043
-
1044
1025
  function renderNamePattern(pattern, data = {}, label = 'pattern') {
1045
1026
  if (typeof pattern !== 'string') return null;
1046
1027
  const trimmed = pattern.trim();
@@ -1108,13 +1089,15 @@ function getTemplateOverride(templateName, extension, data = {}) {
1108
1089
  * - layoutImportPath: Path to import the layout
1109
1090
  * - featureImportPath: Path to import the feature component
1110
1091
  * - featureComponentName: Name of the feature component
1092
+ * - layoutProps: Optional properties for the layout
1111
1093
  */
1112
- function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro') {
1094
+ function generateRouteTemplate(layoutName, layoutImportPath, featureImportPath, featureComponentName, extension = '.astro', layoutProps = {}) {
1113
1095
  const override = getTemplateOverride('route', extension, {
1114
1096
  layoutName,
1115
1097
  layoutImportPath,
1116
1098
  featureImportPath,
1117
- featureComponentName
1099
+ featureComponentName,
1100
+ ...layoutProps
1118
1101
  });
1119
1102
  if (override) return override;
1120
1103
 
@@ -1127,12 +1110,26 @@ import ${featureComponentName} from '${featureImportPath}';
1127
1110
  `;
1128
1111
  }
1129
1112
 
1113
+ const propsStr = Object.entries(layoutProps)
1114
+ .map(([key, value]) => {
1115
+ if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
1116
+ return `${key}=${value}`;
1117
+ }
1118
+ if (typeof value === 'string') {
1119
+ return `${key}="${value}"`;
1120
+ }
1121
+ return `${key}={${JSON.stringify(value)}}`;
1122
+ })
1123
+ .join(' ');
1124
+
1125
+ const layoutOpening = propsStr ? `<${layoutName} ${propsStr}>` : `<${layoutName}>`;
1126
+
1130
1127
  return `---
1131
1128
  import ${layoutName} from '${layoutImportPath}';
1132
1129
  import ${featureComponentName} from '${featureImportPath}';
1133
1130
  ---
1134
1131
 
1135
- <${layoutName}>
1132
+ ${layoutOpening}
1136
1133
  <${featureComponentName} />
1137
1134
  </${layoutName}>
1138
1135
  `;
@@ -1343,6 +1340,11 @@ function generateIndexTemplate(componentName, componentExtension) {
1343
1340
  const override = getTemplateOverride('index', '.ts', { componentName, componentExtension });
1344
1341
  if (override) return override;
1345
1342
 
1343
+ if (componentExtension === '.astro') {
1344
+ return `export * from './types';
1345
+ `;
1346
+ }
1347
+
1346
1348
  return `export { default as ${componentName} } from './${componentName}${componentExtension}';
1347
1349
  export * from './types';
1348
1350
  `;
@@ -1798,6 +1800,7 @@ async function addSectionCommand(route, featurePath, options) {
1798
1800
  const {
1799
1801
  framework,
1800
1802
  layout,
1803
+ layoutProps: configLayoutProps,
1801
1804
  createSubComponentsDir: shouldCreateSubComponentsDir,
1802
1805
  createScriptsDir: shouldCreateScriptsDir,
1803
1806
  createApi: shouldCreateApi,
@@ -1969,7 +1972,7 @@ async function addSectionCommand(route, featurePath, options) {
1969
1972
  }
1970
1973
 
1971
1974
  // Update imports in the moved file
1972
- await updateImportsInFile$1(reorg.to, reorg.from, reorg.to);
1975
+ await updateImportsInFile$2(reorg.to, reorg.from, reorg.to);
1973
1976
 
1974
1977
  // Update hash in state after import updates
1975
1978
  if (state.files[newRelative]) {
@@ -1998,6 +2001,32 @@ async function addSectionCommand(route, featurePath, options) {
1998
2001
  if (shouldCreateScriptsDir) await ensureNotExists(scriptsIndexPath, options.force);
1999
2002
 
2000
2003
  let layoutImportPath = null;
2004
+ const cliProps = options.prop || {};
2005
+ const rawLayoutProps = { ...configLayoutProps, ...cliProps };
2006
+ const layoutProps = {};
2007
+
2008
+ // Resolve variables in layoutProps
2009
+ const substitutionData = enrichData({
2010
+ componentName: featureComponentName,
2011
+ layoutName: layout,
2012
+ featureComponentName: featureComponentName
2013
+ });
2014
+
2015
+ for (const [key, value] of Object.entries(rawLayoutProps)) {
2016
+ if (typeof value === 'string') {
2017
+ let resolvedValue = value;
2018
+ for (const [varKey, varValue] of Object.entries(substitutionData)) {
2019
+ const regex = new RegExp(`{{${varKey}}}`, 'g');
2020
+ resolvedValue = resolvedValue.replace(regex, varValue);
2021
+ const underscoreRegex = new RegExp(`__${varKey}__`, 'g');
2022
+ resolvedValue = resolvedValue.replace(underscoreRegex, varValue);
2023
+ }
2024
+ layoutProps[key] = resolvedValue;
2025
+ } else {
2026
+ layoutProps[key] = value;
2027
+ }
2028
+ }
2029
+
2001
2030
  if (routeFilePath && layout !== 'none') {
2002
2031
  if (config.importAliases.layouts) {
2003
2032
  layoutImportPath = `${config.importAliases.layouts}/${layout}.astro`;
@@ -2010,7 +2039,7 @@ async function addSectionCommand(route, featurePath, options) {
2010
2039
  let featureImportPath = null;
2011
2040
  if (routeFilePath) {
2012
2041
  if (config.importAliases.features) {
2013
- const entryPart = effectiveOptions.entry === 'index' ? '' : `/${featureComponentName}`;
2042
+ const entryPart = effectiveOptions.entry === 'index' ? '/index' : `/${featureComponentName}`;
2014
2043
  // In Astro, we can often omit the extension for .tsx files, but not for .astro files if using aliases sometimes.
2015
2044
  // However, to be safe, we use the configured extension.
2016
2045
  featureImportPath = `${config.importAliases.features}/${normalizedFeaturePath}${entryPart}${config.naming.featureExtension}`;
@@ -2043,7 +2072,8 @@ async function addSectionCommand(route, featurePath, options) {
2043
2072
  layoutImportPath,
2044
2073
  featureImportPath,
2045
2074
  featureComponentName,
2046
- routeExtension
2075
+ routeExtension,
2076
+ layoutProps
2047
2077
  );
2048
2078
  routeSignature = getSignature(config, 'astro');
2049
2079
  }
@@ -2328,7 +2358,7 @@ async function addSectionCommand(route, featurePath, options) {
2328
2358
  }
2329
2359
  }
2330
2360
 
2331
- async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
2361
+ async function updateImportsInFile$2(filePath, oldFilePath, newFilePath) {
2332
2362
  if (!existsSync(filePath)) return;
2333
2363
 
2334
2364
  let content = await readFile(filePath, 'utf-8');
@@ -2538,7 +2568,7 @@ async function removeSectionCommand(route, featurePath, options) {
2538
2568
  state.files[newRelative] = { ...state.files[oldRelative] };
2539
2569
  delete state.files[oldRelative];
2540
2570
 
2541
- await updateImportsInFile(flatFilePath, loneFilePath, flatFilePath);
2571
+ await updateImportsInFile$1(flatFilePath, loneFilePath, flatFilePath);
2542
2572
 
2543
2573
  // Update hash in state after import updates
2544
2574
  const content = await readFile(flatFilePath, 'utf-8');
@@ -2568,7 +2598,7 @@ async function removeSectionCommand(route, featurePath, options) {
2568
2598
  }
2569
2599
  }
2570
2600
 
2571
- async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
2601
+ async function updateImportsInFile$1(filePath, oldFilePath, newFilePath) {
2572
2602
  if (!existsSync(filePath)) return;
2573
2603
 
2574
2604
  let content = await readFile(filePath, 'utf-8');
@@ -2741,9 +2771,14 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
2741
2771
  const fromFeatureComponentName = getFeatureComponentName(normalizedFromFeature);
2742
2772
  const toFeatureComponentName = getFeatureComponentName(targetFeature);
2743
2773
 
2744
- // Update component name in JSX
2774
+ // First, update all relative imports in the file because it moved
2775
+ await updateImportsInFile(toRoutePath, fromRoutePath, toRoutePath);
2776
+
2777
+ let content = await readFile(toRoutePath, 'utf-8');
2778
+ let changed = false;
2779
+
2780
+ // Update component name in JSX tags
2745
2781
  if (fromFeatureComponentName !== toFeatureComponentName) {
2746
- let content = await readFile(toRoutePath, 'utf-8');
2747
2782
  content = content.replace(
2748
2783
  new RegExp(`<${fromFeatureComponentName}`, 'g'),
2749
2784
  `<${toFeatureComponentName}`
@@ -2752,44 +2787,54 @@ async function moveSectionCommand(fromRoute, fromFeature, toRoute, toFeature, op
2752
2787
  new RegExp(`</${fromFeatureComponentName}`, 'g'),
2753
2788
  `</${toFeatureComponentName}`
2754
2789
  );
2755
- await writeFile(toRoutePath, content, 'utf-8');
2790
+ changed = true;
2756
2791
  }
2757
2792
 
2758
2793
  if (config.importAliases.features) {
2759
- if (normalizedFromFeature !== targetFeature) {
2760
- const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2761
- const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2762
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2763
-
2764
- // Replace both the path and the component name if they are different
2765
- await updateSignature(toRoutePath,
2766
- `import ${fromFeatureComponentName} from '${oldAliasPath}/${fromFeatureComponentName}${ext}'`,
2767
- `import ${toFeatureComponentName} from '${newAliasPath}/${toFeatureComponentName}${ext}'`
2768
- );
2769
-
2770
- // Fallback for prefix only replacement
2771
- await updateSignature(toRoutePath, oldAliasPath, newAliasPath);
2772
- } else if (fromFeatureComponentName !== toFeatureComponentName) {
2773
- // Name changed but path didn't
2774
- const aliasPath = `${config.importAliases.features}/${targetFeature}`;
2775
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2776
- await updateSignature(toRoutePath,
2777
- `import ${fromFeatureComponentName} from '${aliasPath}/${fromFeatureComponentName}${ext}'`,
2778
- `import ${toFeatureComponentName} from '${aliasPath}/${toFeatureComponentName}${ext}'`
2779
- );
2794
+ const oldAliasPath = `${config.importAliases.features}/${normalizedFromFeature}`;
2795
+ const newAliasPath = `${config.importAliases.features}/${targetFeature}`;
2796
+
2797
+ // Flexible regex to match import identifier and path with alias
2798
+ const importRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldAliasPath}(/[^'"]+)?(['"])`, 'g');
2799
+
2800
+ if (importRegex.test(content)) {
2801
+ content = content.replace(importRegex, (match, p1, p2, p3, subPath, p5) => {
2802
+ let newSubPath = subPath || '';
2803
+ if (subPath && subPath.includes(fromFeatureComponentName)) {
2804
+ newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
2805
+ }
2806
+ return `${p1}${toFeatureComponentName}${p3}${newAliasPath}${newSubPath}${p5}`;
2807
+ });
2808
+ changed = true;
2809
+ } else if (content.includes(oldAliasPath)) {
2810
+ // Fallback for path only replacement
2811
+ content = content.replace(new RegExp(oldAliasPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newAliasPath);
2812
+ changed = true;
2780
2813
  }
2781
2814
  } else {
2782
- const oldRelativeDir = getRelativeImportPath(fromRoutePath, fromFeatureDirPath);
2815
+ const oldRelativeDir = getRelativeImportPath(toRoutePath, fromFeatureDirPath);
2783
2816
  const newRelativeDir = getRelativeImportPath(toRoutePath, toFeatureDirPath);
2784
- const ext = config.naming.featureExtension === '.astro' ? '.astro' : '';
2785
2817
 
2786
- const oldImportPath = `import ${fromFeatureComponentName} from '${oldRelativeDir}/${fromFeatureComponentName}${ext}'`;
2787
- const newImportPath = `import ${toFeatureComponentName} from '${newRelativeDir}/${toFeatureComponentName}${ext}'`;
2818
+ // Flexible regex for relative imports
2819
+ const relImportRegex = new RegExp(`(import\\s+)(${fromFeatureComponentName})(\\s+from\\s+['"])${oldRelativeDir}(/[^'"]+)?(['"])`, 'g');
2788
2820
 
2789
- if (oldImportPath !== newImportPath) {
2790
- await updateSignature(toRoutePath, oldImportPath, newImportPath);
2821
+ if (relImportRegex.test(content)) {
2822
+ content = content.replace(relImportRegex, (match, p1, p2, p3, subPath, p5) => {
2823
+ let newSubPath = subPath || '';
2824
+ if (subPath && subPath.includes(fromFeatureComponentName)) {
2825
+ newSubPath = subPath.replace(fromFeatureComponentName, toFeatureComponentName);
2826
+ }
2827
+ return `${p1}${toFeatureComponentName}${p3}${newRelativeDir}${newSubPath}${p5}`;
2828
+ });
2829
+ changed = true;
2791
2830
  }
2792
2831
  }
2832
+
2833
+ if (changed) {
2834
+ await writeFile(toRoutePath, content, 'utf-8');
2835
+ // Update hash in state after changes
2836
+ state.files[normalizedToRouteRelative].hash = calculateHash(content, config.hashing?.normalization);
2837
+ }
2793
2838
  }
2794
2839
 
2795
2840
  if (!isRouteOnly && normalizedFromFeature && normalizedToFeature && normalizedFromFeature !== normalizedToFeature) {
@@ -2860,7 +2905,6 @@ async function scanAndReplaceImports(config, state, fromInfo, toInfo, options) {
2860
2905
  const { toFeaturePath, toComponentName } = toInfo;
2861
2906
 
2862
2907
  const allFiles = new Set();
2863
- const { scanDirectory, calculateHash } = await Promise.resolve().then(function () { return filesystem; });
2864
2908
  await scanDirectory(process.cwd(), allFiles);
2865
2909
 
2866
2910
  const featuresRoot = resolvePath(config, 'features');
@@ -3001,7 +3045,6 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
3001
3045
  if (hasChanged) {
3002
3046
  await writeFile(toEntryPath, content, 'utf-8');
3003
3047
  // Re-calculate hash after content update
3004
- const { calculateHash } = await Promise.resolve().then(function () { return filesystem; });
3005
3048
  const updatedHash = calculateHash(content, config.hashing?.normalization);
3006
3049
 
3007
3050
  const normalizedToRelative = path.relative(process.cwd(), toEntryPath).replace(/\\/g, '/');
@@ -3034,6 +3077,39 @@ async function moveDirectory(fromPath, toPath, state, config, options = {}) {
3034
3077
  }
3035
3078
  }
3036
3079
 
3080
+ async function updateImportsInFile(filePath, oldFilePath, newFilePath) {
3081
+ if (!existsSync(filePath)) return;
3082
+
3083
+ let content = await readFile(filePath, 'utf-8');
3084
+ const oldDir = path.dirname(oldFilePath);
3085
+ const newDir = path.dirname(newFilePath);
3086
+
3087
+ if (oldDir === newDir) return;
3088
+
3089
+ // Find all relative imports
3090
+ const relativeImportRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
3091
+ let match;
3092
+ const replacements = [];
3093
+
3094
+ while ((match = relativeImportRegex.exec(content)) !== null) {
3095
+ const relativePath = match[1];
3096
+ const absoluteTarget = path.resolve(oldDir, relativePath);
3097
+ const newRelativePath = getRelativeImportPath(newFilePath, absoluteTarget);
3098
+
3099
+ replacements.push({
3100
+ full: match[0],
3101
+ oldRel: relativePath,
3102
+ newRel: newRelativePath
3103
+ });
3104
+ }
3105
+
3106
+ for (const repl of replacements) {
3107
+ content = content.replace(repl.full, `from '${repl.newRel}'`);
3108
+ }
3109
+
3110
+ await writeFile(filePath, content, 'utf-8');
3111
+ }
3112
+
3037
3113
  async function createComponentCommand(componentName, options) {
3038
3114
  try {
3039
3115
  const config = await loadConfig();
@@ -4286,6 +4362,11 @@ program
4286
4362
  .option('--index', 'Create index.ts')
4287
4363
  .option('--no-sub-components-dir', 'Skip creating sub-components directory')
4288
4364
  .option('--no-scripts-dir', 'Skip creating scripts directory')
4365
+ .option('--prop <key=value>', 'Layout property', (val, memo) => {
4366
+ const [key, ...rest] = val.split('=');
4367
+ memo[key] = rest.join('=');
4368
+ return memo;
4369
+ }, {})
4289
4370
  .option('--dry-run', 'Show what would be created without creating')
4290
4371
  .option('--force', 'Overwrite existing files')
4291
4372
  .action(addSectionCommand);
@@ -1 +1 @@
1
- {"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}