viewlogic 1.2.4 → 1.2.5

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/README.md CHANGED
@@ -187,33 +187,23 @@ export default {
187
187
 
188
188
  **src/layouts/default.html** (optional)
189
189
  ```html
190
- <!DOCTYPE html>
191
- <html>
192
- <head>
193
- <title>My ViewLogic App</title>
194
- <meta charset="utf-8">
195
- <meta name="viewport" content="width=device-width, initial-scale=1">
196
- </head>
197
- <body>
198
- <header class="navbar">
199
- <h1>My App</h1>
200
- <nav>
201
- <a href="#/home">Home</a>
202
- <a href="#/about">About</a>
203
- </nav>
204
- </header>
205
-
206
- <main class="main-content">
207
- <div class="container">
208
- {{ content }}
209
- </div>
210
- </main>
211
-
212
- <footer>
213
- <p>&copy; 2024 My ViewLogic App</p>
214
- </footer>
215
- </body>
216
- </html>
190
+ <header class="navbar">
191
+ <h1>My App</h1>
192
+ <nav>
193
+ <a href="#/home">Home</a>
194
+ <a href="#/about">About</a>
195
+ </nav>
196
+ </header>
197
+
198
+ <main class="main-content">
199
+ <div class="container">
200
+ {{ content }}
201
+ </div>
202
+ </main>
203
+
204
+ <footer>
205
+ <p>&copy; 2024 My ViewLogic App</p>
206
+ </footer>
217
207
  ```
218
208
 
219
209
  ### Query Parameter Example
@@ -357,7 +347,8 @@ if (this.$state.has('user')) {
357
347
  // Watch for changes (reactive)
358
348
  this.$state.watch('user', (newValue, oldValue) => {
359
349
  console.log('User changed:', newValue);
360
- this.updateUI();
350
+ // Update component data to trigger reactivity
351
+ this.currentUser = newValue;
361
352
  });
362
353
 
363
354
  // Bulk updates
@@ -389,8 +380,7 @@ this.setToken('jwt-token-here');
389
380
 
390
381
  // Login with options
391
382
  this.setToken('jwt-token', {
392
- storage: 'localStorage', // 'localStorage', 'sessionStorage', 'cookie'
393
- skipValidation: false // Skip JWT validation
383
+ storage: 'localStorage' // 'localStorage', 'sessionStorage', 'cookie'
394
384
  });
395
385
 
396
386
  // Get current token
@@ -685,8 +675,9 @@ const router = new ViewLogicRouter({
685
675
  maxCacheSize: 100, // Maximum number of cached items
686
676
 
687
677
  // API settings
688
- apiBaseURL: '/api', // Base URL for API requests
689
- apiTimeout: 10000, // Request timeout in milliseconds
678
+ apiBaseURL: 'https://api.example.com/v1', // Base URL for API requests
679
+ requestTimeout: 30000, // Form submission timeout in milliseconds (30 seconds)
680
+ uploadTimeout: 300000, // File upload timeout in milliseconds (5 minutes)
690
681
 
691
682
  // Development settings
692
683
  environment: 'development', // 'development' or 'production'
@@ -355,8 +355,7 @@ var AuthManager = class {
355
355
  checkAuthFunction: options.checkAuthFunction || null,
356
356
  redirectAfterLogin: options.redirectAfterLogin || "home",
357
357
  authCookieName: options.authCookieName || "authToken",
358
- authStorage: options.authStorage || "localStorage",
359
- authSkipValidation: options.authSkipValidation || false
358
+ authStorage: options.authStorage || "localStorage"
360
359
  };
361
360
  this.router = router;
362
361
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -510,11 +509,10 @@ var AuthManager = class {
510
509
  }
511
510
  const {
512
511
  storage = this.config.authStorage,
513
- cookieOptions = this.config.authCookieOptions,
514
- skipValidation = this.config.authSkipValidation
512
+ cookieOptions = this.config.authCookieOptions
515
513
  } = options;
516
514
  try {
517
- if (!skipValidation && !this.isTokenValid(token)) {
515
+ if (!this.isTokenValid(token)) {
518
516
  this.log("warn", "\u274C Token is expired or invalid");
519
517
  return false;
520
518
  }
@@ -541,7 +539,7 @@ var AuthManager = class {
541
539
  });
542
540
  return true;
543
541
  } catch (error) {
544
- this.log("Failed to set token:", error);
542
+ this.log("error", "Failed to set token:", error);
545
543
  return false;
546
544
  }
547
545
  }
@@ -592,14 +590,14 @@ var AuthManager = class {
592
590
  break;
593
591
  }
594
592
  this.emitAuthEvent("token_removed", { storage });
595
- this.log(`Token removed from: ${storage}`);
593
+ this.log("debug", `Token removed from: ${storage}`);
596
594
  }
597
595
  /**
598
596
  * 로그인 성공 처리
599
597
  */
600
598
  loginSuccess(targetRoute = null) {
601
599
  const redirectRoute = targetRoute || this.config.redirectAfterLogin;
602
- this.log(`\u{1F389} Login success, redirecting to: ${redirectRoute}`);
600
+ this.log("info", `\u{1F389} Login success, redirecting to: ${redirectRoute}`);
603
601
  this.emitAuthEvent("login_success", { targetRoute: redirectRoute });
604
602
  if (this.router && typeof this.router.navigateTo === "function") {
605
603
  this.router.navigateTo(redirectRoute);
@@ -610,7 +608,7 @@ var AuthManager = class {
610
608
  * 로그아웃 처리
611
609
  */
612
610
  logout() {
613
- this.log("\u{1F44B} Logging out user");
611
+ this.log("info", "\u{1F44B} Logging out user");
614
612
  this.removeAccessToken();
615
613
  this.emitAuthEvent("logout", {});
616
614
  if (this.router && typeof this.router.navigateTo === "function") {
@@ -635,11 +633,11 @@ var AuthManager = class {
635
633
  try {
636
634
  listener(data);
637
635
  } catch (error) {
638
- this.log("Event listener error:", error);
636
+ this.log("error", "Event listener error:", error);
639
637
  }
640
638
  });
641
639
  }
642
- this.log(`\u{1F514} Auth event emitted: ${eventType}`, data);
640
+ this.log("debug", `\u{1F514} Auth event emitted: ${eventType}`, data);
643
641
  }
644
642
  /**
645
643
  * 이벤트 리스너 등록
@@ -812,7 +810,7 @@ var CacheManager = class {
812
810
  patterns.forEach((pattern) => {
813
811
  totalInvalidated += this.deleteByPattern(pattern);
814
812
  });
815
- this.log(`\u{1F504} Deleted component cache for route: ${routeName} (${totalInvalidated} entries)`);
813
+ this.log("debug", `\u{1F504} Deleted component cache for route: ${routeName} (${totalInvalidated} entries)`);
816
814
  return totalInvalidated;
817
815
  }
818
816
  /**
@@ -824,7 +822,7 @@ var CacheManager = class {
824
822
  componentPatterns.forEach((pattern) => {
825
823
  totalCleared += this.deleteByPattern(pattern);
826
824
  });
827
- this.log(`\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
825
+ this.log("debug", `\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
828
826
  return totalCleared;
829
827
  }
830
828
  /**
@@ -835,7 +833,7 @@ var CacheManager = class {
835
833
  this.cache.clear();
836
834
  this.cacheTimestamps.clear();
837
835
  this.lruOrder = [];
838
- this.log(`\u{1F525} Cleared all cache (${size} entries)`);
836
+ this.log("debug", `\u{1F525} Cleared all cache (${size} entries)`);
839
837
  return size;
840
838
  }
841
839
  /**
@@ -860,7 +858,7 @@ var CacheManager = class {
860
858
  }
861
859
  });
862
860
  if (expiredKeys.length > 0) {
863
- this.log(`\u23F1\uFE0F Cleaned ${expiredKeys.length} expired cache entries`);
861
+ this.log("debug", `\u23F1\uFE0F Cleaned ${expiredKeys.length} expired cache entries`);
864
862
  }
865
863
  return expiredKeys.length;
866
864
  }
@@ -952,7 +950,7 @@ var CacheManager = class {
952
950
  this.cleanupInterval = setInterval(() => {
953
951
  this.cleanExpired();
954
952
  }, interval);
955
- this.log(`\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
953
+ this.log("debug", `\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
956
954
  }
957
955
  /**
958
956
  * 자동 정리 중지
@@ -1174,11 +1172,8 @@ var QueryManager = class {
1174
1172
  var FormHandler = class {
1175
1173
  constructor(router, options = {}) {
1176
1174
  this.router = router;
1177
- this.config = {
1178
- debug: options.debug || false,
1179
- requestTimeout: options.requestTimeout || 3e4,
1180
- ...options
1181
- };
1175
+ this.requestTimeout = options.requestTimeout || 3e4;
1176
+ this.uploadTimeout = options.uploadTimeout || 3e5;
1182
1177
  this.log("debug", "FormHandler initialized");
1183
1178
  }
1184
1179
  /**
@@ -1205,11 +1200,13 @@ var FormHandler = class {
1205
1200
  startFormSubmission(form) {
1206
1201
  form._isSubmitting = true;
1207
1202
  form._abortController = new AbortController();
1203
+ const hasFile = Array.from(form.elements).some((el) => el.type === "file" && el.files.length > 0);
1204
+ const timeout = hasFile ? this.uploadTimeout : this.requestTimeout;
1208
1205
  form._timeoutId = setTimeout(() => {
1209
1206
  if (form._isSubmitting) {
1210
1207
  this.abortFormSubmission(form);
1211
1208
  }
1212
- }, this.config.requestTimeout);
1209
+ }, timeout);
1213
1210
  }
1214
1211
  /**
1215
1212
  * 폼 제출 완료
@@ -1413,12 +1410,7 @@ var FormHandler = class {
1413
1410
  var ApiHandler = class {
1414
1411
  constructor(router, options = {}) {
1415
1412
  this.router = router;
1416
- this.config = {
1417
- debug: options.debug || false,
1418
- timeout: options.timeout || 1e4,
1419
- retries: options.retries || 1,
1420
- ...options
1421
- };
1413
+ this.apiBaseURL = options.apiBaseURL || "";
1422
1414
  this.log("debug", "ApiHandler initialized");
1423
1415
  }
1424
1416
  /**
@@ -1435,6 +1427,9 @@ var ApiHandler = class {
1435
1427
  async fetchData(dataURL, component = null, options = {}) {
1436
1428
  try {
1437
1429
  let processedURL = this.processURLParameters(dataURL, component);
1430
+ if (this.apiBaseURL && !this.isAbsoluteURL(processedURL)) {
1431
+ processedURL = this.combineURLs(this.apiBaseURL, processedURL);
1432
+ }
1438
1433
  const queryString = this.router.queryManager?.buildQueryString(this.router.queryManager?.getQueryParams()) || "";
1439
1434
  const fullURL = queryString ? `${processedURL}?${queryString}` : processedURL;
1440
1435
  this.log("debug", `Fetching data from: ${fullURL}`);
@@ -1525,15 +1520,15 @@ var ApiHandler = class {
1525
1520
  const paramName = match.slice(1, -1);
1526
1521
  try {
1527
1522
  let paramValue = null;
1528
- if (component.getParam) {
1529
- paramValue = component.getParam(paramName);
1523
+ if (component.$options?.computed?.[paramName]) {
1524
+ paramValue = component[paramName];
1530
1525
  }
1531
1526
  if (paramValue === null || paramValue === void 0) {
1532
1527
  paramValue = component[paramName];
1533
1528
  }
1534
1529
  if (paramValue === null || paramValue === void 0) {
1535
- if (component.$options?.computed?.[paramName]) {
1536
- paramValue = component[paramName];
1530
+ if (component.getParam) {
1531
+ paramValue = component.getParam(paramName);
1537
1532
  }
1538
1533
  }
1539
1534
  if (paramValue === null || paramValue === void 0) {
@@ -1587,6 +1582,20 @@ var ApiHandler = class {
1587
1582
  fetchMultipleData: (dataConfig) => this.fetchMultipleData(dataConfig, component)
1588
1583
  };
1589
1584
  }
1585
+ /**
1586
+ * 절대 URL인지 확인
1587
+ */
1588
+ isAbsoluteURL(url) {
1589
+ return /^https?:\/\//.test(url) || url.startsWith("//");
1590
+ }
1591
+ /**
1592
+ * 두 URL을 조합
1593
+ */
1594
+ combineURLs(baseURL, relativeURL) {
1595
+ const cleanBase = baseURL.replace(/\/$/, "");
1596
+ const cleanRelative = relativeURL.startsWith("/") ? relativeURL : `/${relativeURL}`;
1597
+ return `${cleanBase}${cleanRelative}`;
1598
+ }
1590
1599
  /**
1591
1600
  * 정리 (메모리 누수 방지)
1592
1601
  */
@@ -1602,7 +1611,6 @@ var ComponentLoader = class {
1602
1611
  this.config = {
1603
1612
  componentsPath: options.componentsPath || "/components",
1604
1613
  // srcPath 기준 상대 경로
1605
- debug: options.debug || false,
1606
1614
  environment: options.environment || "development",
1607
1615
  ...options
1608
1616
  };
@@ -1873,8 +1881,7 @@ var RouteLoader = class {
1873
1881
  // 프로덕션 라우트 경로
1874
1882
  environment: options.environment || "development",
1875
1883
  useLayout: options.useLayout !== false,
1876
- defaultLayout: options.defaultLayout || "default",
1877
- debug: options.debug || false
1884
+ defaultLayout: options.defaultLayout || "default"
1878
1885
  };
1879
1886
  this.router = router;
1880
1887
  this.formHandler = new FormHandler(router, this.config);
@@ -2189,9 +2196,7 @@ ${template}`;
2189
2196
  var ErrorHandler = class {
2190
2197
  constructor(router, options = {}) {
2191
2198
  this.config = {
2192
- enableErrorReporting: options.enableErrorReporting !== false,
2193
- debug: options.debug || false,
2194
- logLevel: options.logLevel || (options.debug ? "debug" : "info"),
2199
+ logLevel: options.logLevel || "info",
2195
2200
  environment: options.environment || "development"
2196
2201
  };
2197
2202
  this.router = router;
@@ -2221,9 +2226,7 @@ var ErrorHandler = class {
2221
2226
  errorMessage = "\uD398\uC774\uC9C0\uC5D0 \uC811\uADFC\uD560 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
2222
2227
  }
2223
2228
  this.debug("ErrorHandler", `\uC5D0\uB7EC \uCF54\uB4DC \uACB0\uC815: ${errorCode} (\uB77C\uC6B0\uD2B8: ${routeName})`);
2224
- if (this.config.enableErrorReporting) {
2225
- this.reportError(routeName, error, errorCode);
2226
- }
2229
+ this.reportError(routeName, error, errorCode);
2227
2230
  try {
2228
2231
  if (errorCode === 404) {
2229
2232
  await this.load404Page();
@@ -2394,7 +2397,7 @@ var ErrorHandler = class {
2394
2397
  if (typeof level !== "string" || !this.logLevels.hasOwnProperty(level)) {
2395
2398
  args = [component, ...args];
2396
2399
  component = level;
2397
- level = this.config.debug ? "debug" : "info";
2400
+ level = "info";
2398
2401
  }
2399
2402
  const currentLevelValue = this.logLevels[this.config.logLevel] || this.logLevels.info;
2400
2403
  const messageLevelValue = this.logLevels[level] || this.logLevels.info;
@@ -2635,6 +2638,9 @@ var ViewLogicRouter = class {
2635
2638
  i18nPath: "/i18n",
2636
2639
  // 다국어 파일 경로
2637
2640
  logLevel: "info",
2641
+ apiBaseURL: "",
2642
+ requestTimeout: 3e4,
2643
+ uploadTimeout: 3e5,
2638
2644
  authEnabled: false,
2639
2645
  loginRoute: "login",
2640
2646
  protectedRoutes: [],
@@ -2643,8 +2649,7 @@ var ViewLogicRouter = class {
2643
2649
  checkAuthFunction: null,
2644
2650
  redirectAfterLogin: "home",
2645
2651
  authCookieName: "authToken",
2646
- authStorage: "localStorage",
2647
- authSkipValidation: false
2652
+ authStorage: "localStorage"
2648
2653
  };
2649
2654
  const config = { ...defaults, ...options };
2650
2655
  config.srcPath = this.resolvePath(config.srcPath, config.basePath);
@@ -2653,49 +2658,42 @@ var ViewLogicRouter = class {
2653
2658
  return config;
2654
2659
  }
2655
2660
  /**
2656
- * 통합 경로 해결 - 서브폴더 배포 basePath 지원
2661
+ * 경로를 안전하게 조합 정규화 (기본 유틸리티)
2662
+ * @param {string} basePath - 기본 경로
2663
+ * @param {string} relativePath - 상대 경로
2664
+ * @returns {string} 조합된 경로 (예: '/examples' + '/about' → '/examples/about')
2665
+ */
2666
+ combinePaths(basePath, relativePath) {
2667
+ if (!basePath || basePath === "/") {
2668
+ return relativePath.replace(/\/+/g, "/");
2669
+ }
2670
+ const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
2671
+ const cleanRelative = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
2672
+ const combined = `${cleanBase}${cleanRelative}`;
2673
+ return combined.replace(/\/+/g, "/");
2674
+ }
2675
+ /**
2676
+ * 상대 경로를 완전한 절대 URL로 변환 (파일 시스템 전용)
2677
+ * @param {string} path - 변환할 경로
2678
+ * @param {string} basePath - 기본 경로 (옵션)
2679
+ * @returns {string} 완전한 절대 URL (예: 'http://localhost:3000/examples/about')
2657
2680
  */
2658
2681
  resolvePath(path, basePath = null) {
2682
+ if (basePath === null) {
2683
+ basePath = this.config?.basePath || "/";
2684
+ }
2659
2685
  const currentOrigin = window.location.origin;
2660
2686
  if (path.startsWith("http")) {
2661
2687
  return path;
2662
2688
  }
2663
2689
  if (path.startsWith("/")) {
2664
- if (basePath && basePath !== "/") {
2665
- const cleanBasePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
2666
- const cleanPath = path.startsWith("/") ? path : `/${path}`;
2667
- const fullPath = `${cleanBasePath}${cleanPath}`;
2668
- const fullUrl2 = `${currentOrigin}${fullPath}`;
2669
- return fullUrl2.replace(/([^:])\/{2,}/g, "$1/");
2670
- }
2671
- return `${currentOrigin}${path}`;
2690
+ const combinedPath = this.combinePaths(basePath, path);
2691
+ return `${currentOrigin}${combinedPath}`;
2672
2692
  }
2673
2693
  const currentPathname = window.location.pathname;
2674
2694
  const currentBase = currentPathname.endsWith("/") ? currentPathname : currentPathname.substring(0, currentPathname.lastIndexOf("/") + 1);
2675
- const resolvedPath = this.normalizePath(currentBase + path);
2676
- const fullUrl = `${currentOrigin}${resolvedPath}`;
2677
- return fullUrl.replace(/([^:])\/{2,}/g, "$1/");
2678
- }
2679
- /**
2680
- * URL 경로 정규화 (이중 슬래시 제거 및 ../, ./ 처리)
2681
- */
2682
- normalizePath(path) {
2683
- path = path.replace(/\/+/g, "/");
2684
- const parts = path.split("/").filter((part) => part !== "" && part !== ".");
2685
- const stack = [];
2686
- for (const part of parts) {
2687
- if (part === "..") {
2688
- if (stack.length > 0 && stack[stack.length - 1] !== "..") {
2689
- stack.pop();
2690
- } else if (!path.startsWith("/")) {
2691
- stack.push(part);
2692
- }
2693
- } else {
2694
- stack.push(part);
2695
- }
2696
- }
2697
- const normalized = "/" + stack.join("/");
2698
- return normalized === "/" ? "/" : normalized;
2695
+ const resolvedPath = this.combinePaths(currentBase, path);
2696
+ return `${currentOrigin}${resolvedPath}`;
2699
2697
  }
2700
2698
  /**
2701
2699
  * 로깅 래퍼 메서드
@@ -2936,31 +2934,17 @@ var ViewLogicRouter = class {
2936
2934
  updateURL(route, params = null) {
2937
2935
  const queryParams = params || this.queryManager?.getQueryParams() || {};
2938
2936
  const queryString = this.queryManager?.buildQueryString(queryParams) || "";
2939
- const buildURL = (route2, queryString2, isHash = true) => {
2940
- let base = route2 === "home" ? "/" : `/${route2}`;
2941
- if (!isHash && this.config.basePath && this.config.basePath !== "/") {
2942
- base = `${this.config.basePath}${base}`;
2943
- }
2944
- const url = queryString2 ? `${base}?${queryString2}` : base;
2945
- return isHash ? `#${url}` : url;
2946
- };
2937
+ let base = route === "home" ? "/" : `/${route}`;
2947
2938
  if (this.config.mode === "hash") {
2948
- const newHash = buildURL(route, queryString);
2939
+ const url = queryString ? `${base}?${queryString}` : base;
2940
+ const newHash = `#${url}`;
2949
2941
  if (window.location.hash !== newHash) {
2950
2942
  window.location.hash = newHash;
2951
2943
  }
2952
2944
  } else {
2953
- const newPath = buildURL(route, queryString, false);
2954
- let expectedPath = route === "home" ? "/" : `/${route}`;
2955
- if (this.config.basePath && this.config.basePath !== "/") {
2956
- expectedPath = `${this.config.basePath}${expectedPath}`;
2957
- }
2958
- const isSameRoute = window.location.pathname === expectedPath;
2959
- if (isSameRoute) {
2960
- window.history.replaceState({}, "", newPath);
2961
- } else {
2962
- window.history.pushState({}, "", newPath);
2963
- }
2945
+ base = this.combinePaths(this.config.basePath, base);
2946
+ const url = queryString ? `${base}?${queryString}` : base;
2947
+ window.history.pushState({}, "", url);
2964
2948
  this.handleRouteChange();
2965
2949
  }
2966
2950
  }