@iqual/nuxt-icms 1.6.0-rc.3 → 1.6.0-rc.5

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.
@@ -8,10 +8,10 @@
8
8
  @error="console.log"
9
9
  noValidate
10
10
  >
11
-
12
- <DesignSystemAtomsButton
13
- v-if="debug"
14
- @click="console.log(schema.safeParse(state))""
11
+
12
+ <DesignSystemAtomsButton
13
+ v-if="debug"
14
+ @click="console.log(schema.safeParse(state))"
15
15
  label="Debug Schema"
16
16
  />
17
17
 
@@ -24,7 +24,7 @@
24
24
  v-model="token"
25
25
  v-if="formElement?.metadata?.key === 'actions'"
26
26
  /> -->
27
-
27
+
28
28
  <component
29
29
  v-if="formElement?.metadata?.type"
30
30
  :is="getComponent(formElement?.metadata?.type)"
@@ -34,8 +34,8 @@
34
34
  :debug="debug"
35
35
  />
36
36
  </div>
37
- <DesignSystemAtomsButton
38
- v-if="debug"
37
+ <DesignSystemAtomsButton
38
+ v-if="debug"
39
39
  @click="console.log(schema.safeParse(state))"
40
40
  label="Debug Schema"
41
41
  :buttonStyle="'focus'"
package/config/blokkli.ts CHANGED
@@ -72,6 +72,7 @@ export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
72
72
  },
73
73
  pattern: [
74
74
  './components/Paragraph/**/*.{js,ts,vue}',
75
+ './components/Blokkli/**/*.{js,ts,vue}',
75
76
  join(currentLayerDir, './components/Blokkli/**/*.{js,ts,vue}'),
76
77
  ],
77
78
  globalOptions: {
@@ -385,4 +386,4 @@ export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
385
386
  // current page language.
386
387
  forceDefaultLanguage: false,
387
388
  }
388
- })
389
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iqual/nuxt-icms",
3
- "version": "1.6.0-rc.3",
3
+ "version": "1.6.0-rc.5",
4
4
  "description": "ICMS Base",
5
5
  "type": "module",
6
6
  "private": false,
@@ -32,7 +32,7 @@
32
32
  "nitropack": "~2.13.4",
33
33
  "nuxt": "~4.4.8",
34
34
  "nuxt-easy-texts": "~2.1.2",
35
- "nuxt-graphql-middleware": "~5.4.0",
35
+ "nuxt-graphql-middleware": "5.4.0",
36
36
  "nuxt-language-negotiation": "~2.0.2",
37
37
  "nuxt-maplibre": "~1.2.2",
38
38
  "nuxt-multi-cache": "~4.0.3",
@@ -0,0 +1,164 @@
1
+ diff --git a/node_modules/@blokkli/editor/dist/module.mjs b/node_modules/@blokkli/editor/dist/module.mjs
2
+ index 0ff0d3b..5329e9f 100644
3
+ --- a/node_modules/@blokkli/editor/dist/module.mjs
4
+ +++ b/node_modules/@blokkli/editor/dist/module.mjs
5
+ @@ -271,16 +271,31 @@ class CollectedBlockFile extends CollectedFile {
6
+ }
7
+ class BlockCollector extends Collector {
8
+ patterns;
9
+ + positivePatterns;
10
+ + negativePatterns;
11
+ + // Files that define the same block bundle (or fragment name) as a file in a
12
+ + // higher-priority Nuxt layer. They are kept here instead of in `files` so
13
+ + // they can be re-activated when the overriding file is removed.
14
+ + shadowed = /* @__PURE__ */ new Map();
15
+ constructor(helper) {
16
+ super(helper);
17
+ - this.patterns = (helper.options.pattern || []).map((pattern) => {
18
+ + this.patterns = (helper.options.pattern || []).map((raw) => {
19
+ + // Support negated glob patterns (e.g. "!**/components/Blokkli/Text/**")
20
+ + // to exclude components from collection.
21
+ + const isNegated = raw.startsWith("!");
22
+ + const pattern = isNegated ? raw.slice(1) : raw;
23
+ + let resolved = pattern;
24
+ if (pattern.startsWith("/")) {
25
+ - return pattern;
26
+ + resolved = pattern;
27
+ } else if (pattern.startsWith(".")) {
28
+ - return helper.resolvers.src.resolve(pattern);
29
+ + resolved = helper.resolvers.src.resolve(pattern);
30
+ + } else {
31
+ + resolved = resolveAlias(pattern);
32
+ }
33
+ - return resolveAlias(pattern);
34
+ + return isNegated ? "!" + resolved : resolved;
35
+ });
36
+ + this.positivePatterns = this.patterns.filter((v) => !v.startsWith("!"));
37
+ + this.negativePatterns = this.patterns.filter((v) => v.startsWith("!")).map((v) => v.slice(1));
38
+ }
39
+ async init() {
40
+ const files = await resolveFiles(
41
+ @@ -295,6 +310,90 @@ class BlockCollector extends Collector {
42
+ }
43
+ }
44
+ await Promise.all(promises);
45
+ + this.resolveLayerOverrides();
46
+ + }
47
+ + /**
48
+ + * Determine the Nuxt layer priority of a file.
49
+ + *
50
+ + * Lower value = higher priority. The app/project layer is index 0, extended
51
+ + * layers follow in the order they are defined. Files that can't be matched
52
+ + * to a layer get the lowest priority.
53
+ + */
54
+ + getLayerPriority(filePath) {
55
+ + const layers = this.helper.nuxt.options._layers || [];
56
+ + let priority = layers.length;
57
+ + let longestMatch = -1;
58
+ + for (let i = 0; i < layers.length; i++) {
59
+ + const config = layers[i].config || {};
60
+ + const dirs = [config.srcDir, config.rootDir, layers[i].cwd];
61
+ + for (const dir of dirs) {
62
+ + if (!dir) {
63
+ + continue;
64
+ + }
65
+ + const prefix = dir.endsWith("/") ? dir : dir + "/";
66
+ + if (filePath.startsWith(prefix) && dir.length > longestMatch) {
67
+ + priority = i;
68
+ + longestMatch = dir.length;
69
+ + }
70
+ + }
71
+ + }
72
+ + return priority;
73
+ + }
74
+ + /**
75
+ + * Resolve collisions where multiple collected files define the same block
76
+ + * bundle (or fragment name): the file from the highest-priority Nuxt layer
77
+ + * wins, the others are shadowed. Files in the same layer are left untouched
78
+ + * so genuine duplicates still surface as errors.
79
+ + *
80
+ + * @returns Whether the set of active files has changed.
81
+ + */
82
+ + resolveLayerOverrides() {
83
+ + const previouslyShadowed = this.shadowed;
84
+ + this.shadowed = /* @__PURE__ */ new Map();
85
+ + for (const [filePath, file] of previouslyShadowed) {
86
+ + if (!this.files.has(filePath)) {
87
+ + this.files.set(filePath, file);
88
+ + }
89
+ + }
90
+ + const byIdentifier = /* @__PURE__ */ new Map();
91
+ + for (const file of this.files.values()) {
92
+ + if (!file.definition || !file.identifier) {
93
+ + continue;
94
+ + }
95
+ + const group = byIdentifier.get(file.identifier);
96
+ + if (group) {
97
+ + group.push(file);
98
+ + } else {
99
+ + byIdentifier.set(file.identifier, [file]);
100
+ + }
101
+ + }
102
+ + for (const group of byIdentifier.values()) {
103
+ + if (group.length < 2) {
104
+ + continue;
105
+ + }
106
+ + const priorities = group.map((file) => this.getLayerPriority(file.filePath));
107
+ + const winningPriority = Math.min(...priorities);
108
+ + group.forEach((file, index) => {
109
+ + if (priorities[index] > winningPriority) {
110
+ + this.shadowed.set(file.filePath, file);
111
+ + this.files.delete(file.filePath);
112
+ + }
113
+ + });
114
+ + }
115
+ + if (previouslyShadowed.size !== this.shadowed.size) {
116
+ + return true;
117
+ + }
118
+ + for (const filePath of this.shadowed.keys()) {
119
+ + if (!previouslyShadowed.has(filePath)) {
120
+ + return true;
121
+ + }
122
+ + }
123
+ + return false;
124
+ + }
125
+ + async handleWatchEvent(event, filePath) {
126
+ + const result = await super.handleWatchEvent(event, filePath);
127
+ + const overridesChanged = this.resolveLayerOverrides();
128
+ + return { hasChanged: result.hasChanged || overridesChanged };
129
+ }
130
+ runHooks() {
131
+ return this.helper.nuxt.hooks.callHook("blokkli:alter-blocks", {
132
+ @@ -348,7 +447,10 @@ class BlockCollector extends Collector {
133
+ if (!filePath.endsWith(".vue")) {
134
+ return false;
135
+ }
136
+ - if (!micromatch.isMatch(filePath, this.patterns)) {
137
+ + if (!micromatch.isMatch(filePath, this.positivePatterns)) {
138
+ + return false;
139
+ + }
140
+ + if (this.negativePatterns.length && micromatch.isMatch(filePath, this.negativePatterns)) {
141
+ return false;
142
+ }
143
+ if (isEditComponent(filePath)) {
144
+ @@ -470,7 +572,19 @@ class IconCollector extends Collector {
145
+ this.helper.paths.srcDir,
146
+ "**/icon-blokkli-*.svg"
147
+ );
148
+ - const allFiles = [...filesModule, ...filesApp];
149
+ + // Also scan all Nuxt layer directories for icon SVGs.
150
+ + const layerFiles = [];
151
+ + const layers = this.helper.nuxt.options._layers || [];
152
+ + for (const layer of layers) {
153
+ + if (layer.config && layer.config.srcDir && layer.config.srcDir !== this.helper.paths.srcDir) {
154
+ + const files = await resolveFiles(
155
+ + layer.config.srcDir,
156
+ + "**/icon-blokkli-*.svg"
157
+ + );
158
+ + layerFiles.push(...files);
159
+ + }
160
+ + }
161
+ + const allFiles = [...filesModule, ...filesApp, ...layerFiles];
162
+ await Promise.all(allFiles.map((filePath) => this.addFile(filePath)));
163
+ }
164
+ runHooks() {
@@ -0,0 +1,232 @@
1
+ diff --git a/node_modules/nuxt-graphql-middleware/dist/module.mjs b/node_modules/nuxt-graphql-middleware/dist/module.mjs
2
+ index 8396abf..62f5068 100644
3
+ --- a/node_modules/nuxt-graphql-middleware/dist/module.mjs
4
+ +++ b/node_modules/nuxt-graphql-middleware/dist/module.mjs
5
+ @@ -215,12 +215,115 @@ class Collector {
6
+ */
7
+ fragments = [];
8
+ isInitialised = false;
9
+ + /**
10
+ + * The definition keys (kind:name) currently synced to the generator, per
11
+ + * file path.
12
+ + */
13
+ + syncedDefinitionKeys = /* @__PURE__ */ new Map();
14
+ + /**
15
+ + * File paths whose contents have changed since the last generator sync.
16
+ + */
17
+ + dirtyFiles = /* @__PURE__ */ new Set();
18
+ async reset() {
19
+ this.files.clear();
20
+ + this.syncedDefinitionKeys.clear();
21
+ + this.dirtyFiles.clear();
22
+ this.generator.reset();
23
+ this.operationTimestamps.clear();
24
+ this.rpcItems.clear();
25
+ }
26
+ + /**
27
+ + * Determine the Nuxt layer priority of a file.
28
+ + *
29
+ + * Lower value = higher priority. The app/project layer is index 0, extended
30
+ + * layers follow in the order they are defined. Documents provided via hooks
31
+ + * get the highest priority. Files that can't be matched to a layer get the
32
+ + * lowest priority.
33
+ + */
34
+ + getLayerPriority(filePath) {
35
+ + if (filePath.startsWith("hook:")) {
36
+ + return -1;
37
+ + }
38
+ + const layers = this.helper.nuxt.options._layers || [];
39
+ + let priority = layers.length;
40
+ + let longestMatch = -1;
41
+ + for (let i = 0; i < layers.length; i++) {
42
+ + const config = layers[i].config || {};
43
+ + const dirs = [config.srcDir, config.rootDir, layers[i].cwd];
44
+ + for (const dir of dirs) {
45
+ + if (!dir) {
46
+ + continue;
47
+ + }
48
+ + const prefix = dir.endsWith("/") ? dir : dir + "/";
49
+ + if (filePath.startsWith(prefix) && dir.length > longestMatch) {
50
+ + priority = i;
51
+ + longestMatch = dir.length;
52
+ + }
53
+ + }
54
+ + }
55
+ + return priority;
56
+ + }
57
+ + /**
58
+ + * Sync all collected files to the generator, resolving cross-layer
59
+ + * definition collisions: when multiple files define a fragment or operation
60
+ + * with the same name, the definition from the highest-priority Nuxt layer
61
+ + * (the app/project first, then extended layers in order) wins and the
62
+ + * shadowed definitions are excluded from the documents passed to the
63
+ + * generator. This makes it possible to override a fragment or operation
64
+ + * from a layer in the app or in a higher-priority layer. Definitions with
65
+ + * the same layer priority are left untouched (existing behaviour).
66
+ + */
67
+ + syncGenerator() {
68
+ + const owners = /* @__PURE__ */ new Map();
69
+ + for (const [filePath, file] of this.files) {
70
+ + const priority = this.getLayerPriority(filePath);
71
+ + for (const def of file.parsed.definitions) {
72
+ + if (def.kind !== "FragmentDefinition" && def.kind !== "OperationDefinition") {
73
+ + continue;
74
+ + }
75
+ + const name = def.name?.value;
76
+ + if (!name) {
77
+ + continue;
78
+ + }
79
+ + const key = def.kind + ":" + name;
80
+ + const owner = owners.get(key);
81
+ + if (!owner || priority < owner.priority) {
82
+ + owners.set(key, { priority });
83
+ + }
84
+ + }
85
+ + }
86
+ + for (const [filePath, file] of this.files) {
87
+ + const priority = this.getLayerPriority(filePath);
88
+ + const definitions = file.parsed.definitions.filter((def) => {
89
+ + if (def.kind !== "FragmentDefinition" && def.kind !== "OperationDefinition") {
90
+ + return true;
91
+ + }
92
+ + const name = def.name?.value;
93
+ + if (!name) {
94
+ + return true;
95
+ + }
96
+ + const owner = owners.get(def.kind + ":" + name);
97
+ + return !owner || owner.priority >= priority;
98
+ + });
99
+ + const keys = definitions.map((def) => def.kind + ":" + (def.name?.value || "")).join("|");
100
+ + const previousKeys = this.syncedDefinitionKeys.get(filePath);
101
+ + const documentNode = definitions.length === file.parsed.definitions.length ? file.parsed : { ...file.parsed, definitions };
102
+ + if (previousKeys === void 0) {
103
+ + this.generator.add({ filePath, documentNode });
104
+ + this.syncedDefinitionKeys.set(filePath, keys);
105
+ + } else if (previousKeys !== keys || this.dirtyFiles.has(filePath)) {
106
+ + this.generator.update({ filePath, documentNode });
107
+ + this.syncedDefinitionKeys.set(filePath, keys);
108
+ + }
109
+ + }
110
+ + for (const filePath of [...this.syncedDefinitionKeys.keys()]) {
111
+ + if (!this.files.has(filePath)) {
112
+ + this.generator.remove(filePath);
113
+ + this.syncedDefinitionKeys.delete(filePath);
114
+ + }
115
+ + }
116
+ + this.dirtyFiles.clear();
117
+ + }
118
+ async updateSchema(schema) {
119
+ this.schema = schema;
120
+ this.generator.updateSchema(schema);
121
+ @@ -436,10 +539,10 @@ class Collector {
122
+ const exists = this.hookDocuments.has(fullIdentifier);
123
+ this.hookDocuments.set(fullIdentifier, source);
124
+ if (exists && this.isInitialised) {
125
+ - this.generator.update({
126
+ - filePath: fullIdentifier,
127
+ - document: source
128
+ - });
129
+ + const file = new CollectedFile(fullIdentifier, source, false);
130
+ + this.files.set(fullIdentifier, file);
131
+ + this.dirtyFiles.add(fullIdentifier);
132
+ + this.syncGenerator();
133
+ await this.buildState();
134
+ }
135
+ }
136
+ @@ -460,23 +563,16 @@ class Collector {
137
+ const filePath = this.helper.paths.nuxtConfig;
138
+ const file = new CollectedFile(filePath, nuxtConfigDocuments, false);
139
+ this.files.set(filePath, file);
140
+ - this.generator.add({
141
+ - filePath,
142
+ - documentNode: file.parsed
143
+ - });
144
+ }
145
+ const hookDocuments = [...this.hookDocuments.entries()];
146
+ hookDocuments.forEach(([identifier, source]) => {
147
+ const file = new CollectedFile(identifier, source, false);
148
+ this.files.set(identifier, file);
149
+ - this.generator.add({
150
+ - filePath: identifier,
151
+ - documentNode: file.parsed
152
+ - });
153
+ });
154
+ for (const filePath of this.hookFiles) {
155
+ await this.addFile(filePath);
156
+ }
157
+ + this.syncGenerator();
158
+ await this.buildState();
159
+ if (!this.helper.isPrepare) {
160
+ logger.success("All GraphQL documents are valid.");
161
+ @@ -495,10 +591,6 @@ class Collector {
162
+ return null;
163
+ }
164
+ this.files.set(filePath, file);
165
+ - this.generator.add({
166
+ - filePath,
167
+ - documentNode: file.parsed
168
+ - });
169
+ return file;
170
+ }
171
+ matchesPatternOrExists(filePath) {
172
+ @@ -509,7 +601,11 @@ class Collector {
173
+ return false;
174
+ }
175
+ const result = await this.addFile(filePath);
176
+ - return !!result;
177
+ + if (!result) {
178
+ + return false;
179
+ + }
180
+ + this.syncGenerator();
181
+ + return true;
182
+ }
183
+ async handleChange(filePath) {
184
+ if (!this.matchesPatternOrExists(filePath)) {
185
+ @@ -524,10 +620,8 @@ class Collector {
186
+ if (!needsUpdate) {
187
+ return false;
188
+ }
189
+ - this.generator.update({
190
+ - filePath,
191
+ - documentNode: file.parsed
192
+ - });
193
+ + this.dirtyFiles.add(filePath);
194
+ + this.syncGenerator();
195
+ } catch {
196
+ return this.handleUnlink(filePath);
197
+ }
198
+ @@ -539,7 +633,7 @@ class Collector {
199
+ return false;
200
+ }
201
+ this.files.delete(filePath);
202
+ - this.generator.remove(filePath);
203
+ + this.syncGenerator();
204
+ return true;
205
+ }
206
+ handleUnlinkDir(folderPath) {
207
+ @@ -1128,7 +1222,24 @@ class ModuleHelper {
208
+ );
209
+ }
210
+ matchesImportPattern(filePath) {
211
+ - return micromatch.isMatch(filePath, this.options.autoImportPatterns) || this.options.autoImportPatterns.includes(filePath);
212
+ + // micromatch.isMatch with an array returns true if *any* pattern matches.
213
+ + // A negated pattern (e.g. "!node_modules") matches every path that does
214
+ + // not equal it, which would make every file match. Split the patterns and
215
+ + // apply proper include/exclude semantics instead.
216
+ + const positive = this.options.autoImportPatterns.filter(
217
+ + (v) => !v.startsWith("!")
218
+ + );
219
+ + const negative = this.options.autoImportPatterns.filter((v) => v.startsWith("!")).map((v) => v.slice(1));
220
+ + if (this.options.autoImportPatterns.includes(filePath)) {
221
+ + return true;
222
+ + }
223
+ + if (!micromatch.isMatch(filePath, positive)) {
224
+ + return false;
225
+ + }
226
+ + if (negative.length && micromatch.isMatch(filePath, negative)) {
227
+ + return false;
228
+ + }
229
+ + return true;
230
+ }
231
+ addAlias(name, path) {
232
+ this.nuxt.options.alias[name] = path;