@pagebridge/sanity 0.0.1 → 0.0.2
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/.turbo/turbo-build.log +1 -1
- package/README.md +214 -214
- package/dist/components/DecayAlertCard.d.ts +16 -0
- package/dist/components/DecayAlertCard.d.ts.map +1 -0
- package/dist/components/DecayAlertCard.js +24 -0
- package/dist/components/DecayBadge.d.ts +3 -0
- package/dist/components/DecayBadge.d.ts.map +1 -0
- package/dist/components/DecayBadge.js +19 -0
- package/dist/components/PerformanceChart.d.ts +12 -0
- package/dist/components/PerformanceChart.d.ts.map +1 -0
- package/dist/components/PerformanceChart.js +18 -0
- package/dist/components/PerformanceInspector.d.ts +3 -0
- package/dist/components/PerformanceInspector.d.ts.map +1 -0
- package/dist/components/PerformanceInspector.js +174 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +4 -2
- package/dist/schemas/gscKeywordTarget.d.ts +22 -0
- package/dist/schemas/gscKeywordTarget.d.ts.map +1 -0
- package/dist/schemas/gscKeywordTarget.js +43 -0
- package/package.json +2 -1
- package/src/index.ts +2 -2
- package/src/plugin.ts +10 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[?9001h[?1004h[?25l[2J[m[2;1H> @pagebridge/sanity@0.0.
|
|
1
|
+
[?9001h[?1004h[?25l[2J[m[2;1H> @pagebridge/sanity@0.0.2 build F:\Code\pagebridge\oss\packages\sanity-plugin
|
|
2
2
|
> pnpm exec tsc[5;1H]0;C:\WINDOWS\system32\cmd.exe[?25h[?9001l[?1004l
|
package/README.md
CHANGED
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
# @pagebridge/sanity-plugin
|
|
2
|
-
|
|
3
|
-
Sanity Studio v3 plugin for PageBridge. Provides document schemas, UI components, and tools for viewing search performance data and managing content refresh tasks.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @pagebridge/sanity-plugin
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
12
|
-
|
|
13
|
-
### 1. Add the Plugin
|
|
14
|
-
|
|
15
|
-
In your `sanity.config.ts`:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { defineConfig } from 'sanity';
|
|
19
|
-
import { gscPlugin } from '@pagebridge/sanity-plugin';
|
|
20
|
-
|
|
21
|
-
export default defineConfig({
|
|
22
|
-
// ... other config
|
|
23
|
-
plugins: [
|
|
24
|
-
gscPlugin({
|
|
25
|
-
contentTypes: ['post', 'page'], // Document types to track
|
|
26
|
-
}),
|
|
27
|
-
],
|
|
28
|
-
});
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. Add the Structure Resolver
|
|
32
|
-
|
|
33
|
-
To display the Performance pane on your content documents:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { defineConfig } from 'sanity';
|
|
37
|
-
import { structureTool } from 'sanity/structure';
|
|
38
|
-
import { gscPlugin, createGscStructureResolver } from '@pagebridge/sanity-plugin';
|
|
39
|
-
|
|
40
|
-
export default defineConfig({
|
|
41
|
-
// ... other config
|
|
42
|
-
plugins: [
|
|
43
|
-
structureTool({
|
|
44
|
-
structure: createGscStructureResolver({
|
|
45
|
-
contentTypes: ['post', 'page'],
|
|
46
|
-
}),
|
|
47
|
-
}),
|
|
48
|
-
gscPlugin({
|
|
49
|
-
contentTypes: ['post', 'page'],
|
|
50
|
-
}),
|
|
51
|
-
],
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Document Schemas
|
|
56
|
-
|
|
57
|
-
The plugin registers three document types:
|
|
58
|
-
|
|
59
|
-
### gscSite
|
|
60
|
-
|
|
61
|
-
Represents a Google Search Console property.
|
|
62
|
-
|
|
63
|
-
| Field | Type | Description |
|
|
64
|
-
|-------|------|-------------|
|
|
65
|
-
| siteUrl | string | GSC site URL (e.g., `sc-domain:example.com`) |
|
|
66
|
-
| slug | slug | URL-friendly identifier |
|
|
67
|
-
| defaultLocale | string | Default locale (default: "en") |
|
|
68
|
-
| pathPrefix | string | Path prefix for URL matching (e.g., `/blog`) |
|
|
69
|
-
| lastSyncedAt | datetime | Last sync timestamp (read-only) |
|
|
70
|
-
|
|
71
|
-
### gscSnapshot
|
|
72
|
-
|
|
73
|
-
Performance metrics snapshot linked to content documents.
|
|
74
|
-
|
|
75
|
-
| Field | Type | Description |
|
|
76
|
-
|-------|------|-------------|
|
|
77
|
-
| site | reference | Reference to gscSite |
|
|
78
|
-
| page | string | Page URL |
|
|
79
|
-
| linkedDocument | reference | Matched content document |
|
|
80
|
-
| period | string | `last7`, `last28`, or `last90` |
|
|
81
|
-
| clicks | number | Total clicks |
|
|
82
|
-
| impressions | number | Total impressions |
|
|
83
|
-
| ctr | number | Click-through rate |
|
|
84
|
-
| position | number | Average position |
|
|
85
|
-
| topQueries | array | Top search queries with metrics |
|
|
86
|
-
| fetchedAt | datetime | When data was fetched |
|
|
87
|
-
| indexStatus | object | Google index status details |
|
|
88
|
-
|
|
89
|
-
### gscRefreshTask
|
|
90
|
-
|
|
91
|
-
Content refresh task with decay signal information.
|
|
92
|
-
|
|
93
|
-
| Field | Type | Description |
|
|
94
|
-
|-------|------|-------------|
|
|
95
|
-
| site | reference | Reference to gscSite |
|
|
96
|
-
| linkedDocument | reference | Content document needing refresh |
|
|
97
|
-
| reason | string | `position_decay`, `low_ctr`, `impressions_drop`, `manual` |
|
|
98
|
-
| severity | string | `low`, `medium`, `high` |
|
|
99
|
-
| status | string | `open`, `snoozed`, `in_progress`, `done`, `dismissed` |
|
|
100
|
-
| snoozedUntil | datetime | When to resurface (if snoozed) |
|
|
101
|
-
| metrics | object | Position, CTR, impressions data |
|
|
102
|
-
| queryContext | array | Top 5 queries with stats |
|
|
103
|
-
| notes | text | Resolution notes |
|
|
104
|
-
| createdAt | datetime | Task creation time |
|
|
105
|
-
| resolvedAt | datetime | Task resolution time |
|
|
106
|
-
|
|
107
|
-
## Components
|
|
108
|
-
|
|
109
|
-
### SearchPerformancePane
|
|
110
|
-
|
|
111
|
-
Document view pane showing performance metrics for a content document.
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
import { SearchPerformancePane } from '@pagebridge/sanity-plugin';
|
|
115
|
-
|
|
116
|
-
// Used automatically when you configure the structure resolver
|
|
117
|
-
// Can also be used directly in custom document views
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
The pane displays:
|
|
121
|
-
- Clicks, impressions, CTR, and position metrics
|
|
122
|
-
- Top search queries driving traffic
|
|
123
|
-
- Google index status
|
|
124
|
-
- Link to associated refresh tasks
|
|
125
|
-
|
|
126
|
-
### RefreshQueueTool
|
|
127
|
-
|
|
128
|
-
Sanity tool for managing content refresh tasks. Accessible from the Studio sidebar.
|
|
129
|
-
|
|
130
|
-
Features:
|
|
131
|
-
- Filter tasks by status (open, in progress, snoozed, done, dismissed)
|
|
132
|
-
- Sort by severity or creation date
|
|
133
|
-
- View decay signal details
|
|
134
|
-
- Update task status
|
|
135
|
-
- Add resolution notes
|
|
136
|
-
|
|
137
|
-
## Configuration Options
|
|
138
|
-
|
|
139
|
-
### gscPlugin
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
interface GscPluginConfig {
|
|
143
|
-
// Document types that can be linked to snapshots and tasks
|
|
144
|
-
contentTypes: string[];
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### createGscStructureResolver
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
interface StructureResolverConfig {
|
|
152
|
-
// Document types to show the Performance pane on
|
|
153
|
-
contentTypes: string[];
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Using Schemas Directly
|
|
158
|
-
|
|
159
|
-
If you need to customize the schemas or use them without the plugin:
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
import {
|
|
163
|
-
gscSite,
|
|
164
|
-
createGscSnapshot,
|
|
165
|
-
createGscRefreshTask,
|
|
166
|
-
} from '@pagebridge/sanity-plugin/schemas';
|
|
167
|
-
|
|
168
|
-
// Create snapshot schema with custom content types
|
|
169
|
-
const customSnapshot = createGscSnapshot({
|
|
170
|
-
contentTypes: ['article', 'guide'],
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Create task schema with custom content types
|
|
174
|
-
const customTask = createGscRefreshTask({
|
|
175
|
-
contentTypes: ['article', 'guide'],
|
|
176
|
-
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Styling
|
|
180
|
-
|
|
181
|
-
The components use Sanity UI and follow the Studio's theme. No additional CSS is required.
|
|
182
|
-
|
|
183
|
-
## Peer Dependencies
|
|
184
|
-
|
|
185
|
-
- `sanity` >= 3.0.0
|
|
186
|
-
- `react` >= 18.0.0
|
|
187
|
-
- `react-dom` >= 18.0.0
|
|
188
|
-
- `@sanity/ui` >= 2.0.0
|
|
189
|
-
- `@sanity/icons` >= 3.0.0
|
|
190
|
-
|
|
191
|
-
## Exports
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
// Plugin
|
|
195
|
-
export { gscPlugin, createGscStructureResolver } from '@pagebridge/sanity-plugin';
|
|
196
|
-
export type { GscPluginConfig } from '@pagebridge/sanity-plugin';
|
|
197
|
-
|
|
198
|
-
// Components
|
|
199
|
-
export { SearchPerformancePane, RefreshQueueTool } from '@pagebridge/sanity-plugin';
|
|
200
|
-
|
|
201
|
-
// Schemas
|
|
202
|
-
export {
|
|
203
|
-
gscSite,
|
|
204
|
-
gscSnapshot,
|
|
205
|
-
gscRefreshTask,
|
|
206
|
-
createGscSnapshot,
|
|
207
|
-
createGscRefreshTask,
|
|
208
|
-
} from '@pagebridge/sanity-plugin/schemas';
|
|
209
|
-
export type { GscSnapshotOptions, GscRefreshTaskOptions } from '@pagebridge/sanity-plugin/schemas';
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## License
|
|
213
|
-
|
|
214
|
-
MIT
|
|
1
|
+
# @pagebridge/sanity-plugin
|
|
2
|
+
|
|
3
|
+
Sanity Studio v3 plugin for PageBridge. Provides document schemas, UI components, and tools for viewing search performance data and managing content refresh tasks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @pagebridge/sanity-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Add the Plugin
|
|
14
|
+
|
|
15
|
+
In your `sanity.config.ts`:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { defineConfig } from 'sanity';
|
|
19
|
+
import { gscPlugin } from '@pagebridge/sanity-plugin';
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
// ... other config
|
|
23
|
+
plugins: [
|
|
24
|
+
gscPlugin({
|
|
25
|
+
contentTypes: ['post', 'page'], // Document types to track
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Add the Structure Resolver
|
|
32
|
+
|
|
33
|
+
To display the Performance pane on your content documents:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { defineConfig } from 'sanity';
|
|
37
|
+
import { structureTool } from 'sanity/structure';
|
|
38
|
+
import { gscPlugin, createGscStructureResolver } from '@pagebridge/sanity-plugin';
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
// ... other config
|
|
42
|
+
plugins: [
|
|
43
|
+
structureTool({
|
|
44
|
+
structure: createGscStructureResolver({
|
|
45
|
+
contentTypes: ['post', 'page'],
|
|
46
|
+
}),
|
|
47
|
+
}),
|
|
48
|
+
gscPlugin({
|
|
49
|
+
contentTypes: ['post', 'page'],
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Document Schemas
|
|
56
|
+
|
|
57
|
+
The plugin registers three document types:
|
|
58
|
+
|
|
59
|
+
### gscSite
|
|
60
|
+
|
|
61
|
+
Represents a Google Search Console property.
|
|
62
|
+
|
|
63
|
+
| Field | Type | Description |
|
|
64
|
+
|-------|------|-------------|
|
|
65
|
+
| siteUrl | string | GSC site URL (e.g., `sc-domain:example.com`) |
|
|
66
|
+
| slug | slug | URL-friendly identifier |
|
|
67
|
+
| defaultLocale | string | Default locale (default: "en") |
|
|
68
|
+
| pathPrefix | string | Path prefix for URL matching (e.g., `/blog`) |
|
|
69
|
+
| lastSyncedAt | datetime | Last sync timestamp (read-only) |
|
|
70
|
+
|
|
71
|
+
### gscSnapshot
|
|
72
|
+
|
|
73
|
+
Performance metrics snapshot linked to content documents.
|
|
74
|
+
|
|
75
|
+
| Field | Type | Description |
|
|
76
|
+
|-------|------|-------------|
|
|
77
|
+
| site | reference | Reference to gscSite |
|
|
78
|
+
| page | string | Page URL |
|
|
79
|
+
| linkedDocument | reference | Matched content document |
|
|
80
|
+
| period | string | `last7`, `last28`, or `last90` |
|
|
81
|
+
| clicks | number | Total clicks |
|
|
82
|
+
| impressions | number | Total impressions |
|
|
83
|
+
| ctr | number | Click-through rate |
|
|
84
|
+
| position | number | Average position |
|
|
85
|
+
| topQueries | array | Top search queries with metrics |
|
|
86
|
+
| fetchedAt | datetime | When data was fetched |
|
|
87
|
+
| indexStatus | object | Google index status details |
|
|
88
|
+
|
|
89
|
+
### gscRefreshTask
|
|
90
|
+
|
|
91
|
+
Content refresh task with decay signal information.
|
|
92
|
+
|
|
93
|
+
| Field | Type | Description |
|
|
94
|
+
|-------|------|-------------|
|
|
95
|
+
| site | reference | Reference to gscSite |
|
|
96
|
+
| linkedDocument | reference | Content document needing refresh |
|
|
97
|
+
| reason | string | `position_decay`, `low_ctr`, `impressions_drop`, `manual` |
|
|
98
|
+
| severity | string | `low`, `medium`, `high` |
|
|
99
|
+
| status | string | `open`, `snoozed`, `in_progress`, `done`, `dismissed` |
|
|
100
|
+
| snoozedUntil | datetime | When to resurface (if snoozed) |
|
|
101
|
+
| metrics | object | Position, CTR, impressions data |
|
|
102
|
+
| queryContext | array | Top 5 queries with stats |
|
|
103
|
+
| notes | text | Resolution notes |
|
|
104
|
+
| createdAt | datetime | Task creation time |
|
|
105
|
+
| resolvedAt | datetime | Task resolution time |
|
|
106
|
+
|
|
107
|
+
## Components
|
|
108
|
+
|
|
109
|
+
### SearchPerformancePane
|
|
110
|
+
|
|
111
|
+
Document view pane showing performance metrics for a content document.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { SearchPerformancePane } from '@pagebridge/sanity-plugin';
|
|
115
|
+
|
|
116
|
+
// Used automatically when you configure the structure resolver
|
|
117
|
+
// Can also be used directly in custom document views
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The pane displays:
|
|
121
|
+
- Clicks, impressions, CTR, and position metrics
|
|
122
|
+
- Top search queries driving traffic
|
|
123
|
+
- Google index status
|
|
124
|
+
- Link to associated refresh tasks
|
|
125
|
+
|
|
126
|
+
### RefreshQueueTool
|
|
127
|
+
|
|
128
|
+
Sanity tool for managing content refresh tasks. Accessible from the Studio sidebar.
|
|
129
|
+
|
|
130
|
+
Features:
|
|
131
|
+
- Filter tasks by status (open, in progress, snoozed, done, dismissed)
|
|
132
|
+
- Sort by severity or creation date
|
|
133
|
+
- View decay signal details
|
|
134
|
+
- Update task status
|
|
135
|
+
- Add resolution notes
|
|
136
|
+
|
|
137
|
+
## Configuration Options
|
|
138
|
+
|
|
139
|
+
### gscPlugin
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface GscPluginConfig {
|
|
143
|
+
// Document types that can be linked to snapshots and tasks
|
|
144
|
+
contentTypes: string[];
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### createGscStructureResolver
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface StructureResolverConfig {
|
|
152
|
+
// Document types to show the Performance pane on
|
|
153
|
+
contentTypes: string[];
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Using Schemas Directly
|
|
158
|
+
|
|
159
|
+
If you need to customize the schemas or use them without the plugin:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import {
|
|
163
|
+
gscSite,
|
|
164
|
+
createGscSnapshot,
|
|
165
|
+
createGscRefreshTask,
|
|
166
|
+
} from '@pagebridge/sanity-plugin/schemas';
|
|
167
|
+
|
|
168
|
+
// Create snapshot schema with custom content types
|
|
169
|
+
const customSnapshot = createGscSnapshot({
|
|
170
|
+
contentTypes: ['article', 'guide'],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Create task schema with custom content types
|
|
174
|
+
const customTask = createGscRefreshTask({
|
|
175
|
+
contentTypes: ['article', 'guide'],
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Styling
|
|
180
|
+
|
|
181
|
+
The components use Sanity UI and follow the Studio's theme. No additional CSS is required.
|
|
182
|
+
|
|
183
|
+
## Peer Dependencies
|
|
184
|
+
|
|
185
|
+
- `sanity` >= 3.0.0
|
|
186
|
+
- `react` >= 18.0.0
|
|
187
|
+
- `react-dom` >= 18.0.0
|
|
188
|
+
- `@sanity/ui` >= 2.0.0
|
|
189
|
+
- `@sanity/icons` >= 3.0.0
|
|
190
|
+
|
|
191
|
+
## Exports
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Plugin
|
|
195
|
+
export { gscPlugin, createGscStructureResolver } from '@pagebridge/sanity-plugin';
|
|
196
|
+
export type { GscPluginConfig } from '@pagebridge/sanity-plugin';
|
|
197
|
+
|
|
198
|
+
// Components
|
|
199
|
+
export { SearchPerformancePane, RefreshQueueTool } from '@pagebridge/sanity-plugin';
|
|
200
|
+
|
|
201
|
+
// Schemas
|
|
202
|
+
export {
|
|
203
|
+
gscSite,
|
|
204
|
+
gscSnapshot,
|
|
205
|
+
gscRefreshTask,
|
|
206
|
+
createGscSnapshot,
|
|
207
|
+
createGscRefreshTask,
|
|
208
|
+
} from '@pagebridge/sanity-plugin/schemas';
|
|
209
|
+
export type { GscSnapshotOptions, GscRefreshTaskOptions } from '@pagebridge/sanity-plugin/schemas';
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface DecayMetrics {
|
|
2
|
+
positionBefore?: number;
|
|
3
|
+
positionNow?: number;
|
|
4
|
+
positionDelta?: number;
|
|
5
|
+
ctrBefore?: number;
|
|
6
|
+
ctrNow?: number;
|
|
7
|
+
impressions?: number;
|
|
8
|
+
}
|
|
9
|
+
interface DecayAlertCardProps {
|
|
10
|
+
reason: "position_decay" | "low_ctr" | "impressions_drop" | "manual";
|
|
11
|
+
severity: "low" | "medium" | "high";
|
|
12
|
+
metrics?: DecayMetrics;
|
|
13
|
+
}
|
|
14
|
+
export declare function DecayAlertCard({ reason, severity, metrics, }: DecayAlertCardProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=DecayAlertCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecayAlertCard.d.ts","sourceRoot":"","sources":["../../src/components/DecayAlertCard.tsx"],"names":[],"mappings":"AAGA,UAAU,YAAY;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,kBAAkB,GAAG,QAAQ,CAAC;IACrE,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAwBD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,OAAO,GACR,EAAE,mBAAmB,2CAkBrB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, Stack, Text, Flex } from "@sanity/ui";
|
|
3
|
+
import { WarningOutlineIcon } from "@sanity/icons";
|
|
4
|
+
function getDecayMessage(reason, metrics) {
|
|
5
|
+
switch (reason) {
|
|
6
|
+
case "position_decay":
|
|
7
|
+
if (metrics?.positionBefore != null && metrics?.positionNow != null) {
|
|
8
|
+
return `Position dropped ${metrics.positionBefore.toFixed(0)} \u2192 ${metrics.positionNow.toFixed(0)} over 30 days`;
|
|
9
|
+
}
|
|
10
|
+
return "Search position has declined over 30 days";
|
|
11
|
+
case "low_ctr":
|
|
12
|
+
if (metrics?.ctrNow != null) {
|
|
13
|
+
return `CTR at ${(metrics.ctrNow * 100).toFixed(1)}% despite top-10 position`;
|
|
14
|
+
}
|
|
15
|
+
return "Click-through rate is below threshold";
|
|
16
|
+
case "impressions_drop":
|
|
17
|
+
return "Impressions dropped significantly over 30 days";
|
|
18
|
+
case "manual":
|
|
19
|
+
return "Manually flagged for review";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function DecayAlertCard({ reason, severity, metrics, }) {
|
|
23
|
+
return (_jsx(Card, { padding: 3, radius: 2, tone: "critical", children: _jsxs(Stack, { space: 2, children: [_jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Text, { size: 1, children: _jsx(WarningOutlineIcon, {}) }), _jsx(Text, { size: 1, weight: "semibold", children: "Decay Detected" })] }), _jsx(Text, { size: 1, muted: true, children: getDecayMessage(reason, metrics) })] }) }));
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecayBadge.d.ts","sourceRoot":"","sources":["../../src/components/DecayBadge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAErD,eAAO,MAAM,UAAU,EAAE,sBAqBxB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useClient } from "sanity";
|
|
3
|
+
export const DecayBadge = (props) => {
|
|
4
|
+
const client = useClient({ apiVersion: "2025-02-07" });
|
|
5
|
+
const [hasDecay, setHasDecay] = useState(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
client
|
|
8
|
+
.fetch(`count(*[_type == "gscRefreshTask" && linkedDocument._ref == $id && status == "open"])`, { id: props.id })
|
|
9
|
+
.then((count) => setHasDecay(count > 0))
|
|
10
|
+
.catch(() => setHasDecay(false));
|
|
11
|
+
}, [props.id, client]);
|
|
12
|
+
if (!hasDecay)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
label: "Decay detected",
|
|
16
|
+
title: "Content decay has been detected for this document",
|
|
17
|
+
color: "danger",
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ChartBar {
|
|
2
|
+
/** Weekly click value for this bar */
|
|
3
|
+
value: number;
|
|
4
|
+
/** Whether this segment is trending down relative to the longer-term average */
|
|
5
|
+
declining: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface PerformanceChartProps {
|
|
8
|
+
bars: ChartBar[];
|
|
9
|
+
}
|
|
10
|
+
export declare function PerformanceChart({ bars }: PerformanceChartProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=PerformanceChart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PerformanceChart.d.ts","sourceRoot":"","sources":["../../src/components/PerformanceChart.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,EAAE,qBAAqB,2CA+C/D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, Stack, Text, Flex } from "@sanity/ui";
|
|
3
|
+
export function PerformanceChart({ bars }) {
|
|
4
|
+
const maxValue = Math.max(...bars.map((b) => b.value), 1);
|
|
5
|
+
return (_jsx(Card, { padding: 3, radius: 2, shadow: 1, children: _jsxs(Stack, { space: 3, children: [_jsxs(Stack, { space: 1, children: [_jsx(Text, { size: 0, weight: "semibold", muted: true, children: "PERFORMANCE (30D)" }), _jsx(Text, { size: 0, muted: true, children: "Weekly clicks trend" })] }), _jsx("div", { style: {
|
|
6
|
+
display: "flex",
|
|
7
|
+
alignItems: "flex-end",
|
|
8
|
+
height: 120,
|
|
9
|
+
gap: 3,
|
|
10
|
+
padding: "0 4px",
|
|
11
|
+
}, children: bars.map((bar, i) => (_jsx("div", { style: {
|
|
12
|
+
flex: 1,
|
|
13
|
+
height: `${Math.max((bar.value / maxValue) * 100, 4)}%`,
|
|
14
|
+
backgroundColor: bar.declining ? "#f4726d" : "#43d675",
|
|
15
|
+
borderRadius: "3px 3px 0 0",
|
|
16
|
+
minHeight: 4,
|
|
17
|
+
} }, i))) }), _jsxs(Flex, { justify: "space-between", style: { padding: "0 4px" }, children: [_jsx(Text, { size: 0, muted: true, children: "older" }), _jsx(Text, { size: 0, muted: true, children: "recent" })] })] }) }));
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PerformanceInspector.d.ts","sourceRoot":"","sources":["../../src/components/PerformanceInspector.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAiJrD,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,GACX,EAAE,sBAAsB,2CA+RxB"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState, useCallback } from "react";
|
|
3
|
+
import { useClient } from "sanity";
|
|
4
|
+
import { Card, Stack, Text, Badge, Flex, Spinner, Box, TextInput, Button, } from "@sanity/ui";
|
|
5
|
+
import { AddIcon, TrashIcon, CheckmarkCircleIcon, CircleIcon, } from "@sanity/icons";
|
|
6
|
+
import { PerformanceChart } from "./PerformanceChart";
|
|
7
|
+
import { DecayAlertCard } from "./DecayAlertCard";
|
|
8
|
+
/**
|
|
9
|
+
* Deduplicate snapshots — keep only the most recent per period.
|
|
10
|
+
*/
|
|
11
|
+
function deduplicateSnapshots(snapshots) {
|
|
12
|
+
const byPeriod = new Map();
|
|
13
|
+
for (const snap of snapshots) {
|
|
14
|
+
const existing = byPeriod.get(snap.period);
|
|
15
|
+
if (!existing || snap.fetchedAt > existing.fetchedAt) {
|
|
16
|
+
byPeriod.set(snap.period, snap);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return byPeriod;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build ~12 weekly bars from period aggregates.
|
|
23
|
+
*
|
|
24
|
+
* Derives weekly click totals from the 3 period snapshots, then colors
|
|
25
|
+
* each bar based on the overall trend direction:
|
|
26
|
+
* - If the page is declining (recent < older), older bars are green
|
|
27
|
+
* and recent bars are red — showing the transition from healthy to declining.
|
|
28
|
+
* - If the page is stable/growing, all bars are green.
|
|
29
|
+
*/
|
|
30
|
+
function buildChartBars(snapshots) {
|
|
31
|
+
const s7 = snapshots.get("last7");
|
|
32
|
+
const s28 = snapshots.get("last28");
|
|
33
|
+
const s90 = snapshots.get("last90");
|
|
34
|
+
if (!s28 && !s7)
|
|
35
|
+
return [];
|
|
36
|
+
const clicks7 = s7?.clicks ?? 0;
|
|
37
|
+
const clicks28 = s28?.clicks ?? clicks7;
|
|
38
|
+
const clicks90 = s90?.clicks ?? clicks28;
|
|
39
|
+
// Derive weekly values for each segment
|
|
40
|
+
const recentWeekly = clicks7; // most recent week
|
|
41
|
+
const midWeekly = Math.max(clicks28 - clicks7, 0) / 3; // weeks 2-4 avg
|
|
42
|
+
const olderWeekly = s90 ? Math.max(clicks90 - clicks28, 0) / 8 : 0; // weeks 5-12 avg
|
|
43
|
+
// Determine overall trend: is recent worse than historical?
|
|
44
|
+
const historicalWeeklyAvg = s90
|
|
45
|
+
? (clicks90 - clicks7) / 11
|
|
46
|
+
: s28
|
|
47
|
+
? (clicks28 - clicks7) / 3
|
|
48
|
+
: clicks7;
|
|
49
|
+
const isDeclining = recentWeekly < historicalWeeklyAvg * 0.85;
|
|
50
|
+
const bars = [];
|
|
51
|
+
// Older weeks (5-12) — 8 bars — green if declining (these were the good days)
|
|
52
|
+
if (s90) {
|
|
53
|
+
for (let i = 0; i < 8; i++) {
|
|
54
|
+
bars.push({ value: olderWeekly, declining: false });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Middle weeks (2-4) — 3 bars — transition zone
|
|
58
|
+
if (s28) {
|
|
59
|
+
for (let i = 0; i < 3; i++) {
|
|
60
|
+
bars.push({
|
|
61
|
+
value: midWeekly,
|
|
62
|
+
declining: isDeclining && midWeekly < historicalWeeklyAvg * 0.85,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Most recent week — 1 bar — red if the page is declining
|
|
67
|
+
bars.push({ value: recentWeekly, declining: isDeclining });
|
|
68
|
+
return bars;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a target keyword matches any of the top queries.
|
|
72
|
+
* Uses case-insensitive substring matching.
|
|
73
|
+
*/
|
|
74
|
+
function isKeywordInQueries(keyword, queries) {
|
|
75
|
+
const lower = keyword.toLowerCase();
|
|
76
|
+
return queries.some((q) => q.query.toLowerCase().includes(lower));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a query matches any of the target keywords.
|
|
80
|
+
*/
|
|
81
|
+
function isQueryInTargets(query, targets) {
|
|
82
|
+
const lower = query.toLowerCase();
|
|
83
|
+
return targets.some((t) => lower.includes(t.toLowerCase()));
|
|
84
|
+
}
|
|
85
|
+
export function PerformanceInspector({ documentId, }) {
|
|
86
|
+
const client = useClient({ apiVersion: "2024-01-01" });
|
|
87
|
+
const [snapshots, setSnapshots] = useState(new Map());
|
|
88
|
+
const [decayTask, setDecayTask] = useState(null);
|
|
89
|
+
const [keywordTarget, setKeywordTarget] = useState(null);
|
|
90
|
+
const [newKeyword, setNewKeyword] = useState("");
|
|
91
|
+
const [loading, setLoading] = useState(true);
|
|
92
|
+
const fetchData = useCallback(async () => {
|
|
93
|
+
const [snapshotResults, taskResult, keywordResult] = await Promise.all([
|
|
94
|
+
client.fetch(`*[_type == "gscSnapshot" && linkedDocument._ref == $id] | order(fetchedAt desc) {
|
|
95
|
+
period,
|
|
96
|
+
clicks,
|
|
97
|
+
impressions,
|
|
98
|
+
ctr,
|
|
99
|
+
position,
|
|
100
|
+
topQueries,
|
|
101
|
+
fetchedAt
|
|
102
|
+
}`, { id: documentId }),
|
|
103
|
+
client.fetch(`*[_type == "gscRefreshTask" && linkedDocument._ref == $id && status == "open"] | order(severity desc)[0] {
|
|
104
|
+
reason,
|
|
105
|
+
severity,
|
|
106
|
+
metrics
|
|
107
|
+
}`, { id: documentId }),
|
|
108
|
+
client.fetch(`*[_type == "gscKeywordTarget" && linkedDocument._ref == $id][0] {
|
|
109
|
+
_id,
|
|
110
|
+
keywords
|
|
111
|
+
}`, { id: documentId }),
|
|
112
|
+
]);
|
|
113
|
+
setSnapshots(deduplicateSnapshots(snapshotResults ?? []));
|
|
114
|
+
setDecayTask(taskResult);
|
|
115
|
+
setKeywordTarget(keywordResult);
|
|
116
|
+
setLoading(false);
|
|
117
|
+
}, [documentId, client]);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
fetchData();
|
|
120
|
+
}, [fetchData]);
|
|
121
|
+
const addKeyword = useCallback(async () => {
|
|
122
|
+
const keyword = newKeyword.trim();
|
|
123
|
+
if (!keyword)
|
|
124
|
+
return;
|
|
125
|
+
if (keywordTarget) {
|
|
126
|
+
const updated = [...(keywordTarget.keywords ?? []), keyword];
|
|
127
|
+
await client.patch(keywordTarget._id).set({ keywords: updated }).commit();
|
|
128
|
+
setKeywordTarget({ ...keywordTarget, keywords: updated });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const doc = await client.create({
|
|
132
|
+
_type: "gscKeywordTarget",
|
|
133
|
+
linkedDocument: { _type: "reference", _ref: documentId },
|
|
134
|
+
keywords: [keyword],
|
|
135
|
+
});
|
|
136
|
+
setKeywordTarget({ _id: doc._id, keywords: [keyword] });
|
|
137
|
+
}
|
|
138
|
+
setNewKeyword("");
|
|
139
|
+
}, [newKeyword, keywordTarget, documentId, client]);
|
|
140
|
+
const removeKeyword = useCallback(async (keyword) => {
|
|
141
|
+
if (!keywordTarget)
|
|
142
|
+
return;
|
|
143
|
+
const updated = keywordTarget.keywords.filter((k) => k !== keyword);
|
|
144
|
+
await client.patch(keywordTarget._id).set({ keywords: updated }).commit();
|
|
145
|
+
setKeywordTarget({ ...keywordTarget, keywords: updated });
|
|
146
|
+
}, [keywordTarget, client]);
|
|
147
|
+
if (loading) {
|
|
148
|
+
return (_jsx(Card, { padding: 4, children: _jsx(Flex, { justify: "center", align: "center", style: { minHeight: 200 }, children: _jsx(Spinner, {}) }) }));
|
|
149
|
+
}
|
|
150
|
+
const snapshot28 = snapshots.get("last28");
|
|
151
|
+
const chartBars = buildChartBars(snapshots);
|
|
152
|
+
const topQueries = snapshot28?.topQueries?.slice(0, 5) ?? [];
|
|
153
|
+
const targetKeywords = keywordTarget?.keywords ?? [];
|
|
154
|
+
return (_jsx(Card, { padding: 4, style: { height: "100%", overflowY: "auto" }, children: _jsxs(Stack, { space: 4, children: [_jsxs(Flex, { justify: "space-between", align: "center", children: [_jsxs(Flex, { align: "center", gap: 2, children: [_jsx("div", { style: {
|
|
155
|
+
width: 10,
|
|
156
|
+
height: 10,
|
|
157
|
+
borderRadius: "50%",
|
|
158
|
+
backgroundColor: "#43d675",
|
|
159
|
+
flexShrink: 0,
|
|
160
|
+
} }), _jsx(Text, { weight: "semibold", size: 2, children: "PageBridge" })] }), _jsx(Badge, { tone: "primary", fontSize: 0, children: "Live GSC data" })] }), chartBars.length > 0 ? (_jsx(PerformanceChart, { bars: chartBars })) : (_jsx(Card, { padding: 4, tone: "caution", radius: 2, children: _jsx(Text, { size: 1, children: "No performance data available yet." }) })), decayTask && (_jsx(DecayAlertCard, { reason: decayTask.reason, severity: decayTask.severity, metrics: decayTask.metrics })), snapshot28 && (_jsxs(Flex, { gap: 3, children: [_jsx(Card, { padding: 3, radius: 2, shadow: 1, style: { flex: 1 }, children: _jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 0, muted: true, children: "Clicks" }), _jsx(Text, { size: 3, weight: "semibold", children: snapshot28.clicks.toLocaleString() })] }) }), _jsx(Card, { padding: 3, radius: 2, shadow: 1, style: { flex: 1 }, children: _jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 0, muted: true, children: "CTR" }), _jsxs(Text, { size: 3, weight: "semibold", children: [(snapshot28.ctr * 100).toFixed(1), "%"] })] }) })] })), topQueries.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "semibold", style: { marginBottom: 8 }, children: "Top Queries" }), _jsx(Stack, { space: 2, children: topQueries.map((q, i) => {
|
|
161
|
+
const isTarget = isQueryInTargets(q.query, targetKeywords);
|
|
162
|
+
return (_jsx(Card, { padding: 2, radius: 2, tone: isTarget ? "positive" : "default", shadow: isTarget ? 1 : 0, children: _jsxs(Flex, { justify: "space-between", align: "center", children: [_jsxs(Flex, { align: "center", gap: 2, style: { flex: 1, minWidth: 0 }, children: [isTarget && (_jsx(Text, { size: 0, children: _jsx(CheckmarkCircleIcon, {}) })), _jsx(Text, { size: 1, style: {
|
|
163
|
+
overflow: "hidden",
|
|
164
|
+
textOverflow: "ellipsis",
|
|
165
|
+
whiteSpace: "nowrap",
|
|
166
|
+
}, children: q.query })] }), _jsxs(Text, { size: 0, muted: true, style: { flexShrink: 0, marginLeft: 8 }, children: [q.clicks, "cl \u00B7 pos ", q.position.toFixed(1)] })] }) }, i));
|
|
167
|
+
}) })] })), _jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "semibold", style: { marginBottom: 8 }, children: "Target Keywords" }), _jsxs(Stack, { space: 2, children: [targetKeywords.map((kw) => {
|
|
168
|
+
const isRanking = isKeywordInQueries(kw, topQueries);
|
|
169
|
+
return (_jsx(Card, { padding: 2, radius: 2, tone: isRanking ? "positive" : "caution", shadow: 1, children: _jsxs(Flex, { justify: "space-between", align: "center", children: [_jsxs(Flex, { align: "center", gap: 2, style: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { size: 0, children: isRanking ? _jsx(CheckmarkCircleIcon, {}) : _jsx(CircleIcon, {}) }), _jsx(Text, { size: 1, children: kw })] }), _jsxs(Flex, { align: "center", gap: 2, style: { flexShrink: 0 }, children: [isRanking && (_jsx(Badge, { tone: "positive", fontSize: 0, children: "Ranking" })), _jsx(Button, { icon: TrashIcon, mode: "ghost", tone: "critical", fontSize: 0, padding: 1, onClick: () => removeKeyword(kw) })] })] }) }, kw));
|
|
170
|
+
}), targetKeywords.length === 0 && (_jsx(Text, { size: 1, muted: true, children: "No target keywords set." })), _jsxs(Flex, { gap: 2, children: [_jsx(Box, { style: { flex: 1 }, children: _jsx(TextInput, { fontSize: 1, placeholder: "Add target keyword...", value: newKeyword, onChange: (e) => setNewKeyword(e.target.value), onKeyDown: (e) => {
|
|
171
|
+
if (e.key === "Enter")
|
|
172
|
+
addKeyword();
|
|
173
|
+
} }) }), _jsx(Button, { icon: AddIcon, mode: "ghost", fontSize: 1, padding: 2, onClick: addKeyword, disabled: !newKeyword.trim() })] })] })] })] }) }));
|
|
174
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { SearchPerformancePane } from "./components/SearchPerformancePane";
|
|
2
|
-
export { RefreshQueueTool } from "./components/RefreshQueueTool";
|
|
3
1
|
export { gscPlugin, createGscStructureResolver, createPageBridgeStructure, PAGEBRIDGE_TYPES, type GscPluginConfig, } from "./plugin";
|
|
4
2
|
export { gscSite, gscSnapshot, gscRefreshTask, createGscSnapshot, createGscRefreshTask, type GscSnapshotOptions, type GscRefreshTaskOptions, } from "./schemas";
|
|
5
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,SAAS,EACT,0BAA0B,EAC1B,yBAAyB,EACzB,gBAAgB,EAChB,KAAK,eAAe,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,OAAO,EACP,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Note: React components are not exported here to avoid loading them during schema extraction
|
|
2
|
+
// They are lazy-loaded within the plugin when needed
|
|
3
3
|
export { gscPlugin, createGscStructureResolver, createPageBridgeStructure, PAGEBRIDGE_TYPES, } from "./plugin";
|
|
4
4
|
export { gscSite, gscSnapshot, gscRefreshTask, createGscSnapshot, createGscRefreshTask, } from "./schemas";
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,uDAInB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,yBAAyB,GAAI,GAAG,gBAAgB,+CAqBxD,CAAC;AAEN;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GACrC,eAAc,MAAM,EAAO,KAC1B,2BAiBF,CAAC;AAEF,eAAO,MAAM,SAAS,iDAuBpB,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -3,8 +3,6 @@ import { ChartUpwardIcon, EarthGlobeIcon } from "@sanity/icons";
|
|
|
3
3
|
import { gscSite } from "./schemas/gscSite";
|
|
4
4
|
import { createGscSnapshot } from "./schemas/gscSnapshot";
|
|
5
5
|
import { createGscRefreshTask } from "./schemas/gscRefreshTask";
|
|
6
|
-
import { RefreshQueueTool } from "./components/RefreshQueueTool";
|
|
7
|
-
import { SearchPerformancePane } from "./components/SearchPerformancePane";
|
|
8
6
|
/** Document type names registered by the PageBridge plugin */
|
|
9
7
|
export const PAGEBRIDGE_TYPES = [
|
|
10
8
|
"gscSite",
|
|
@@ -58,6 +56,8 @@ export const createPageBridgeStructure = (S) => S.listItem()
|
|
|
58
56
|
export const createGscStructureResolver = (contentTypes = []) => {
|
|
59
57
|
return (S, { schemaType }) => {
|
|
60
58
|
if (contentTypes.includes(schemaType)) {
|
|
59
|
+
// Lazy import to avoid loading React component during schema extraction
|
|
60
|
+
const SearchPerformancePane = require("./components/SearchPerformancePane").SearchPerformancePane;
|
|
61
61
|
return S.document().views([
|
|
62
62
|
S.view.form(),
|
|
63
63
|
S.view
|
|
@@ -73,6 +73,8 @@ export const gscPlugin = definePlugin((config) => {
|
|
|
73
73
|
const contentTypes = config?.contentTypes ?? [];
|
|
74
74
|
const gscSnapshot = createGscSnapshot({ contentTypes });
|
|
75
75
|
const gscRefreshTask = createGscRefreshTask({ contentTypes });
|
|
76
|
+
// Lazy import to avoid loading React component during schema extraction
|
|
77
|
+
const RefreshQueueTool = require("./components/RefreshQueueTool").RefreshQueueTool;
|
|
76
78
|
return {
|
|
77
79
|
name: "pagebridge-sanity",
|
|
78
80
|
schema: {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GscKeywordTargetOptions {
|
|
2
|
+
contentTypes?: string[];
|
|
3
|
+
}
|
|
4
|
+
export declare const createGscKeywordTarget: (options?: GscKeywordTargetOptions) => {
|
|
5
|
+
type: "document";
|
|
6
|
+
name: "gscKeywordTarget";
|
|
7
|
+
} & Omit<import("sanity").DocumentDefinition, "preview"> & {
|
|
8
|
+
preview?: import("sanity").PreviewConfig<{
|
|
9
|
+
title: string;
|
|
10
|
+
keywords: string;
|
|
11
|
+
}, Record<"title" | "keywords", any>> | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare const gscKeywordTarget: {
|
|
14
|
+
type: "document";
|
|
15
|
+
name: "gscKeywordTarget";
|
|
16
|
+
} & Omit<import("sanity").DocumentDefinition, "preview"> & {
|
|
17
|
+
preview?: import("sanity").PreviewConfig<{
|
|
18
|
+
title: string;
|
|
19
|
+
keywords: string;
|
|
20
|
+
}, Record<"title" | "keywords", any>> | undefined;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=gscKeywordTarget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gscKeywordTarget.d.ts","sourceRoot":"","sources":["../../src/schemas/gscKeywordTarget.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,sBAAsB,GACjC,UAAS,uBAA4B;;;;;;;;CA0CtC,CAAC;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;CAA2B,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineType, defineField } from "sanity";
|
|
2
|
+
export const createGscKeywordTarget = (options = {}) => {
|
|
3
|
+
const contentTypes = options.contentTypes ?? [];
|
|
4
|
+
return defineType({
|
|
5
|
+
name: "gscKeywordTarget",
|
|
6
|
+
title: "GSC Keyword Target",
|
|
7
|
+
type: "document",
|
|
8
|
+
fields: [
|
|
9
|
+
...(contentTypes.length > 0
|
|
10
|
+
? [
|
|
11
|
+
defineField({
|
|
12
|
+
name: "linkedDocument",
|
|
13
|
+
title: "Linked Document",
|
|
14
|
+
type: "reference",
|
|
15
|
+
to: contentTypes.map((type) => ({ type })),
|
|
16
|
+
validation: (Rule) => Rule.required(),
|
|
17
|
+
}),
|
|
18
|
+
]
|
|
19
|
+
: []),
|
|
20
|
+
defineField({
|
|
21
|
+
name: "keywords",
|
|
22
|
+
title: "Target Keywords",
|
|
23
|
+
type: "array",
|
|
24
|
+
of: [{ type: "string" }],
|
|
25
|
+
description: "Keywords you want this page to rank for",
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
preview: {
|
|
29
|
+
select: {
|
|
30
|
+
title: "linkedDocument.title",
|
|
31
|
+
keywords: "keywords",
|
|
32
|
+
},
|
|
33
|
+
prepare({ title, keywords }) {
|
|
34
|
+
const count = keywords?.length ?? 0;
|
|
35
|
+
return {
|
|
36
|
+
title: title || "Untitled",
|
|
37
|
+
subtitle: `${count} target keyword${count !== 1 ? "s" : ""}`,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
export const gscKeywordTarget = createGscKeywordTarget();
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Note: React components are not exported here to avoid loading them during schema extraction
|
|
2
|
+
// They are lazy-loaded within the plugin when needed
|
|
3
3
|
export {
|
|
4
4
|
gscPlugin,
|
|
5
5
|
createGscStructureResolver,
|
package/src/plugin.ts
CHANGED
|
@@ -7,8 +7,7 @@ import { ChartUpwardIcon, EarthGlobeIcon } from "@sanity/icons";
|
|
|
7
7
|
import { gscSite } from "./schemas/gscSite";
|
|
8
8
|
import { createGscSnapshot } from "./schemas/gscSnapshot";
|
|
9
9
|
import { createGscRefreshTask } from "./schemas/gscRefreshTask";
|
|
10
|
-
import {
|
|
11
|
-
import { SearchPerformancePane } from "./components/SearchPerformancePane";
|
|
10
|
+
import type { ComponentType } from "react";
|
|
12
11
|
|
|
13
12
|
export interface GscPluginConfig {
|
|
14
13
|
/**
|
|
@@ -66,9 +65,7 @@ export const createPageBridgeStructure = (S: StructureBuilder) =>
|
|
|
66
65
|
S.listItem()
|
|
67
66
|
.title("Refresh Tasks")
|
|
68
67
|
.schemaType("gscRefreshTask")
|
|
69
|
-
.child(
|
|
70
|
-
S.documentTypeList("gscRefreshTask").title("Refresh Tasks"),
|
|
71
|
-
),
|
|
68
|
+
.child(S.documentTypeList("gscRefreshTask").title("Refresh Tasks")),
|
|
72
69
|
]),
|
|
73
70
|
);
|
|
74
71
|
|
|
@@ -81,6 +78,10 @@ export const createGscStructureResolver = (
|
|
|
81
78
|
): DefaultDocumentNodeResolver => {
|
|
82
79
|
return (S, { schemaType }) => {
|
|
83
80
|
if (contentTypes.includes(schemaType)) {
|
|
81
|
+
// Lazy import to avoid loading React component during schema extraction
|
|
82
|
+
const SearchPerformancePane: ComponentType<any> =
|
|
83
|
+
require("./components/SearchPerformancePane").SearchPerformancePane;
|
|
84
|
+
|
|
84
85
|
return S.document().views([
|
|
85
86
|
S.view.form(),
|
|
86
87
|
S.view
|
|
@@ -99,6 +100,10 @@ export const gscPlugin = definePlugin<GscPluginConfig | void>((config) => {
|
|
|
99
100
|
const gscSnapshot = createGscSnapshot({ contentTypes });
|
|
100
101
|
const gscRefreshTask = createGscRefreshTask({ contentTypes });
|
|
101
102
|
|
|
103
|
+
// Lazy import to avoid loading React component during schema extraction
|
|
104
|
+
const RefreshQueueTool: ComponentType<any> =
|
|
105
|
+
require("./components/RefreshQueueTool").RefreshQueueTool;
|
|
106
|
+
|
|
102
107
|
return {
|
|
103
108
|
name: "pagebridge-sanity",
|
|
104
109
|
schema: {
|