nuxt-google-sheets-import 0.1.6 → 0.1.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 +38 -168
- package/dist/module.d.mts +0 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +16 -14
- package/dist/runtime/app/components/GoogleSheetsImportExport.vue +212 -0
- package/dist/runtime/{components → app/components}/GoogleSheetsImportSchemaGuide.vue +28 -48
- package/dist/runtime/app/examples/googleSheetsImportSchemasExample.d.ts +1 -0
- package/dist/runtime/app/examples/googleSheetsImportSchemasExample.js +0 -0
- package/dist/runtime/app/pages/google-sheets-import.d.vue.ts +3 -0
- package/dist/runtime/app/pages/google-sheets-import.vue +45 -0
- package/dist/runtime/app/pages/google-sheets-import.vue.d.ts +3 -0
- package/dist/runtime/app/utils/clipboard.d.ts +15 -0
- package/dist/runtime/app/utils/clipboard.js +12 -0
- package/dist/runtime/app/utils/delimited.d.ts +2 -0
- package/dist/runtime/app/utils/delimited.js +9 -0
- package/dist/runtime/app/utils/pathMapping.d.ts +4 -0
- package/dist/runtime/app/utils/pathMapping.js +86 -0
- package/dist/runtime/server/api/sheets.get.js +2 -2
- package/dist/runtime/server/api/values.post.js +2 -2
- package/dist/runtime/server/utils/transform.js +1 -42
- package/package.json +5 -2
- package/dist/runtime/pages/google-sheets-import.vue +0 -14
- /package/dist/runtime/{assets → app/assets}/css/main.css +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportExecute.d.vue.ts +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportExecute.vue +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportExecute.vue.d.ts +0 -0
- /package/dist/runtime/{pages/google-sheets-import.d.vue.ts → app/components/GoogleSheetsImportExport.d.vue.ts} +0 -0
- /package/dist/runtime/{pages/google-sheets-import.vue.d.ts → app/components/GoogleSheetsImportExport.vue.d.ts} +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportSchemaGuide.d.vue.ts +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportSchemaGuide.vue.d.ts +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportSource.d.vue.ts +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportSource.vue +0 -0
- /package/dist/runtime/{components → app/components}/GoogleSheetsImportSource.vue.d.ts +0 -0
- /package/dist/runtime/{composables → app/composables}/useGoogleSheetsImport.d.ts +0 -0
- /package/dist/runtime/{composables → app/composables}/useGoogleSheetsImport.js +0 -0
- /package/dist/runtime/{composables → app/composables}/useGoogleSheetsImportWorkflow.d.ts +0 -0
- /package/dist/runtime/{composables → app/composables}/useGoogleSheetsImportWorkflow.js +0 -0
package/README.md
CHANGED
|
@@ -1,154 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# nuxt-google-sheets-import
|
|
2
2
|
|
|
3
|
-
Schema-driven Google Sheets
|
|
3
|
+
Schema-driven Google Sheets import and export workflows for Nuxt Content.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Documentation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Project documentation now lives in the Docus site under [docs/content](docs/content).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Intro: [docs/content/1.getting-started/2.introduction.md](docs/content/1.getting-started/2.introduction.md)
|
|
10
|
+
- Installation: [docs/content/1.getting-started/3.installation.md](docs/content/1.getting-started/3.installation.md)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- Validates/transforms rows with Zod schemas
|
|
13
|
-
- Writes frontmatter markdown, JSON, or YAML output
|
|
14
|
-
- Supports overwrite strategies (`overwrite`, `skip`, `overwrite-frontmatter`)
|
|
15
|
-
- Exposes UI components and composables for import workflow
|
|
12
|
+
Use this root README as a repo quick-start and contributor guide. Keep long-form user documentation in the docs site.
|
|
16
13
|
|
|
17
|
-
##
|
|
14
|
+
## Module Quick Start
|
|
15
|
+
|
|
16
|
+
Install:
|
|
18
17
|
|
|
19
18
|
```bash
|
|
20
|
-
pnpm add
|
|
19
|
+
pnpm add nuxt-google-sheets-import
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Enable the module:
|
|
24
23
|
|
|
25
24
|
```ts
|
|
26
25
|
// nuxt.config.ts
|
|
27
26
|
export default defineNuxtConfig({
|
|
28
|
-
modules: ['
|
|
27
|
+
modules: ['nuxt-google-sheets-import'],
|
|
29
28
|
googleSheetsImport: {
|
|
30
29
|
apiBase: '/api/google-sheets-import',
|
|
31
|
-
|
|
32
|
-
defaultContentDir: 'content'
|
|
30
|
+
defaultContentDir: 'content/data'
|
|
33
31
|
}
|
|
34
32
|
})
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
## Environment
|
|
35
|
+
Environment:
|
|
40
36
|
|
|
41
37
|
```bash
|
|
42
38
|
NUXT_GOOGLE_API_KEY=your_google_sheets_api_key
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
This module currently reads Google Sheets using an API key, so the sheet must be publicly readable.
|
|
48
|
-
|
|
49
|
-
> Security note:
|
|
50
|
-
> API-key access is best suited to non-sensitive sheets that are intentionally shared as `Anyone with the link`.
|
|
51
|
-
> For private or sensitive spreadsheets, prefer OAuth 2.0 or a service account flow instead of API-key access.
|
|
52
|
-
|
|
53
|
-
### 1) Set sheet permissions (Google Sheets)
|
|
54
|
-
|
|
55
|
-
1. Open your sheet in Google Sheets.
|
|
56
|
-
2. Click `Share`.
|
|
57
|
-
3. Under `General access`, set to `Anyone with the link`.
|
|
58
|
-
4. Set role to `Viewer`.
|
|
59
|
-
5. Copy the spreadsheet ID from the URL:
|
|
60
|
-
- `https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit`
|
|
61
|
-
|
|
62
|
-
If your Google Workspace policy blocks public link sharing, API key access will fail. In that case you need OAuth/service-account based auth (not part of this module yet).
|
|
63
|
-
|
|
64
|
-
### 2) Create API key (Google Cloud Console)
|
|
65
|
-
|
|
66
|
-
1. Open Google Cloud Console: https://console.cloud.google.com/
|
|
67
|
-
2. Create/select a project.
|
|
68
|
-
3. Enable Google Sheets API:
|
|
69
|
-
- https://console.cloud.google.com/apis/library/sheets.googleapis.com
|
|
70
|
-
4. Create credentials (API key):
|
|
71
|
-
- https://console.cloud.google.com/apis/credentials
|
|
72
|
-
- Click `Create credentials` → `API key`
|
|
73
|
-
5. Restrict the key (recommended):
|
|
74
|
-
- **API restrictions**: `Restrict key` → select `Google Sheets API`
|
|
75
|
-
- **Application restrictions**:
|
|
76
|
-
- Server usage: `IP addresses` (recommended for backend)
|
|
77
|
-
- Browser-only usage: `HTTP referrers` (if applicable)
|
|
78
|
-
6. Put the key into `NUXT_GOOGLE_API_KEY`.
|
|
79
|
-
|
|
80
|
-
### 3) Quick verification
|
|
81
|
-
|
|
82
|
-
Call your module endpoint with a known sheet ID and confirm it returns tab metadata:
|
|
83
|
-
|
|
84
|
-
`GET /api/google-sheets-import/sheets?spreadsheetId=<SPREADSHEET_ID>`
|
|
41
|
+
## What The Module Provides
|
|
85
42
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
- The key is wrong, restricted to the wrong API, or Sheets API is not enabled.
|
|
94
|
-
- Fix: enable `Google Sheets API` and ensure key restriction includes it.
|
|
95
|
-
|
|
96
|
-
- `403 Requests from this referrer/IP are blocked`
|
|
97
|
-
- Your key application restrictions do not match where requests come from.
|
|
98
|
-
- Fix: update key restrictions (`IP addresses` for server use is preferred).
|
|
99
|
-
|
|
100
|
-
- `404 Requested entity was not found`
|
|
101
|
-
- Spreadsheet ID is incorrect or malformed.
|
|
102
|
-
- Fix: copy ID from `https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit`.
|
|
103
|
-
|
|
104
|
-
- `400 Unable to parse range`
|
|
105
|
-
- Invalid A1 range (for example typo in sheet tab or columns).
|
|
106
|
-
- Fix: verify tab name and use ranges like `A:Z`.
|
|
107
|
-
|
|
108
|
-
## Exported runtime
|
|
109
|
-
|
|
110
|
-
- Components: `GoogleSheetsImportSource`, `GoogleSheetsImportExecute`, `GoogleSheetsImportSchemaGuide`
|
|
111
|
-
- Composables: `useGoogleSheetsImport`, `useGoogleSheetsImportWorkflow`
|
|
112
|
-
|
|
113
|
-
### Schema helper component
|
|
114
|
-
|
|
115
|
-
Use `GoogleSheetsImportSchemaGuide` to let editors choose a schema and see the expected Google Sheet column headers.
|
|
116
|
-
|
|
117
|
-
```vue
|
|
118
|
-
<GoogleSheetsImportSchemaGuide />
|
|
119
|
-
```
|
|
43
|
+
- A built-in route at `/google-sheets-import`
|
|
44
|
+
- Tabbed workflow with schema setup, import, and export steps
|
|
45
|
+
- Zod schema-driven row validation/transforms
|
|
46
|
+
- Writes to markdown frontmatter, JSON, and YAML
|
|
47
|
+
- Export helpers:
|
|
48
|
+
- copy TSV to clipboard for Google Sheets paste
|
|
49
|
+
- download CSV
|
|
120
50
|
|
|
121
|
-
|
|
51
|
+
Collection type (`page` vs `data`) is resolved from your Nuxt Content collections.
|
|
122
52
|
|
|
123
|
-
|
|
124
|
-
- Supports nested/array header patterns used by schema mapping (for example `items[0].name`)
|
|
125
|
-
- Uses a single column for arrays of scalar values (for example `tags` with `foo, bar, baz`)
|
|
126
|
-
- For `page` collections, shows Nuxt Content built-in page override fields and allows copying them separately
|
|
127
|
-
- Supports two copy modes:
|
|
128
|
-
- line-by-line copy
|
|
129
|
-
- CSV-row copy (pastes horizontally into Google Sheets)
|
|
53
|
+
## Schema Source
|
|
130
54
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `initialSchema?: string`
|
|
134
|
-
|
|
135
|
-
### Suggested Cell Examples
|
|
136
|
-
|
|
137
|
-
Use these value patterns when filling sheets:
|
|
138
|
-
|
|
139
|
-
- `string`: `example text`
|
|
140
|
-
- `number`: `123`
|
|
141
|
-
- `boolean`: `true` or `false`
|
|
142
|
-
- `enum` / `literal`: use one of the schema's allowed values
|
|
143
|
-
- `date-like string`: `2026-01-01`
|
|
144
|
-
- `string[]` (scalar array): `foo, bar, baz` in a single cell
|
|
145
|
-
- `object[]` (array of objects): use indexed headers like `items[0].name`, `items[0].price`
|
|
146
|
-
|
|
147
|
-
## Schema source
|
|
148
|
-
|
|
149
|
-
The module resolves Zod schemas from Nuxt auto-imports using `#imports.schemas`.
|
|
150
|
-
|
|
151
|
-
Define a `schemas` export in `~/utils/googleSheetImportSchemas.ts`:
|
|
55
|
+
Define and export `schemas` in `~/utils/googleSheetImportSchemas.ts`.
|
|
56
|
+
The module auto-imports it as `googleSheetsImportSchemas` in app and server runtime.
|
|
152
57
|
|
|
153
58
|
```ts
|
|
154
59
|
export const schemas = {
|
|
@@ -157,61 +62,26 @@ export const schemas = {
|
|
|
157
62
|
}
|
|
158
63
|
```
|
|
159
64
|
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
The playground includes:
|
|
163
|
-
|
|
164
|
-
- `playground/utils/googleSheetImportSchemas.ts` (tiny local schema registry)
|
|
165
|
-
- `POST /api/google-sheets-import/values-smoke` (local transform/validation payload)
|
|
166
|
-
- `playground/scripts/smoke.mjs` (calls `values-smoke`, then module `write` for `frontmatter`, `json`, `yaml`)
|
|
167
|
-
|
|
168
|
-
Run with Nuxt dev server active:
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
pnpm --dir packages/nuxt-google-sheets-import smoke:playground
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Optional custom base URL:
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
SMOKE_BASE_URL=http://localhost:3000 pnpm --dir packages/nuxt-google-sheets-import smoke:playground
|
|
178
|
-
```
|
|
65
|
+
## Repository Development
|
|
179
66
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- `GET {apiBase}/schema-columns`
|
|
183
|
-
- Query: `schema?`
|
|
184
|
-
- Returns:
|
|
185
|
-
- `schemas`: available schema keys
|
|
186
|
-
- `columns`: expected import header names for selected schema
|
|
187
|
-
- `collectionType`: `page | data | unknown`
|
|
188
|
-
- `pageOverrideColumns`: Nuxt Content page override fields (when `collectionType === 'page'`)
|
|
189
|
-
|
|
190
|
-
## Publish checklist
|
|
191
|
-
|
|
192
|
-
- Add playground integration tests for `/values` and `/write`
|
|
193
|
-
- Add CI (`lint`, `typecheck`, `build`) and release workflow
|
|
194
|
-
- Verify Nuxt 4 peer compatibility matrix
|
|
195
|
-
|
|
196
|
-
## Publish (next step)
|
|
67
|
+
Module development:
|
|
197
68
|
|
|
198
69
|
```bash
|
|
199
|
-
|
|
70
|
+
npm install
|
|
71
|
+
npm run dev:prepare
|
|
72
|
+
npm run test
|
|
200
73
|
```
|
|
201
74
|
|
|
202
|
-
|
|
75
|
+
Playground app:
|
|
203
76
|
|
|
204
77
|
```bash
|
|
205
|
-
npm
|
|
206
|
-
pnpm --dir packages/nuxt-google-sheets-import publish --access public
|
|
78
|
+
npm run dev
|
|
207
79
|
```
|
|
208
80
|
|
|
209
|
-
|
|
81
|
+
Docs site:
|
|
210
82
|
|
|
211
83
|
```bash
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
84
|
+
cd docs
|
|
85
|
+
npm install
|
|
86
|
+
npm run dev
|
|
215
87
|
```
|
|
216
|
-
|
|
217
|
-
These scripts use `npm version --no-git-tag-version`, so they update `package.json` version without creating a git tag/commit.
|
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, extendPages, addServerHandler, addImportsDir, addComponentsDir, addImports, addServerImports } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, addTemplate, extendPages, addServerHandler, addImportsDir, addComponentsDir, addImports, addServerImports } from '@nuxt/kit';
|
|
2
2
|
|
|
3
3
|
const module$1 = defineNuxtModule({
|
|
4
4
|
meta: {
|
|
@@ -6,21 +6,28 @@ const module$1 = defineNuxtModule({
|
|
|
6
6
|
configKey: "googleSheetsImport"
|
|
7
7
|
},
|
|
8
8
|
moduleDependencies: {
|
|
9
|
+
"@nuxt/content": {
|
|
10
|
+
version: ">=3"
|
|
11
|
+
},
|
|
9
12
|
"@nuxt/ui": {
|
|
10
13
|
version: ">=4"
|
|
11
14
|
}
|
|
12
15
|
},
|
|
13
16
|
defaults: {
|
|
14
17
|
apiBase: "/api/google-sheets-import",
|
|
15
|
-
googleApiKeyRuntimeKey: "googleApiKey",
|
|
16
18
|
defaultContentDir: "content/data"
|
|
17
19
|
},
|
|
18
20
|
setup(options, nuxt) {
|
|
19
21
|
const resolver = createResolver(import.meta.url);
|
|
22
|
+
addTemplate({
|
|
23
|
+
filename: "googleSheetsImportSchemaExample.ts",
|
|
24
|
+
src: resolver.resolve("./runtime/app/examples/googleSheetsImportSchemasExample.ts"),
|
|
25
|
+
dst: "utils/googleSheetsImportSchemasExample.ts",
|
|
26
|
+
write: true
|
|
27
|
+
});
|
|
20
28
|
nuxt.options.runtimeConfig.googleSheetsImport = {
|
|
21
29
|
...nuxt.options.runtimeConfig.googleSheetsImport,
|
|
22
30
|
apiBase: options.apiBase,
|
|
23
|
-
googleApiKeyRuntimeKey: options.googleApiKeyRuntimeKey,
|
|
24
31
|
defaultContentDir: options.defaultContentDir
|
|
25
32
|
};
|
|
26
33
|
nuxt.options.runtimeConfig.public.googleSheetsImport = {
|
|
@@ -28,12 +35,12 @@ const module$1 = defineNuxtModule({
|
|
|
28
35
|
apiBase: options.apiBase,
|
|
29
36
|
defaultContentDir: options.defaultContentDir
|
|
30
37
|
};
|
|
31
|
-
nuxt.options.css.push(resolver.resolve("./runtime/assets/css/main.css"));
|
|
38
|
+
nuxt.options.css.push(resolver.resolve("./runtime/app/assets/css/main.css"));
|
|
32
39
|
extendPages((pages) => {
|
|
33
40
|
pages.push({
|
|
34
41
|
name: "google-sheets-import",
|
|
35
42
|
path: "/google-sheets-import",
|
|
36
|
-
file: resolver.resolve("./runtime/pages/google-sheets-import.vue")
|
|
43
|
+
file: resolver.resolve("./runtime/app/pages/google-sheets-import.vue")
|
|
37
44
|
});
|
|
38
45
|
});
|
|
39
46
|
addServerHandler({
|
|
@@ -61,16 +68,11 @@ const module$1 = defineNuxtModule({
|
|
|
61
68
|
method: "post",
|
|
62
69
|
handler: resolver.resolve("./runtime/server/api/write.post")
|
|
63
70
|
});
|
|
64
|
-
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
71
|
+
addImportsDir(resolver.resolve("./runtime/app/composables"));
|
|
65
72
|
addComponentsDir({
|
|
66
|
-
path: resolver.resolve("./runtime/components")
|
|
73
|
+
path: resolver.resolve("./runtime/app/components")
|
|
67
74
|
});
|
|
68
75
|
addImports([
|
|
69
|
-
{
|
|
70
|
-
from: "~/utils/googleSheetImportSchemas",
|
|
71
|
-
name: "schemas",
|
|
72
|
-
as: "googleSheetsImportSchemas"
|
|
73
|
-
},
|
|
74
76
|
{
|
|
75
77
|
from: resolver.resolve("./runtime/types/googleSheetsApi"),
|
|
76
78
|
name: "GoogleSheetsApiValues",
|
|
@@ -104,8 +106,8 @@ const module$1 = defineNuxtModule({
|
|
|
104
106
|
]);
|
|
105
107
|
addServerImports([
|
|
106
108
|
{
|
|
107
|
-
from: "~/utils/
|
|
108
|
-
name: "
|
|
109
|
+
from: "~/utils/googleSheetsImportSchemas",
|
|
110
|
+
name: "googleSheetsImportSchemas",
|
|
109
111
|
as: "googleSheetsImportSchemas"
|
|
110
112
|
}
|
|
111
113
|
]);
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { queryCollection, useAsyncData, useToast } from "#imports";
|
|
4
|
+
import { useGoogleSheetsImport } from "../composables/useGoogleSheetsImport";
|
|
5
|
+
import { copyTextWithSuccessToast } from "../utils/clipboard";
|
|
6
|
+
import { toCsvRow, toTsvRow } from "../utils/delimited";
|
|
7
|
+
import { flattenRecordToStringMap } from "../utils/pathMapping";
|
|
8
|
+
const toast = useToast();
|
|
9
|
+
const { getSchemaColumns } = useGoogleSheetsImport();
|
|
10
|
+
const queryCollectionAny = queryCollection;
|
|
11
|
+
function queryCollectionRows(collection) {
|
|
12
|
+
const query = queryCollectionAny(collection);
|
|
13
|
+
return query.all();
|
|
14
|
+
}
|
|
15
|
+
const selectedSchema = ref("");
|
|
16
|
+
const {
|
|
17
|
+
data: rowsData,
|
|
18
|
+
pending,
|
|
19
|
+
error: loadError,
|
|
20
|
+
status,
|
|
21
|
+
execute,
|
|
22
|
+
clear
|
|
23
|
+
} = useAsyncData(
|
|
24
|
+
async () => {
|
|
25
|
+
if (!selectedSchema.value) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return await queryCollectionRows(selectedSchema.value);
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
immediate: false,
|
|
32
|
+
default: () => []
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const {
|
|
36
|
+
data: schemaNames,
|
|
37
|
+
error: schemaNamesError
|
|
38
|
+
} = useAsyncData(
|
|
39
|
+
"google-sheets-import-export-schemas",
|
|
40
|
+
async () => {
|
|
41
|
+
const response = await getSchemaColumns();
|
|
42
|
+
return response.schemas ?? [];
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
default: () => []
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
const rows = computed(() => rowsData.value ?? []);
|
|
49
|
+
const error = computed(() => loadError.value?.message ?? schemaNamesError.value?.message ?? "");
|
|
50
|
+
watch(selectedSchema, () => {
|
|
51
|
+
clear();
|
|
52
|
+
});
|
|
53
|
+
const schemaOptions = computed(() => (schemaNames.value ?? []).sort((left, right) => left.localeCompare(right)).map((schema) => ({ label: schema, value: schema })));
|
|
54
|
+
const headers = computed(() => {
|
|
55
|
+
const all = /* @__PURE__ */ new Set();
|
|
56
|
+
for (const row of rows.value) {
|
|
57
|
+
flattenRecordToStringMap(row).forEach((_, key) => all.add(key));
|
|
58
|
+
}
|
|
59
|
+
return Array.from(all).sort((left, right) => left.localeCompare(right));
|
|
60
|
+
});
|
|
61
|
+
const csvText = computed(() => {
|
|
62
|
+
if (!headers.value.length) {
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
const lines = [];
|
|
66
|
+
lines.push(toCsvRow(headers.value));
|
|
67
|
+
for (const row of rows.value) {
|
|
68
|
+
const flat = flattenRecordToStringMap(row);
|
|
69
|
+
const values = headers.value.map((header) => flat.get(header) ?? "");
|
|
70
|
+
lines.push(toCsvRow(values));
|
|
71
|
+
}
|
|
72
|
+
return `${lines.join("\n")}
|
|
73
|
+
`;
|
|
74
|
+
});
|
|
75
|
+
const tsvText = computed(() => {
|
|
76
|
+
if (!headers.value.length) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
const lines = [];
|
|
80
|
+
lines.push(toTsvRow(headers.value));
|
|
81
|
+
for (const row of rows.value) {
|
|
82
|
+
const flat = flattenRecordToStringMap(row);
|
|
83
|
+
const values = headers.value.map((header) => flat.get(header) ?? "");
|
|
84
|
+
lines.push(toTsvRow(values));
|
|
85
|
+
}
|
|
86
|
+
return `${lines.join("\n")}
|
|
87
|
+
`;
|
|
88
|
+
});
|
|
89
|
+
const previewText = computed(() => {
|
|
90
|
+
if (!csvText.value) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
return csvText.value.split("\n").slice(0, 11).join("\n");
|
|
94
|
+
});
|
|
95
|
+
async function loadRecords() {
|
|
96
|
+
if (!selectedSchema.value) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await execute();
|
|
100
|
+
if (status.value === "success") {
|
|
101
|
+
toast.add({
|
|
102
|
+
title: "Export ready",
|
|
103
|
+
description: `Loaded ${rows.value.length} row(s) from collection ${selectedSchema.value}.`,
|
|
104
|
+
color: "success"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function copyCsv() {
|
|
109
|
+
await copyTextWithSuccessToast({
|
|
110
|
+
text: tsvText.value,
|
|
111
|
+
toast,
|
|
112
|
+
description: "Tab-separated rows copied for Google Sheets paste.",
|
|
113
|
+
title: "Copied"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function downloadCsv() {
|
|
117
|
+
if (!csvText.value) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const blob = new Blob([csvText.value], { type: "text/csv;charset=utf-8" });
|
|
121
|
+
const url = URL.createObjectURL(blob);
|
|
122
|
+
const link = document.createElement("a");
|
|
123
|
+
link.href = url;
|
|
124
|
+
link.download = `${selectedSchema.value || "export"}.csv`;
|
|
125
|
+
link.click();
|
|
126
|
+
URL.revokeObjectURL(url);
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<template>
|
|
131
|
+
<div class="space-y-4">
|
|
132
|
+
<UAlert
|
|
133
|
+
title="Export Existing Content"
|
|
134
|
+
description="Load records from a Nuxt Content collection and export them as CSV for Google Sheets."
|
|
135
|
+
color="neutral"
|
|
136
|
+
variant="subtle"
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<UAlert
|
|
140
|
+
v-if="error"
|
|
141
|
+
title="Could not load collection"
|
|
142
|
+
:description="error"
|
|
143
|
+
color="error"
|
|
144
|
+
variant="subtle"
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
<UFormField
|
|
148
|
+
label="Collection schema"
|
|
149
|
+
name="schema"
|
|
150
|
+
description="Pick the schema/collection to export."
|
|
151
|
+
>
|
|
152
|
+
<USelect
|
|
153
|
+
v-model="selectedSchema"
|
|
154
|
+
:items="schemaOptions"
|
|
155
|
+
value-key="value"
|
|
156
|
+
class="w-full max-w-sm"
|
|
157
|
+
icon="i-heroicons-cube-20-solid"
|
|
158
|
+
/>
|
|
159
|
+
</UFormField>
|
|
160
|
+
|
|
161
|
+
<div class="flex flex-wrap gap-2">
|
|
162
|
+
<UButton
|
|
163
|
+
label="Load records"
|
|
164
|
+
icon="i-heroicons-arrow-down-tray-20-solid"
|
|
165
|
+
:disabled="!selectedSchema"
|
|
166
|
+
:loading="pending"
|
|
167
|
+
@click="loadRecords"
|
|
168
|
+
/>
|
|
169
|
+
<UButton
|
|
170
|
+
label="Copy for Google Sheets"
|
|
171
|
+
icon="i-heroicons-clipboard-document-20-solid"
|
|
172
|
+
color="neutral"
|
|
173
|
+
variant="subtle"
|
|
174
|
+
:disabled="!tsvText"
|
|
175
|
+
@click="copyCsv"
|
|
176
|
+
/>
|
|
177
|
+
<UButton
|
|
178
|
+
label="Download .csv"
|
|
179
|
+
icon="i-heroicons-document-arrow-down-20-solid"
|
|
180
|
+
color="neutral"
|
|
181
|
+
variant="subtle"
|
|
182
|
+
:disabled="!csvText"
|
|
183
|
+
@click="downloadCsv"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<UAlert
|
|
188
|
+
v-if="status === 'success'"
|
|
189
|
+
title="Export prepared"
|
|
190
|
+
:description="`${rows.length} row(s), ${headers.length} column(s).`"
|
|
191
|
+
color="success"
|
|
192
|
+
variant="subtle"
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<UCollapsible
|
|
196
|
+
v-if="previewText"
|
|
197
|
+
class="flex flex-col gap-2 w-full"
|
|
198
|
+
>
|
|
199
|
+
<UButton
|
|
200
|
+
label="Preview CSV (first 10 rows)"
|
|
201
|
+
color="neutral"
|
|
202
|
+
variant="subtle"
|
|
203
|
+
trailing-icon="i-lucide-chevron-down"
|
|
204
|
+
block
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
<template #content>
|
|
208
|
+
<pre class="text-xs whitespace-pre-wrap">{{ previewText }}</pre>
|
|
209
|
+
</template>
|
|
210
|
+
</UCollapsible>
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { computed, onMounted, ref, watch } from "vue";
|
|
3
3
|
import { useToast } from "#imports";
|
|
4
4
|
import { useGoogleSheetsImport } from "../composables/useGoogleSheetsImport";
|
|
5
|
+
import { copyTextWithSuccessToast } from "../utils/clipboard";
|
|
6
|
+
import { toTsvRow } from "../utils/delimited";
|
|
5
7
|
const props = defineProps({
|
|
6
8
|
initialSchema: { type: String, required: false, default: "" }
|
|
7
9
|
});
|
|
@@ -61,58 +63,36 @@ onMounted(async () => {
|
|
|
61
63
|
await loadColumns(selectedSchema.value);
|
|
62
64
|
}
|
|
63
65
|
});
|
|
64
|
-
function copyColumns() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
navigator.clipboard.writeText(content);
|
|
70
|
-
toast.add({
|
|
71
|
-
title: "Copied",
|
|
66
|
+
async function copyColumns() {
|
|
67
|
+
await copyTextWithSuccessToast({
|
|
68
|
+
text: columns.value.join("\n"),
|
|
69
|
+
toast,
|
|
72
70
|
description: "Column names copied to clipboard.",
|
|
73
|
-
|
|
71
|
+
title: "Copied"
|
|
74
72
|
});
|
|
75
73
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
function copyColumnsCsv() {
|
|
83
|
-
const content = csvRow(columns.value);
|
|
84
|
-
if (!content) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
navigator.clipboard.writeText(content);
|
|
88
|
-
toast.add({
|
|
89
|
-
title: "Copied",
|
|
90
|
-
description: "Column names copied as a CSV row.",
|
|
91
|
-
color: "success"
|
|
74
|
+
async function copyColumnsTsv() {
|
|
75
|
+
await copyTextWithSuccessToast({
|
|
76
|
+
text: toTsvRow(columns.value),
|
|
77
|
+
toast,
|
|
78
|
+
description: "Column names copied as a tab-separated row for Google Sheets.",
|
|
79
|
+
title: "Copied"
|
|
92
80
|
});
|
|
93
81
|
}
|
|
94
|
-
function copyPageOverrideColumns() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
navigator.clipboard.writeText(content);
|
|
100
|
-
toast.add({
|
|
101
|
-
title: "Copied",
|
|
82
|
+
async function copyPageOverrideColumns() {
|
|
83
|
+
await copyTextWithSuccessToast({
|
|
84
|
+
text: pageOverrideColumns.value.join("\n"),
|
|
85
|
+
toast,
|
|
102
86
|
description: "Page override column names copied to clipboard.",
|
|
103
|
-
|
|
87
|
+
title: "Copied"
|
|
104
88
|
});
|
|
105
89
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
toast.add({
|
|
113
|
-
title: "Copied",
|
|
114
|
-
description: "Page override column names copied as a CSV row.",
|
|
115
|
-
color: "success"
|
|
90
|
+
async function copyPageOverrideColumnsTsv() {
|
|
91
|
+
await copyTextWithSuccessToast({
|
|
92
|
+
text: toTsvRow(pageOverrideColumns.value),
|
|
93
|
+
toast,
|
|
94
|
+
description: "Page override column names copied as a tab-separated row for Google Sheets.",
|
|
95
|
+
title: "Copied"
|
|
116
96
|
});
|
|
117
97
|
}
|
|
118
98
|
</script>
|
|
@@ -170,11 +150,11 @@ function copyPageOverrideColumnsCsv() {
|
|
|
170
150
|
@click="copyColumns"
|
|
171
151
|
/>
|
|
172
152
|
<UButton
|
|
173
|
-
label="Copy as
|
|
153
|
+
label="Copy as tab-separated row"
|
|
174
154
|
icon="i-heroicons-table-cells-20-solid"
|
|
175
155
|
color="neutral"
|
|
176
156
|
variant="subtle"
|
|
177
|
-
@click="
|
|
157
|
+
@click="copyColumnsTsv"
|
|
178
158
|
/>
|
|
179
159
|
</div>
|
|
180
160
|
<pre class="text-xs whitespace-pre-wrap">{{ columns.join("\n") }}</pre>
|
|
@@ -200,11 +180,11 @@ function copyPageOverrideColumnsCsv() {
|
|
|
200
180
|
@click="copyPageOverrideColumns"
|
|
201
181
|
/>
|
|
202
182
|
<UButton
|
|
203
|
-
label="Copy overrides as
|
|
183
|
+
label="Copy overrides as tab-separated row"
|
|
204
184
|
icon="i-heroicons-table-cells-20-solid"
|
|
205
185
|
color="neutral"
|
|
206
186
|
variant="subtle"
|
|
207
|
-
@click="
|
|
187
|
+
@click="copyPageOverrideColumnsTsv"
|
|
208
188
|
/>
|
|
209
189
|
</div>
|
|
210
190
|
<pre class="text-xs whitespace-pre-wrap">{{ pageOverrideColumns.join("\n") }}</pre>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
File without changes
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
const sheetsList = ref([
|
|
4
|
+
{ id: "1NKS0cTX6u5urtgQ3Q4Z2motiR2-9JmyPxcd05yVc1bc", label: "Metzner" },
|
|
5
|
+
{ id: "1tGZCEoiikXfg3mOpfVWWTS1SSSsj18xv6Z3owrnnt4s", label: "Example Sheet" }
|
|
6
|
+
]);
|
|
7
|
+
const items = ref([
|
|
8
|
+
{
|
|
9
|
+
label: "Setup Google Sheet",
|
|
10
|
+
icon: "i-lucide-layout-dashboard",
|
|
11
|
+
slot: "sheet"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: "Import data",
|
|
15
|
+
icon: "i-lucide-upload",
|
|
16
|
+
slot: "import"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Export data",
|
|
20
|
+
icon: "i-lucide-download",
|
|
21
|
+
slot: "export"
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<UContainer class="max-w-36">
|
|
28
|
+
<UTabs
|
|
29
|
+
:items="items"
|
|
30
|
+
variant="link"
|
|
31
|
+
color="neutral"
|
|
32
|
+
class="w-full"
|
|
33
|
+
>
|
|
34
|
+
<template #sheet>
|
|
35
|
+
<GoogleSheetsImportSchemaGuide :initial-schema="'Example Sheet'" />
|
|
36
|
+
</template>
|
|
37
|
+
<template #import>
|
|
38
|
+
<GoogleSheetsImportSource :google-sheets="sheetsList" />
|
|
39
|
+
</template>
|
|
40
|
+
<template #export>
|
|
41
|
+
<GoogleSheetsImportExport />
|
|
42
|
+
</template>
|
|
43
|
+
</UTabs>
|
|
44
|
+
</UContainer>
|
|
45
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ToastLike {
|
|
2
|
+
add: (payload: {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
color?: 'success' | 'error' | 'warning' | 'info' | 'neutral';
|
|
6
|
+
}) => void;
|
|
7
|
+
}
|
|
8
|
+
interface CopyTextWithToastOptions {
|
|
9
|
+
text: string;
|
|
10
|
+
toast: ToastLike;
|
|
11
|
+
description: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function copyTextWithSuccessToast(options: CopyTextWithToastOptions): Promise<boolean>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function copyTextWithSuccessToast(options) {
|
|
2
|
+
if (!options.text) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
await navigator.clipboard.writeText(options.text);
|
|
6
|
+
options.toast.add({
|
|
7
|
+
title: options.title ?? "Copied",
|
|
8
|
+
description: options.description,
|
|
9
|
+
color: "success"
|
|
10
|
+
});
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function toCsvRow(values) {
|
|
2
|
+
return values.map((value) => {
|
|
3
|
+
const escaped = value.replaceAll('"', '""');
|
|
4
|
+
return `"${escaped}"`;
|
|
5
|
+
}).join(",");
|
|
6
|
+
}
|
|
7
|
+
export function toTsvRow(values) {
|
|
8
|
+
return values.map((value) => value.replace(/[\t\r\n]+/g, " ")).join(" ");
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type PathSegment = string | number;
|
|
2
|
+
export declare function parseHeaderPath(header: string): PathSegment[];
|
|
3
|
+
export declare function setDeep(target: Record<string, unknown>, path: PathSegment[], value: unknown): void;
|
|
4
|
+
export declare function flattenRecordToStringMap(record: Record<string, unknown>, prefix?: string): Map<string, string>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const EXCLUDED_EXPORT_PATHS = /* @__PURE__ */ new Set(["__hash__", "body"]);
|
|
2
|
+
export function parseHeaderPath(header) {
|
|
3
|
+
const tokens = [];
|
|
4
|
+
const parts = header.split(".");
|
|
5
|
+
for (const part of parts) {
|
|
6
|
+
const matches = part.matchAll(/([^[]+)|(\[(\d+)\])/g);
|
|
7
|
+
for (const match of matches) {
|
|
8
|
+
if (match[1]) {
|
|
9
|
+
tokens.push(match[1]);
|
|
10
|
+
}
|
|
11
|
+
if (match[3]) {
|
|
12
|
+
tokens.push(Number.parseInt(match[3], 10));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return tokens;
|
|
17
|
+
}
|
|
18
|
+
export function setDeep(target, path, value) {
|
|
19
|
+
if (!path.length) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let cursor = target;
|
|
23
|
+
for (let index = 0; index < path.length; index++) {
|
|
24
|
+
const key = path[index];
|
|
25
|
+
if (key === void 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const isLast = index === path.length - 1;
|
|
29
|
+
const nextKey = path[index + 1];
|
|
30
|
+
if (isLast) {
|
|
31
|
+
cursor[key] = value;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (cursor[key] === void 0) {
|
|
35
|
+
cursor[key] = typeof nextKey === "number" ? [] : {};
|
|
36
|
+
}
|
|
37
|
+
const nextCursor = cursor[key];
|
|
38
|
+
if (!nextCursor || typeof nextCursor !== "object") {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
cursor = nextCursor;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function flattenRecordToStringMap(record, prefix = "") {
|
|
45
|
+
const out = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const [key, value] of Object.entries(record)) {
|
|
47
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
48
|
+
if (EXCLUDED_EXPORT_PATHS.has(path)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (value === null || value === void 0) {
|
|
52
|
+
out.set(path, "");
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
if (value.every((item) => item === null || ["string", "number", "boolean"].includes(typeof item))) {
|
|
57
|
+
out.set(path, value.map((item) => item == null ? "" : String(item)).join(", "));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
for (let index = 0; index < value.length; index++) {
|
|
61
|
+
const item = value[index];
|
|
62
|
+
const arrayPath = `${path}[${index}]`;
|
|
63
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
64
|
+
flattenRecordToStringMap(item, arrayPath).forEach((nestedValue, nestedKey) => {
|
|
65
|
+
out.set(nestedKey, nestedValue);
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
out.set(arrayPath, item == null ? "" : String(item));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (value instanceof Date) {
|
|
74
|
+
out.set(path, value.toISOString());
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "object") {
|
|
78
|
+
flattenRecordToStringMap(value, path).forEach((nestedValue, nestedKey) => {
|
|
79
|
+
out.set(nestedKey, nestedValue);
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
out.set(path, String(value));
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
@@ -6,8 +6,8 @@ const querySchema = z.object({
|
|
|
6
6
|
spreadsheetId: z.string().length(44)
|
|
7
7
|
});
|
|
8
8
|
export default defineEventHandler(async (event) => {
|
|
9
|
-
const
|
|
10
|
-
const apiKey =
|
|
9
|
+
const config = useRuntimeConfig(event);
|
|
10
|
+
const apiKey = config.googleApiKey;
|
|
11
11
|
const { spreadsheetId } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
12
12
|
if (!apiKey || typeof apiKey !== "string") {
|
|
13
13
|
throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
|
|
@@ -10,8 +10,8 @@ const bodySchema = z.object({
|
|
|
10
10
|
});
|
|
11
11
|
export default defineEventHandler(async (event) => {
|
|
12
12
|
const body = bodySchema.parse(await readBody(event));
|
|
13
|
-
const
|
|
14
|
-
const apiKey =
|
|
13
|
+
const config = useRuntimeConfig(event);
|
|
14
|
+
const apiKey = config.googleApiKey;
|
|
15
15
|
if (!apiKey || typeof apiKey !== "string") {
|
|
16
16
|
throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
|
|
17
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { parseHeaderPath, setDeep } from "../../../runtime/app/utils/pathMapping";
|
|
2
3
|
function isZodType(value) {
|
|
3
4
|
return typeof value === "object" && value !== null && "_def" in value;
|
|
4
5
|
}
|
|
@@ -47,48 +48,6 @@ function hasWrapper(schema, wrapper) {
|
|
|
47
48
|
}
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
|
-
function parseHeaderPath(header) {
|
|
51
|
-
const tokens = [];
|
|
52
|
-
const parts = header.split(".");
|
|
53
|
-
for (const part of parts) {
|
|
54
|
-
const matches = part.matchAll(/([^[]+)|(\[(\d+)\])/g);
|
|
55
|
-
for (const match of matches) {
|
|
56
|
-
if (match[1]) {
|
|
57
|
-
tokens.push(match[1]);
|
|
58
|
-
}
|
|
59
|
-
if (match[3]) {
|
|
60
|
-
tokens.push(Number.parseInt(match[3], 10));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return tokens;
|
|
65
|
-
}
|
|
66
|
-
function setDeep(target, path, value) {
|
|
67
|
-
if (!path.length) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
let cursor = target;
|
|
71
|
-
for (let index = 0; index < path.length; index++) {
|
|
72
|
-
const key = path[index];
|
|
73
|
-
if (key === void 0) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const isLast = index === path.length - 1;
|
|
77
|
-
const nextKey = path[index + 1];
|
|
78
|
-
if (isLast) {
|
|
79
|
-
cursor[key] = value;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (cursor[key] === void 0) {
|
|
83
|
-
cursor[key] = typeof nextKey === "number" ? [] : {};
|
|
84
|
-
}
|
|
85
|
-
const nextCursor = cursor[key];
|
|
86
|
-
if (!nextCursor || typeof nextCursor !== "object") {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
cursor = nextCursor;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
51
|
function getObjectShape(schema) {
|
|
93
52
|
const unwrapped = unwrapSchema(schema);
|
|
94
53
|
if (!unwrapped) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-google-sheets-import",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Schema-driven Google Sheets import module for Nuxt Content",
|
|
5
5
|
"repository": "tribeweb/nuxt-google-sheets-import",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"workspaces": [
|
|
26
|
-
"playground"
|
|
26
|
+
"playground",
|
|
27
|
+
"docs"
|
|
27
28
|
],
|
|
28
29
|
"scripts": {
|
|
30
|
+
"docs:dev": "cd docs && nuxt dev --extends docus",
|
|
31
|
+
"docs:build": "nuxt build docs --extends docus",
|
|
29
32
|
"prepack": "nuxt-module-build build",
|
|
30
33
|
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
31
34
|
"dev:build": "nuxt build playground",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { ref } from "vue";
|
|
3
|
-
const sheetsList = ref([
|
|
4
|
-
{ id: "1NKS0cTX6u5urtgQ3Q4Z2motiR2-9JmyPxcd05yVc1bc", label: "Metzner" },
|
|
5
|
-
{ id: "1tGZCEoiikXfg3mOpfVWWTS1SSSsj18xv6Z3owrnnt4s", label: "Example Sheet" }
|
|
6
|
-
]);
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<template>
|
|
10
|
-
<UContainer>
|
|
11
|
-
<GoogleSheetsImportSchemaGuide />
|
|
12
|
-
<GoogleSheetsImportSource :google-sheets="sheetsList" />
|
|
13
|
-
</UContainer>
|
|
14
|
-
</template>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|