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