@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
  */
@@ -130,14 +130,16 @@
130
130
  return url.toString();
131
131
  }
132
132
  /**
133
- * Encode state into URL-safe string
133
+ * Encode state into URL-safe string (Toggles and Tabs only currently)
134
134
  */
135
135
  static encodeState(state) {
136
136
  try {
137
137
  // Create a compact representation
138
- const compact = {
139
- t: state.toggles
140
- };
138
+ const compact = {};
139
+ // Add toggles if present and non-empty
140
+ if (state.toggles && state.toggles.length > 0) {
141
+ compact.t = state.toggles;
142
+ }
141
143
  // Add tab groups if present
142
144
  if (state.tabs && Object.keys(state.tabs).length > 0) {
143
145
  compact.g = Object.entries(state.tabs);
@@ -163,7 +165,7 @@
163
165
  }
164
166
  }
165
167
  /**
166
- * Decode custom state from URL parameter
168
+ * Decode custom state from URL parameter (Toggles and Tabs only currently)
167
169
  */
168
170
  static decodeState(encoded) {
169
171
  try {
@@ -188,10 +190,12 @@
188
190
  if (!compact || typeof compact !== 'object') {
189
191
  throw new Error('Invalid compact state structure');
190
192
  }
193
+ // Reconstruct State from compact format
194
+ // Reconstruct Toggles
191
195
  const state = {
192
196
  toggles: Array.isArray(compact.t) ? compact.t : []
193
197
  };
194
- // Reconstruct tabs from compact format
198
+ // Reconstruct Tabs
195
199
  if (Array.isArray(compact.g)) {
196
200
  state.tabs = {};
197
201
  for (const [groupId, tabId] of compact.g) {
@@ -281,8 +285,12 @@
281
285
  document.head.appendChild(link);
282
286
  }
283
287
  function replaceIconShortcodes(text) {
284
- // Handle Font Awesome shortcodes
285
- return text.replace(/:fa-([\w-]+):/g, (_, icon) => `<i class="fa fa-${icon}"></i>`);
288
+ // Matches :fa-*, :fas-*, :fab-* etc.
289
+ return text.replace(/:(fa[b|s|r]?)-([\w-]+):/g, (_, style, icon) => {
290
+ // style = fa, fas, fab, far, etc.
291
+ // Default to "fa" if only "fa-" is given
292
+ return `<i class="${style} fa-${icon}"></i>`;
293
+ });
286
294
  }
287
295
  /** --- Basic renderers --- */
288
296
  function renderImage(el, asset) {
@@ -874,7 +882,7 @@ ${TAB_STYLES}
874
882
  this.rootEl = opt.rootEl || document.body;
875
883
  this.persistenceManager = new PersistenceManager();
876
884
  this.visibilityManager = new VisibilityManager();
877
- this.showUrlEnabled = opt.showUrl ?? true;
885
+ this.showUrlEnabled = opt.showUrl ?? false;
878
886
  this.lastAppliedState = this.cloneState(this.config?.defaultState);
879
887
  }
880
888
  getConfig() {
@@ -1074,9 +1082,9 @@ ${TAB_STYLES}
1074
1082
  });
1075
1083
  }
1076
1084
  cloneState(state) {
1077
- const toggles = state?.toggles ? [...state.toggles] : [];
1078
- const tabs = state?.tabs ? { ...state.tabs } : undefined;
1079
- return tabs ? { toggles, tabs } : { toggles };
1085
+ if (!state)
1086
+ return {};
1087
+ return JSON.parse(JSON.stringify(state));
1080
1088
  }
1081
1089
  getTrackedStateSnapshot() {
1082
1090
  if (this.lastAppliedState) {
@@ -1085,7 +1093,7 @@ ${TAB_STYLES}
1085
1093
  if (this.config) {
1086
1094
  return this.cloneState(this.config.defaultState);
1087
1095
  }
1088
- return { toggles: [] };
1096
+ return {};
1089
1097
  }
1090
1098
  }
1091
1099
 
@@ -1176,8 +1184,14 @@ ${TAB_STYLES}
1176
1184
  const baseURL = opts.baseURL || '';
1177
1185
  if (opts.assetsJsonPath) {
1178
1186
  const assetsPath = prependBaseUrl(opts.assetsJsonPath, baseURL);
1179
- const assetsJson = await (await fetch(assetsPath)).json();
1180
- assetsManager = new AssetsManager(assetsJson, baseURL);
1187
+ try {
1188
+ const assetsJson = await (await fetch(assetsPath)).json();
1189
+ assetsManager = new AssetsManager(assetsJson, baseURL);
1190
+ }
1191
+ catch (error) {
1192
+ console.error(`[CustomViews] Failed to load assets JSON from ${assetsPath}:`, error);
1193
+ assetsManager = new AssetsManager({}, baseURL);
1194
+ }
1181
1195
  }
1182
1196
  else {
1183
1197
  assetsManager = new AssetsManager({}, baseURL);
@@ -1190,7 +1204,7 @@ ${TAB_STYLES}
1190
1204
  else {
1191
1205
  console.error("No config provided, using minimal default config");
1192
1206
  // Create a minimal default config
1193
- config = { allToggles: [], defaultState: { toggles: [] } };
1207
+ config = { allToggles: [], defaultState: {} };
1194
1208
  }
1195
1209
  const coreOptions = {
1196
1210
  assetsManager,
@@ -1651,9 +1665,61 @@ ${TAB_STYLES}
1651
1665
  margin: 0;
1652
1666
  }
1653
1667
 
1654
- .cv-custom-toggle-checkbox {
1655
- margin-right: 8px;
1656
- width: auto;
1668
+ .cv-toggle-switch {
1669
+ position: relative;
1670
+ width: 44px;
1671
+ height: 24px;
1672
+ background: #ccc;
1673
+ border-radius: 12px;
1674
+ margin-right: 12px;
1675
+ cursor: pointer;
1676
+ transition: background-color 0.3s ease;
1677
+ flex-shrink: 0;
1678
+ }
1679
+
1680
+ .cv-toggle-switch:hover {
1681
+ background: #bbb;
1682
+ }
1683
+
1684
+ .cv-toggle-switch.cv-toggle-active {
1685
+ background: #007bff;
1686
+ }
1687
+
1688
+ .cv-toggle-switch.cv-toggle-active:hover {
1689
+ background: #0056b3;
1690
+ }
1691
+
1692
+ .cv-toggle-handle {
1693
+ position: absolute;
1694
+ top: 2px;
1695
+ left: 2px;
1696
+ width: 20px;
1697
+ height: 20px;
1698
+ background: white;
1699
+ border-radius: 50%;
1700
+ transition: transform 0.3s ease;
1701
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
1702
+ }
1703
+
1704
+ .cv-toggle-switch.cv-toggle-active .cv-toggle-handle {
1705
+ transform: translateX(20px);
1706
+ }
1707
+
1708
+ /* Dark theme toggle switch styles */
1709
+ .cv-widget-theme-dark .cv-toggle-switch {
1710
+ background: #4a5568;
1711
+ }
1712
+
1713
+ .cv-widget-theme-dark .cv-toggle-switch:hover {
1714
+ background: #5a6578;
1715
+ }
1716
+
1717
+ .cv-widget-theme-dark .cv-toggle-switch.cv-toggle-active {
1718
+ background: #63b3ed;
1719
+ }
1720
+
1721
+ .cv-widget-theme-dark .cv-toggle-switch.cv-toggle-active:hover {
1722
+ background: #4299e1;
1657
1723
  }
1658
1724
 
1659
1725
  .cv-tab-groups {
@@ -1882,11 +1948,11 @@ ${TAB_STYLES}
1882
1948
  position: options.position || 'middle-left',
1883
1949
  theme: options.theme || 'light',
1884
1950
  showReset: options.showReset ?? true,
1885
- title: options.title || 'Custom Views',
1951
+ title: options.title || 'Customize View',
1886
1952
  description: options.description || 'Toggle different content sections to customize your view. Changes are applied instantly and the URL will be updated for sharing.',
1887
1953
  showWelcome: options.showWelcome ?? false,
1888
- welcomeTitle: options.welcomeTitle || 'This website uses Custom Views',
1889
- 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.',
1954
+ welcomeTitle: options.welcomeTitle || 'Site Customization',
1955
+ 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>.',
1890
1956
  showTabGroups: options.showTabGroups ?? true
1891
1957
  };
1892
1958
  // No external state manager to initialize
@@ -1969,7 +2035,9 @@ ${TAB_STYLES}
1969
2035
  ? toggles.map(toggle => `
1970
2036
  <div class="cv-custom-state-toggle">
1971
2037
  <label>
1972
- <input type="checkbox" class="cv-custom-toggle-checkbox" data-toggle="${toggle}" />
2038
+ <div class="cv-toggle-switch" data-toggle="${toggle}">
2039
+ <div class="cv-toggle-handle"></div>
2040
+ </div>
1973
2041
  ${this.formatToggleName(toggle)}
1974
2042
  </label>
1975
2043
  </div>
@@ -1978,12 +2046,34 @@ ${TAB_STYLES}
1978
2046
  // Get tab groups
1979
2047
  const tabGroups = this.core.getTabGroups();
1980
2048
  let tabGroupsHTML = '';
2049
+ // Check if any tab group or tab labels contain Font Awesome shortcodes
2050
+ let hasFontAwesomeShortcodes = false;
2051
+ if (this.options.showTabGroups && tabGroups && tabGroups.length > 0) {
2052
+ for (const group of tabGroups) {
2053
+ if (group.label && /:fa-[\w-]+:/.test(group.label)) {
2054
+ hasFontAwesomeShortcodes = true;
2055
+ break;
2056
+ }
2057
+ for (const tab of group.tabs) {
2058
+ if (tab.label && /:fa-[\w-]+:/.test(tab.label)) {
2059
+ hasFontAwesomeShortcodes = true;
2060
+ break;
2061
+ }
2062
+ }
2063
+ if (hasFontAwesomeShortcodes)
2064
+ break;
2065
+ }
2066
+ }
2067
+ // Inject Font Awesome only if shortcodes are found
2068
+ if (hasFontAwesomeShortcodes) {
2069
+ ensureFontAwesomeInjected();
2070
+ }
1981
2071
  if (this.options.showTabGroups && tabGroups && tabGroups.length > 0) {
1982
2072
  const tabGroupControls = tabGroups.map(group => {
1983
- const options = group.tabs.map(tab => `<option value="${tab.id}">${tab.label || tab.id}</option>`).join('');
2073
+ const options = group.tabs.map(tab => `<option value="${tab.id}">${replaceIconShortcodes(tab.label || tab.id)}</option>`).join('');
1984
2074
  return `
1985
2075
  <div class="cv-tab-group-control">
1986
- <label for="tab-group-${group.id}">${group.label || group.id}</label>
2076
+ <label for="tab-group-${group.id}">${replaceIconShortcodes(group.label || group.id)}</label>
1987
2077
  <select id="tab-group-${group.id}" class="cv-tab-group-select" data-group-id="${group.id}">
1988
2078
  ${options}
1989
2079
  </select>
@@ -2000,7 +2090,7 @@ ${TAB_STYLES}
2000
2090
  this.modal.innerHTML = `
2001
2091
  <div class="cv-widget-modal cv-custom-state-modal">
2002
2092
  <div class="cv-widget-modal-header">
2003
- <h3>Customize View</h3>
2093
+ <h3>${this.options.title}</h3>
2004
2094
  <button class="cv-widget-modal-close" aria-label="Close modal">×</button>
2005
2095
  </div>
2006
2096
  <div class="cv-widget-modal-content">
@@ -2055,10 +2145,11 @@ ${TAB_STYLES}
2055
2145
  this.loadCurrentStateIntoForm();
2056
2146
  });
2057
2147
  }
2058
- // Listen to toggle checkboxes
2059
- const toggleCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2060
- toggleCheckboxes.forEach(checkbox => {
2061
- checkbox.addEventListener('change', () => {
2148
+ // Listen to toggle switches
2149
+ const toggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2150
+ toggleSwitches.forEach(toggleSwitch => {
2151
+ toggleSwitch.addEventListener('click', () => {
2152
+ toggleSwitch.classList.toggle('cv-toggle-active');
2062
2153
  const state = this.getCurrentCustomStateFromModal();
2063
2154
  this.core.applyState(state);
2064
2155
  });
@@ -2107,14 +2198,14 @@ ${TAB_STYLES}
2107
2198
  */
2108
2199
  getCurrentCustomStateFromModal() {
2109
2200
  if (!this.modal) {
2110
- return { toggles: [] };
2201
+ return {};
2111
2202
  }
2112
2203
  // Collect toggle values
2113
2204
  const toggles = [];
2114
- const toggleCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2115
- toggleCheckboxes.forEach(checkbox => {
2116
- const toggle = checkbox.dataset.toggle;
2117
- if (toggle && checkbox.checked) {
2205
+ const toggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2206
+ toggleSwitches.forEach(toggleSwitch => {
2207
+ const toggle = toggleSwitch.dataset.toggle;
2208
+ if (toggle && toggleSwitch.classList.contains('cv-toggle-active')) {
2118
2209
  toggles.push(toggle);
2119
2210
  }
2120
2211
  });
@@ -2127,7 +2218,11 @@ ${TAB_STYLES}
2127
2218
  tabs[groupId] = select.value;
2128
2219
  }
2129
2220
  });
2130
- return Object.keys(tabs).length > 0 ? { toggles, tabs } : { toggles };
2221
+ const result = { toggles };
2222
+ if (Object.keys(tabs).length > 0) {
2223
+ result.tabs = tabs;
2224
+ }
2225
+ return result;
2131
2226
  }
2132
2227
  /**
2133
2228
  * Copy shareable URL to clipboard
@@ -2147,20 +2242,16 @@ ${TAB_STYLES}
2147
2242
  return;
2148
2243
  // Get currently active toggles (from custom state or default configuration)
2149
2244
  const activeToggles = this.core.getCurrentActiveToggles();
2150
- // First, uncheck all checkboxes
2151
- const allCheckboxes = this.modal.querySelectorAll('.cv-custom-toggle-checkbox');
2152
- allCheckboxes.forEach(checkbox => {
2153
- checkbox.checked = false;
2154
- checkbox.disabled = false;
2155
- checkbox.parentElement?.removeAttribute('aria-hidden');
2245
+ // First, deactivate all toggle switches
2246
+ const allToggleSwitches = this.modal.querySelectorAll('.cv-toggle-switch');
2247
+ allToggleSwitches.forEach(toggleSwitch => {
2248
+ toggleSwitch.classList.remove('cv-toggle-active');
2156
2249
  });
2157
- // Then check the ones that should be active
2250
+ // Then activate the ones that should be active
2158
2251
  activeToggles.forEach(toggle => {
2159
- const checkbox = this.modal?.querySelector(`[data-toggle="${toggle}"]`);
2160
- if (checkbox) {
2161
- if (!checkbox.disabled) {
2162
- checkbox.checked = true;
2163
- }
2252
+ const toggleSwitch = this.modal?.querySelector(`[data-toggle="${toggle}"]`);
2253
+ if (toggleSwitch) {
2254
+ toggleSwitch.classList.add('cv-toggle-active');
2164
2255
  }
2165
2256
  });
2166
2257
  // Load tab group selections
@@ -2213,7 +2304,7 @@ ${TAB_STYLES}
2213
2304
  </div>
2214
2305
  <div class="cv-widget-modal-content">
2215
2306
  <div class="cv-welcome-content">
2216
- <p>${this.options.welcomeMessage}</p>
2307
+ <p style="text-align: justify;">${this.options.welcomeMessage}</p>
2217
2308
 
2218
2309
  <div class="cv-welcome-widget-preview">
2219
2310
  <div class="cv-welcome-widget-icon">⚙</div>
@@ -2334,13 +2425,8 @@ ${TAB_STYLES}
2334
2425
  console.warn(`[CustomViews] Config file not found at ${fullConfigPath}. Using defaults.`);
2335
2426
  // Provide minimal default config structure
2336
2427
  configFile = {
2337
- config: {
2338
- allToggles: [],
2339
- defaultState: { toggles: [] }
2340
- },
2341
- widget: {
2342
- enabled: true
2343
- }
2428
+ config: { allToggles: [], defaultState: {} },
2429
+ widget: { enabled: true }
2344
2430
  };
2345
2431
  }
2346
2432
  else {