@customviews-js/customviews 1.1.2 → 1.1.4

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @customviews-js/customviews v1.1.2
2
+ * @customviews-js/customviews v1.1.4
3
3
  * (c) 2025 Chan Ger Teck
4
4
  * Released under the MIT License.
5
5
  */
@@ -124,14 +124,16 @@ class URLStateManager {
124
124
  return url.toString();
125
125
  }
126
126
  /**
127
- * Encode state into URL-safe string
127
+ * Encode state into URL-safe string (Toggles and Tabs only currently)
128
128
  */
129
129
  static encodeState(state) {
130
130
  try {
131
131
  // Create a compact representation
132
- const compact = {
133
- t: state.toggles
134
- };
132
+ const compact = {};
133
+ // Add toggles if present and non-empty
134
+ if (state.toggles && state.toggles.length > 0) {
135
+ compact.t = state.toggles;
136
+ }
135
137
  // Add tab groups if present
136
138
  if (state.tabs && Object.keys(state.tabs).length > 0) {
137
139
  compact.g = Object.entries(state.tabs);
@@ -157,7 +159,7 @@ class URLStateManager {
157
159
  }
158
160
  }
159
161
  /**
160
- * Decode custom state from URL parameter
162
+ * Decode custom state from URL parameter (Toggles and Tabs only currently)
161
163
  */
162
164
  static decodeState(encoded) {
163
165
  try {
@@ -182,10 +184,12 @@ class URLStateManager {
182
184
  if (!compact || typeof compact !== 'object') {
183
185
  throw new Error('Invalid compact state structure');
184
186
  }
187
+ // Reconstruct State from compact format
188
+ // Reconstruct Toggles
185
189
  const state = {
186
190
  toggles: Array.isArray(compact.t) ? compact.t : []
187
191
  };
188
- // Reconstruct tabs from compact format
192
+ // Reconstruct Tabs
189
193
  if (Array.isArray(compact.g)) {
190
194
  state.tabs = {};
191
195
  for (const [groupId, tabId] of compact.g) {
@@ -275,8 +279,12 @@ function ensureFontAwesomeInjected() {
275
279
  document.head.appendChild(link);
276
280
  }
277
281
  function replaceIconShortcodes(text) {
278
- // Handle Font Awesome shortcodes
279
- return text.replace(/:fa-([\w-]+):/g, (_, icon) => `<i class="fa fa-${icon}"></i>`);
282
+ // Matches :fa-*, :fas-*, :fab-* etc.
283
+ return text.replace(/:(fa[b|s|r]?)-([\w-]+):/g, (_, style, icon) => {
284
+ // style = fa, fas, fab, far, etc.
285
+ // Default to "fa" if only "fa-" is given
286
+ return `<i class="${style} fa-${icon}"></i>`;
287
+ });
280
288
  }
281
289
  /** --- Basic renderers --- */
282
290
  function renderImage(el, asset) {
@@ -868,7 +876,7 @@ class CustomViewsCore {
868
876
  this.rootEl = opt.rootEl || document.body;
869
877
  this.persistenceManager = new PersistenceManager();
870
878
  this.visibilityManager = new VisibilityManager();
871
- this.showUrlEnabled = opt.showUrl ?? true;
879
+ this.showUrlEnabled = opt.showUrl ?? false;
872
880
  this.lastAppliedState = this.cloneState(this.config?.defaultState);
873
881
  }
874
882
  getConfig() {
@@ -1068,9 +1076,9 @@ class CustomViewsCore {
1068
1076
  });
1069
1077
  }
1070
1078
  cloneState(state) {
1071
- const toggles = state?.toggles ? [...state.toggles] : [];
1072
- const tabs = state?.tabs ? { ...state.tabs } : undefined;
1073
- return tabs ? { toggles, tabs } : { toggles };
1079
+ if (!state)
1080
+ return {};
1081
+ return JSON.parse(JSON.stringify(state));
1074
1082
  }
1075
1083
  getTrackedStateSnapshot() {
1076
1084
  if (this.lastAppliedState) {
@@ -1079,7 +1087,7 @@ class CustomViewsCore {
1079
1087
  if (this.config) {
1080
1088
  return this.cloneState(this.config.defaultState);
1081
1089
  }
1082
- return { toggles: [] };
1090
+ return {};
1083
1091
  }
1084
1092
  }
1085
1093
 
@@ -1170,8 +1178,14 @@ class CustomViews {
1170
1178
  const baseURL = opts.baseURL || '';
1171
1179
  if (opts.assetsJsonPath) {
1172
1180
  const assetsPath = prependBaseUrl(opts.assetsJsonPath, baseURL);
1173
- const assetsJson = await (await fetch(assetsPath)).json();
1174
- assetsManager = new AssetsManager(assetsJson, baseURL);
1181
+ try {
1182
+ const assetsJson = await (await fetch(assetsPath)).json();
1183
+ assetsManager = new AssetsManager(assetsJson, baseURL);
1184
+ }
1185
+ catch (error) {
1186
+ console.error(`[CustomViews] Failed to load assets JSON from ${assetsPath}:`, error);
1187
+ assetsManager = new AssetsManager({}, baseURL);
1188
+ }
1175
1189
  }
1176
1190
  else {
1177
1191
  assetsManager = new AssetsManager({}, baseURL);
@@ -1184,7 +1198,7 @@ class CustomViews {
1184
1198
  else {
1185
1199
  console.error("No config provided, using minimal default config");
1186
1200
  // Create a minimal default config
1187
- config = { allToggles: [], defaultState: { toggles: [] } };
1201
+ config = { allToggles: [], defaultState: {} };
1188
1202
  }
1189
1203
  const coreOptions = {
1190
1204
  assetsManager,
@@ -1645,9 +1659,61 @@ const WIDGET_STYLES = `
1645
1659
  margin: 0;
1646
1660
  }
1647
1661
 
1648
- .cv-custom-toggle-checkbox {
1649
- margin-right: 8px;
1650
- width: auto;
1662
+ .cv-toggle-switch {
1663
+ position: relative;
1664
+ width: 44px;
1665
+ height: 24px;
1666
+ background: #ccc;
1667
+ border-radius: 12px;
1668
+ margin-right: 12px;
1669
+ cursor: pointer;
1670
+ transition: background-color 0.3s ease;
1671
+ flex-shrink: 0;
1672
+ }
1673
+
1674
+ .cv-toggle-switch:hover {
1675
+ background: #bbb;
1676
+ }
1677
+
1678
+ .cv-toggle-switch.cv-toggle-active {
1679
+ background: #007bff;
1680
+ }
1681
+
1682
+ .cv-toggle-switch.cv-toggle-active:hover {
1683
+ background: #0056b3;
1684
+ }
1685
+
1686
+ .cv-toggle-handle {
1687
+ position: absolute;
1688
+ top: 2px;
1689
+ left: 2px;
1690
+ width: 20px;
1691
+ height: 20px;
1692
+ background: white;
1693
+ border-radius: 50%;
1694
+ transition: transform 0.3s ease;
1695
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
1696
+ }
1697
+
1698
+ .cv-toggle-switch.cv-toggle-active .cv-toggle-handle {
1699
+ transform: translateX(20px);
1700
+ }
1701
+
1702
+ /* Dark theme toggle switch styles */
1703
+ .cv-widget-theme-dark .cv-toggle-switch {
1704
+ background: #4a5568;
1705
+ }
1706
+
1707
+ .cv-widget-theme-dark .cv-toggle-switch:hover {
1708
+ background: #5a6578;
1709
+ }
1710
+
1711
+ .cv-widget-theme-dark .cv-toggle-switch.cv-toggle-active {
1712
+ background: #63b3ed;
1713
+ }
1714
+
1715
+ .cv-widget-theme-dark .cv-toggle-switch.cv-toggle-active:hover {
1716
+ background: #4299e1;
1651
1717
  }
1652
1718
 
1653
1719
  .cv-tab-groups {
@@ -1876,11 +1942,11 @@ class CustomViewsWidget {
1876
1942
  position: options.position || 'middle-left',
1877
1943
  theme: options.theme || 'light',
1878
1944
  showReset: options.showReset ?? true,
1879
- title: options.title || 'Custom Views',
1945
+ title: options.title || 'Customize View',
1880
1946
  description: options.description || 'Toggle different content sections to customize your view. Changes are applied instantly and the URL will be updated for sharing.',
1881
1947
  showWelcome: options.showWelcome ?? false,
1882
- welcomeTitle: options.welcomeTitle || 'This website uses Custom Views',
1883
- welcomeMessage: options.welcomeMessage || 'This site uses Custom Views to let you personalize your experience. Use the widget on the side (⚙) to show or hide different content sections based on your preferences. Your selections will be saved and can be shared via URL.',
1948
+ welcomeTitle: options.welcomeTitle || 'Site Customization',
1949
+ welcomeMessage: options.welcomeMessage || 'This site is powered by Custom Views. Use the widget on the side (⚙) to customize your experience. Your preferences will be saved and can be shared via URL.<br><br>Learn more at <a href="https://github.com/customviews-js/customviews" target="_blank">customviews GitHub</a>.',
1884
1950
  showTabGroups: options.showTabGroups ?? true
1885
1951
  };
1886
1952
  // No external state manager to initialize
@@ -1963,7 +2029,9 @@ class CustomViewsWidget {
1963
2029
  ? toggles.map(toggle => `
1964
2030
  <div class="cv-custom-state-toggle">
1965
2031
  <label>
1966
- <input type="checkbox" class="cv-custom-toggle-checkbox" data-toggle="${toggle}" />
2032
+ <div class="cv-toggle-switch" data-toggle="${toggle}">
2033
+ <div class="cv-toggle-handle"></div>
2034
+ </div>
1967
2035
  ${this.formatToggleName(toggle)}
1968
2036
  </label>
1969
2037
  </div>
@@ -1972,12 +2040,34 @@ class CustomViewsWidget {
1972
2040
  // Get tab groups
1973
2041
  const tabGroups = this.core.getTabGroups();
1974
2042
  let tabGroupsHTML = '';
2043
+ // Check if any tab group or tab labels contain Font Awesome shortcodes
2044
+ let hasFontAwesomeShortcodes = false;
2045
+ if (this.options.showTabGroups && tabGroups && tabGroups.length > 0) {
2046
+ for (const group of tabGroups) {
2047
+ if (group.label && /:fa-[\w-]+:/.test(group.label)) {
2048
+ hasFontAwesomeShortcodes = true;
2049
+ break;
2050
+ }
2051
+ for (const tab of group.tabs) {
2052
+ if (tab.label && /:fa-[\w-]+:/.test(tab.label)) {
2053
+ hasFontAwesomeShortcodes = true;
2054
+ break;
2055
+ }
2056
+ }
2057
+ if (hasFontAwesomeShortcodes)
2058
+ break;
2059
+ }
2060
+ }
2061
+ // Inject Font Awesome only if shortcodes are found
2062
+ if (hasFontAwesomeShortcodes) {
2063
+ ensureFontAwesomeInjected();
2064
+ }
1975
2065
  if (this.options.showTabGroups && tabGroups && tabGroups.length > 0) {
1976
2066
  const tabGroupControls = tabGroups.map(group => {
1977
- const options = group.tabs.map(tab => `<option value="${tab.id}">${tab.label || tab.id}</option>`).join('');
2067
+ const options = group.tabs.map(tab => `<option value="${tab.id}">${replaceIconShortcodes(tab.label || tab.id)}</option>`).join('');
1978
2068
  return `
1979
2069
  <div class="cv-tab-group-control">
1980
- <label for="tab-group-${group.id}">${group.label || group.id}</label>
2070
+ <label for="tab-group-${group.id}">${replaceIconShortcodes(group.label || group.id)}</label>
1981
2071
  <select id="tab-group-${group.id}" class="cv-tab-group-select" data-group-id="${group.id}">
1982
2072
  ${options}
1983
2073
  </select>
@@ -1994,7 +2084,7 @@ class CustomViewsWidget {
1994
2084
  this.modal.innerHTML = `
1995
2085
  <div class="cv-widget-modal cv-custom-state-modal">
1996
2086
  <div class="cv-widget-modal-header">
1997
- <h3>Customize View</h3>
2087
+ <h3>${this.options.title}</h3>
1998
2088
  <button class="cv-widget-modal-close" aria-label="Close modal">×</button>
1999
2089
  </div>
2000
2090
  <div class="cv-widget-modal-content">
@@ -2049,10 +2139,11 @@ class CustomViewsWidget {
2049
2139
  this.loadCurrentStateIntoForm();
2050
2140
  });
2051
2141
  }
2052
- // Listen to toggle checkboxes
2053
- const toggleCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2054
- toggleCheckboxes.forEach(checkbox => {
2055
- checkbox.addEventListener('change', () => {
2142
+ // Listen to toggle switches
2143
+ const toggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2144
+ toggleSwitches.forEach(toggleSwitch => {
2145
+ toggleSwitch.addEventListener('click', () => {
2146
+ toggleSwitch.classList.toggle('cv-toggle-active');
2056
2147
  const state = this.getCurrentCustomStateFromModal();
2057
2148
  this.core.applyState(state);
2058
2149
  });
@@ -2101,14 +2192,14 @@ class CustomViewsWidget {
2101
2192
  */
2102
2193
  getCurrentCustomStateFromModal() {
2103
2194
  if (!this.modal) {
2104
- return { toggles: [] };
2195
+ return {};
2105
2196
  }
2106
2197
  // Collect toggle values
2107
2198
  const toggles = [];
2108
- const toggleCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2109
- toggleCheckboxes.forEach(checkbox => {
2110
- const toggle = checkbox.dataset.toggle;
2111
- if (toggle && checkbox.checked) {
2199
+ const toggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2200
+ toggleSwitches.forEach(toggleSwitch => {
2201
+ const toggle = toggleSwitch.dataset.toggle;
2202
+ if (toggle && toggleSwitch.classList.contains('cv-toggle-active')) {
2112
2203
  toggles.push(toggle);
2113
2204
  }
2114
2205
  });
@@ -2121,7 +2212,11 @@ class CustomViewsWidget {
2121
2212
  tabs[groupId] = select.value;
2122
2213
  }
2123
2214
  });
2124
- return Object.keys(tabs).length > 0 ? { toggles, tabs } : { toggles };
2215
+ const result = { toggles };
2216
+ if (Object.keys(tabs).length > 0) {
2217
+ result.tabs = tabs;
2218
+ }
2219
+ return result;
2125
2220
  }
2126
2221
  /**
2127
2222
  * Copy shareable URL to clipboard
@@ -2141,20 +2236,16 @@ class CustomViewsWidget {
2141
2236
  return;
2142
2237
  // Get currently active toggles (from custom state or default configuration)
2143
2238
  const activeToggles = this.core.getCurrentActiveToggles();
2144
- // First, uncheck all checkboxes
2145
- const allCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2146
- allCheckboxes.forEach(checkbox => {
2147
- checkbox.checked = false;
2148
- checkbox.disabled = false;
2149
- checkbox.parentElement?.removeAttribute('aria-hidden');
2239
+ // First, deactivate all toggle switches
2240
+ const allToggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2241
+ allToggleSwitches.forEach(toggleSwitch => {
2242
+ toggleSwitch.classList.remove('cv-toggle-active');
2150
2243
  });
2151
- // Then check the ones that should be active
2244
+ // Then activate the ones that should be active
2152
2245
  activeToggles.forEach(toggle => {
2153
- const checkbox = this.modal?.querySelector(`[data-toggle="${toggle}"]`);
2154
- if (checkbox) {
2155
- if (!checkbox.disabled) {
2156
- checkbox.checked = true;
2157
- }
2246
+ const toggleSwitch = this.modal?.querySelector(`[data-toggle="${toggle}"]`);
2247
+ if (toggleSwitch) {
2248
+ toggleSwitch.classList.add('cv-toggle-active');
2158
2249
  }
2159
2250
  });
2160
2251
  // Load tab group selections
@@ -2207,7 +2298,7 @@ class CustomViewsWidget {
2207
2298
  </div>
2208
2299
  <div class="cv-widget-modal-content">
2209
2300
  <div class="cv-welcome-content">
2210
- <p>${this.options.welcomeMessage}</p>
2301
+ <p style="text-align: justify;">${this.options.welcomeMessage}</p>
2211
2302
 
2212
2303
  <div class="cv-welcome-widget-preview">
2213
2304
  <div class="cv-welcome-widget-icon">⚙</div>
@@ -2328,13 +2419,8 @@ function initializeFromScript() {
2328
2419
  console.warn(`[CustomViews] Config file not found at ${fullConfigPath}. Using defaults.`);
2329
2420
  // Provide minimal default config structure
2330
2421
  configFile = {
2331
- config: {
2332
- allToggles: [],
2333
- defaultState: { toggles: [] }
2334
- },
2335
- widget: {
2336
- enabled: true
2337
- }
2422
+ config: { allToggles: [], defaultState: {} },
2423
+ widget: { enabled: true }
2338
2424
  };
2339
2425
  }
2340
2426
  else {