UNPKG

@inspirer-dev/hero-widget-selector

Version:

A custom field plugin for Strapi v5 that provides a widget selector with size filtering capabilities. Perfect for selecting hero widgets from a filtered collection based on size (S, M, L, XL).

329 lines (328 loc) 10.2 kB
const bootstrap = ({ strapi }) => { }; const destroy = ({ strapi }) => { }; const register = ({ strapi }) => { strapi.customFields.register({ name: "widget-selector", plugin: "hero-widget-selector", // This must match the pluginId in admin registration type: "string" }); }; const config = { default: {}, validator() { } }; const contentTypes = {}; const controller = ({ strapi }) => ({ index(ctx) { ctx.body = strapi.plugin("hero-widget-selector").service("service").getWelcomeMessage(); }, async getFilteredWidgets(ctx) { const { size, sizes } = ctx.query; try { let entities; if (size) { const knex = strapi.db.connection; const results = await knex("widgets").select("id", "document_id", "title", "sizes", "subtitle").whereRaw("sizes->>'sizes' LIKE ?", [`%"${size}"%`]); entities = results.map((row) => ({ id: row.id, documentId: row.document_id, title: row.title, subtitle: row.subtitle, sizes: typeof row.sizes === "string" ? JSON.parse(row.sizes) : row.sizes })); const populatedEntities = await Promise.all( entities.map(async (entity) => { const fullEntity = await strapi.entityService.findOne("api::widget.widget", entity.id, { populate: { image: { fields: ["id", "documentId", "name", "url", "alternativeText"] }, ctaButton: true } }); return { ...entity, image: fullEntity?.image, ctaButton: fullEntity?.ctaButton }; }) ); entities = populatedEntities; } else { entities = await strapi.entityService.findMany("api::widget.widget", { fields: ["id", "documentId", "title", "sizes", "subtitle"], populate: { image: { fields: ["id", "documentId", "name", "url", "alternativeText"] }, ctaButton: true }, pagination: { pageSize: 100 } }); } ctx.body = { results: entities.map((entity) => ({ id: entity.id, documentId: entity.documentId, title: entity.title, subtitle: entity.subtitle, sizes: entity.sizes, image: entity.image, ctaButton: entity.ctaButton })) }; } catch (err) { strapi.log.error("Failed to fetch widgets:", err); ctx.throw(500, `Failed to fetch widgets: ${err.message}`); } }, async getHeroLayouts(ctx) { const { search, omitLayoutId, omitLayoutSlug, excludeIndex, fallbackOnly } = ctx.query; try { const heroSection = await strapi.entityService.findOne("api::hero-section.hero-section", 1, { populate: { defaultLayouts: { populate: "*" }, fallbackLayout: { populate: "*" } } }); if (!heroSection) { ctx.body = { results: [] }; return; } let layouts = []; if (fallbackOnly === "true") { layouts = heroSection.fallbackLayout || []; console.log("Fetching fallback layouts only:", layouts.length); } else { layouts = heroSection.defaultLayouts || []; console.log("Fetching default layouts:", layouts.length); if (heroSection.fallbackLayout && heroSection.fallbackLayout.length > 0) { const fallbackComponents = new Set( heroSection.fallbackLayout.map((layout) => layout.__component) ); const fallbackSlugs = new Set( heroSection.fallbackLayout.map((layout) => layout.slug).filter(Boolean) ); layouts = layouts.filter((layout) => { const isFallbackComponent = fallbackComponents.has(layout.__component); const isFallbackSlug = layout.slug && fallbackSlugs.has(layout.slug); return !isFallbackComponent && !isFallbackSlug; }); console.log("Filtered out fallback layouts, remaining:", layouts.length); } } if (omitLayoutId) { const omitId = omitLayoutId.toString(); layouts = layouts.filter((layout) => { const layoutIdStr = layout.id ? layout.id.toString() : ""; const documentIdStr = layout.documentId ? layout.documentId.toString() : ""; return layoutIdStr !== omitId && documentIdStr !== omitId; }); console.log(`Filtering out layout ID: ${omitId}, remaining layouts:`, layouts.length); } if (omitLayoutSlug) { layouts = layouts.filter((layout) => layout.slug !== omitLayoutSlug); console.log( `Filtering out layout slug: ${omitLayoutSlug}, remaining layouts:`, layouts.length ); } if (!omitLayoutId && excludeIndex !== void 0) { const excludeIdx = parseInt(excludeIndex); layouts = layouts.filter((layout, index2) => index2 !== excludeIdx); console.log( `Filtering out layout at index: ${excludeIdx}, remaining layouts:`, layouts.length ); } if (search && search.trim()) { const searchTerm = search.toLowerCase().trim(); layouts = layouts.filter((layout) => { const name = layout.name?.toLowerCase() || ""; const displayName = layout.__component?.split(".")[1]?.replace(/-/g, " ") || ""; return name.includes(searchTerm) || displayName.includes(searchTerm); }); } const results = layouts.map((layout) => { const autoSlug = layout.slug || layout.name?.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") || layout.__component?.split(".")[1] || `layout-${layout.id}`; console.log("Layout data:", { id: layout.id, documentId: layout.documentId, slug: layout.slug || autoSlug, name: layout.name, component: layout.__component, isFallback: fallbackOnly === "true" }); return { id: layout.id, documentId: layout.documentId, slug: layout.slug || autoSlug, // Use actual slug or fallback name: layout.name || "", displayName: layout.__component?.split(".")[1]?.replace(/-/g, " ").toUpperCase() || "Unknown Layout", component: layout.__component, __component: layout.__component, isFallback: fallbackOnly === "true" }; }); console.log("Returning layout results:", results); ctx.body = { results }; } catch (err) { strapi.log.error("Failed to fetch hero layouts:", err); ctx.throw(500, `Failed to fetch hero layouts: ${err.message}`); } }, async getPopulatedLayout(ctx) { const { layoutId, layoutSlug } = ctx.params; try { const heroSection = await strapi.entityService.findOne("api::hero-section.hero-section", 1, { populate: { defaultLayouts: { populate: { extraLarge: true, small1: true, small2: true, medium: true, large1: true, large2: true } } } }); if (!heroSection || !heroSection.defaultLayouts) { ctx.throw(404, "Hero section not found"); } let layout; if (layoutSlug) { layout = heroSection.defaultLayouts.find((l) => l.slug === layoutSlug); } else if (layoutId) { layout = heroSection.defaultLayouts.find((l) => l.id.toString() === layoutId); } if (!layout) { ctx.throw(404, "Layout not found"); } const populatedLayout = { id: layout.id, name: layout.name, component: layout.__component, displayName: layout.__component?.split(".")[1]?.replace(/-/g, " ").toUpperCase() || "Unknown Layout", widgets: {} }; const positions = ["extraLarge", "small1", "small2", "medium", "large1", "large2"]; for (const position of positions) { if (layout[position]) { let widgetData = layout[position]; if (typeof widgetData === "string") { try { widgetData = JSON.parse(widgetData); } catch (e) { widgetData = { widgetId: widgetData }; } } populatedLayout.widgets[position] = widgetData; } } ctx.body = { layout: populatedLayout }; } catch (err) { strapi.log.error("Failed to fetch populated layout:", err); ctx.throw(500, `Failed to fetch populated layout: ${err.message}`); } } }); const controllers = { controller }; const middlewares = {}; const policies = {}; const contentAPIRoutes = [ { method: "GET", path: "/", // name of the controller file & the method. handler: "controller.index", config: { policies: [] } } ]; const adminAPIRoutes = [ { method: "GET", path: "/widgets/admin", handler: "controller.getFilteredWidgets", config: { policies: [], auth: false } }, { method: "GET", path: "/hero-layouts/admin", handler: "controller.getHeroLayouts", config: { policies: [], auth: false } }, { method: "GET", path: "/hero-layouts/:layoutId/populated", handler: "controller.getPopulatedLayout", config: { policies: [], auth: false } }, { method: "GET", path: "/hero-layouts/slug/:layoutSlug/populated", handler: "controller.getPopulatedLayout", config: { policies: [], auth: false } } ]; const routes = { "content-api": { type: "content-api", routes: contentAPIRoutes }, admin: { type: "admin", routes: adminAPIRoutes } }; const service = ({ strapi }) => ({ getWelcomeMessage() { return "Welcome to Strapi 🚀"; } }); const services = { service }; const index = { register, bootstrap, destroy, config, controllers, routes, services, contentTypes, policies, middlewares }; export { index as default };