@gruodis/slug-for-strapi 1.0.0
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/LICENSE +21 -0
- package/README.md +120 -0
- package/package.json +57 -0
- package/server/bootstrap.js +103 -0
- package/server/config/index.js +31 -0
- package/server/controllers/general.js +29 -0
- package/server/controllers/index.js +7 -0
- package/server/index.js +15 -0
- package/server/routes/index.js +5 -0
- package/server/services/index.js +7 -0
- package/server/services/slug-generator.js +178 -0
- package/strapi-admin.js +12 -0
- package/strapi-server.js +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gruodis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Slug For Strapi
|
|
2
|
+
|
|
3
|
+
## 🚀 Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @gruodis/slug-for-strapi
|
|
7
|
+
# or
|
|
8
|
+
yarn add @gruodis/slug-for-strapi
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
`config/plugins.ts`:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
module.exports = {
|
|
17
|
+
'slug-for-strapi': {
|
|
18
|
+
enabled: true,
|
|
19
|
+
resolve: './node_modules/@gruodis/slug-for-strapi', // Path to the plugin
|
|
20
|
+
config: {
|
|
21
|
+
enabled: true, // Enable/disable plugin globally
|
|
22
|
+
sourceField: 'title', // Primary field to generate slug from
|
|
23
|
+
fallbackField: 'name', // Fallback field if primary is empty
|
|
24
|
+
addSuffixForUnique: true, // Add suffixes for uniqueness
|
|
25
|
+
updateExistingSlugs: true, // Update existing slugs when title changes
|
|
26
|
+
slugifyOptions: {
|
|
27
|
+
lower: true,
|
|
28
|
+
strict: true,
|
|
29
|
+
locale: 'lt'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 📖 Usage
|
|
37
|
+
|
|
38
|
+
1. **Add a `slug` field** to any content type in your Strapi schema
|
|
39
|
+
2. **Create or edit entries** - slugs will be automatically generated from `title` or `name` fields
|
|
40
|
+
3. **String support** - Works with regular string fields
|
|
41
|
+
|
|
42
|
+
### Example Content Type Schema
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"kind": "collectionType",
|
|
47
|
+
"collectionName": "articles",
|
|
48
|
+
"info": {
|
|
49
|
+
"singularName": "article",
|
|
50
|
+
"pluralName": "articles",
|
|
51
|
+
"displayName": "Articles"
|
|
52
|
+
},
|
|
53
|
+
"attributes": {
|
|
54
|
+
"title": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"slug": {
|
|
58
|
+
"type": "uid",
|
|
59
|
+
"targetField": "title"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 🔧 API Endpoints
|
|
66
|
+
|
|
67
|
+
### 🔍 Find By Slug
|
|
68
|
+
|
|
69
|
+
For every content type with a generated slug, the plugin automatically creates a GET endpoint:
|
|
70
|
+
|
|
71
|
+
- `GET /api/:pluralApiId/slug/:slug`
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
- `GET /api/articles/slug/my-awesome-article`
|
|
75
|
+
|
|
76
|
+
Response:
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"data": {
|
|
80
|
+
"id": 1,
|
|
81
|
+
"documentId": "...",
|
|
82
|
+
"title": "My Awesome Article",
|
|
83
|
+
"slug": "my-awesome-article",
|
|
84
|
+
...
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> **Note:** These endpoints are read-only and public by default.
|
|
90
|
+
|
|
91
|
+
## 📝 Field Types Supported
|
|
92
|
+
|
|
93
|
+
### Regular String
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"title": "My Article Title"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Will generate: `my-article-title`
|
|
101
|
+
|
|
102
|
+
## Multi-locale Support
|
|
103
|
+
|
|
104
|
+
The plugin supports different locales for transliteration. You can change the locale in your `config/plugins.ts`.
|
|
105
|
+
|
|
106
|
+
## 🔧 Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Install dependencies
|
|
112
|
+
npm install
|
|
113
|
+
|
|
114
|
+
# Build the plugin
|
|
115
|
+
npm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 📄 License
|
|
119
|
+
|
|
120
|
+
MIT License.
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gruodis/slug-for-strapi",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Does what it says. Slug for Strapi.",
|
|
5
|
+
"main": "strapi-server.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"develop": "tsc -w",
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 0"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"strapi",
|
|
16
|
+
"plugin",
|
|
17
|
+
"slug",
|
|
18
|
+
"auto-generation",
|
|
19
|
+
"universal"
|
|
20
|
+
],
|
|
21
|
+
"author": "Gruodis",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/Gruodis/slug-for-strapi.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/Gruodis/slug-for-strapi/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/Gruodis/slug-for-strapi#readme",
|
|
31
|
+
"strapi": {
|
|
32
|
+
"name": "slug-for-strapi",
|
|
33
|
+
"displayName": "Slug For Strapi",
|
|
34
|
+
"description": "Does what it says. Slug for Strapi.",
|
|
35
|
+
"kind": "plugin"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"slugify": "^1.6.6"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@strapi/strapi": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0",
|
|
45
|
+
"npm": ">=6.0.0"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"server",
|
|
50
|
+
"admin",
|
|
51
|
+
"strapi-admin.js",
|
|
52
|
+
"strapi-server.js",
|
|
53
|
+
"package.json",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = ({ strapi }) => {
|
|
4
|
+
// Register universal lifecycle hooks at Strapi startup
|
|
5
|
+
const registerSlugLifecycles = () => {
|
|
6
|
+
console.log('🚀 [Slug For Strapi] Initializing plugin...');
|
|
7
|
+
|
|
8
|
+
// Get plugin configuration from Strapi
|
|
9
|
+
const pluginConfig = strapi.config.get('plugin.slug-for-strapi') || {};
|
|
10
|
+
console.log('⚙️ [Slug For Strapi] Plugin configuration:', pluginConfig);
|
|
11
|
+
|
|
12
|
+
const slugService = strapi.plugin('slug-for-strapi').service('slug-generator');
|
|
13
|
+
|
|
14
|
+
// Get all content-types with slug field
|
|
15
|
+
const contentTypesWithSlug = slugService.getContentTypesWithSlug();
|
|
16
|
+
|
|
17
|
+
if (contentTypesWithSlug.length === 0) {
|
|
18
|
+
console.log('⚠️ [Slug For Strapi] No content-types found with slug field');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Register lifecycle hooks for each content-type
|
|
23
|
+
contentTypesWithSlug.forEach(({ uid, displayName }) => {
|
|
24
|
+
console.log(`📝 [Slug For Strapi] Registering lifecycle for ${displayName} (${uid})`);
|
|
25
|
+
|
|
26
|
+
// In Strapi v5 use direct registration via strapi.db.lifecycles
|
|
27
|
+
strapi.db.lifecycles.subscribe({
|
|
28
|
+
models: [uid],
|
|
29
|
+
|
|
30
|
+
// beforeCreate hook
|
|
31
|
+
async beforeCreate(event) {
|
|
32
|
+
const { data } = event.params;
|
|
33
|
+
console.log(`🆕 [Slug For Strapi] beforeCreate for ${uid}:`, data.title || data.name);
|
|
34
|
+
|
|
35
|
+
if (!data.slug) {
|
|
36
|
+
const slug = await slugService.generateSlugForEntry(data, uid);
|
|
37
|
+
if (slug) {
|
|
38
|
+
data.slug = slug;
|
|
39
|
+
console.log(`✅ [Slug For Strapi] Slug created: "${slug}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// beforeUpdate hook
|
|
45
|
+
async beforeUpdate(event) {
|
|
46
|
+
const { data, where } = event.params;
|
|
47
|
+
console.log(`🔄 [Slug For Strapi] beforeUpdate for ${uid}:`, data.title || data.name);
|
|
48
|
+
|
|
49
|
+
// Generate slug only if it's missing or needs update
|
|
50
|
+
if (data.title || data.name) {
|
|
51
|
+
// Get current entity
|
|
52
|
+
const currentEntity = await strapi.db.query(uid).findOne({ where });
|
|
53
|
+
|
|
54
|
+
// Try to generate slug (service decides whether to update or not)
|
|
55
|
+
const slug = await slugService.generateSlugForEntry(data, uid, currentEntity?.documentId);
|
|
56
|
+
if (slug) {
|
|
57
|
+
data.slug = slug;
|
|
58
|
+
console.log(`✅ [Slug For Strapi] Slug updated: "${slug}"`);
|
|
59
|
+
} else if (currentEntity?.slug) {
|
|
60
|
+
console.log(`⚠️ [Slug For Strapi] Slug already exists, skipping: "${currentEntity.slug}"`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(`✅ [Slug For Strapi] Plugin initialized for ${contentTypesWithSlug.length} content-types`);
|
|
68
|
+
|
|
69
|
+
// Register findBySlug routes for each content-type
|
|
70
|
+
contentTypesWithSlug.forEach(({ uid, displayName }) => {
|
|
71
|
+
const contentType = strapi.contentTypes[uid];
|
|
72
|
+
const pluralName = contentType.info.pluralName;
|
|
73
|
+
|
|
74
|
+
if (!pluralName) {
|
|
75
|
+
console.warn(`⚠️ [Slug For Strapi] Could not determine pluralName for ${uid}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if route already exists (plugin might re-initialize)
|
|
80
|
+
const routePath = `/api/${pluralName}/slug/:slug`;
|
|
81
|
+
const routes = strapi.server.router.stack.filter(layer => layer.path === routePath);
|
|
82
|
+
|
|
83
|
+
if (routes.length > 0) {
|
|
84
|
+
console.log(`ℹ️ [Slug For Strapi] Route already exists, skipping: GET ${routePath}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`🛣️ [Slug For Strapi] Registering route: GET ${routePath}`);
|
|
89
|
+
|
|
90
|
+
// Add route to Strapi Koa router
|
|
91
|
+
strapi.server.router.get(routePath, async (ctx, next) => {
|
|
92
|
+
// Add UID to params for controller
|
|
93
|
+
ctx.params.uid = uid;
|
|
94
|
+
|
|
95
|
+
// Call controller
|
|
96
|
+
await strapi.plugin('slug-for-strapi').controller('general').findBySlug(ctx, next);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Run registration after full Strapi initialization
|
|
102
|
+
registerSlugLifecycles();
|
|
103
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
default: {
|
|
5
|
+
// Global settings
|
|
6
|
+
enabled: true,
|
|
7
|
+
sourceField: 'title', // Primary field to generate slug from
|
|
8
|
+
fallbackField: 'name', // Fallback field if primary is empty
|
|
9
|
+
addSuffixForUnique: true, // Add suffixes for uniqueness (-1, -2, -3)
|
|
10
|
+
slugifyOptions: {
|
|
11
|
+
lower: true,
|
|
12
|
+
strict: true,
|
|
13
|
+
locale: 'lt'
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
// Content-types settings (filled automatically)
|
|
17
|
+
contentTypes: {
|
|
18
|
+
// 'api::article.article': { enabled: true },
|
|
19
|
+
// 'api::page.page': { enabled: true },
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
validator: (config) => {
|
|
23
|
+
// Configuration validation
|
|
24
|
+
if (typeof config.enabled !== 'boolean') {
|
|
25
|
+
throw new Error('enabled must be a boolean');
|
|
26
|
+
}
|
|
27
|
+
if (typeof config.sourceField !== 'string') {
|
|
28
|
+
throw new Error('sourceField must be a string');
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = ({ strapi }) => ({
|
|
4
|
+
async findBySlug(ctx) {
|
|
5
|
+
const { uid, slug } = ctx.params;
|
|
6
|
+
|
|
7
|
+
if (!uid || !slug) {
|
|
8
|
+
return ctx.badRequest('Missing uid or slug');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const entity = await strapi.db.query(uid).findOne({
|
|
13
|
+
where: { slug },
|
|
14
|
+
populate: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (!entity) {
|
|
18
|
+
return ctx.notFound();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sanitizedEntity = await strapi.contentAPI.sanitize.output(entity, strapi.getModel(uid));
|
|
22
|
+
|
|
23
|
+
ctx.body = { data: sanitizedEntity };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
strapi.log.error(error);
|
|
26
|
+
ctx.internalServerError('An error occurred while fetching the entity by slug');
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
package/server/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const services = require('./services');
|
|
5
|
+
const controllers = require('./controllers');
|
|
6
|
+
const routes = require('./routes');
|
|
7
|
+
const bootstrap = require('./bootstrap');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
config,
|
|
11
|
+
services,
|
|
12
|
+
controllers,
|
|
13
|
+
routes,
|
|
14
|
+
bootstrap,
|
|
15
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const slugify = require('slugify');
|
|
4
|
+
|
|
5
|
+
module.exports = ({ strapi }) => ({
|
|
6
|
+
/**
|
|
7
|
+
* Extracts text from field (string)
|
|
8
|
+
* @param {any} fieldValue - field value
|
|
9
|
+
* @returns {string} - text for slug generation
|
|
10
|
+
*/
|
|
11
|
+
extractTextFromField(fieldValue) {
|
|
12
|
+
if (!fieldValue) return '';
|
|
13
|
+
|
|
14
|
+
// If it's a regular string
|
|
15
|
+
if (typeof fieldValue === 'string') {
|
|
16
|
+
console.log('🔍 [Slug For Strapi] Regular string detected');
|
|
17
|
+
return fieldValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('⚠️ [Slug For Strapi] Unsupported field type (not a string):', typeof fieldValue, fieldValue);
|
|
21
|
+
return '';
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generates unique slug
|
|
26
|
+
* @param {string} text - source text
|
|
27
|
+
* @param {string} contentType - content type
|
|
28
|
+
* @param {string} documentId - document ID (to exclude from check)
|
|
29
|
+
* @param {object} options - slugify options
|
|
30
|
+
* @returns {Promise<string>} - unique slug
|
|
31
|
+
*/
|
|
32
|
+
async generateUniqueSlug(text, contentType, documentId = null, options = {}) {
|
|
33
|
+
if (!text) {
|
|
34
|
+
console.log('⚠️ [Slug For Strapi] Empty text for slug generation');
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get settings from Strapi config
|
|
39
|
+
const config = strapi.config.get('plugin.slug-for-strapi');
|
|
40
|
+
|
|
41
|
+
// Generate base slug with settings from configuration
|
|
42
|
+
const baseSlug = slugify(text, {
|
|
43
|
+
...config.slugifyOptions,
|
|
44
|
+
...options
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log('🔄 [Slug For Strapi] Base slug:', baseSlug);
|
|
48
|
+
|
|
49
|
+
// Check uniqueness
|
|
50
|
+
let slug = baseSlug;
|
|
51
|
+
let counter = 1;
|
|
52
|
+
|
|
53
|
+
while (true) {
|
|
54
|
+
// Find existing entries with this slug
|
|
55
|
+
const existing = await strapi.db.query(contentType).findOne({
|
|
56
|
+
where: {
|
|
57
|
+
slug: slug,
|
|
58
|
+
...(documentId && { documentId: { $ne: documentId } })
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!existing) {
|
|
63
|
+
console.log('✅ [Slug For Strapi] Unique slug found:', slug);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
slug = `${baseSlug}-${counter}`;
|
|
68
|
+
counter++;
|
|
69
|
+
console.log('🔄 [Slug For Strapi] Trying slug:', slug);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return slug;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generates slug for entry
|
|
77
|
+
* @param {object} data - entry data
|
|
78
|
+
* @param {string} contentType - content type
|
|
79
|
+
* @param {string} documentId - document ID
|
|
80
|
+
* @returns {Promise<string|null>} - generated slug or null
|
|
81
|
+
*/
|
|
82
|
+
async generateSlugForEntry(data, contentType, documentId = null) {
|
|
83
|
+
console.log(`🔍 [Slug For Strapi] generateSlugForEntry called for ${contentType}`);
|
|
84
|
+
console.log(`📋 [Slug For Strapi] Data:`, JSON.stringify(data, null, 2));
|
|
85
|
+
|
|
86
|
+
// Get current settings from Strapi config
|
|
87
|
+
const config = strapi.config.get('plugin.slug-for-strapi');
|
|
88
|
+
console.log(`⚙️ [Slug For Strapi] Configuration:`, config);
|
|
89
|
+
|
|
90
|
+
// Check if plugin is enabled globally
|
|
91
|
+
if (!config.enabled) {
|
|
92
|
+
console.log(`❌ [Slug For Strapi] Plugin disabled globally`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if enabled for this content-type
|
|
97
|
+
const contentTypeConfig = config.contentTypes[contentType];
|
|
98
|
+
if (contentTypeConfig && contentTypeConfig.enabled === false) {
|
|
99
|
+
console.log(`⚠️ [Slug For Strapi] Generation disabled for ${contentType}`);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If slug already exists, check update settings
|
|
104
|
+
if (data.slug && !config.updateExistingSlugs) {
|
|
105
|
+
console.log(`⚠️ [Slug For Strapi] Slug already exists, skipping: "${data.slug}"`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (data.slug && config.updateExistingSlugs) {
|
|
110
|
+
console.log(`🔄 [Slug For Strapi] Slug exists, but updating according to settings: "${data.slug}"`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`🔍 [Slug For Strapi] Looking for text in field "${config.sourceField}":`, data[config.sourceField]);
|
|
114
|
+
|
|
115
|
+
// Get text from primary field
|
|
116
|
+
let sourceText = this.extractTextFromField(
|
|
117
|
+
data[config.sourceField]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
console.log(`📝 [Slug For Strapi] Extracted text from primary field:`, sourceText);
|
|
121
|
+
|
|
122
|
+
// If primary field is empty, try fallback
|
|
123
|
+
if (!sourceText && config.fallbackField) {
|
|
124
|
+
console.log(`🔍 [Slug For Strapi] Trying fallback field "${config.fallbackField}":`, data[config.fallbackField]);
|
|
125
|
+
sourceText = this.extractTextFromField(
|
|
126
|
+
data[config.fallbackField]
|
|
127
|
+
);
|
|
128
|
+
console.log(`📝 [Slug For Strapi] Extracted text from fallback field:`, sourceText);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!sourceText) {
|
|
132
|
+
console.log(`⚠️ [Slug For Strapi] No text found for slug generation in ${contentType}`);
|
|
133
|
+
console.log(`🔍 [Slug For Strapi] Checked fields: ${config.sourceField}, ${config.fallbackField}`);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`🚀 [Slug For Strapi] Generating slug for ${contentType} from text:`, sourceText);
|
|
138
|
+
|
|
139
|
+
// Generate unique slug
|
|
140
|
+
const slug = await this.generateUniqueSlug(
|
|
141
|
+
sourceText,
|
|
142
|
+
contentType,
|
|
143
|
+
documentId,
|
|
144
|
+
config.slugifyOptions
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
console.log(`✅ [Slug For Strapi] Final slug:`, slug);
|
|
148
|
+
return slug;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Processes all content-types and finds those with slug field
|
|
153
|
+
* @returns {Array} - list of content-types with slug field
|
|
154
|
+
*/
|
|
155
|
+
getContentTypesWithSlug() {
|
|
156
|
+
const contentTypes = strapi.contentTypes;
|
|
157
|
+
const typesWithSlug = [];
|
|
158
|
+
|
|
159
|
+
for (const [uid, contentType] of Object.entries(contentTypes)) {
|
|
160
|
+
// Skip system types
|
|
161
|
+
if (!uid.startsWith('api::')) continue;
|
|
162
|
+
|
|
163
|
+
// Check if slug field exists
|
|
164
|
+
if (contentType.attributes && contentType.attributes.slug) {
|
|
165
|
+
typesWithSlug.push({
|
|
166
|
+
uid,
|
|
167
|
+
displayName: contentType.info?.displayName || uid,
|
|
168
|
+
hasSlugField: true,
|
|
169
|
+
hasTitleField: !!contentType.attributes.title,
|
|
170
|
+
hasNameField: !!contentType.attributes.name,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('📋 [Slug For Strapi] Found content-types with slug field:', typesWithSlug);
|
|
176
|
+
return typesWithSlug;
|
|
177
|
+
}
|
|
178
|
+
});
|
package/strapi-admin.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
register(app) {
|
|
5
|
+
// Registration of link in main menu removed, as settings are done via config file
|
|
6
|
+
// app.addMenuLink({...});
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
bootstrap(app) {
|
|
10
|
+
console.log('🚀 [Slug For Strapi] Admin panel bootstrap');
|
|
11
|
+
},
|
|
12
|
+
};
|
package/strapi-server.js
ADDED