@lucca-front/ng 22.0.3-rc.2 → 22.0.3
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 +1 -0
- package/fesm2022/lucca-front-ng-activity-feed.mjs +2 -2
- package/fesm2022/lucca-front-ng-activity-feed.mjs.map +1 -1
- package/fesm2022/lucca-front-ng-core-select.mjs +55 -53
- package/fesm2022/lucca-front-ng-core-select.mjs.map +1 -1
- package/fesm2022/lucca-front-ng-dialog.mjs +5 -2
- package/fesm2022/lucca-front-ng-dialog.mjs.map +1 -1
- package/fesm2022/lucca-front-ng-dropdown.mjs +3 -0
- package/fesm2022/lucca-front-ng-dropdown.mjs.map +1 -1
- package/package.json +4 -4
- package/schematics/collection.json +5 -0
- package/schematics/deprecated-resolver/index.js +1 -4
- package/schematics/file-upload/index.js +22 -0
- package/schematics/file-upload/migration.js +230 -0
- package/schematics/file-upload/migration.spec.js +56 -0
- package/schematics/file-upload/schema.json +23 -0
- package/schematics/lib/angular-component-ast.js +3 -1
- package/schematics/lib/deprecated-mapper.js +2 -10
- package/schematics/lib/html-ast.js +26 -0
- package/types/lucca-front-ng-core-select.d.ts +7 -0
|
@@ -62,10 +62,7 @@ exports.default = (options) => {
|
|
|
62
62
|
'lu-divider': { withRole: '' },
|
|
63
63
|
'button': { delete: 'critical' },
|
|
64
64
|
'lu-loading': { type: { fullpage: 'fullPage' } },
|
|
65
|
-
'lu-single-file-upload': { illustration: { paper: 'invoice' }
|
|
66
|
-
'lu-multi-file-upload': { size: { 'S': 'L' } },
|
|
67
|
-
'lu-file-entry': { size: { 'S': 'L' } },
|
|
68
|
-
'lu-file-dropzone': { size: { 'S': 'L' } },
|
|
65
|
+
'lu-single-file-upload': { illustration: { paper: 'invoice' } },
|
|
69
66
|
'lu-highlight-data': { icon: { 'manifying-glass': 'magnifying-glass' } },
|
|
70
67
|
},
|
|
71
68
|
}).run();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const lib_1 = require("../lib");
|
|
4
|
+
const migration_1 = require("./migration");
|
|
5
|
+
// Nx need to see "@angular-devkit/schematics" in order to run this migration correctly (see https://github.com/nrwl/nx/blob/d9fed4b832bf01d1b9a44ae9e486a5e5cd2d2253/packages/nx/src/command-line/migrate/migrate.ts#L1729-L1738)
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
require('@angular-devkit/schematics');
|
|
8
|
+
exports.default = (options) => {
|
|
9
|
+
return async (tree, context) => {
|
|
10
|
+
await lib_1.currentSchematicContext.init(context, options);
|
|
11
|
+
// Templates are reached through their component: rendering a FileEntry or a wrapper needs the
|
|
12
|
+
// matching symbol in the `imports` of the component that owns the template.
|
|
13
|
+
tree.visit((path, entry) => {
|
|
14
|
+
if (path.includes('node_modules') || !entry) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
|
|
18
|
+
(0, lib_1.migrateFile)(path, entry, tree, (content) => (0, migration_1.migrateComponent)(path, content, tree));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrateComponent = migrateComponent;
|
|
4
|
+
const change_1 = require("@schematics/angular/utility/change");
|
|
5
|
+
const typescript_1 = require("typescript");
|
|
6
|
+
const lib_1 = require("../lib");
|
|
7
|
+
const fileUploadEntrypoint = '@lucca-front/ng/file-upload';
|
|
8
|
+
const wrapperClass = 'fileEntryDisplayWrapper';
|
|
9
|
+
/** Alias given to the migrated entry in the generated `@if` block. */
|
|
10
|
+
const entryAlias = 'fileEntry';
|
|
11
|
+
/** Components whose `size` input has been flipped: `S` became the default, `L` restores the previous rendering. */
|
|
12
|
+
const sizedComponents = ['lu-single-file-upload', 'lu-multi-file-upload', 'lu-file-entry'];
|
|
13
|
+
/** Inputs `lu-single-file-upload` used to forward to the `lu-file-entry` it rendered itself. */
|
|
14
|
+
const fileEntryInputs = ['entry', 'state', 'previewUrl', 'inlineMessageError', 'displayFileName'];
|
|
15
|
+
/** Outputs `lu-single-file-upload` used to forward to the `lu-file-entry` it rendered itself. */
|
|
16
|
+
const fileEntryOutputs = ['deleteFile'];
|
|
17
|
+
function migrateComponent(path, content, tree) {
|
|
18
|
+
const sourceFile = (0, typescript_1.createSourceFile)(path, content, typescript_1.ScriptTarget.ESNext);
|
|
19
|
+
const symbolsToImport = new Set();
|
|
20
|
+
// External templates are rewritten in place, they are not part of the component file.
|
|
21
|
+
(0, lib_1.extractNgTemplatesIncludingHtml)(sourceFile, tree, path)
|
|
22
|
+
.filter((template) => template.filePath !== path)
|
|
23
|
+
.forEach((template) => {
|
|
24
|
+
const migrated = migrateTemplate(template.filePath, template.content, symbolsToImport);
|
|
25
|
+
if (migrated !== template.content) {
|
|
26
|
+
tree.overwrite(template.filePath, migrated);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const result = (0, lib_1.updateAngularTemplate)(path, content, (template) => migrateTemplate(path, template, symbolsToImport));
|
|
30
|
+
if (symbolsToImport.size === 0) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
// Imports are inserted one symbol at a time: every insertion shifts the offsets the next one relies on.
|
|
34
|
+
tree.overwrite(path, result);
|
|
35
|
+
symbolsToImport.forEach((symbol) => {
|
|
36
|
+
const updatedSourceFile = (0, typescript_1.createSourceFile)(path, tree.readText(path), typescript_1.ScriptTarget.ESNext);
|
|
37
|
+
const recorder = tree.beginUpdate(path);
|
|
38
|
+
(0, change_1.applyToUpdateRecorder)(recorder, [(0, lib_1.insertTSImportIfNeeded)(updatedSourceFile, path, symbol, fileUploadEntrypoint), (0, lib_1.insertAngularImportIfNeeded)(updatedSourceFile, path, symbol)]);
|
|
39
|
+
tree.commitUpdate(recorder);
|
|
40
|
+
});
|
|
41
|
+
return tree.readText(path);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rules are applied in this order on purpose:
|
|
45
|
+
* 1. sizes are normalized first, so that splitting a `lu-single-file-upload` reads an already migrated size;
|
|
46
|
+
* 2. the split then emits its own `lu-file-entry-wrapper`, which the class rule must not visit again.
|
|
47
|
+
*/
|
|
48
|
+
function migrateTemplate(path, template, symbolsToImport) {
|
|
49
|
+
let result = migrateSize(path, template);
|
|
50
|
+
result = migrateFileEntryRendering(path, result, symbolsToImport);
|
|
51
|
+
return migrateWrapperClass(path, result, symbolsToImport);
|
|
52
|
+
}
|
|
53
|
+
function migrateSize(path, template) {
|
|
54
|
+
return (0, lib_1.updateContent)(template, (updates) => {
|
|
55
|
+
const htmlAst = new lib_1.HtmlAst(template);
|
|
56
|
+
sizedComponents.forEach((component) => {
|
|
57
|
+
htmlAst.visitElements(component, (element) => {
|
|
58
|
+
const boundSize = element.inputs.find((input) => input.name === 'size');
|
|
59
|
+
if (boundSize) {
|
|
60
|
+
const source = boundSize.value instanceof lib_1.currentSchematicContext.angularCompiler.ASTWithSource ? boundSize.value.source ?? '' : '';
|
|
61
|
+
switch (source.trim()) {
|
|
62
|
+
case `'S'`:
|
|
63
|
+
case `"S"`:
|
|
64
|
+
updates.push((0, lib_1.removeAttributeUpdate)(template, boundSize));
|
|
65
|
+
lib_1.currentSchematicContext.logSuccess(`Removing redundant [size] on <${component}> in ${path}`);
|
|
66
|
+
break;
|
|
67
|
+
case 'null':
|
|
68
|
+
updates.push({
|
|
69
|
+
position: boundSize.value.sourceSpan.start,
|
|
70
|
+
oldContent: source,
|
|
71
|
+
newContent: source.replace('null', `'L'`),
|
|
72
|
+
});
|
|
73
|
+
lib_1.currentSchematicContext.logSuccess(`Setting [size] to 'L' on <${component}> in ${path}`);
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
lib_1.currentSchematicContext.warn(`${path}: [size] of <${component}> is bound to an expression, migrate it manually ('S' is now the default value, 'L' is the previous one).`);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const size = element.attributes.find((attribute) => attribute.name === 'size');
|
|
81
|
+
if (!size) {
|
|
82
|
+
updates.push((0, lib_1.addAttributeUpdate)(element, 'size', 'L'));
|
|
83
|
+
lib_1.currentSchematicContext.logSuccess(`Adding size="L" on <${component}> in ${path}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (size.value === 'S') {
|
|
87
|
+
updates.push((0, lib_1.removeAttributeUpdate)(template, size));
|
|
88
|
+
lib_1.currentSchematicContext.logSuccess(`Removing redundant size="S" on <${component}> in ${path}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
lib_1.currentSchematicContext.warn(`${path}: size="${size.value}" of <${component}> is not a known value, migrate it manually ('S' is now the default value, 'L' is the previous one).`);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `lu-single-file-upload` no longer renders the `FileEntry` it receives: the parent now displays it itself,
|
|
98
|
+
* through a `lu-file-entry` wrapped in a `lu-file-entry-wrapper`.
|
|
99
|
+
*/
|
|
100
|
+
function migrateFileEntryRendering(path, template, symbolsToImport) {
|
|
101
|
+
return (0, lib_1.updateContent)(template, (updates) => {
|
|
102
|
+
const htmlAst = new lib_1.HtmlAst(template);
|
|
103
|
+
htmlAst.visitElements('lu-single-file-upload', (element) => {
|
|
104
|
+
const entry = element.inputs.find((input) => input.name === 'entry');
|
|
105
|
+
if (!entry) {
|
|
106
|
+
if (element.attributes.some((attribute) => attribute.name === 'entry')) {
|
|
107
|
+
lib_1.currentSchematicContext.warn(`${path}: <lu-single-file-upload> has a static entry attribute, migrate it manually.`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// A size left bound to an expression by the size rule cannot be turned into the `media` of the file
|
|
112
|
+
// entry, which is what the file upload used to derive from it.
|
|
113
|
+
if (element.inputs.some((input) => input.name === 'size') && !hasSizeL(element)) {
|
|
114
|
+
lib_1.currentSchematicContext.warn(`${path}: <lu-single-file-upload [entry]> has a [size] bound to an expression, migrate it manually.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const openingTag = template.slice(element.startSourceSpan.start.offset, element.startSourceSpan.end.offset);
|
|
118
|
+
if (/\s\*[\w-]+/.test(openingTag)) {
|
|
119
|
+
lib_1.currentSchematicContext.warn(`${path}: <lu-single-file-upload [entry]> carries a structural directive, migrate it manually.`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const entryExpression = entry.value instanceof lib_1.currentSchematicContext.angularCompiler.ASTWithSource ? (entry.value.source ?? '').trim() : '';
|
|
123
|
+
if (!entryExpression) {
|
|
124
|
+
lib_1.currentSchematicContext.warn(`${path}: could not read the [entry] expression of <lu-single-file-upload>, migrate it manually.`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
updates.push(buildFileEntryRendering(template, element, entryExpression));
|
|
128
|
+
symbolsToImport.add('FileEntryComponent');
|
|
129
|
+
symbolsToImport.add('FileEntryWrapperComponent');
|
|
130
|
+
lib_1.currentSchematicContext.logSuccess(`Extracting the FileEntry of <lu-single-file-upload> in ${path}`);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function buildFileEntryRendering(template, element, entryExpression) {
|
|
135
|
+
const start = element.sourceSpan.start.offset;
|
|
136
|
+
const end = (element.endSourceSpan ?? element.sourceSpan).end.offset;
|
|
137
|
+
const movedAttributes = [
|
|
138
|
+
...element.inputs.filter((input) => fileEntryInputs.includes(input.name)),
|
|
139
|
+
...element.attributes.filter((attribute) => fileEntryInputs.includes(attribute.name)),
|
|
140
|
+
...element.outputs.filter((output) => fileEntryOutputs.includes(output.name)),
|
|
141
|
+
];
|
|
142
|
+
// `lu-single-file-upload` used to render its entry as a media only in its large variant, always at size L.
|
|
143
|
+
const isLarge = hasSizeL(element);
|
|
144
|
+
const hasStructure = [...element.attributes, ...element.inputs].some((attribute) => attribute.name === 'structure');
|
|
145
|
+
const fileEntryAttributes = [
|
|
146
|
+
`[entry]="${entryAlias}"`,
|
|
147
|
+
'size="L"',
|
|
148
|
+
isLarge ? 'media' : '',
|
|
149
|
+
hasStructure ? 'structure' : '',
|
|
150
|
+
...movedAttributes.filter((attribute) => attribute.name !== 'entry').map((attribute) => template.slice(attribute.sourceSpan.start.offset, attribute.sourceSpan.end.offset)),
|
|
151
|
+
].filter(Boolean);
|
|
152
|
+
// The remaining file upload keeps everything the file entry did not take, formatting included.
|
|
153
|
+
const fileUpload = (0, lib_1.applyUpdates)(template.slice(start, end), movedAttributes.map((attribute) => {
|
|
154
|
+
const update = (0, lib_1.removeAttributeUpdate)(template, attribute);
|
|
155
|
+
return { ...update, position: update.position - start };
|
|
156
|
+
}));
|
|
157
|
+
const indent = getIndent(template, start);
|
|
158
|
+
const unit = indent.includes(' ') && !indent.includes('\t') ? ' ' : '\t';
|
|
159
|
+
return {
|
|
160
|
+
position: start,
|
|
161
|
+
oldContent: template.slice(start, end),
|
|
162
|
+
newContent: [
|
|
163
|
+
`@if (${entryExpression}; as ${entryAlias}) {`,
|
|
164
|
+
`${indent}${unit}<lu-file-entry-wrapper>`,
|
|
165
|
+
`${indent}${unit}${unit}<lu-file-entry ${fileEntryAttributes.join(' ')} />`,
|
|
166
|
+
`${indent}${unit}</lu-file-entry-wrapper>`,
|
|
167
|
+
`${indent}} @else {`,
|
|
168
|
+
`${indent}${unit}${fileUpload}`,
|
|
169
|
+
`${indent}}`,
|
|
170
|
+
].join('\n'),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** The `fileEntryDisplayWrapper` CSS class is now carried by the `lu-file-entry-wrapper` component. */
|
|
174
|
+
function migrateWrapperClass(path, template, symbolsToImport) {
|
|
175
|
+
return (0, lib_1.updateContent)(template, (updates) => {
|
|
176
|
+
const htmlAst = new lib_1.HtmlAst(template);
|
|
177
|
+
htmlAst.visitElements(/.*/, (element) => {
|
|
178
|
+
const boundClass = element.inputs.find((input) => input.name === 'class' || input.name === `class.${wrapperClass}` || input.name === 'ngClass');
|
|
179
|
+
if (boundClass && template.slice(boundClass.sourceSpan.start.offset, boundClass.sourceSpan.end.offset).includes(wrapperClass)) {
|
|
180
|
+
lib_1.currentSchematicContext.warn(`${path}: .${wrapperClass} is applied through a binding on <${element.name}>, migrate it manually.`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const classAttribute = element.attributes.find((attribute) => attribute.name === 'class');
|
|
184
|
+
const classes = classAttribute?.value.split(/\s+/).filter(Boolean) ?? [];
|
|
185
|
+
if (!classes.includes(wrapperClass) || !classAttribute) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
updates.push({
|
|
189
|
+
position: element.startSourceSpan.start.offset + '<'.length,
|
|
190
|
+
oldContent: element.name,
|
|
191
|
+
newContent: 'lu-file-entry-wrapper',
|
|
192
|
+
});
|
|
193
|
+
if (element.endSourceSpan && !element.isSelfClosing) {
|
|
194
|
+
updates.push({
|
|
195
|
+
position: element.endSourceSpan.start.offset + '</'.length,
|
|
196
|
+
oldContent: element.name,
|
|
197
|
+
newContent: 'lu-file-entry-wrapper',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const remainingClasses = classes.filter((className) => className !== wrapperClass);
|
|
201
|
+
if (remainingClasses.length === 0) {
|
|
202
|
+
updates.push((0, lib_1.removeAttributeUpdate)(template, classAttribute));
|
|
203
|
+
}
|
|
204
|
+
else if (classAttribute.valueSpan) {
|
|
205
|
+
updates.push({
|
|
206
|
+
position: classAttribute.valueSpan.start.offset,
|
|
207
|
+
oldContent: classAttribute.value,
|
|
208
|
+
newContent: remainingClasses.join(' '),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
symbolsToImport.add('FileEntryWrapperComponent');
|
|
212
|
+
lib_1.currentSchematicContext.logSuccess(`Replacing .${wrapperClass} with <lu-file-entry-wrapper> in ${path}`);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function hasSizeL(element) {
|
|
217
|
+
if (element.attributes.some((attribute) => attribute.name === 'size' && attribute.value === 'L')) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
return element.inputs.some((input) => {
|
|
221
|
+
const source = input.value instanceof lib_1.currentSchematicContext.angularCompiler.ASTWithSource ? input.value.source ?? '' : '';
|
|
222
|
+
return input.name === 'size' && /^['"]L['"]$/.test(source.trim());
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** Whitespace preceding the element on its own line, used to indent the generated block. */
|
|
226
|
+
function getIndent(template, position) {
|
|
227
|
+
const lineStart = template.lastIndexOf('\n', position - 1) + 1;
|
|
228
|
+
const prefix = template.slice(lineStart, position);
|
|
229
|
+
return /^\s*$/.test(prefix) ? prefix : '';
|
|
230
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const lib_1 = require("../lib");
|
|
38
|
+
const collectionPath = path.normalize(path.join(__dirname, '..', 'collection.json'));
|
|
39
|
+
const testsRoot = path.join(__dirname, 'tests');
|
|
40
|
+
describe('file-upload Migration', () => {
|
|
41
|
+
it('should handle basic case files', async () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
const tree = (0, lib_1.createTreeFromFolder)(path.join(testsRoot, 'input'));
|
|
44
|
+
const expectedTree = (0, lib_1.createTreeFromFolder)(path.join(testsRoot, 'output'));
|
|
45
|
+
// Act
|
|
46
|
+
try {
|
|
47
|
+
await (0, lib_1.runSchematic)('collection', collectionPath, 'file-upload', { skipInstall: true }, tree);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(error);
|
|
52
|
+
}
|
|
53
|
+
// Assert
|
|
54
|
+
(0, lib_1.expectTree)(tree).toMatchTree(expectedTree);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
+
"$id": "LuccaFrontFileUpload",
|
|
4
|
+
"title": "Lucca Front File Upload Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"dryRun": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"description": "Run through the migration without making any changes.",
|
|
10
|
+
"default": false
|
|
11
|
+
},
|
|
12
|
+
"skipInstall": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"description": "Skip installing dependencies.",
|
|
15
|
+
"default": false
|
|
16
|
+
},
|
|
17
|
+
"verbose": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"description": "Enable verbose logging.",
|
|
20
|
+
"default": false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -132,7 +132,9 @@ function insertTSImportIfNeeded(sourceFile, fileToEdit, symbolName, fileName) {
|
|
|
132
132
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
133
133
|
if (!imports.some((node) => (node.propertyName || node.name)?.text === symbolName)) {
|
|
134
134
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
|
|
135
|
-
|
|
135
|
+
// getStart() and not getFullStart(): the latter points before the leading trivia, which inserts the
|
|
136
|
+
// symbol right after the opening brace and leaves a double space behind it.
|
|
137
|
+
const insertPos = imports[0].getStart(sourceFile) || 0;
|
|
136
138
|
return new change_1.InsertChange(fileToEdit, insertPos, `${symbolName}, `);
|
|
137
139
|
}
|
|
138
140
|
return new change_1.NoopChange();
|
|
@@ -88,19 +88,11 @@ class DeprecatedMapper {
|
|
|
88
88
|
const elAst = new html_ast_1.HtmlAstVisitor(el);
|
|
89
89
|
// Static text attribute: `attrName` (boolean) or `attrName="value"`
|
|
90
90
|
elAst.visitAttribute(attrName, (attr) => {
|
|
91
|
-
|
|
92
|
-
const to = attr.sourceSpan.end.offset;
|
|
93
|
-
const hasLeadingSpace = from > 0 && template[from - 1] === ' ';
|
|
94
|
-
const removeFrom = hasLeadingSpace ? from - 1 : from;
|
|
95
|
-
updates.push({ position: removeFrom, oldContent: template.slice(removeFrom, to), newContent: '' });
|
|
91
|
+
updates.push((0, html_ast_1.removeAttributeUpdate)(template, attr));
|
|
96
92
|
});
|
|
97
93
|
// Bound attribute: `[attrName]="expr"`
|
|
98
94
|
elAst.visitBoundAttribute(attrName, (attr) => {
|
|
99
|
-
|
|
100
|
-
const to = attr.sourceSpan.end.offset;
|
|
101
|
-
const hasLeadingSpace = from > 0 && template[from - 1] === ' ';
|
|
102
|
-
const removeFrom = hasLeadingSpace ? from - 1 : from;
|
|
103
|
-
updates.push({ position: removeFrom, oldContent: template.slice(removeFrom, to), newContent: '' });
|
|
95
|
+
updates.push((0, html_ast_1.removeAttributeUpdate)(template, attr));
|
|
104
96
|
});
|
|
105
97
|
});
|
|
106
98
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HtmlAst = exports.HtmlAstVisitor = void 0;
|
|
4
|
+
exports.removeAttributeUpdate = removeAttributeUpdate;
|
|
5
|
+
exports.addAttributeUpdate = addAttributeUpdate;
|
|
4
6
|
exports.updateCssClassNames = updateCssClassNames;
|
|
5
7
|
exports.extractAllCssClassNames = extractAllCssClassNames;
|
|
6
8
|
exports.extractAllHtmlElementNames = extractAllHtmlElementNames;
|
|
@@ -109,6 +111,30 @@ class HtmlAst extends HtmlAstVisitor {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
exports.HtmlAst = HtmlAst;
|
|
114
|
+
/**
|
|
115
|
+
* Builds the update removing an attribute from the template it belongs to, along with the whitespace preceding it.
|
|
116
|
+
*/
|
|
117
|
+
function removeAttributeUpdate(template, attribute) {
|
|
118
|
+
let position = attribute.sourceSpan.start.offset;
|
|
119
|
+
while (position > 0 && /\s/.test(template[position - 1])) {
|
|
120
|
+
position--;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
position,
|
|
124
|
+
oldContent: template.slice(position, attribute.sourceSpan.end.offset),
|
|
125
|
+
newContent: ''
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Builds the update adding `name="value"` right after the tag name of the given element.
|
|
130
|
+
*/
|
|
131
|
+
function addAttributeUpdate(element, name, value) {
|
|
132
|
+
return {
|
|
133
|
+
position: element.startSourceSpan.start.offset + '<'.length + element.name.length,
|
|
134
|
+
oldContent: '',
|
|
135
|
+
newContent: ` ${name}="${value}"`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
112
138
|
function updateCssClassNames(content, oldClassToNewClass) {
|
|
113
139
|
return (0, file_update_js_1.updateContent)(content, (updates) => {
|
|
114
140
|
const root = new HtmlAst(content);
|
|
@@ -19,6 +19,13 @@ interface SelectDataSource<TOption, TGroup = never> {
|
|
|
19
19
|
paramsChange?: Observable<unknown>;
|
|
20
20
|
/** Optional debounce in ms for clue-based re-queries (useful for API data sources) */
|
|
21
21
|
clueDebounceMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Whether `getOptions` answers page by page. When false, it emits the whole list at once and `page`
|
|
24
|
+
* is always 0: the select accumulates nothing, `nextPage` is only a request for more, and the loading
|
|
25
|
+
* row is left to the consumer through the `loading` input.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
paginated?: boolean;
|
|
22
29
|
getOptions(params: SelectDataSourceParams): Observable<readonly TOption[]>;
|
|
23
30
|
/**
|
|
24
31
|
* Optional post-processing applied to the whole list of loaded options (all pages accumulated),
|