@hashtagcms/web-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 HashtagCMS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @hashtagcms/web-sdk
2
+
3
+ > Core JavaScript SDK for the HashtagCMS ecosystem
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ This package contains the core JavaScript logic and components for HashtagCMS, providing shared functionality that is used across both **Blade/PHP** (`@hashtagcms/web-ui-kit`) and **Java** (`@hashtagcms/theme-java`) theme ecosystems.
8
+
9
+ ## 📚 Documentation
10
+
11
+ For more detailed information, check the following guides:
12
+
13
+ - [Architecture & Design](./docs/01-architecture.md)
14
+ - [Bootstrap & Global Config](./docs/02-bootstrap.md)
15
+ - [Component API Reference](./docs/03-components.md)
16
+ - [Integrating with Themes (PHP/Java)](./docs/04-integration.md)
17
+
18
+ ## ✨ Features
19
+
20
+ - 🛠️ **Core Components** - Shared logic like Configuration forms and Analytics tracking
21
+ - 🔧 **Configurable** - Manage application settings with `AppConfig`
22
+ - 🌐 **Standardized Requests** - Automatic CSRF token handling and Axios configuration
23
+ - 📦 **Framework Agnostic** - Vanilla JavaScript logic that works anywhere
24
+ - 🚀 **Lightweight** - Minimized footprint for high-performance builds
25
+
26
+ ## 📦 Installation
27
+
28
+ ```bash
29
+ npm install @hashtagcms/web-sdk
30
+ ```
31
+
32
+ ## 🚀 Quick Start
33
+
34
+ ### Basic Usage
35
+
36
+ ```javascript
37
+ import { Analytics, Subscribe, AppConfig } from "@hashtagcms/web-sdk";
38
+
39
+ // Initialize Components
40
+ const subscribe = new Subscribe();
41
+ const analytics = new Analytics();
42
+ const config = new AppConfig({
43
+ media: { http_path: "https://cdn.example.com" }
44
+ });
45
+
46
+ // Use analytics
47
+ analytics.trackPageView('/home');
48
+ ```
49
+
50
+ ## 🛠️ API Reference
51
+
52
+ ### `Subscribe`
53
+ Handles newsletter or contact form configuration.
54
+ - `init()`: Automatically finds form elements and attaches listeners.
55
+ - `subscribeNow()`: Programmatically triggers a configuration request.
56
+
57
+ ### `Analytics`
58
+ Standardized tracking for HashtagCMS.
59
+ - `init(data)`: Initializes tracking with initial data.
60
+ - `trackPageView(url)`: Tracks a page view event.
61
+ - `trackEventView(category, action, value)`: Tracks a custom interaction.
62
+
63
+ ### `AppConfig`
64
+ A helper class to manage CMS configuration.
65
+ - `setConfigData(data)`: Update the current configuration.
66
+ - `getValue(key, defaultVal)`: Safely retrieve configuration values.
67
+ - `getMedia(path)`: Get the full CDN/Server path for a media asset.
68
+
69
+ ## 📄 License
70
+
71
+ [MIT](LICENSE) © HashtagCMS
@@ -0,0 +1,27 @@
1
+ # Web SDK Architecture
2
+
3
+ The `@hashtagcms/web-sdk` is designed as a lightweight, framework-agnostic collection of utilities and components used by the HashtagCMS ecosystem.
4
+
5
+ ## 📁 Project Structure
6
+
7
+ ```
8
+ @hashtagcms/web-sdk/
9
+ ├── src/
10
+ │ ├── bootstrap.js # Global initialization (Axios, CSRF)
11
+ │ ├── index.js # Main entry point (Exports)
12
+ │ ├── components/ # UI-related logic
13
+ │ │ └── subscribe.js # Configuration form handler
14
+ │ ├── helpers/ # Shared helpers
15
+ │ │ └── common.js # AppConfig and utilities
16
+ │ └── utils/ # General utilities
17
+ │ └── analytics.js # Tracking logic
18
+ ├── package.json
19
+ └── README.md
20
+ ```
21
+
22
+ ## 🏗️ Design Principles
23
+
24
+ 1. **Framework Agnostic**: No dependency on React, Vue, or Angular. Uses vanilla JavaScript to ensure compatibility with any frontend environment (Blade templates, Thymeleaf, etc.).
25
+ 2. **Minimal Dependencies**: Keeps the bundle size small. Currently only depends on `axios` for HTTP requests.
26
+ 3. **Singleton Compatibility**: Designed to be initialized once per page load to manage global state like configuration and tracking.
27
+ 4. **Data-Attr Driven**: Components are designed to find their target elements using HTML5 `data-` attributes, reducing the need for explicit DOM passing in most cases.
@@ -0,0 +1,32 @@
1
+ # Bootstrap and Initialization
2
+
3
+ The `bootstrap.js` file handles the global setup required for HashtagCMS to communicate with the backend.
4
+
5
+ ## 🛠️ Automated Setup
6
+
7
+ When you import the SDK, it automatically performs several actions:
8
+
9
+ 1. **Axios Configuration**:
10
+ - Sets the `X-Requested-With: XMLHttpRequest` header.
11
+ - Enables `withCredentials` for cross-site cookie handling.
12
+ 2. **CSRF Protection**:
13
+ - Automatically looks for a `<meta name="csrf-token" content="...">` tag in the document head.
14
+ - Sets the `X-CSRF-TOKEN` header for all Axios requests.
15
+ - Sets the `Authorization: Bearer <token>` header for API compatibility.
16
+
17
+ ## 🚀 Manual Usage
18
+
19
+ In most cases, you don't need to call bootstrap manually. It is executed as a side-effect when you import the main SDK index or components.
20
+
21
+ ```javascript
22
+ // This will trigger bootstrap.js
23
+ import { Subscribe } from '@hashtagcms/web-sdk';
24
+ ```
25
+
26
+ ## 🌐 Global Axios
27
+
28
+ The SDK exposes `axios` to the global `window` object for convenience in legacy scripts or simple inline templates:
29
+
30
+ ```javascript
31
+ window.axios.get('/api/endpoint').then(...);
32
+ ```
@@ -0,0 +1,62 @@
1
+ # Component Documentation
2
+
3
+ ## `Newsletter`
4
+
5
+ Handles newsletter and subscription forms using a standardized HTML structure.
6
+ *Note: Also exported as `Subscribe` for backward compatibility.*
7
+
8
+ ### Usage
9
+ ```javascript
10
+ import { Newsletter } from '@hashtagcms/web-sdk';
11
+ const newsletter = new Newsletter();
12
+ ```
13
+
14
+ ### HTML Requirements
15
+ The component automatically detects if it should use the new `newsletter` or legacy `subscribe` selectors.
16
+
17
+ **Primary (New):**
18
+ - `form[data-form='newsletter-form']`
19
+ - `span[data-class='newsletter-message']`
20
+ - `div[data-message-holder='newsletter-message-holder']`
21
+ - `span[data-class='newsletter-close']`
22
+
23
+ **Legacy (Fallback):**
24
+ - `form[data-form='subscribe-form']`
25
+ - `span[data-class='subscribe-message']`
26
+ - `div[data-message-holder='subscribe-message-holder']`
27
+ - `span[data-class='subscribe-close']`
28
+
29
+ ---
30
+
31
+ ## `Analytics`
32
+
33
+ Provides a unified interface for tracking page views and custom events.
34
+
35
+ ### Methods
36
+ - `init(data)`: Optional initialization with default data.
37
+ - `trackPageView(url)`: Sends a page view event to the CMS and Google Analytics (if detected).
38
+ - `trackEventView(category, action, value)`: Tracks custom interactions.
39
+
40
+ ### Example
41
+ ```javascript
42
+ const analytics = new Analytics();
43
+ analytics.trackPageView(window.location.pathname);
44
+ ```
45
+
46
+ ---
47
+
48
+ ## `AppConfig`
49
+
50
+ Manages configuration data provided by the CMS backend.
51
+
52
+ ### Methods
53
+ - `constructor(initialData)`
54
+ - `setConfigData(data)`: Replace the current configuration.
55
+ - `getValue(key, defaultVal)`: Safely retrieve a value (e.g., `getValue('site_name', 'Default Name')`).
56
+ - `getMedia(path)`: Constructs a full URL for a media asset using the configured media HTTP path.
57
+
58
+ ### Example
59
+ ```javascript
60
+ const config = new AppConfig(window.cmsConfig);
61
+ const logoUrl = config.getMedia('logo.png');
62
+ ```
@@ -0,0 +1,39 @@
1
+ # Theme Integration Guide
2
+
3
+ The Web SDK isdesigned to be the functional core for any HashtagCMS theme, regardless of the rendering engine.
4
+
5
+ ## 🐘 PHP/Blade Integration (`@hashtagcms/web-ui-kit`)
6
+
7
+ In a Laravel environment, themes typically use standard bundlers like Webpack or Vite.
8
+
9
+ 1. Install the SDK: `npm install @hashtagcms/web-sdk`
10
+ 2. Import in your `app.js`:
11
+ ```javascript
12
+ import { Subscribe, Analytics, AppConfig } from '@hashtagcms/web-sdk';
13
+
14
+ window.HashtagCms = {
15
+ Subscribe: new Subscribe(),
16
+ Analytics: new Analytics(),
17
+ AppConfig: new AppConfig(window.cmsData)
18
+ };
19
+ ```
20
+
21
+ ## ☕ Java/Thymeleaf Integration (`@hashtagcms/theme-java`)
22
+
23
+ In the Java ecosystem, the SDK can be integrated via NPM (if using a modern JS build pipeline) or by including the bundled distribution in your templates.
24
+
25
+ 1. Ensure the `csrf-token` meta tag is populated by the Java backend.
26
+ 2. Initialize the SDK in your main template:
27
+ ```html
28
+ <script th:inline="javascript">
29
+ /* Initialize config from server-side model */
30
+ const config = new HashtagCms.AppConfig([[${cmsConfig}]]);
31
+ </script>
32
+ ```
33
+
34
+ ## 🏗️ Future Platforms
35
+
36
+ Because the SDK is vanilla Javascript and focused on API interactions, it can be easily ported to:
37
+ - Mobile web views.
38
+ - Headless CMS frontends (Next.js, Nuxt.js).
39
+ - Static site generators.
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@hashtagcms/web-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Core SDK for HashtagCMS Frontend",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "build": "webpack --mode production"
9
+ },
10
+ "author": "HashtagCMS",
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "axios": "^1.8.0"
14
+ },
15
+ "devDependencies": {
16
+ "webpack": "^5.104.1",
17
+ "webpack-cli": "^6.0.1"
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ import axios from "axios";
2
+ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
3
+ let token = document.head.querySelector('meta[name="csrf-token"]');
4
+ if (token) {
5
+ axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
6
+ axios.defaults.headers.common['Authorization'] = 'Bearer '+token.content;
7
+ axios.defaults.withCredentials = true;
8
+ } else {
9
+ // console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
10
+ }
11
+ window.axios = axios;
@@ -0,0 +1,75 @@
1
+ export default class Newsletter {
2
+ constructor() {
3
+ this.elements = {
4
+ close: "span[data-class='newsletter-close']",
5
+ message: "span[data-class='newsletter-message']",
6
+ form: "form[data-form='newsletter-form']",
7
+ messageHolder: "div[data-message-holder='newsletter-message-holder']"
8
+ };
9
+
10
+ // Backward compatibility: Use 'subscribe' selectors if 'newsletter' form is not found
11
+ if (!document.querySelector(this.elements.form)) {
12
+ this.elements = {
13
+ close: "span[data-class='subscribe-close']",
14
+ message: "span[data-class='subscribe-message']",
15
+ form: "form[data-form='subscribe-form']",
16
+ messageHolder: "div[data-message-holder='subscribe-message-holder']"
17
+ };
18
+ }
19
+
20
+ this.init();
21
+ }
22
+
23
+ init() {
24
+
25
+ if (document.querySelector(this.elements.close)) {
26
+ document.querySelector(this.elements.close).addEventListener('click', (ele) => {
27
+ document.querySelector(this.elements.messageHolder).style.display = "none";
28
+ });
29
+ }
30
+ }
31
+
32
+ newsletterNow() {
33
+ let showMessage = function (message) {
34
+ document.querySelector($this.elements.message).innerText = message;
35
+ }
36
+ let $this = this;
37
+ let afterSave = function (data) {
38
+ console.log("data ", data);
39
+ document.querySelector($this.elements.messageHolder).style.display = "";
40
+ if (data.success == true) {
41
+ showMessage(data.message);
42
+ document.querySelector($this.elements.message).innerText = data.message;
43
+ document.querySelector($this.elements.form + " input[type='email']").value = '';
44
+ document.querySelector($this.elements.message).classList.remove("text-danger");
45
+ document.querySelector($this.elements.message).classList.add("text-success");
46
+ } else {
47
+ let errorMsg = (data.message && data.message.email && data.message.email[0]) ? data.message.email[0] : (data.message || "There is some error.");
48
+ showMessage(errorMsg);
49
+ document.querySelector($this.elements.message).classList.remove("text-success");
50
+ document.querySelector($this.elements.message).classList.add("text-danger");
51
+ }
52
+ }
53
+
54
+ let email = document.querySelector(this.elements.form + " input[type='email']");
55
+ let url = "/common/newsletter";
56
+ let data = { email: email.value };
57
+ axios.post(url, data)
58
+ .then(response => {
59
+ afterSave(response.data);
60
+ }).catch((error) => {
61
+ afterSave(error.response.data);
62
+ });
63
+
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Alias for newsletterNow
69
+ * @deprecated
70
+ */
71
+ subscribeNow() {
72
+ return this.newsletterNow();
73
+ }
74
+
75
+ }
@@ -0,0 +1,18 @@
1
+ export class AppConfig {
2
+ constructor(data) {
3
+ this.configData = data;
4
+ }
5
+
6
+ setConfigData(data) {
7
+ this.configData = data;
8
+ }
9
+
10
+ getValue(key, defaultVal) {
11
+ return this.configData[key] || defaultVal;
12
+ }
13
+
14
+ getMedia(path) {
15
+ let media = this.getValue("media");
16
+ return media.http_path+"/"+path;
17
+ }
18
+ };
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import './bootstrap';
2
+ import Newsletter from './components/newsletter';
3
+ import Analytics from './utils/analytics';
4
+ import { AppConfig } from './helpers/common';
5
+
6
+ export {
7
+ Newsletter,
8
+ Newsletter as Subscribe, // Alias for backward compatibility
9
+ Analytics,
10
+ AppConfig
11
+ };
@@ -0,0 +1,85 @@
1
+ export default class Analytics {
2
+
3
+ constructor() {
4
+
5
+ }
6
+
7
+ init(data) {
8
+ this.readCounter(data);
9
+ }
10
+ readCounter(data) {
11
+
12
+ this.submit("post", "/analytics/publish", data);
13
+ }
14
+
15
+ /**
16
+ * Submit request.
17
+ *
18
+ * @param {string} requestType
19
+ * @param {string} url
20
+ * @param data
21
+ */
22
+ submit(requestType, url, data) {
23
+ /*data = new Blob([JSON.stringify(data)], {type : 'application/json'});
24
+ data.csrfToken = window.Laravel.csrfToken;
25
+ console.log("data",data);
26
+ navigator.sendBeacon(url, data);*/
27
+ return new Promise((resolve, reject) => {
28
+ axios[requestType](url, data)
29
+ .then(response => {
30
+ resolve(response.data);
31
+ }).catch(error => {
32
+ reject(error.response.data);
33
+ });
34
+ });
35
+ }
36
+
37
+ trackEventView (category, action ,value, cb) {
38
+ try {
39
+ //very very old ga
40
+ _gaq.push(['_trackEvent', category, action, value]);
41
+ } catch(e) {
42
+ }
43
+ if ( typeof ga != "undefined") {
44
+ try {
45
+ ga('send', {
46
+ hitType : 'event',
47
+ eventCategory : category,
48
+ eventAction : action,
49
+ eventLabel : value
50
+ });
51
+ } catch(e) {
52
+ }
53
+ }
54
+ if (cb) {
55
+ cb.apply(this, arguments);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Track Page view
61
+ * @param value
62
+ * @param cb
63
+ */
64
+ trackPageView (value, cb) {
65
+ try {
66
+ //Very very old ga
67
+ _gaq.push(['_trackPageview', value]);
68
+ } catch(e) {
69
+ }
70
+
71
+ if ( typeof ga != "undefined") {
72
+ try {
73
+ ga('send',
74
+ {hitType: 'pageview',
75
+ page: value
76
+ });
77
+ } catch(e) {}
78
+ }
79
+
80
+ if (cb) {
81
+ cb.apply(this, arguments);
82
+ }
83
+ }
84
+
85
+ }