From ea4eb48fa517e8b549737a1d7a301831dcc86637 Mon Sep 17 00:00:00 2001
From: "lukas.fischer" <lukas.fischer@netnode.ch>
Date: Thu, 22 Feb 2024 14:23:11 +0100
Subject: [PATCH] added content browser functionality

---
 .gitignore                                    |   1 +
 apps/content-browser/.eslintrc.cjs            |  21 +++
 apps/content-browser/.gitignore               |  24 +++
 apps/content-browser/README.md                |   8 +
 apps/content-browser/index.html               |  13 ++
 apps/content-browser/package.json             |  29 ++++
 apps/content-browser/postcss.config.js        |   6 +
 apps/content-browser/public/vite.svg          |   1 +
 apps/content-browser/src/App.css              |   0
 apps/content-browser/src/App.jsx              |  98 ++++++++++++
 apps/content-browser/src/assets/react.svg     |   1 +
 apps/content-browser/src/index.css            |   3 +
 apps/content-browser/src/main.jsx             |  10 ++
 apps/content-browser/tailwind.config.js       |  12 ++
 apps/content-browser/vite.config.js           |   7 +
 css/menu-icons.css                            |   5 +
 .../beekeeper/nodehive/beekeeper.menu.yml     |   8 +-
 nodehive_core.routing.yml                     |  25 +++
 src/Controller/ContentBrowserController.php   |  45 ++++++
 src/Controller/DataModelsController.php       | 148 ++++++++++++++++++
 src/Form/NodeHiveSettingsForm.php             |  18 ---
 .../NodeHiveQuickLinkContentModelBlock.php    |   2 +-
 22 files changed, 462 insertions(+), 23 deletions(-)
 create mode 100644 apps/content-browser/.eslintrc.cjs
 create mode 100644 apps/content-browser/.gitignore
 create mode 100644 apps/content-browser/README.md
 create mode 100644 apps/content-browser/index.html
 create mode 100644 apps/content-browser/package.json
 create mode 100644 apps/content-browser/postcss.config.js
 create mode 100644 apps/content-browser/public/vite.svg
 create mode 100644 apps/content-browser/src/App.css
 create mode 100644 apps/content-browser/src/App.jsx
 create mode 100644 apps/content-browser/src/assets/react.svg
 create mode 100644 apps/content-browser/src/index.css
 create mode 100644 apps/content-browser/src/main.jsx
 create mode 100644 apps/content-browser/tailwind.config.js
 create mode 100644 apps/content-browser/vite.config.js
 create mode 100644 src/Controller/ContentBrowserController.php
 create mode 100644 src/Controller/DataModelsController.php

diff --git a/.gitignore b/.gitignore
index d5f19d8..ef3772b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 node_modules
 package-lock.json
+apps/content-browser/node_modules
diff --git a/apps/content-browser/.eslintrc.cjs b/apps/content-browser/.eslintrc.cjs
new file mode 100644
index 0000000..3e212e1
--- /dev/null
+++ b/apps/content-browser/.eslintrc.cjs
@@ -0,0 +1,21 @@
+module.exports = {
+  root: true,
+  env: { browser: true, es2020: true },
+  extends: [
+    'eslint:recommended',
+    'plugin:react/recommended',
+    'plugin:react/jsx-runtime',
+    'plugin:react-hooks/recommended',
+  ],
+  ignorePatterns: ['dist', '.eslintrc.cjs'],
+  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+  settings: { react: { version: '18.2' } },
+  plugins: ['react-refresh'],
+  rules: {
+    'react/jsx-no-target-blank': 'off',
+    'react-refresh/only-export-components': [
+      'warn',
+      { allowConstantExport: true },
+    ],
+  },
+}
diff --git a/apps/content-browser/.gitignore b/apps/content-browser/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/apps/content-browser/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/content-browser/README.md b/apps/content-browser/README.md
new file mode 100644
index 0000000..f768e33
--- /dev/null
+++ b/apps/content-browser/README.md
@@ -0,0 +1,8 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
diff --git a/apps/content-browser/index.html b/apps/content-browser/index.html
new file mode 100644
index 0000000..0c589ec
--- /dev/null
+++ b/apps/content-browser/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>
diff --git a/apps/content-browser/package.json b/apps/content-browser/package.json
new file mode 100644
index 0000000..5cce270
--- /dev/null
+++ b/apps/content-browser/package.json
@@ -0,0 +1,29 @@
+{
+  "name": "content-browser",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
+  },
+  "devDependencies": {
+    "@types/react": "^18.2.56",
+    "@types/react-dom": "^18.2.19",
+    "@vitejs/plugin-react": "^4.2.1",
+    "autoprefixer": "^10.4.17",
+    "eslint": "^8.56.0",
+    "eslint-plugin-react": "^7.33.2",
+    "eslint-plugin-react-hooks": "^4.6.0",
+    "eslint-plugin-react-refresh": "^0.4.5",
+    "postcss": "^8.4.35",
+    "tailwindcss": "^3.4.1",
+    "vite": "^5.1.4"
+  }
+}
diff --git a/apps/content-browser/postcss.config.js b/apps/content-browser/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/apps/content-browser/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}
diff --git a/apps/content-browser/public/vite.svg b/apps/content-browser/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/apps/content-browser/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
diff --git a/apps/content-browser/src/App.css b/apps/content-browser/src/App.css
new file mode 100644
index 0000000..e69de29
diff --git a/apps/content-browser/src/App.jsx b/apps/content-browser/src/App.jsx
new file mode 100644
index 0000000..bf2fe41
--- /dev/null
+++ b/apps/content-browser/src/App.jsx
@@ -0,0 +1,98 @@
+import { useState, useEffect } from 'react';
+import './App.css';
+
+function App() {
+  const [posts, setPosts] = useState([]);
+  const [searchTerm, setSearchTerm] = useState('');
+  const [selectedNodeType, setSelectedNodeType] = useState('');
+  const [iframeUrl, setIframeUrl] = useState(''); // State to hold the URL for the iframe
+
+  useEffect(() => {
+    const fetchPosts = async () => {
+      let apiUrl = 'https://netnode.nodehive.app.ddev.site/nodehive/api/content-browser';
+      let queryParts = [];
+
+      if (searchTerm) {
+        queryParts.push(`search=${searchTerm}`);
+      }
+
+      if (selectedNodeType) {
+        queryParts.push(`type=${selectedNodeType}`);
+      }
+
+      if (queryParts.length) {
+        apiUrl += '?' + queryParts.join('&');
+      }
+
+      try {
+        const response = await fetch(apiUrl);
+        const data = await response.json();
+        setPosts(data);
+      } catch (error) {
+        console.error('Error fetching data: ', error);
+      }
+    };
+
+    fetchPosts();
+  }, [searchTerm, selectedNodeType]);
+
+  return (
+    <>
+      <div className='flex max-w-full'>
+        {/* Left side - Iframe */}
+        {iframeUrl && (
+          <iframe
+            src={iframeUrl}
+            style={{ width: '100vw', height: '100vh' }}
+            title="Content Edit"
+          ></iframe>
+        )}
+
+        {/* Right side - Content Browser */}
+        <div className='w-96 bg-slate-400 p-4 overflow-auto'>
+          <h2 className='text-3xl text-center mb-4'>Content Browser</h2>
+          <select
+            value={selectedNodeType}
+            onChange={(e) => setSelectedNodeType(e.target.value)}
+            className="mb-4 p-2"
+          >
+            <option value="">All Types</option>
+            <option value="article">Article</option>
+            <option value="page">Page</option>
+          </select>
+          <input
+            type="text"
+            placeholder="Search by title..."
+            className="mb-4 p-2 w-full"
+            value={searchTerm}
+            onChange={(e) => setSearchTerm(e.target.value)}
+          />
+          <ul>
+            {posts.map((post, index) => (
+              <li key={index} className='bg-slate-300 rounded-md p-2 my-2 flex justify-between items-center'>
+                <h3 className='text-xl'>{post.nid} {post.title}</h3>
+                <div>
+
+                <span>
+                <a
+                    href="#"
+                    onClick={(e) => {
+                      e.preventDefault();
+                      setIframeUrl(`https://netnode.nodehive.app.ddev.site/node/${post.nid}/edit`);
+                    }}
+                    className="text-blue-600 hover:text-blue-800"
+                  >
+                    Edit
+                  </a>
+                </span> {post.type}
+                </div>
+              </li>
+            ))}
+          </ul>
+        </div>
+      </div>
+    </>
+  );
+}
+
+export default App;
diff --git a/apps/content-browser/src/assets/react.svg b/apps/content-browser/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/apps/content-browser/src/assets/react.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file
diff --git a/apps/content-browser/src/index.css b/apps/content-browser/src/index.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/apps/content-browser/src/index.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/apps/content-browser/src/main.jsx b/apps/content-browser/src/main.jsx
new file mode 100644
index 0000000..54b39dd
--- /dev/null
+++ b/apps/content-browser/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>,
+)
diff --git a/apps/content-browser/tailwind.config.js b/apps/content-browser/tailwind.config.js
new file mode 100644
index 0000000..d37737f
--- /dev/null
+++ b/apps/content-browser/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: [
+    "./index.html",
+    "./src/**/*.{js,ts,jsx,tsx}",
+  ],
+  theme: {
+    extend: {},
+  },
+  plugins: [],
+}
+
diff --git a/apps/content-browser/vite.config.js b/apps/content-browser/vite.config.js
new file mode 100644
index 0000000..5a33944
--- /dev/null
+++ b/apps/content-browser/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [react()],
+})
diff --git a/css/menu-icons.css b/css/menu-icons.css
index 9b11dd0..176fc5b 100644
--- a/css/menu-icons.css
+++ b/css/menu-icons.css
@@ -23,6 +23,11 @@ a[data-drupal-link-system-path="admin/structure/types"]::before {
   -webkit-mask-image: url(/themes/contrib/gin/dist/media/sprite.svg#structure-view) !important;
 }
 
+a[data-drupal-link-system-path="nodehive/models"]::before {
+  mask-image: url(/themes/contrib/gin/dist/media/sprite.svg#structure-view);
+  -webkit-mask-image: url(/themes/contrib/gin/dist/media/sprite.svg#structure-view) !important;
+}
+
 a[data-drupal-link-system-path="admin/config"]::before {
   mask-image: url(/themes/contrib/gin/dist/media/sprite.svg#config-view);
   -webkit-mask-image: url(/themes/contrib/gin/dist/media/sprite.svg#config-view) !important;
diff --git a/modules/nodehive_core_beekeeper/config/beekeeper/nodehive/beekeeper.menu.yml b/modules/nodehive_core_beekeeper/config/beekeeper/nodehive/beekeeper.menu.yml
index 8ed3843..e80ea45 100644
--- a/modules/nodehive_core_beekeeper/config/beekeeper/nodehive/beekeeper.menu.yml
+++ b/modules/nodehive_core_beekeeper/config/beekeeper/nodehive/beekeeper.menu.yml
@@ -52,8 +52,8 @@ nodehive-admin:
       link: "internal:/admin/content/space"
       weight: -1
     data:
-      title: "Content"
-      link: "internal:/admin/content"
+      title: "Content Manager"
+      link: "internal:/nodehive/content"
       weight: 2
       children:
         all_content:
@@ -75,8 +75,8 @@ nodehive-admin:
           title: "All Webforms"
           link: "internal:/admin/structure/webform"
     data_models:
-      title: "Data Models"
-      link: "internal:/admin/structure/types"
+      title: "Content Models"
+      link: "internal:/nodehive/models"
       weight: 3
       children:
         content_types:
diff --git a/nodehive_core.routing.yml b/nodehive_core.routing.yml
index 7a7e404..5c709d0 100644
--- a/nodehive_core.routing.yml
+++ b/nodehive_core.routing.yml
@@ -37,3 +37,28 @@ nodehive.jsonapi_translated_paths:
   methods: [GET]
   requirements:
     _permission: 'access content'
+
+nodehive_core.data_modeller:
+  path: '/nodehive/models'
+  defaults:
+    _controller: '\Drupal\nodehive_core\Controller\DataModelsController::dataModeller'
+    _title: 'Data Modeller'
+  requirements:
+    _permission: 'access content'
+
+nodehive_core.content_browser:
+  path: '/nodehive/content'
+  defaults:
+    _controller: '\Drupal\nodehive_core\Controller\ContentBrowserController::contentBrowser'
+    _title: 'Content Browser'
+  requirements:
+    _permission: 'access content'
+
+nodehive_core.content_browser_api:
+  path: '/nodehive/api/content-browser'
+  defaults:
+    _controller: '\Drupal\nodehive_core\Controller\ContentBrowserController::content'
+    _title: 'Content Browser API'
+  methods: [GET]
+  requirements:
+    _permission: 'access content'
diff --git a/src/Controller/ContentBrowserController.php b/src/Controller/ContentBrowserController.php
new file mode 100644
index 0000000..ed2089a
--- /dev/null
+++ b/src/Controller/ContentBrowserController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\nodehive_core\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Database\Database;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class ContentBrowserController extends ControllerBase {
+
+  public function contentBrowser() {
+    return [];
+  }
+  public function content() {
+
+    $search = '';
+    if (isset($_GET['search'])) {
+      $search = $_GET['search'];
+    }
+    // Database connection
+    $connection = Database::getConnection();
+    $query = $connection->select('node_field_data', 'n');
+    $query->fields('n', ['nid', 'title', 'type']);
+
+    // Add search filter if $search parameter is provided
+    if (!empty($search)) {
+      $query->condition('n.title', '%' . $connection->escapeLike($search) . '%', 'LIKE');
+    }
+
+    $query->range(0, 50); // Limit to 50 results for example
+    $result = $query->execute()->fetchAll();
+
+    $data = [];
+    foreach ($result as $row) {
+      $data[] = [
+        'nid' => $row->nid,
+        'title' => $row->title,
+        'type' => $row->type,
+      ];
+    }
+
+    return new JsonResponse($data);
+  }
+
+}
diff --git a/src/Controller/DataModelsController.php b/src/Controller/DataModelsController.php
new file mode 100644
index 0000000..065cc99
--- /dev/null
+++ b/src/Controller/DataModelsController.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\nodehive_core\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines DataModelsController class for the NodeHive module.
+ */
+class DataModelsController extends ControllerBase
+{
+
+  /**
+   * Display the custom page with NodeHive quick links.
+   *
+   * @return array
+   *   A render array for a Drupal page.
+   */
+  public function dataModeller()
+  {
+    $create_links = $this->getQuickLinks();
+    $build = $this->renderLinks('create', $create_links);
+
+    // Attach the library for styling and JavaScript if needed.
+    $build['#attached']['library'][] = 'nodehive_core/quick_links';
+
+    return $build;
+  }
+
+  /**
+   * Get the array of quick links to be rendered.
+   *
+   * @return array
+   *   The quick links configuration.
+   */
+  private function getQuickLinks()
+  {
+    // Define your links here, similar to how they were defined in the block plugin.
+    // This example uses a simplified structure for demonstration.
+    $create_links = [
+      'configure_content_types' => [
+        'title' => $this->t('Configure Content Types'),
+        'description' => $this->t('Configure your custom content types like Page, Article or Event.'),
+        'url' => '/admin/structure/types',
+        'link_text' => 'Configure Content Types',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+        <rect x="4" y="4" width="16" height="16" rx="2" stroke-linecap="round" stroke-linejoin="round" />
+        <path stroke-linecap="round" stroke-linejoin="round" d="M8 8h8M8 12h5M8 16h3" />
+      </svg>
+      '
+      ],
+      'configure_paragraph_types' => [
+        'title' => $this->t('Configure Paragraph Types'),
+        'description' => $this->t('Configure your paragraph types like a Text Section, Gallery Section or Teaser List.'),
+        'url' => '/admin/structure/paragraphs_type',
+        'link_text' => 'Configure Paragraph Types',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+        <path stroke-linecap="round" stroke-linejoin="round" d="M4 7h16M4 11h16M4 15h16" />
+        <circle cx="18" cy="19" r="2" fill="currentColor" />
+      </svg> '
+      ],
+
+      'configure_taxonomy' => [
+        'title' => $this->t('Configure Taxonomy'),
+        'description' => $this->t('Configure your taxonomies like Article categories, Event Types or Blog Post Tags.'),
+        'url' => '/admin/structure/taxonomy',
+        'link_text' => 'Configure Taxonomies',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+      <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" />
+      <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
+    </svg>
+    '
+      ],
+      'configure_fragment_types' => [
+        'title' => $this->t('Configure Fragement Types'),
+        'description' => $this->t('Configure your fragment types like social media links, global CTAs and reusable content.'),
+        'url' => '/admin/structure/fragment_type',
+        'link_text' => 'Configure Fragment Types',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+        <path stroke-linecap="round" stroke-linejoin="round" d="M3 10h4v4H3zM17 10h4v4h-4zM10 3h4v4h-4zM10 17h4v4h-4z" />
+      </svg>'
+      ],
+
+
+      'configure_area' => [
+        'title' => $this->t('Configure Areas'),
+        'description' => $this->t('Configure your areas to place fragments in the header, footer or anywhere you need them.'),
+        'url' => '/admin/content/area',
+        'link_text' => 'Configure Areas',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+        <path stroke-linecap="round" stroke-linejoin="round" d="M6 3h12M6 21h12M3 6v12M21 6v12M6 6h12v12H6z" />
+      </svg>'
+      ],
+
+      'configure_menus' => [
+        'title' => $this->t('Configure Menus'),
+        'description' => $this->t('Configure navigation menus with links and attributes.'),
+        'url' => '/admin/structure/menu',
+        'link_text' => 'Configure Menus',
+        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+        <path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M3 12h12m-12 6h9" />
+      </svg>'
+      ]
+
+    ];
+    return $create_links;
+  }
+
+  /**
+   * Render the links into a structured array for Drupal rendering system.
+   *
+   * @param array $links
+   *   The links to render.
+   *
+   * @return array
+   *   The renderable array of links.
+   */
+  function renderLinks($section, $links)
+  {
+    $build[$section] = [
+      '#type' => 'container',
+      '#attributes' => ['class' => ['gin-layer-wrapper', 'quick-links', 'quick-links-grid']],
+    ];
+    foreach ($links as $key => $info) {
+      $build[$section][$key] = [
+        '#type' => 'fieldset',
+        '#title' => $info['title'],
+        '#open' => TRUE,
+      ];
+
+      $icon_markup = !empty($info['icon']) ? $info['icon'] : '';
+
+      $build[$section][$key]['content'] = [
+        '#type' => 'item',
+        '#markup' =>
+          '<div class="link-content">' .
+          '<div class="icon-container">' . $icon_markup . '</div>' .
+          '<div>' . $info['description'] . '</div>' .
+          '</div>' .
+          '<p><a href="' . $info['url'] . '" class="button button--small">' . $info['link_text'] . '</a></p>',
+
+        '#allowed_tags' => ['div', 'p', 'a', 'svg', 'path', 'rect', 'circle'], // Add more allowed tags as needed.
+      ];
+    }
+    return $build;
+  }
+}
diff --git a/src/Form/NodeHiveSettingsForm.php b/src/Form/NodeHiveSettingsForm.php
index e5ffcb5..3fd5b98 100644
--- a/src/Form/NodeHiveSettingsForm.php
+++ b/src/Form/NodeHiveSettingsForm.php
@@ -190,23 +190,6 @@ class NodeHiveSettingsForm extends ConfigFormBase {
       '#description' => t('Only applied if users may set their own time zone.'),
     ];
 
-    $form['subscription'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Subscription and limits'),
-      '#tree' => TRUE,
-      '#open' => TRUE,
-    ];
-
-    $subscription = $config->get('subscription');
-    $form['subscription']['key'] = [
-      '#type' => 'radios',
-      '#title' => t('NodeHive Subscription'),
-      '#default_value' => isset($subscription['key']) ? $subscription['key'] : "trial",
-      '#options' => [
-        'trial' => t('Trial')
-      ],
-    ];
-
     return parent::buildForm($form, $form_state);
   }
 
@@ -221,7 +204,6 @@ class NodeHiveSettingsForm extends ConfigFormBase {
     $this->config('nodehive_core.settings')
       ->set('company_name', $form_state->getValue('company_name'))
       ->set('ckeditor_styles_url', $ckeditor_styles_url)
-      ->set('subscription', $subscription)
       ->save();
 
     // Parse the new css file.
diff --git a/src/Plugin/DashboardBlock/NodeHiveQuickLinkContentModelBlock.php b/src/Plugin/DashboardBlock/NodeHiveQuickLinkContentModelBlock.php
index 93238b7..a0fba34 100644
--- a/src/Plugin/DashboardBlock/NodeHiveQuickLinkContentModelBlock.php
+++ b/src/Plugin/DashboardBlock/NodeHiveQuickLinkContentModelBlock.php
@@ -134,7 +134,7 @@ class NodeHiveQuickLinkContentModelBlock extends DashboardBlockBase implements C
           '</div>' .
           '<p><a href="' . $info['url'] . '" class="button button--small">' . $info['link_text'] . '</a></p>',
 
-        '#allowed_tags' => ['p', 'a', 'path', 'svg', 'rect', 'div', 'text', 'line', 'circle', 'g', 'title'],
+          '#allowed_tags' => ['div', 'p', 'a', 'svg', 'path', 'rect', 'circle'], // Add more allowed tags as needed.
       ];
     }
     return $build;
-- 
GitLab