migration wip
This commit is contained in:
2
.env
2
.env
@@ -1,4 +1,4 @@
|
||||
WOOCOMMERCE_URL=https://klz-cables.com
|
||||
WOOCOMMERCE_CONSUMER_KEY=ck_38d97df86880e8fefbd54ab5cdf47a9c5a9e5b39
|
||||
WOOCOMMERCE_CONSUMER_SECRET=cs_d675ee2ac2ec7c22de84ae5451c07e42b1717759
|
||||
WORDPRESS_APP_PASSWORD=DlJH 49dp fC3a Itc3 Sl7Z Wz0k
|
||||
WORDPRESS_APP_PASSWORD=DlJH 49dp fC3a Itc3 Sl7Z Wz0k‘
|
||||
@@ -16,6 +16,26 @@
|
||||
"static/chunks/main-app.js",
|
||||
"static/css/app/layout.css",
|
||||
"static/chunks/app/layout.js"
|
||||
],
|
||||
"/[locale]/blog/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/[locale]/blog/page.js"
|
||||
],
|
||||
"/[locale]/products/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/[locale]/products/page.js"
|
||||
],
|
||||
"/[locale]/blog/[slug]/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/[locale]/blog/[slug]/page.js"
|
||||
],
|
||||
"/_not-found/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/_not-found/page.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
.next/cache/.tsbuildinfo
vendored
2
.next/cache/.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
BIN
.next/cache/webpack/client-development/10.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/10.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/11.pack.gz
vendored
Normal file
BIN
.next/cache/webpack/client-development/11.pack.gz
vendored
Normal file
Binary file not shown.
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
BIN
.next/cache/webpack/client-development/2.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
BIN
.next/cache/webpack/client-development/3.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/4.pack.gz
vendored
BIN
.next/cache/webpack/client-development/4.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
BIN
.next/cache/webpack/client-development/5.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/6.pack.gz
vendored
BIN
.next/cache/webpack/client-development/6.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/7.pack.gz
vendored
BIN
.next/cache/webpack/client-development/7.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/8.pack.gz
vendored
BIN
.next/cache/webpack/client-development/8.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/9.pack.gz
vendored
BIN
.next/cache/webpack/client-development/9.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
BIN
.next/cache/webpack/client-production/0.pack
vendored
BIN
.next/cache/webpack/client-production/0.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/1.pack
vendored
BIN
.next/cache/webpack/client-production/1.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/5.pack
vendored
BIN
.next/cache/webpack/client-production/5.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/7.pack
vendored
BIN
.next/cache/webpack/client-production/7.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/8.pack
vendored
BIN
.next/cache/webpack/client-production/8.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/index.pack
vendored
BIN
.next/cache/webpack/client-production/index.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-production/index.pack.old
vendored
BIN
.next/cache/webpack/client-production/index.pack.old
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
BIN
.next/cache/webpack/server-development/1.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/10.pack.gz
vendored
BIN
.next/cache/webpack/server-development/10.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/11.pack.gz
vendored
BIN
.next/cache/webpack/server-development/11.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/12.pack.gz
vendored
BIN
.next/cache/webpack/server-development/12.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/2.pack.gz
vendored
BIN
.next/cache/webpack/server-development/2.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/3.pack.gz
vendored
BIN
.next/cache/webpack/server-development/3.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/4.pack.gz
vendored
BIN
.next/cache/webpack/server-development/4.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/5.pack.gz
vendored
BIN
.next/cache/webpack/server-development/5.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/6.pack.gz
vendored
BIN
.next/cache/webpack/server-development/6.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/7.pack.gz
vendored
BIN
.next/cache/webpack/server-development/7.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/8.pack.gz
vendored
BIN
.next/cache/webpack/server-development/8.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/9.pack.gz
vendored
BIN
.next/cache/webpack/server-development/9.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
BIN
.next/cache/webpack/server-production/0.pack
vendored
BIN
.next/cache/webpack/server-production/0.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-production/1.pack
vendored
BIN
.next/cache/webpack/server-production/1.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-production/3.pack
vendored
BIN
.next/cache/webpack/server-production/3.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-production/4.pack
vendored
BIN
.next/cache/webpack/server-production/4.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-production/index.pack
vendored
BIN
.next/cache/webpack/server-production/index.pack
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-production/index.pack.old
vendored
BIN
.next/cache/webpack/server-production/index.pack.old
vendored
Binary file not shown.
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"/[locale]/page": "app/[locale]/page.js"
|
||||
"/_not-found/page": "app/_not-found/page.js",
|
||||
"/[locale]/page": "app/[locale]/page.js",
|
||||
"/[locale]/blog/page": "app/[locale]/blog/page.js",
|
||||
"/[locale]/products/page": "app/[locale]/products/page.js",
|
||||
"/[locale]/blog/[slug]/page": "app/[locale]/blog/[slug]/page.js"
|
||||
}
|
||||
537
.next/server/app/[locale]/blog/[slug]/page.js
Normal file
537
.next/server/app/[locale]/blog/[slug]/page.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
537
.next/server/app/[locale]/blog/page.js
Normal file
537
.next/server/app/[locale]/blog/page.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
567
.next/server/app/[locale]/products/page.js
Normal file
567
.next/server/app/[locale]/products/page.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
122
.next/server/app/_not-found/page.js
Normal file
122
.next/server/app/_not-found/page.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@
|
||||
"assets": [],
|
||||
"env": {
|
||||
"__NEXT_BUILD_ID": "development",
|
||||
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "0R3AeKRiCnSumpEO3wgP/k8sKq7OCUs1SBp+q3dmhMw="
|
||||
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "vpVS86dElDrbPzxNqRuItYHDN8vPlob6QXj8YKqnVE4="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "0R3AeKRiCnSumpEO3wgP/k8sKq7OCUs1SBp+q3dmhMw="
|
||||
"encryptionKey": "vpVS86dElDrbPzxNqRuItYHDN8vPlob6QXj8YKqnVE4="
|
||||
}
|
||||
@@ -125,7 +125,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("c80591bbb933a4a4")
|
||||
/******/ __webpack_require__.h = () => ("3335aa53b0ba7807")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
|
||||
598
.next/static/chunks/app/[locale]/blog/[slug]/page.js
Normal file
598
.next/static/chunks/app/[locale]/blog/[slug]/page.js
Normal file
File diff suppressed because one or more lines are too long
598
.next/static/chunks/app/[locale]/blog/page.js
Normal file
598
.next/static/chunks/app/[locale]/blog/page.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
446
.next/static/chunks/app/[locale]/products/page.js
Normal file
446
.next/static/chunks/app/[locale]/products/page.js
Normal file
File diff suppressed because one or more lines are too long
39
.next/static/chunks/app/_not-found/page.js
Normal file
39
.next/static/chunks/app/_not-found/page.js
Normal file
File diff suppressed because one or more lines are too long
@@ -192,7 +192,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "9767c8ef819f2dbe"; }
|
||||
/******/ __webpack_require__.h = function() { return "6b1ffabe65414c81"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/global */
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"c":["webpack"],"r":[],"m":[]}
|
||||
18
.next/static/webpack/webpack.05e2afb4fe600b40.hot-update.js
Normal file
18
.next/static/webpack/webpack.05e2afb4fe600b40.hot-update.js
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "9583239507d2ac6e"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
18
.next/static/webpack/webpack.35708be37ce0df2c.hot-update.js
Normal file
18
.next/static/webpack/webpack.35708be37ce0df2c.hot-update.js
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "6b1ffabe65414c81"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
18
.next/static/webpack/webpack.9583239507d2ac6e.hot-update.js
Normal file
18
.next/static/webpack/webpack.9583239507d2ac6e.hot-update.js
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "35708be37ce0df2c"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
18
.next/static/webpack/webpack.a4342a594ffa166c.hot-update.js
Normal file
18
.next/static/webpack/webpack.a4342a594ffa166c.hot-update.js
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
/*
|
||||
* ATTENTION: An "eval-source-map" devtool has been used.
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
self["webpackHotUpdate_N_E"]("webpack",{},
|
||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ !function() {
|
||||
/******/ __webpack_require__.h = function() { return "05e2afb4fe600b40"; }
|
||||
/******/ }();
|
||||
/******/
|
||||
/******/ }
|
||||
);
|
||||
21
.next/trace
21
.next/trace
File diff suppressed because one or more lines are too long
79
.next/types/app/[locale]/blog/[slug]/page.ts
Normal file
79
.next/types/app/[locale]/blog/[slug]/page.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// File: /Users/marcmintel/Projects/klz-2026/app/[locale]/blog/[slug]/page.tsx
|
||||
import * as entry from '../../../../../../app/[locale]/blog/[slug]/page.js'
|
||||
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||
|
||||
type TEntry = typeof import('../../../../../../app/[locale]/blog/[slug]/page.js')
|
||||
|
||||
// Check that the entry is a valid entry
|
||||
checkFields<Diff<{
|
||||
default: Function
|
||||
config?: {}
|
||||
generateStaticParams?: Function
|
||||
revalidate?: RevalidateRange<TEntry> | false
|
||||
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||
dynamicParams?: boolean
|
||||
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||
maxDuration?: number
|
||||
|
||||
metadata?: any
|
||||
generateMetadata?: Function
|
||||
viewport?: any
|
||||
generateViewport?: Function
|
||||
|
||||
}, TEntry, ''>>()
|
||||
|
||||
// Check the prop type of the entry function
|
||||
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||
|
||||
// Check the arguments and return type of the generateMetadata function
|
||||
if ('generateMetadata' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateViewport function
|
||||
if ('generateViewport' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateStaticParams function
|
||||
if ('generateStaticParams' in entry) {
|
||||
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||
}
|
||||
|
||||
type PageParams = any
|
||||
export interface PageProps {
|
||||
params?: any
|
||||
searchParams?: any
|
||||
}
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
||||
params?: any
|
||||
}
|
||||
|
||||
// =============
|
||||
// Utility types
|
||||
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||
|
||||
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||
|
||||
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||
|
||||
|
||||
|
||||
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||
|
||||
// https://github.com/sindresorhus/type-fest
|
||||
type Numeric = number | bigint
|
||||
type Zero = 0 | 0n
|
||||
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||
79
.next/types/app/[locale]/blog/page.ts
Normal file
79
.next/types/app/[locale]/blog/page.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// File: /Users/marcmintel/Projects/klz-2026/app/[locale]/blog/page.tsx
|
||||
import * as entry from '../../../../../app/[locale]/blog/page.js'
|
||||
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||
|
||||
type TEntry = typeof import('../../../../../app/[locale]/blog/page.js')
|
||||
|
||||
// Check that the entry is a valid entry
|
||||
checkFields<Diff<{
|
||||
default: Function
|
||||
config?: {}
|
||||
generateStaticParams?: Function
|
||||
revalidate?: RevalidateRange<TEntry> | false
|
||||
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||
dynamicParams?: boolean
|
||||
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||
maxDuration?: number
|
||||
|
||||
metadata?: any
|
||||
generateMetadata?: Function
|
||||
viewport?: any
|
||||
generateViewport?: Function
|
||||
|
||||
}, TEntry, ''>>()
|
||||
|
||||
// Check the prop type of the entry function
|
||||
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||
|
||||
// Check the arguments and return type of the generateMetadata function
|
||||
if ('generateMetadata' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateViewport function
|
||||
if ('generateViewport' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateStaticParams function
|
||||
if ('generateStaticParams' in entry) {
|
||||
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||
}
|
||||
|
||||
type PageParams = any
|
||||
export interface PageProps {
|
||||
params?: any
|
||||
searchParams?: any
|
||||
}
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
||||
params?: any
|
||||
}
|
||||
|
||||
// =============
|
||||
// Utility types
|
||||
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||
|
||||
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||
|
||||
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||
|
||||
|
||||
|
||||
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||
|
||||
// https://github.com/sindresorhus/type-fest
|
||||
type Numeric = number | bigint
|
||||
type Zero = 0 | 0n
|
||||
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||
79
.next/types/app/[locale]/products/page.ts
Normal file
79
.next/types/app/[locale]/products/page.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// File: /Users/marcmintel/Projects/klz-2026/app/[locale]/products/page.tsx
|
||||
import * as entry from '../../../../../app/[locale]/products/page.js'
|
||||
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
||||
|
||||
type TEntry = typeof import('../../../../../app/[locale]/products/page.js')
|
||||
|
||||
// Check that the entry is a valid entry
|
||||
checkFields<Diff<{
|
||||
default: Function
|
||||
config?: {}
|
||||
generateStaticParams?: Function
|
||||
revalidate?: RevalidateRange<TEntry> | false
|
||||
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
||||
dynamicParams?: boolean
|
||||
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
||||
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
||||
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
||||
maxDuration?: number
|
||||
|
||||
metadata?: any
|
||||
generateMetadata?: Function
|
||||
viewport?: any
|
||||
generateViewport?: Function
|
||||
|
||||
}, TEntry, ''>>()
|
||||
|
||||
// Check the prop type of the entry function
|
||||
checkFields<Diff<PageProps, FirstArg<TEntry['default']>, 'default'>>()
|
||||
|
||||
// Check the arguments and return type of the generateMetadata function
|
||||
if ('generateMetadata' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
checkFields<Diff<ResolvingMetadata, SecondArg<MaybeField<TEntry, 'generateMetadata'>>, 'generateMetadata'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateViewport function
|
||||
if ('generateViewport' in entry) {
|
||||
checkFields<Diff<PageProps, FirstArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
checkFields<Diff<ResolvingViewport, SecondArg<MaybeField<TEntry, 'generateViewport'>>, 'generateViewport'>>()
|
||||
}
|
||||
|
||||
// Check the arguments and return type of the generateStaticParams function
|
||||
if ('generateStaticParams' in entry) {
|
||||
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
||||
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
||||
}
|
||||
|
||||
type PageParams = any
|
||||
export interface PageProps {
|
||||
params?: any
|
||||
searchParams?: any
|
||||
}
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
|
||||
params?: any
|
||||
}
|
||||
|
||||
// =============
|
||||
// Utility types
|
||||
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
||||
|
||||
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
||||
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
||||
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
||||
|
||||
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
||||
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
||||
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
||||
|
||||
|
||||
|
||||
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
||||
|
||||
// https://github.com/sindresorhus/type-fest
|
||||
type Numeric = number | bigint
|
||||
type Zero = 0 | 0n
|
||||
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
||||
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
||||
@@ -58,9 +58,9 @@ function RelatedPosts({ currentSlug, locale }: { currentSlug: string; locale: 'e
|
||||
<h3 className="font-semibold text-gray-900 group-hover:text-blue-600 transition-colors mb-2 line-clamp-2">
|
||||
{post.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 line-clamp-2 mb-2">
|
||||
{post.excerptHtml}
|
||||
</p>
|
||||
<div className="text-sm text-gray-600 line-clamp-2 mb-2">
|
||||
<ContentRenderer content={post.excerptHtml} />
|
||||
</div>
|
||||
<span className="text-xs text-blue-600 font-medium">
|
||||
{t('blog.readMore', locale as 'en' | 'de')} →
|
||||
</span>
|
||||
@@ -214,7 +214,7 @@ export default async function BlogDetailPage({ params }: PageProps) {
|
||||
{/* Article Content */}
|
||||
<div className="mb-12">
|
||||
<ContentRenderer
|
||||
content={post.contentHtml}
|
||||
content={processedContent}
|
||||
className="prose prose-lg prose-blue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -74,10 +74,12 @@ export default async function Page({ params }: PageProps) {
|
||||
}
|
||||
|
||||
// Use contentHtml if available, otherwise use excerptHtml
|
||||
const contentToDisplay = page.contentHtml && page.contentHtml.trim() !== ''
|
||||
? page.contentHtml
|
||||
// Both should be processed through ContentRenderer which handles shortcodes
|
||||
const contentToDisplay = page.contentHtml && page.contentHtml.trim() !== ''
|
||||
? page.contentHtml
|
||||
: page.excerptHtml;
|
||||
|
||||
// Process the content to handle shortcodes and convert to HTML
|
||||
const processedContent = processHTML(contentToDisplay || '');
|
||||
|
||||
// Get featured image if available
|
||||
@@ -119,7 +121,7 @@ export default async function Page({ params }: PageProps) {
|
||||
</h1>
|
||||
{page.excerptHtml && (
|
||||
<ContentRenderer
|
||||
content={page.excerptHtml}
|
||||
content={processHTML(page.excerptHtml)}
|
||||
className="text-lg sm:text-xl text-gray-600 leading-relaxed"
|
||||
/>
|
||||
)}
|
||||
@@ -129,7 +131,7 @@ export default async function Page({ params }: PageProps) {
|
||||
{processedContent && (
|
||||
<ResponsiveWrapper className="bg-white rounded-lg shadow-sm p-6 sm:p-8" container={true} maxWidth="full">
|
||||
<ContentRenderer
|
||||
content={contentToDisplay || ''}
|
||||
content={processedContent}
|
||||
className="prose prose-lg max-w-none"
|
||||
/>
|
||||
</ResponsiveWrapper>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { getDictionary } from '@/lib/i18n';
|
||||
import { Card, CardBody, CardHeader, Button } from '@/components/ui';
|
||||
import { FormField, FormInput, FormTextarea, FormError, FormSuccess } from '@/components/forms';
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
@@ -140,116 +141,68 @@ export function ContactForm() {
|
||||
subtitle={t('contact.subtitle')}
|
||||
/>
|
||||
<CardBody>
|
||||
{status === 'success' && (
|
||||
<div className="p-4 bg-green-50 border border-green-200 rounded-lg text-green-800 mb-4">
|
||||
{t('contact.success')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-red-800 mb-4">
|
||||
{t('contact.error')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormSuccess message={status === 'success' ? t('contact.success') : undefined} />
|
||||
<FormError errors={status === 'error' ? t('contact.error') : undefined} />
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('contact.name')} *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all ${
|
||||
errors.name ? 'border-red-500 bg-red-50' : 'border-gray-300'
|
||||
}`}
|
||||
disabled={status === 'loading'}
|
||||
aria-invalid={!!errors.name}
|
||||
aria-describedby={errors.name ? 'name-error' : undefined}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p id="name-error" className="mt-1 text-sm text-red-600">{errors.name}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('contact.email')} *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all ${
|
||||
errors.email ? 'border-red-500 bg-red-50' : 'border-gray-300'
|
||||
}`}
|
||||
disabled={status === 'loading'}
|
||||
aria-invalid={!!errors.email}
|
||||
aria-describedby={errors.email ? 'email-error' : undefined}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p id="email-error" className="mt-1 text-sm text-red-600">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('contact.phone')}
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all"
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('contact.subject')}
|
||||
</label>
|
||||
<input
|
||||
<FormField
|
||||
name="name"
|
||||
label={t('contact.name')}
|
||||
required
|
||||
error={errors.name}
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all"
|
||||
value={formData.name}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, name: value }))}
|
||||
disabled={status === 'loading'}
|
||||
placeholder={t('contact.name')}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="email"
|
||||
label={t('contact.email')}
|
||||
required
|
||||
error={errors.email}
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, email: value }))}
|
||||
disabled={status === 'loading'}
|
||||
placeholder={t('contact.email')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t('contact.message')} *
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows={6}
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all ${
|
||||
errors.message ? 'border-red-500 bg-red-50' : 'border-gray-300'
|
||||
}`}
|
||||
disabled={status === 'loading'}
|
||||
aria-invalid={!!errors.message}
|
||||
aria-describedby={errors.message ? 'message-error' : undefined}
|
||||
/>
|
||||
{errors.message && (
|
||||
<p id="message-error" className="mt-1 text-sm text-red-600">{errors.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
name="phone"
|
||||
label={t('contact.phone')}
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, phone: value }))}
|
||||
disabled={status === 'loading'}
|
||||
placeholder={t('contact.phone')}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="subject"
|
||||
label={t('contact.subject')}
|
||||
type="text"
|
||||
value={formData.subject}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, subject: value }))}
|
||||
disabled={status === 'loading'}
|
||||
placeholder={t('contact.subject')}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
name="message"
|
||||
label={t('contact.message')}
|
||||
required
|
||||
error={errors.message}
|
||||
type="textarea"
|
||||
value={formData.message}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, message: value }))}
|
||||
disabled={status === 'loading'}
|
||||
placeholder={t('contact.message')}
|
||||
rows={6}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { processHTML } from '../../lib/html-compat';
|
||||
import { processHTML, processShortcodes } from '../../lib/html-compat';
|
||||
import { getMediaByUrl, getMediaById, getAssetMap } from '../../lib/data';
|
||||
|
||||
interface ContentRendererProps {
|
||||
@@ -35,19 +35,25 @@ export const ContentRenderer: React.FC<ContentRendererProps> = ({
|
||||
// Process the HTML content
|
||||
const processedContent = React.useMemo(() => {
|
||||
let html = content;
|
||||
|
||||
|
||||
// Check for raw shortcodes and force processing if detected
|
||||
const shortcodeRegex = /\[[^\]]*\]/;
|
||||
if (shortcodeRegex.test(html)) {
|
||||
html = processShortcodes(html);
|
||||
}
|
||||
|
||||
if (sanitize) {
|
||||
html = processHTML(html);
|
||||
}
|
||||
|
||||
|
||||
if (processAssets) {
|
||||
html = replaceWordPressAssets(html);
|
||||
}
|
||||
|
||||
|
||||
if (convertClasses) {
|
||||
html = convertWordPressClasses(html);
|
||||
}
|
||||
|
||||
|
||||
return html;
|
||||
}, [content, sanitize, processAssets, convertClasses]);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { getLocaleFromPath } from '@/lib/i18n';
|
||||
|
||||
interface MobileMenuProps {
|
||||
locale: string;
|
||||
@@ -15,8 +14,14 @@ interface MobileMenuProps {
|
||||
|
||||
export function MobileMenu({ locale, siteName, onClose }: MobileMenuProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const currentLocale = getLocaleFromPath(pathname);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
// Close menu when route changes
|
||||
|
||||
// Main navigation menu
|
||||
const mainMenu = [
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -139,31 +139,132 @@ function sanitizeHTML(html: string): string {
|
||||
|
||||
/**
|
||||
* Process WordPress shortcodes by converting them to HTML with proper styling
|
||||
* Also handles mixed scenarios where some content is already HTML with WordPress classes
|
||||
*/
|
||||
function processShortcodes(html: string): string {
|
||||
export function processShortcodes(html: string): string {
|
||||
let processed = html;
|
||||
|
||||
try {
|
||||
// Step 1: Convert any existing HTML with WordPress classes back to shortcode format
|
||||
// This ensures we have a consistent format to work with
|
||||
|
||||
// Handle vc_row and vc_row_inner
|
||||
processed = processed.replace(/<div[^>]*class=["'][^"']*(?:vc-row|vc_row|vc_row_inner)[^"']*["'][^>]*>/gi, (match) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
const isInner = match.includes('vc_row_inner') || match.includes('vc-row-inner');
|
||||
return `[${isInner ? 'vc_row_inner' : 'vc_row'} ${attrs}]`;
|
||||
});
|
||||
|
||||
// Handle vc_column and vc_column_inner
|
||||
processed = processed.replace(/<div[^>]*class=["'][^"']*(?:vc-column|vc_column|vc_column_inner)[^"']*["'][^>]*>/gi, (match) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
const isInner = match.includes('vc_column_inner') || match.includes('vc-column-inner');
|
||||
return `[${isInner ? 'vc_column_inner' : 'vc_column'} ${attrs}]`;
|
||||
});
|
||||
|
||||
// Handle vc_column_text
|
||||
processed = processed.replace(/<div[^>]*class=["'][^"']*(?:vc-column-text|vc_column_text)[^"']*["'][^>]*>/gi, (match) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
return `[vc_column_text ${attrs}]`;
|
||||
});
|
||||
|
||||
// Handle vc_single_image
|
||||
processed = processed.replace(/<img[^>]*class=["'][^"']*(?:vc-single-image|vc_single_image)[^"']*["'][^>]*>/gi, (match) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
const imageId = extractAttribute(attrs, 'data-wp-image-id') || extractAttribute(attrs, 'src');
|
||||
const width = extractAttribute(attrs, 'data-width') || '';
|
||||
return `[vc_single_image src="${imageId}" width="${width}"]`;
|
||||
});
|
||||
|
||||
// Handle vc_btn
|
||||
processed = processed.replace(/<a[^>]*class=["'][^"']*(?:vc-btn|vc_btn)[^"']*["'][^>]*>(.*?)<\/a>/gi, (match, content) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
const href = extractAttribute(attrs, 'href');
|
||||
const title = content;
|
||||
return `[vc_btn href="${href}" title="${title}"]`;
|
||||
});
|
||||
|
||||
// Handle vc_separator
|
||||
processed = processed.replace(/<hr[^>]*class=["'][^"']*(?:vc-separator|vc_separator)[^"']*["'][^>]*>/gi, (match) => {
|
||||
const attrs = extractAttributesFromHTML(match);
|
||||
return `[vc_separator ${attrs}]`;
|
||||
});
|
||||
|
||||
// Handle closing div tags by looking for matching opening shortcode tags
|
||||
// This is more complex, so we'll handle it carefully
|
||||
processed = processed.replace(/<\/div>/gi, (match, offset) => {
|
||||
const beforeContent = processed.substring(0, offset);
|
||||
const lastOpenTag = beforeContent.match(/\[(vc_row(?:_inner)?|vc_column(?:_inner)?|vc_column_text)\s*[^\]]*\]$/i);
|
||||
if (lastOpenTag) {
|
||||
return `[/${lastOpenTag[1]}]`;
|
||||
}
|
||||
// If no matching shortcode, keep the div closing tag
|
||||
return match;
|
||||
});
|
||||
|
||||
// Step 2: Process shortcode blocks into HTML
|
||||
processed = processVcRowShortcodes(processed);
|
||||
processed = processVcColumnShortcodes(processed);
|
||||
processed = processVcColumnTextShortcodes(processed);
|
||||
processed = processVcImageShortcodes(processed);
|
||||
processed = processVcButtonShortcodes(processed);
|
||||
processed = processVcSeparatorShortcodes(processed);
|
||||
processed = processVcVideoShortcodes(processed);
|
||||
processed = processBackgroundShortcodes(processed);
|
||||
|
||||
// Step 3: Check for unprocessed shortcodes and log them
|
||||
const unprocessedShortcodes = processed.match(/\[[^\]]*\]/g);
|
||||
if (unprocessedShortcodes && unprocessedShortcodes.length > 0) {
|
||||
console.warn('Unprocessed shortcodes found and will be removed:', unprocessedShortcodes);
|
||||
}
|
||||
|
||||
// Clean up any remaining shortcode artifacts
|
||||
// Only remove shortcodes that weren't processed
|
||||
processed = processed.replace(/\[[^\]]*\]/g, '');
|
||||
|
||||
// Step 4: Clean up any remaining empty div tags
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/g, '');
|
||||
|
||||
return processed;
|
||||
} catch (error) {
|
||||
console.error('Error processing shortcodes:', error);
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract attributes from HTML tag
|
||||
*/
|
||||
function extractAttributesFromHTML(html: string): string {
|
||||
// Extract all key="value" pairs from HTML tag
|
||||
const attrMatches = html.matchAll(/([a-zA-Z-]+)=["']([^"']*)["']/g);
|
||||
const attrs: string[] = [];
|
||||
|
||||
// Process shortcode blocks first (most complex)
|
||||
processed = processVcRowShortcodes(processed);
|
||||
processed = processVcColumnShortcodes(processed);
|
||||
processed = processVcColumnTextShortcodes(processed);
|
||||
processed = processVcImageShortcodes(processed);
|
||||
processed = processVcButtonShortcodes(processed);
|
||||
processed = processVcSeparatorShortcodes(processed);
|
||||
processed = processVcVideoShortcodes(processed);
|
||||
processed = processBackgroundShortcodes(processed);
|
||||
for (const match of attrMatches) {
|
||||
const key = match[1];
|
||||
const value = match[2];
|
||||
|
||||
// Map HTML data attributes back to shortcode attributes
|
||||
if (key.startsWith('data-')) {
|
||||
const shortcodeKey = key.replace('data-', '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
attrs.push(`${shortcodeKey}="${value}"`);
|
||||
} else if (key === 'class') {
|
||||
// Skip class attribute for shortcode conversion
|
||||
continue;
|
||||
} else {
|
||||
attrs.push(`${key}="${value}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[[^\]]*\]/g, '');
|
||||
|
||||
return processed;
|
||||
return attrs.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process [vc_row] shortcodes and convert to flex containers
|
||||
* Also handles underscored versions: vc_row, vc_row_inner
|
||||
*/
|
||||
function processVcRowShortcodes(html: string): string {
|
||||
return html.replace(/\[vc_row([^\]]*)\]([\s\S]*?)\[\/vc_row\]/g, (match, attrs, content) => {
|
||||
return html.replace(/\[vc_row(?:_inner)?([^\]]*)\]([\s\S]*?)\[\/vc_row(?:_inner)?\]/g, (match, attrs, content) => {
|
||||
const classes = ['vc-row', 'flex', 'flex-wrap', '-mx-4'];
|
||||
|
||||
// Parse attributes for background colors, images, etc.
|
||||
|
||||
58
scripts/debug-entities.js
Normal file
58
scripts/debug-entities.js
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Debug what entities are actually in the raw data
|
||||
|
||||
const rawExcerpt = '<p>[vc_row type=”in_container” full_screen_row_position=”middle” column_margin=”default” column_direction=”default” column_direction_tablet=”default” column_direction_phone=”default” scene_position=”center” text_color=”dark” text_align=”left” row_border_radius=”none” row_border_radius_applies=”bg” overflow=”visible” overlay_strength=”0.3″ gradient_direction=”left_to_right” shape_divider_position=”bottom” bg_image_animation=”none”][vc_column column_padding=”no-extra-padding” column_padding_tablet=”inherit” column_padding_phone=”inherit” column_padding_position=”all” column_element_direction_desktop=”default” column_element_spacing=”default” desktop_text_alignment=”default” tablet_text_alignment=”default” phone_text_alignment=”default” background_color_opacity=”1″ background_hover_color_opacity=”1″ column_backdrop_filter=”none” column_shadow=”none”…</p>';
|
||||
|
||||
console.log('=== Raw Data Analysis ===');
|
||||
console.log('Original excerpt:');
|
||||
console.log(rawExcerpt);
|
||||
console.log('\n=== Entity Analysis ===');
|
||||
|
||||
// Check for numeric entities
|
||||
const numericEntities = rawExcerpt.match(/&#\d+;/g);
|
||||
console.log('Numeric entities found:', numericEntities);
|
||||
|
||||
// Check for Unicode characters
|
||||
const unicodeChars = rawExcerpt.match(/[”“‘’–—″′]/g);
|
||||
console.log('Unicode characters found:', unicodeChars);
|
||||
|
||||
// Test what each numeric entity represents
|
||||
if (numericEntities) {
|
||||
console.log('\n=== Numeric Entity Decoding ===');
|
||||
const uniqueEntities = [...new Set(numericEntities)];
|
||||
uniqueEntities.forEach(entity => {
|
||||
const code = parseInt(entity.replace(/[&#;]/g, ''));
|
||||
const char = String.fromCharCode(code);
|
||||
console.log(`${entity} (code ${code}) → "${char}"`);
|
||||
});
|
||||
}
|
||||
|
||||
// Test manual decoding
|
||||
console.log('\n=== Manual Decoding Test ===');
|
||||
let decoded = rawExcerpt
|
||||
.replace(/”/g, '"')
|
||||
.replace(/“/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/″/g, '"')
|
||||
.replace(/′/g, "'")
|
||||
.replace(/…/g, '…');
|
||||
|
||||
console.log('After manual decoding:');
|
||||
console.log(decoded);
|
||||
|
||||
// Test the current function approach
|
||||
console.log('\n=== Current Function Test ===');
|
||||
let processed = rawExcerpt
|
||||
.replace(/”/g, '"') // This won't work because raw has ”
|
||||
.replace(/“/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'");
|
||||
|
||||
console.log('After current function (which won\'t work):');
|
||||
console.log(processed);
|
||||
563
scripts/process-data-fixed.js
Normal file
563
scripts/process-data-fixed.js
Normal file
@@ -0,0 +1,563 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* WordPress → Next.js Data Processing Pipeline
|
||||
* Transforms raw WordPress data into Next.js compatible format
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = path.join(__dirname, '..', 'data');
|
||||
const RAW_DIR = path.join(DATA_DIR, 'raw');
|
||||
const PROCESSED_DIR = path.join(DATA_DIR, 'processed');
|
||||
|
||||
// Create processed directory
|
||||
if (!fs.existsSync(PROCESSED_DIR)) {
|
||||
fs.mkdirSync(PROCESSED_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Find latest export
|
||||
function getLatestExportDir() {
|
||||
const dirs = fs.readdirSync(RAW_DIR).filter(f => {
|
||||
const stat = fs.statSync(path.join(RAW_DIR, f));
|
||||
return stat.isDirectory();
|
||||
});
|
||||
dirs.sort().reverse();
|
||||
return path.join(RAW_DIR, dirs[0]);
|
||||
}
|
||||
|
||||
// HTML sanitization - preserve content but clean dangerous elements
|
||||
function sanitizeHTML(html) {
|
||||
if (!html) return '';
|
||||
|
||||
let sanitized = html;
|
||||
|
||||
// Remove script tags and inline handlers (security)
|
||||
sanitized = sanitized.replace(/<script.*?>.*?<\/script>/gis, '');
|
||||
sanitized = sanitized.replace(/\son\w+=".*?"/gi, '');
|
||||
|
||||
// Remove WPBakery shortcode wrappers but keep their content
|
||||
// Replace vc_row/vc_column with divs to preserve structure
|
||||
sanitized = sanitized.replace(/\[vc_row.*?\]/gi, '<div class="vc-row">');
|
||||
sanitized = sanitized.replace(/\[\/vc_row\]/gi, '</div>');
|
||||
sanitized = sanitized.replace(/\[vc_column.*?\]/gi, '<div class="vc-column">');
|
||||
sanitized = sanitized.replace(/\[\/vc_column\]/gi, '</div>');
|
||||
|
||||
// Remove other shortcodes but keep text content
|
||||
sanitized = sanitized.replace(/\[vc_column_text.*?\]/gi, '<div class="vc-text">');
|
||||
sanitized = sanitized.replace(/\[\/vc_column_text\]/gi, '</div>');
|
||||
|
||||
// Handle Nectar shortcodes - remove them but keep any text content
|
||||
// [nectar_cta] blocks often contain text we want to preserve
|
||||
sanitized = sanitized.replace(/\[nectar_cta.*?\]([\s\S]*?)\[\/nectar_cta\]/gi, '$1');
|
||||
sanitized = sanitized.replace(/\[nectar.*?\]/gi, '');
|
||||
|
||||
// Remove all remaining shortcodes
|
||||
sanitized = sanitized.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Remove empty paragraphs and divs
|
||||
sanitized = sanitized.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
sanitized = sanitized.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace but preserve HTML structure
|
||||
sanitized = sanitized.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// Process excerpts specifically to handle shortcodes comprehensively
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Handle both numeric entities (”) and named entities (")
|
||||
processed = processed
|
||||
// Numeric HTML entities commonly found in WordPress raw data
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Unicode characters (from rendered content)
|
||||
.replace(/”/g, '"') // Right double quote
|
||||
.replace(/“/g, '"') // Left double quote
|
||||
.replace(/„/g, ',') // Low double quote
|
||||
.replace(/‟/g, '"') // High double quote
|
||||
.replace(/‘/g, "'") // Left single quote
|
||||
.replace(/’/g, "'") // Right single quote
|
||||
.replace(/–/g, '-') // En dash
|
||||
.replace(/—/g, '—') // Em dash
|
||||
.replace(/…/g, '…') // Ellipsis
|
||||
.replace(/″/g, '"') // Inches/Prime
|
||||
.replace(/′/g, "'") // Feet/Prime
|
||||
.replace(/•/g, '•') // Bullet
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>')
|
||||
|
||||
// nectar_cta - convert to button
|
||||
.replace(/\[nectar_cta([^\]]*)link_text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-cta">$2</a>')
|
||||
|
||||
// nectar_highlighted_text - convert to span
|
||||
.replace(/\[nectar_highlighted_text([^\]]*)\](.*?)\[\/nectar_highlighted_text\]/gi,
|
||||
'<span class="nectar-highlighted">$2</span>')
|
||||
|
||||
// nectar_responsive_text - convert to span
|
||||
.replace(/\[nectar_responsive_text([^\]]*)\](.*?)\[\/nectar_responsive_text\]/gi,
|
||||
'<span class="nectar-responsive">$2</span>')
|
||||
|
||||
// nectar_icon_list - convert to ul
|
||||
.replace(/\[nectar_icon_list([^\]]*)\]/gi, '<ul class="nectar-icon-list">')
|
||||
.replace(/\[\/nectar_icon_list\]/gi, '</ul>')
|
||||
|
||||
// nectar_icon_list_item - convert to li
|
||||
.replace(/\[nectar_icon_list_item([^\]]*)header="([^"]*)"(.*?)text="([^"]*)"(.*?)\]/gi,
|
||||
'<li><strong>$2</strong>: $4</li>')
|
||||
|
||||
// nectar_btn - convert to button
|
||||
.replace(/\[nectar_btn([^\]]*)text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-btn">$2</a>')
|
||||
|
||||
// split_line_heading - convert to heading
|
||||
.replace(/\[split_line_heading([^\]]*)text_content="([^"]*)"(.*?)\]/gi,
|
||||
'<h2 class="split-line-heading">$2</h2>')
|
||||
|
||||
// vc_row_inner - convert to div
|
||||
.replace(/\[vc_row_inner([^\]]*)\]/gi, '<div class="vc-row-inner">')
|
||||
.replace(/\[\/vc_row_inner\]/gi, '</div>')
|
||||
|
||||
// vc_column_inner - convert to div
|
||||
.replace(/\[vc_column_inner([^\]]*)\]/gi, '<div class="vc-column-inner">')
|
||||
.replace(/\[\/vc_column_inner\]/gi, '</div>')
|
||||
|
||||
// divider - convert to hr
|
||||
.replace(/\[divider([^\]]*)\]/gi, '<hr class="divider" />')
|
||||
|
||||
// vc_gallery - convert to div (placeholder)
|
||||
.replace(/\[vc_gallery([^\]]*)\]/gi, '<div class="vc-gallery">[Gallery]</div>')
|
||||
|
||||
// vc_raw_js - remove or convert to div
|
||||
.replace(/\[vc_raw_js\](.*?)\[\/vc_raw_js\]/gi, '<div class="vc-raw-js">[JavaScript]</div>')
|
||||
|
||||
// nectar_gmap - convert to div
|
||||
.replace(/\[nectar_gmap([^\]]*)\]/gi, '<div class="nectar-gmap">[Google Map]</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Extract excerpt from content
|
||||
function generateExcerpt(content, maxLength = 200) {
|
||||
const text = content.replace(/<[^>]*>/g, '');
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
// Process pages
|
||||
function processPages(pagesEN, pagesDE, translationMapping) {
|
||||
const processed = [];
|
||||
|
||||
// Process English pages
|
||||
pagesEN.forEach(page => {
|
||||
const translationKey = page.slug;
|
||||
const deMatch = translationMapping.pages[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: page.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'en',
|
||||
slug: page.slug,
|
||||
path: `/${page.slug}`,
|
||||
title: page.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: page.titleHtml,
|
||||
contentHtml: sanitizeHTML(page.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(page.excerptHtml) || generateExcerpt(page.contentHtml),
|
||||
featuredImage: page.featuredImage,
|
||||
updatedAt: page.updatedAt,
|
||||
translation: deMatch ? { locale: 'de', id: deMatch.de } : null
|
||||
});
|
||||
});
|
||||
|
||||
// Process German pages
|
||||
pagesDE.forEach(page => {
|
||||
const translationKey = page.slug;
|
||||
const enMatch = translationMapping.pages[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: page.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'de',
|
||||
slug: page.slug,
|
||||
path: `/de/${page.slug}`,
|
||||
title: page.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: page.titleHtml,
|
||||
contentHtml: sanitizeHTML(page.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(page.excerptHtml) || generateExcerpt(page.contentHtml),
|
||||
featuredImage: page.featuredImage,
|
||||
updatedAt: page.updatedAt,
|
||||
translation: enMatch ? { locale: 'en', id: enMatch.en } : null
|
||||
});
|
||||
});
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Process posts
|
||||
function processPosts(postsEN, postsDE, translationMapping) {
|
||||
const processed = [];
|
||||
|
||||
postsEN.forEach(post => {
|
||||
const translationKey = post.slug;
|
||||
const deMatch = translationMapping.posts[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: post.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'en',
|
||||
slug: post.slug,
|
||||
path: `/blog/${post.slug}`,
|
||||
title: post.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: post.titleHtml,
|
||||
contentHtml: sanitizeHTML(post.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(post.excerptHtml) || generateExcerpt(post.contentHtml),
|
||||
featuredImage: post.featuredImage,
|
||||
datePublished: post.datePublished,
|
||||
updatedAt: post.updatedAt,
|
||||
translation: deMatch ? { locale: 'de', id: deMatch.de } : null
|
||||
});
|
||||
});
|
||||
|
||||
postsDE.forEach(post => {
|
||||
const translationKey = post.slug;
|
||||
const enMatch = translationMapping.posts[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: post.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'de',
|
||||
slug: post.slug,
|
||||
path: `/de/blog/${post.slug}`,
|
||||
title: post.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: post.titleHtml,
|
||||
contentHtml: sanitizeHTML(post.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(post.excerptHtml) || generateExcerpt(post.contentHtml),
|
||||
featuredImage: post.featuredImage,
|
||||
datePublished: post.datePublished,
|
||||
updatedAt: post.updatedAt,
|
||||
translation: enMatch ? { locale: 'en', id: enMatch.en } : null
|
||||
});
|
||||
});
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Process products
|
||||
function processProducts(productsEN, productsDE, translationMapping) {
|
||||
const processed = [];
|
||||
|
||||
productsEN.forEach(product => {
|
||||
const translationKey = product.slug;
|
||||
const deMatch = translationMapping.products[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: product.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'en',
|
||||
slug: product.slug,
|
||||
path: `/product/${product.slug}`,
|
||||
name: product.name,
|
||||
shortDescriptionHtml: product.shortDescriptionHtml,
|
||||
descriptionHtml: sanitizeHTML(product.descriptionHtml),
|
||||
images: product.images,
|
||||
featuredImage: product.featuredImage,
|
||||
sku: product.sku,
|
||||
regularPrice: product.regularPrice,
|
||||
salePrice: product.salePrice,
|
||||
currency: product.currency,
|
||||
stockStatus: product.stockStatus,
|
||||
categories: product.categories,
|
||||
attributes: product.attributes,
|
||||
variations: product.variations,
|
||||
updatedAt: product.updatedAt,
|
||||
translation: deMatch ? { locale: 'de', id: deMatch.de } : null
|
||||
});
|
||||
});
|
||||
|
||||
productsDE.forEach(product => {
|
||||
const translationKey = product.slug;
|
||||
const enMatch = translationMapping.products[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: product.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'de',
|
||||
slug: product.slug,
|
||||
path: `/de/product/${product.slug}`,
|
||||
name: product.name,
|
||||
shortDescriptionHtml: product.shortDescriptionHtml,
|
||||
descriptionHtml: sanitizeHTML(product.descriptionHtml),
|
||||
images: product.images,
|
||||
featuredImage: product.featuredImage,
|
||||
sku: product.sku,
|
||||
regularPrice: product.regularPrice,
|
||||
salePrice: product.salePrice,
|
||||
currency: product.currency,
|
||||
stockStatus: product.stockStatus,
|
||||
categories: product.categories,
|
||||
attributes: product.attributes,
|
||||
variations: product.variations,
|
||||
updatedAt: product.updatedAt,
|
||||
translation: enMatch ? { locale: 'en', id: enMatch.en } : null
|
||||
});
|
||||
});
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Process product categories
|
||||
function processProductCategories(categoriesEN, categoriesDE, translationMapping) {
|
||||
const processed = [];
|
||||
|
||||
categoriesEN.forEach(category => {
|
||||
const translationKey = category.slug;
|
||||
const deMatch = translationMapping.productCategories[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: category.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'en',
|
||||
slug: category.slug,
|
||||
name: category.name,
|
||||
path: `/product-category/${category.slug}`,
|
||||
description: category.description,
|
||||
count: category.count,
|
||||
translation: deMatch ? { locale: 'de', id: deMatch.de } : null
|
||||
});
|
||||
});
|
||||
|
||||
categoriesDE.forEach(category => {
|
||||
const translationKey = category.slug;
|
||||
const enMatch = translationMapping.productCategories[translationKey];
|
||||
|
||||
processed.push({
|
||||
id: category.id,
|
||||
translationKey: translationKey,
|
||||
locale: 'de',
|
||||
slug: category.slug,
|
||||
name: category.name,
|
||||
path: `/de/product-category/${category.slug}`,
|
||||
description: category.description,
|
||||
count: category.count,
|
||||
translation: enMatch ? { locale: 'en', id: enMatch.en } : null
|
||||
});
|
||||
});
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Process media manifest
|
||||
function processMedia(media) {
|
||||
return media.map(item => ({
|
||||
id: item.id,
|
||||
filename: item.filename,
|
||||
url: item.url,
|
||||
localPath: `/media/${item.filename}`,
|
||||
alt: item.alt,
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
mimeType: item.mime_type
|
||||
}));
|
||||
}
|
||||
|
||||
// Generate asset map for URL replacement
|
||||
function generateAssetMap(media) {
|
||||
const map = {};
|
||||
media.forEach(item => {
|
||||
if (item.url) {
|
||||
map[item.url] = `/media/${item.filename}`;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
// Main processing function
|
||||
function main() {
|
||||
const exportDir = getLatestExportDir();
|
||||
console.log('🔄 Processing WordPress Data for Next.js');
|
||||
console.log('========================================\n');
|
||||
|
||||
// Load raw data
|
||||
const loadJSON = (file) => {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(path.join(exportDir, file), 'utf8'));
|
||||
} catch (e) {
|
||||
console.error(`❌ Failed to load ${file}:`, e.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const translationMapping = loadJSON('translation-mapping-improved.json');
|
||||
const pagesEN = loadJSON('pages.en.json');
|
||||
const pagesDE = loadJSON('pages.de.json');
|
||||
const postsEN = loadJSON('posts.en.json');
|
||||
const postsDE = loadJSON('posts.de.json');
|
||||
const productsEN = loadJSON('products.en.json');
|
||||
const productsDE = loadJSON('products.de.json');
|
||||
const categoriesEN = loadJSON('product-categories.en.json');
|
||||
const categoriesDE = loadJSON('product-categories.de.json');
|
||||
const media = loadJSON('media.json');
|
||||
const redirects = loadJSON('redirects.json');
|
||||
const siteInfo = loadJSON('site-info.json');
|
||||
|
||||
console.log('📊 Processing content types...\n');
|
||||
|
||||
// Process each content type
|
||||
const pages = processPages(pagesEN, pagesDE, translationMapping);
|
||||
const posts = processPosts(postsEN, postsDE, translationMapping);
|
||||
const products = processProducts(productsEN, productsDE, translationMapping);
|
||||
const categories = processProductCategories(categoriesEN, categoriesDE, translationMapping);
|
||||
const processedMedia = processMedia(media);
|
||||
const assetMap = generateAssetMap(media);
|
||||
|
||||
// Create processed data structure
|
||||
const processedData = {
|
||||
site: {
|
||||
title: siteInfo.siteTitle,
|
||||
description: siteInfo.siteDescription,
|
||||
baseUrl: siteInfo.baseUrl,
|
||||
defaultLocale: siteInfo.defaultLocale || 'en',
|
||||
locales: ['en', 'de']
|
||||
},
|
||||
content: {
|
||||
pages,
|
||||
posts,
|
||||
products,
|
||||
categories
|
||||
},
|
||||
assets: {
|
||||
media: processedMedia,
|
||||
map: assetMap
|
||||
},
|
||||
redirects,
|
||||
exportDate: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Save processed data
|
||||
const outputPath = path.join(PROCESSED_DIR, 'wordpress-data.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(processedData, null, 2));
|
||||
|
||||
// Save individual files for easier access
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'pages.json'), JSON.stringify(pages, null, 2));
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'posts.json'), JSON.stringify(posts, null, 2));
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'products.json'), JSON.stringify(products, null, 2));
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'categories.json'), JSON.stringify(categories, null, 2));
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'media.json'), JSON.stringify(processedMedia, null, 2));
|
||||
fs.writeFileSync(path.join(PROCESSED_DIR, 'asset-map.json'), JSON.stringify(assetMap, null, 2));
|
||||
|
||||
// Summary
|
||||
console.log('✅ Data Processing Complete\n');
|
||||
console.log('📦 Processed Content:');
|
||||
console.log(` Pages: ${pages.length} (with translations)`);
|
||||
console.log(` Posts: ${posts.length} (with translations)`);
|
||||
console.log(` Products: ${products.length} (with translations)`);
|
||||
console.log(` Categories: ${categories.length} (with translations)`);
|
||||
console.log(` Media: ${processedMedia.length} files`);
|
||||
console.log(` Redirects: ${redirects.length} rules\n`);
|
||||
|
||||
console.log('📁 Output Files:');
|
||||
console.log(` ${outputPath}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'pages.json')}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'posts.json')}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'products.json')}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'categories.json')}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'media.json')}`);
|
||||
console.log(` ${path.join(PROCESSED_DIR, 'asset-map.json')}\n`);
|
||||
|
||||
// Sample data
|
||||
if (pages.length > 0) {
|
||||
console.log('📄 Sample Page:');
|
||||
console.log(` Title: ${pages[0].title}`);
|
||||
console.log(` Path: ${pages[0].path}`);
|
||||
console.log(` Locale: ${pages[0].locale}`);
|
||||
console.log(` Translation: ${pages[0].translation ? 'Yes' : 'No'}\n`);
|
||||
}
|
||||
|
||||
if (posts.length > 0) {
|
||||
console.log('📝 Sample Post:');
|
||||
console.log(` Title: ${posts[0].title}`);
|
||||
console.log(` Path: ${posts[0].path}`);
|
||||
console.log(` Locale: ${posts[0].locale}`);
|
||||
console.log(` Date: ${posts[0].datePublished}\n`);
|
||||
}
|
||||
|
||||
console.log('💡 Next: Ready for Next.js project setup!');
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
174
scripts/process-data.js
Executable file → Normal file
174
scripts/process-data.js
Executable file → Normal file
@@ -47,6 +47,13 @@ function sanitizeHTML(html) {
|
||||
// Remove other shortcodes but keep text content
|
||||
sanitized = sanitized.replace(/\[vc_column_text.*?\]/gi, '<div class="vc-text">');
|
||||
sanitized = sanitized.replace(/\[\/vc_column_text\]/gi, '</div>');
|
||||
|
||||
// Handle Nectar shortcodes - remove them but keep any text content
|
||||
// [nectar_cta] blocks often contain text we want to preserve
|
||||
sanitized = sanitized.replace(/\[nectar_cta.*?\]([\s\S]*?)\[\/nectar_cta\]/gi, '$1');
|
||||
sanitized = sanitized.replace(/\[nectar.*?\]/gi, '');
|
||||
|
||||
// Remove all remaining shortcodes
|
||||
sanitized = sanitized.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Remove empty paragraphs and divs
|
||||
@@ -59,6 +66,165 @@ function sanitizeHTML(html) {
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// Process excerpts specifically to handle shortcodes comprehensively
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Handle both numeric entities (”) and named entities (")
|
||||
processed = processed
|
||||
// Decode numeric HTML entities first
|
||||
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||
|
||||
// Then handle any remaining Unicode characters
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes (handle both complete and truncated)
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
// Handle truncated vc_row (no closing bracket)
|
||||
.replace(/\[vc_row([^\]]*)$/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
// Handle both complete and incomplete (truncated) shortcodes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
// Also handle incomplete vc_column shortcodes (truncated at end of excerpt)
|
||||
.replace(/\[vc_column([^\]]*)$/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// Handle truncated vc_column_text
|
||||
.replace(/\[vc_column_text([^\]]*)$/gi, '<div class="vc-column-text">')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>')
|
||||
|
||||
// nectar_cta - convert to button
|
||||
.replace(/\[nectar_cta([^\]]*)link_text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-cta">$2</a>')
|
||||
|
||||
// nectar_highlighted_text - convert to span
|
||||
.replace(/\[nectar_highlighted_text([^\]]*)\](.*?)\[\/nectar_highlighted_text\]/gi,
|
||||
'<span class="nectar-highlighted">$2</span>')
|
||||
|
||||
// nectar_responsive_text - convert to span
|
||||
.replace(/\[nectar_responsive_text([^\]]*)\](.*?)\[\/nectar_responsive_text\]/gi,
|
||||
'<span class="nectar-responsive">$2</span>')
|
||||
|
||||
// nectar_icon_list - convert to ul
|
||||
.replace(/\[nectar_icon_list([^\]]*)\]/gi, '<ul class="nectar-icon-list">')
|
||||
.replace(/\[\/nectar_icon_list\]/gi, '</ul>')
|
||||
|
||||
// nectar_icon_list_item - convert to li
|
||||
.replace(/\[nectar_icon_list_item([^\]]*)header="([^"]*)"(.*?)text="([^"]*)"(.*?)\]/gi,
|
||||
'<li><strong>$2</strong>: $4</li>')
|
||||
|
||||
// nectar_btn - convert to button
|
||||
.replace(/\[nectar_btn([^\]]*)text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-btn">$2</a>')
|
||||
|
||||
// split_line_heading - convert to heading
|
||||
.replace(/\[split_line_heading([^\]]*)text_content="([^"]*)"(.*?)\]/gi,
|
||||
'<h2 class="split-line-heading">$2</h2>')
|
||||
|
||||
// vc_row_inner - convert to div
|
||||
.replace(/\[vc_row_inner([^\]]*)\]/gi, '<div class="vc-row-inner">')
|
||||
.replace(/\[\/vc_row_inner\]/gi, '</div>')
|
||||
|
||||
// vc_column_inner - convert to div
|
||||
.replace(/\[vc_column_inner([^\]]*)\]/gi, '<div class="vc-column-inner">')
|
||||
.replace(/\[\/vc_column_inner\]/gi, '</div>')
|
||||
|
||||
// divider - convert to hr
|
||||
.replace(/\[divider([^\]]*)\]/gi, '<hr class="divider" />')
|
||||
|
||||
// vc_gallery - convert to div (placeholder)
|
||||
.replace(/\[vc_gallery([^\]]*)\]/gi, '<div class="vc-gallery">[Gallery]</div>')
|
||||
|
||||
// vc_raw_js - remove or convert to div
|
||||
.replace(/\[vc_raw_js\](.*?)\[\/vc_raw_js\]/gi, '<div class="vc-raw-js">[JavaScript]</div>')
|
||||
|
||||
// nectar_gmap - convert to div
|
||||
.replace(/\[nectar_gmap([^\]]*)\]/gi, '<div class="nectar-gmap">[Google Map]</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Extract excerpt from content
|
||||
function generateExcerpt(content, maxLength = 200) {
|
||||
const text = content.replace(/<[^>]*>/g, '');
|
||||
@@ -84,7 +250,7 @@ function processPages(pagesEN, pagesDE, translationMapping) {
|
||||
title: page.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: page.titleHtml,
|
||||
contentHtml: sanitizeHTML(page.contentHtml),
|
||||
excerptHtml: page.excerptHtml || generateExcerpt(page.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(page.excerptHtml) || generateExcerpt(page.contentHtml),
|
||||
featuredImage: page.featuredImage,
|
||||
updatedAt: page.updatedAt,
|
||||
translation: deMatch ? { locale: 'de', id: deMatch.de } : null
|
||||
@@ -105,7 +271,7 @@ function processPages(pagesEN, pagesDE, translationMapping) {
|
||||
title: page.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: page.titleHtml,
|
||||
contentHtml: sanitizeHTML(page.contentHtml),
|
||||
excerptHtml: page.excerptHtml || generateExcerpt(page.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(page.excerptHtml) || generateExcerpt(page.contentHtml),
|
||||
featuredImage: page.featuredImage,
|
||||
updatedAt: page.updatedAt,
|
||||
translation: enMatch ? { locale: 'en', id: enMatch.en } : null
|
||||
@@ -132,7 +298,7 @@ function processPosts(postsEN, postsDE, translationMapping) {
|
||||
title: post.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: post.titleHtml,
|
||||
contentHtml: sanitizeHTML(post.contentHtml),
|
||||
excerptHtml: post.excerptHtml || generateExcerpt(post.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(post.excerptHtml) || generateExcerpt(post.contentHtml),
|
||||
featuredImage: post.featuredImage,
|
||||
datePublished: post.datePublished,
|
||||
updatedAt: post.updatedAt,
|
||||
@@ -153,7 +319,7 @@ function processPosts(postsEN, postsDE, translationMapping) {
|
||||
title: post.titleHtml.replace(/<[^>]*>/g, ''),
|
||||
titleHtml: post.titleHtml,
|
||||
contentHtml: sanitizeHTML(post.contentHtml),
|
||||
excerptHtml: post.excerptHtml || generateExcerpt(post.contentHtml),
|
||||
excerptHtml: processExcerptShortcodes(post.excerptHtml) || generateExcerpt(post.contentHtml),
|
||||
featuredImage: post.featuredImage,
|
||||
datePublished: post.datePublished,
|
||||
updatedAt: post.updatedAt,
|
||||
|
||||
132
scripts/test-entity-decoding.js
Normal file
132
scripts/test-entity-decoding.js
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test script to verify HTML entity decoding works correctly
|
||||
|
||||
const testExcerpt = '<p>[vc_row type=”in_container” full_screen_row_position=”middle” column_margin=”default” column_direction=”default” column_direction_tablet=”default” column_direction_phone=”default” scene_position=”center” text_color=”dark” text_align=”left” row_border_radius=”none” row_border_radius_applies=”bg” overflow=”visible” overlay_strength=”0.3″ gradient_direction=”left_to_right” shape_divider_position=”bottom” bg_image_animation=”none”][vc_column column_padding=”no-extra-padding” column_padding_tablet=”inherit” column_padding_phone=”inherit” column_padding_position=”all” column_element_direction_desktop=”default” column_element_spacing=”default” desktop_text_alignment=”default” tablet_text_alignment=”default” phone_text_alignment=”default” background_color_opacity=”1″ background_hover_color_opacity=”1″ column_backdrop_filter=”none” column_shadow=”none” column_border_radius=”none” column_link_target=”_self” column_position=”default” gradient_direction=”left_to_right” overlay_strength=”0.3″ width=”1/1″ tablet_width_inherit=”default” animation_type=”default” bg_image_animation=”none” border_type=”simple” column_border_width=”none” column_border_style=”solid”][vc_column_text css=”” text_direction=”default”]\n<h1 class=\"p1\">Liefer- und Zahlungsbedingungen</h1>\n<p class=\"p1\">Stand November 2024</p>\n[/vc_column_text][/vc_column][/vc_row]</p>';
|
||||
|
||||
// Process excerpts specifically to handle shortcodes comprehensively
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Use a comprehensive approach that handles both numeric and named entities
|
||||
processed = processed
|
||||
// Numeric HTML entities commonly found in WordPress raw data
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Unicode characters (from rendered content)
|
||||
.replace(/"/g, '"') // Right double quote
|
||||
.replace(/"/g, '"') // Left double quote
|
||||
.replace(/„/g, ',') // Low double quote
|
||||
.replace(/‟/g, '"') // High double quote
|
||||
.replace(/'/g, "'") // Left single quote
|
||||
.replace(/'/g, "'") // Right single quote
|
||||
.replace(/–/g, '-') // En dash
|
||||
.replace(/—/g, '—') // Em dash
|
||||
.replace(/…/g, '…') // Ellipsis
|
||||
.replace(/″/g, '"') // Inches/Prime
|
||||
.replace(/′/g, "'") // Feet/Prime
|
||||
.replace(/•/g, '•') // Bullet
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
console.log('=== HTML Entity Decoding Test ===\n');
|
||||
console.log('Original excerpt:');
|
||||
console.log(testExcerpt);
|
||||
console.log('\n--- After processing ---\n');
|
||||
const result = processExcerptShortcodes(testExcerpt);
|
||||
console.log(result);
|
||||
|
||||
// Test specific entity decoding
|
||||
console.log('\n=== Specific Entity Tests ===');
|
||||
const entityTests = [
|
||||
{ input: '”', expected: '"', name: 'Right double quote' },
|
||||
{ input: '“', expected: '"', name: 'Left double quote' },
|
||||
{ input: '–', expected: '-', name: 'En dash' },
|
||||
{ input: '—', expected: '—', name: 'Em dash' },
|
||||
{ input: '‘', expected: "'", name: 'Left single quote' },
|
||||
{ input: '’', expected: "'", name: 'Right single quote' },
|
||||
{ input: 'type=”in_container”', expected: 'type="in_container"', name: 'Full attribute' }
|
||||
];
|
||||
|
||||
entityTests.forEach(test => {
|
||||
const processed = test.input.replace(/”/g, '"').replace(/“/g, '"').replace(/–/g, '-').replace(/—/g, '—').replace(/‘/g, "'").replace(/’/g, "'");
|
||||
const passed = processed === test.expected;
|
||||
console.log(`${test.name}: ${passed ? '✅' : '❌'} "${test.input}" → "${processed}" (expected: "${test.expected}")`);
|
||||
});
|
||||
125
scripts/test-final-function.js
Normal file
125
scripts/test-final-function.js
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test the final function with actual raw data
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Load the actual raw data
|
||||
const rawData = JSON.parse(fs.readFileSync('data/raw/2025-12-27T21-26-12-521Z/pages.en.json', 'utf8'));
|
||||
const testExcerpt = rawData[0].excerptHtml;
|
||||
|
||||
console.log('=== Testing Final Function ===');
|
||||
console.log('Raw excerpt (first 200 chars):');
|
||||
console.log(testExcerpt.substring(0, 200));
|
||||
console.log('');
|
||||
|
||||
// The function from process-data.js
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Handle both numeric entities (”) and named entities (")
|
||||
processed = processed
|
||||
// Decode numeric HTML entities first
|
||||
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||
|
||||
// Then handle any remaining Unicode characters
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
const result = processExcerptShortcodes(testExcerpt);
|
||||
|
||||
console.log('After processing:');
|
||||
console.log(result);
|
||||
console.log('');
|
||||
|
||||
// Check for entities
|
||||
const hasEntities = /[”“‘’–—]/.test(result);
|
||||
const hasNumericEntities = /&#\d+;/.test(result);
|
||||
const hasShortcodes = /\[vc_row|\[vc_column/.test(result);
|
||||
|
||||
console.log('=== Verification ===');
|
||||
console.log('Has Unicode entities:', hasEntities);
|
||||
console.log('Has numeric entities:', hasNumericEntities);
|
||||
console.log('Has shortcodes:', hasShortcodes);
|
||||
console.log('Has proper HTML:', result.includes('<div class="vc-row"') || result.includes('<div class="vc-column"'));
|
||||
console.log('');
|
||||
|
||||
if (!hasEntities && !hasNumericEntities && !hasShortcodes && result.includes('<div class="vc-row"')) {
|
||||
console.log('✅ SUCCESS: Function works correctly!');
|
||||
} else {
|
||||
console.log('❌ Issues found');
|
||||
}
|
||||
151
scripts/test-function.js
Normal file
151
scripts/test-function.js
Normal file
@@ -0,0 +1,151 @@
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Handle both numeric entities (”) and named entities (")
|
||||
processed = processed
|
||||
// Decode numeric HTML entities first
|
||||
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||
|
||||
// Then handle any remaining Unicode characters
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
// Handle both complete and incomplete (truncated) shortcodes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
// Also handle incomplete vc_column shortcodes (truncated at end of excerpt)
|
||||
.replace(/\[vc_column([^\]]*)$/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>')
|
||||
|
||||
// nectar_cta - convert to button
|
||||
.replace(/\[nectar_cta([^\]]*)link_text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-cta">$2</a>')
|
||||
|
||||
// nectar_highlighted_text - convert to span
|
||||
.replace(/\[nectar_highlighted_text([^\]]*)\](.*?)\[\/nectar_highlighted_text\]/gi,
|
||||
'<span class="nectar-highlighted">$2</span>')
|
||||
|
||||
// nectar_responsive_text - convert to span
|
||||
.replace(/\[nectar_responsive_text([^\]]*)\](.*?)\[\/nectar_responsive_text\]/gi,
|
||||
'<span class="nectar-responsive">$2</span>')
|
||||
|
||||
// nectar_icon_list - convert to ul
|
||||
.replace(/\[nectar_icon_list([^\]]*)\]/gi, '<ul class="nectar-icon-list">')
|
||||
.replace(/\[\/nectar_icon_list\]/gi, '</ul>')
|
||||
|
||||
// nectar_icon_list_item - convert to li
|
||||
.replace(/\[nectar_icon_list_item([^\]]*)header="([^"]*)"(.*?)text="([^"]*)"(.*?)\]/gi,
|
||||
'<li><strong>$2</strong>: $4</li>')
|
||||
|
||||
// nectar_btn - convert to button
|
||||
.replace(/\[nectar_btn([^\]]*)text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-btn">$2</a>')
|
||||
|
||||
// split_line_heading - convert to heading
|
||||
.replace(/\[split_line_heading([^\]]*)text_content="([^"]*)"(.*?)\]/gi,
|
||||
'<h2 class="split-line-heading">$2</h2>')
|
||||
|
||||
// vc_row_inner - convert to div
|
||||
.replace(/\[vc_row_inner([^\]]*)\]/gi, '<div class="vc-row-inner">')
|
||||
.replace(/\[\/vc_row_inner\]/gi, '</div>')
|
||||
|
||||
// vc_column_inner - convert to div
|
||||
.replace(/\[vc_column_inner([^\]]*)\]/gi, '<div class="vc-column-inner">')
|
||||
.replace(/\[\/vc_column_inner\]/gi, '</div>')
|
||||
|
||||
// divider - convert to hr
|
||||
.replace(/\[divider([^\]]*)\]/gi, '<hr class="divider" />')
|
||||
|
||||
// vc_gallery - convert to div (placeholder)
|
||||
.replace(/\[vc_gallery([^\]]*)\]/gi, '<div class="vc-gallery">[Gallery]</div>')
|
||||
|
||||
// vc_raw_js - remove or convert to div
|
||||
.replace(/\[vc_raw_js\](.*?)\[\/vc_raw_js\]/gi, '<div class="vc-raw-js">[JavaScript]</div>')
|
||||
|
||||
// nectar_gmap - convert to div
|
||||
.replace(/\[nectar_gmap([^\]]*)\]/gi, '<div class="nectar-gmap">[Google Map]</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Extract excerpt from content
|
||||
|
||||
|
||||
module.exports = processExcerptShortcodes;
|
||||
68
scripts/test-numeric-entities.js
Normal file
68
scripts/test-numeric-entities.js
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test numeric entity decoding
|
||||
|
||||
const testString = 'type=”in_container”';
|
||||
|
||||
console.log('Original:', testString);
|
||||
|
||||
// Method 1: Manual replacement
|
||||
let method1 = testString
|
||||
.replace(/”/g, '"')
|
||||
.replace(/“/g, '"')
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—');
|
||||
|
||||
console.log('Method 1 (Unicode chars):', method1);
|
||||
|
||||
// Method 2: Numeric entity decoding
|
||||
let method2 = testString
|
||||
.replace(/”/g, '"')
|
||||
.replace(/“/g, '"')
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/″/g, '"')
|
||||
.replace(/′/g, "'");
|
||||
|
||||
console.log('Method 2 (Numeric entities):', method2);
|
||||
|
||||
// Method 3: Using a function to decode all numeric entities
|
||||
function decodeHTMLEntities(str) {
|
||||
return str.replace(/&#(\d+);/g, (match, dec) => {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
|
||||
let method3 = decodeHTMLEntities(testString);
|
||||
console.log('Method 3 (All numeric):', method3);
|
||||
|
||||
// Method 4: Combined approach
|
||||
function comprehensiveEntityDecode(str) {
|
||||
return str
|
||||
// First decode numeric entities
|
||||
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||
// Then handle any remaining Unicode characters
|
||||
.replace(/”/g, '"')
|
||||
.replace(/“/g, '"')
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/″/g, '"')
|
||||
.replace(/′/g, "'");
|
||||
}
|
||||
|
||||
let method4 = comprehensiveEntityDecode(testString);
|
||||
console.log('Method 4 (Combined):', method4);
|
||||
|
||||
// Test with the actual excerpt
|
||||
const actualExcerpt = '<p>[vc_row type=”in_container” full_screen_row_position=”middle” column_margin=”default”]';
|
||||
console.log('\n=== Real Test ===');
|
||||
console.log('Original:', actualExcerpt);
|
||||
console.log('Decoded:', comprehensiveEntityDecode(actualExcerpt));
|
||||
88
scripts/verify-output.js
Normal file
88
scripts/verify-output.js
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Load the processed data
|
||||
const processedDir = path.join(__dirname, '..', 'data', 'processed');
|
||||
const pages = JSON.parse(fs.readFileSync(path.join(processedDir, 'pages.json'), 'utf8'));
|
||||
const posts = JSON.parse(fs.readFileSync(path.join(processedDir, 'posts.json'), 'utf8'));
|
||||
|
||||
console.log('=== Verification of HTML Entity Decoding ===\n');
|
||||
|
||||
// Check pages
|
||||
console.log('📄 PAGES:');
|
||||
pages.slice(0, 3).forEach(page => {
|
||||
console.log(`\nPage: ${page.title}`);
|
||||
console.log(`Path: ${page.path}`);
|
||||
console.log(`Excerpt preview: ${page.excerptHtml.substring(0, 150)}...`);
|
||||
|
||||
// Check for problematic entities
|
||||
const hasEntities = /[”“‘’–—]/.test(page.excerptHtml);
|
||||
const hasNumericEntities = /&#\d+;/.test(page.excerptHtml);
|
||||
|
||||
if (hasEntities || hasNumericEntities) {
|
||||
console.log('❌ Still contains HTML entities!');
|
||||
if (hasEntities) console.log(' - Found smart quotes/dashes');
|
||||
if (hasNumericEntities) console.log(' - Found numeric entities');
|
||||
} else {
|
||||
console.log('✅ Clean - no HTML entities found');
|
||||
}
|
||||
});
|
||||
|
||||
// Check posts
|
||||
console.log('\n📝 POSTS:');
|
||||
posts.slice(0, 3).forEach(post => {
|
||||
console.log(`\nPost: ${post.title}`);
|
||||
console.log(`Path: ${post.path}`);
|
||||
console.log(`Excerpt preview: ${post.excerptHtml.substring(0, 150)}...`);
|
||||
|
||||
// Check for problematic entities
|
||||
const hasEntities = /[”“‘’–—]/.test(post.excerptHtml);
|
||||
const hasNumericEntities = /&#\d+;/.test(post.excerptHtml);
|
||||
|
||||
if (hasEntities || hasNumericEntities) {
|
||||
console.log('❌ Still contains HTML entities!');
|
||||
if (hasEntities) console.log(' - Found smart quotes/dashes');
|
||||
if (hasNumericEntities) console.log(' - Found numeric entities');
|
||||
} else {
|
||||
console.log('✅ Clean - no HTML entities found');
|
||||
}
|
||||
});
|
||||
|
||||
// Check for shortcode patterns
|
||||
console.log('\n🔍 SHORTCODE CHECK:');
|
||||
const allPages = [...pages, ...posts];
|
||||
const shortcodesFound = allPages.filter(item => /\[vc_row|\[vc_column|\[nectar/.test(item.excerptHtml));
|
||||
console.log(`Pages/posts with shortcodes in excerpt: ${shortcodesFound.length}`);
|
||||
|
||||
if (shortcodesFound.length > 0) {
|
||||
console.log('\nSample of items with shortcodes:');
|
||||
shortcodesFound.slice(0, 2).forEach(item => {
|
||||
console.log(`- ${item.title}: ${item.excerptHtml.substring(0, 100)}...`);
|
||||
});
|
||||
} else {
|
||||
console.log('✅ No shortcodes found in excerpts');
|
||||
}
|
||||
|
||||
// Check for proper HTML structure
|
||||
console.log('\n📊 HTML STRUCTURE CHECK:');
|
||||
const withProperHTML = allPages.filter(item =>
|
||||
item.excerptHtml.includes('<div class="vc-row"') ||
|
||||
item.excerptHtml.includes('<div class="vc-column"') ||
|
||||
item.excerptHtml.includes('<div class="nectar')
|
||||
);
|
||||
console.log(`Items with converted shortcode HTML: ${withProperHTML.length}`);
|
||||
|
||||
console.log('\n=== Summary ===');
|
||||
console.log(`Total items checked: ${allPages.length}`);
|
||||
console.log(`Items with proper HTML structure: ${withProperHTML.length}`);
|
||||
console.log(`Items with remaining shortcodes: ${shortcodesFound.length}`);
|
||||
|
||||
// Sample the actual content to show it works
|
||||
console.log('\n=== SAMPLE PROCESSED EXCERPTS ===');
|
||||
const sample = pages.find(p => p.excerptHtml.includes('vc-row'));
|
||||
if (sample) {
|
||||
console.log(`\nTitle: ${sample.title}`);
|
||||
console.log(`Excerpt: ${sample.excerptHtml}`);
|
||||
}
|
||||
150
temp-func.js
Normal file
150
temp-func.js
Normal file
@@ -0,0 +1,150 @@
|
||||
function processExcerptShortcodes(excerptHtml) {
|
||||
if (!excerptHtml) return '';
|
||||
|
||||
let processed = excerptHtml;
|
||||
|
||||
// First, decode HTML entities to regular characters
|
||||
// Handle both numeric entities (”) and named entities (")
|
||||
processed = processed
|
||||
// Decode numeric HTML entities first
|
||||
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||
|
||||
// Then handle any remaining Unicode characters
|
||||
.replace(/”/g, '"') // ” - Right double quote
|
||||
.replace(/“/g, '"') // “ - Left double quote
|
||||
.replace(/„/g, ',') // „ - Low double quote
|
||||
.replace(/‟/g, '"') // ‟ - High double quote
|
||||
.replace(/‘/g, "'") // ‘ - Left single quote
|
||||
.replace(/’/g, "'") // ’ - Right single quote
|
||||
.replace(/–/g, '-') // – - En dash
|
||||
.replace(/—/g, '—') // — - Em dash
|
||||
.replace(/…/g, '…') // … - Ellipsis
|
||||
.replace(/″/g, '"') // ″ - Inches/Prime
|
||||
.replace(/′/g, "'") // ′ - Feet/Prime
|
||||
.replace(/‚/g, ',') // ‚ - Single low quote
|
||||
.replace(/‛/g, '`') // ‛ - Single high reversed quote
|
||||
.replace(/•/g, '•') // • - Bullet
|
||||
.replace(/€/g, '€') // € - Euro
|
||||
|
||||
// Named HTML entities
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/‘/g, "'")
|
||||
.replace(/’/g, "'")
|
||||
.replace(/“/g, '"')
|
||||
.replace(/”/g, '"')
|
||||
.replace(/–/g, '-')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…')
|
||||
.replace(/•/g, '•')
|
||||
.replace(/€/g, '€');
|
||||
|
||||
// Process WPBakery shortcodes with HTML entities
|
||||
processed = processed
|
||||
// vc_row - convert to div with classes
|
||||
.replace(/\[vc_row([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-row'];
|
||||
if (attrs.includes('full_width_background')) classes.push('full-width-bg');
|
||||
if (attrs.includes('in_container')) classes.push('in-container');
|
||||
if (attrs.includes('full_width_content')) classes.push('full-width-content');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_row\]/gi, '</div>')
|
||||
|
||||
// vc_column - convert to div with classes
|
||||
// Handle both complete and incomplete (truncated) shortcodes
|
||||
.replace(/\[vc_column([^\]]*)\]/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
// Also handle incomplete vc_column shortcodes (truncated at end of excerpt)
|
||||
.replace(/\[vc_column([^\]]*)$/gi, (match, attrs) => {
|
||||
const classes = ['vc-column'];
|
||||
if (attrs.includes('1/1')) classes.push('col-1-1');
|
||||
if (attrs.includes('1/2')) classes.push('col-1-2');
|
||||
if (attrs.includes('1/3')) classes.push('col-1-3');
|
||||
if (attrs.includes('2/3')) classes.push('col-2-3');
|
||||
if (attrs.includes('1/4')) classes.push('col-1-4');
|
||||
if (attrs.includes('3/4')) classes.push('col-3-4');
|
||||
if (attrs.includes('5/12')) classes.push('col-5-12');
|
||||
if (attrs.includes('7/12')) classes.push('col-7-12');
|
||||
return `<div class="${classes.join(' ')}">`;
|
||||
})
|
||||
.replace(/\[\/vc_column\]/gi, '</div>')
|
||||
|
||||
// vc_column_text - convert to div
|
||||
.replace(/\[vc_column_text([^\]]*)\]/gi, '<div class="vc-column-text">')
|
||||
.replace(/\[\/vc_column_text\]/gi, '</div>')
|
||||
|
||||
// nectar_cta - convert to button
|
||||
.replace(/\[nectar_cta([^\]]*)link_text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-cta">$2</a>')
|
||||
|
||||
// nectar_highlighted_text - convert to span
|
||||
.replace(/\[nectar_highlighted_text([^\]]*)\](.*?)\[\/nectar_highlighted_text\]/gi,
|
||||
'<span class="nectar-highlighted">$2</span>')
|
||||
|
||||
// nectar_responsive_text - convert to span
|
||||
.replace(/\[nectar_responsive_text([^\]]*)\](.*?)\[\/nectar_responsive_text\]/gi,
|
||||
'<span class="nectar-responsive">$2</span>')
|
||||
|
||||
// nectar_icon_list - convert to ul
|
||||
.replace(/\[nectar_icon_list([^\]]*)\]/gi, '<ul class="nectar-icon-list">')
|
||||
.replace(/\[\/nectar_icon_list\]/gi, '</ul>')
|
||||
|
||||
// nectar_icon_list_item - convert to li
|
||||
.replace(/\[nectar_icon_list_item([^\]]*)header="([^"]*)"(.*?)text="([^"]*)"(.*?)\]/gi,
|
||||
'<li><strong>$2</strong>: $4</li>')
|
||||
|
||||
// nectar_btn - convert to button
|
||||
.replace(/\[nectar_btn([^\]]*)text="([^"]*)"(.*?)url="([^"]*)"(.*?)\]/gi,
|
||||
'<a href="$4" class="nectar-btn">$2</a>')
|
||||
|
||||
// split_line_heading - convert to heading
|
||||
.replace(/\[split_line_heading([^\]]*)text_content="([^"]*)"(.*?)\]/gi,
|
||||
'<h2 class="split-line-heading">$2</h2>')
|
||||
|
||||
// vc_row_inner - convert to div
|
||||
.replace(/\[vc_row_inner([^\]]*)\]/gi, '<div class="vc-row-inner">')
|
||||
.replace(/\[\/vc_row_inner\]/gi, '</div>')
|
||||
|
||||
// vc_column_inner - convert to div
|
||||
.replace(/\[vc_column_inner([^\]]*)\]/gi, '<div class="vc-column-inner">')
|
||||
.replace(/\[\/vc_column_inner\]/gi, '</div>')
|
||||
|
||||
// divider - convert to hr
|
||||
.replace(/\[divider([^\]]*)\]/gi, '<hr class="divider" />')
|
||||
|
||||
// vc_gallery - convert to div (placeholder)
|
||||
.replace(/\[vc_gallery([^\]]*)\]/gi, '<div class="vc-gallery">[Gallery]</div>')
|
||||
|
||||
// vc_raw_js - remove or convert to div
|
||||
.replace(/\[vc_raw_js\](.*?)\[\/vc_raw_js\]/gi, '<div class="vc-raw-js">[JavaScript]</div>')
|
||||
|
||||
// nectar_gmap - convert to div
|
||||
.replace(/\[nectar_gmap([^\]]*)\]/gi, '<div class="nectar-gmap">[Google Map]</div>');
|
||||
|
||||
// Remove any remaining shortcodes
|
||||
processed = processed.replace(/\[.*?\]/g, '');
|
||||
|
||||
// Clean up any HTML that might be broken
|
||||
processed = processed.replace(/<p[^>]*>\s*<\/p>/gi, '');
|
||||
processed = processed.replace(/<div[^>]*>\s*<\/div>/gi, '');
|
||||
|
||||
// Normalize whitespace
|
||||
processed = processed.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Extract excerpt from content
|
||||
|
||||
module.exports = processExcerptShortcodes;
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user