viewlogic 1.2.3 → 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 +372 -450
- package/dist/viewlogic-router.js +107 -123
- package/dist/viewlogic-router.js.map +3 -3
- package/dist/viewlogic-router.min.js +3 -3
- package/dist/viewlogic-router.min.js.map +3 -3
- package/package.json +4 -3
package/dist/viewlogic-router.js
CHANGED
|
@@ -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();
|
|
@@ -387,7 +386,12 @@ var AuthManager = class {
|
|
|
387
386
|
}
|
|
388
387
|
if (typeof this.config.checkAuthFunction === "function") {
|
|
389
388
|
try {
|
|
390
|
-
const
|
|
389
|
+
const route = {
|
|
390
|
+
name: routeName,
|
|
391
|
+
$api: this.router.routeLoader.apiHandler.bindToComponent({}),
|
|
392
|
+
$state: this.router.stateHandler
|
|
393
|
+
};
|
|
394
|
+
const isAuthenticated2 = await this.config.checkAuthFunction(route);
|
|
391
395
|
return {
|
|
392
396
|
allowed: isAuthenticated2,
|
|
393
397
|
reason: isAuthenticated2 ? "custom_auth_success" : "custom_auth_failed",
|
|
@@ -505,11 +509,10 @@ var AuthManager = class {
|
|
|
505
509
|
}
|
|
506
510
|
const {
|
|
507
511
|
storage = this.config.authStorage,
|
|
508
|
-
cookieOptions = this.config.authCookieOptions
|
|
509
|
-
skipValidation = this.config.authSkipValidation
|
|
512
|
+
cookieOptions = this.config.authCookieOptions
|
|
510
513
|
} = options;
|
|
511
514
|
try {
|
|
512
|
-
if (!
|
|
515
|
+
if (!this.isTokenValid(token)) {
|
|
513
516
|
this.log("warn", "\u274C Token is expired or invalid");
|
|
514
517
|
return false;
|
|
515
518
|
}
|
|
@@ -536,7 +539,7 @@ var AuthManager = class {
|
|
|
536
539
|
});
|
|
537
540
|
return true;
|
|
538
541
|
} catch (error) {
|
|
539
|
-
this.log("Failed to set token:", error);
|
|
542
|
+
this.log("error", "Failed to set token:", error);
|
|
540
543
|
return false;
|
|
541
544
|
}
|
|
542
545
|
}
|
|
@@ -587,14 +590,14 @@ var AuthManager = class {
|
|
|
587
590
|
break;
|
|
588
591
|
}
|
|
589
592
|
this.emitAuthEvent("token_removed", { storage });
|
|
590
|
-
this.log(`Token removed from: ${storage}`);
|
|
593
|
+
this.log("debug", `Token removed from: ${storage}`);
|
|
591
594
|
}
|
|
592
595
|
/**
|
|
593
596
|
* 로그인 성공 처리
|
|
594
597
|
*/
|
|
595
598
|
loginSuccess(targetRoute = null) {
|
|
596
599
|
const redirectRoute = targetRoute || this.config.redirectAfterLogin;
|
|
597
|
-
this.log(`\u{1F389} Login success, redirecting to: ${redirectRoute}`);
|
|
600
|
+
this.log("info", `\u{1F389} Login success, redirecting to: ${redirectRoute}`);
|
|
598
601
|
this.emitAuthEvent("login_success", { targetRoute: redirectRoute });
|
|
599
602
|
if (this.router && typeof this.router.navigateTo === "function") {
|
|
600
603
|
this.router.navigateTo(redirectRoute);
|
|
@@ -605,7 +608,7 @@ var AuthManager = class {
|
|
|
605
608
|
* 로그아웃 처리
|
|
606
609
|
*/
|
|
607
610
|
logout() {
|
|
608
|
-
this.log("\u{1F44B} Logging out user");
|
|
611
|
+
this.log("info", "\u{1F44B} Logging out user");
|
|
609
612
|
this.removeAccessToken();
|
|
610
613
|
this.emitAuthEvent("logout", {});
|
|
611
614
|
if (this.router && typeof this.router.navigateTo === "function") {
|
|
@@ -630,11 +633,11 @@ var AuthManager = class {
|
|
|
630
633
|
try {
|
|
631
634
|
listener(data);
|
|
632
635
|
} catch (error) {
|
|
633
|
-
this.log("Event listener error:", error);
|
|
636
|
+
this.log("error", "Event listener error:", error);
|
|
634
637
|
}
|
|
635
638
|
});
|
|
636
639
|
}
|
|
637
|
-
this.log(`\u{1F514} Auth event emitted: ${eventType}`, data);
|
|
640
|
+
this.log("debug", `\u{1F514} Auth event emitted: ${eventType}`, data);
|
|
638
641
|
}
|
|
639
642
|
/**
|
|
640
643
|
* 이벤트 리스너 등록
|
|
@@ -807,7 +810,7 @@ var CacheManager = class {
|
|
|
807
810
|
patterns.forEach((pattern) => {
|
|
808
811
|
totalInvalidated += this.deleteByPattern(pattern);
|
|
809
812
|
});
|
|
810
|
-
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)`);
|
|
811
814
|
return totalInvalidated;
|
|
812
815
|
}
|
|
813
816
|
/**
|
|
@@ -819,7 +822,7 @@ var CacheManager = class {
|
|
|
819
822
|
componentPatterns.forEach((pattern) => {
|
|
820
823
|
totalCleared += this.deleteByPattern(pattern);
|
|
821
824
|
});
|
|
822
|
-
this.log(`\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
|
|
825
|
+
this.log("debug", `\u{1F9FD} Deleted all component caches (${totalCleared} entries)`);
|
|
823
826
|
return totalCleared;
|
|
824
827
|
}
|
|
825
828
|
/**
|
|
@@ -830,7 +833,7 @@ var CacheManager = class {
|
|
|
830
833
|
this.cache.clear();
|
|
831
834
|
this.cacheTimestamps.clear();
|
|
832
835
|
this.lruOrder = [];
|
|
833
|
-
this.log(`\u{1F525} Cleared all cache (${size} entries)`);
|
|
836
|
+
this.log("debug", `\u{1F525} Cleared all cache (${size} entries)`);
|
|
834
837
|
return size;
|
|
835
838
|
}
|
|
836
839
|
/**
|
|
@@ -855,7 +858,7 @@ var CacheManager = class {
|
|
|
855
858
|
}
|
|
856
859
|
});
|
|
857
860
|
if (expiredKeys.length > 0) {
|
|
858
|
-
this.log(`\u23F1\uFE0F Cleaned ${expiredKeys.length} expired cache entries`);
|
|
861
|
+
this.log("debug", `\u23F1\uFE0F Cleaned ${expiredKeys.length} expired cache entries`);
|
|
859
862
|
}
|
|
860
863
|
return expiredKeys.length;
|
|
861
864
|
}
|
|
@@ -947,7 +950,7 @@ var CacheManager = class {
|
|
|
947
950
|
this.cleanupInterval = setInterval(() => {
|
|
948
951
|
this.cleanExpired();
|
|
949
952
|
}, interval);
|
|
950
|
-
this.log(`\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
|
|
953
|
+
this.log("debug", `\u{1F916} Auto cleanup started (interval: ${interval}ms)`);
|
|
951
954
|
}
|
|
952
955
|
/**
|
|
953
956
|
* 자동 정리 중지
|
|
@@ -1169,11 +1172,8 @@ var QueryManager = class {
|
|
|
1169
1172
|
var FormHandler = class {
|
|
1170
1173
|
constructor(router, options = {}) {
|
|
1171
1174
|
this.router = router;
|
|
1172
|
-
this.
|
|
1173
|
-
|
|
1174
|
-
requestTimeout: options.requestTimeout || 3e4,
|
|
1175
|
-
...options
|
|
1176
|
-
};
|
|
1175
|
+
this.requestTimeout = options.requestTimeout || 3e4;
|
|
1176
|
+
this.uploadTimeout = options.uploadTimeout || 3e5;
|
|
1177
1177
|
this.log("debug", "FormHandler initialized");
|
|
1178
1178
|
}
|
|
1179
1179
|
/**
|
|
@@ -1200,11 +1200,13 @@ var FormHandler = class {
|
|
|
1200
1200
|
startFormSubmission(form) {
|
|
1201
1201
|
form._isSubmitting = true;
|
|
1202
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;
|
|
1203
1205
|
form._timeoutId = setTimeout(() => {
|
|
1204
1206
|
if (form._isSubmitting) {
|
|
1205
1207
|
this.abortFormSubmission(form);
|
|
1206
1208
|
}
|
|
1207
|
-
},
|
|
1209
|
+
}, timeout);
|
|
1208
1210
|
}
|
|
1209
1211
|
/**
|
|
1210
1212
|
* 폼 제출 완료
|
|
@@ -1247,9 +1249,9 @@ var FormHandler = class {
|
|
|
1247
1249
|
const form = event.target;
|
|
1248
1250
|
let action = form.getAttribute("action");
|
|
1249
1251
|
const method = form.getAttribute("method") || "POST";
|
|
1250
|
-
const successHandler = form.getAttribute("data-success
|
|
1251
|
-
const errorHandler = form.getAttribute("data-error
|
|
1252
|
-
const loadingHandler = form.getAttribute("data-loading
|
|
1252
|
+
const successHandler = form.getAttribute("data-success");
|
|
1253
|
+
const errorHandler = form.getAttribute("data-error");
|
|
1254
|
+
const loadingHandler = form.getAttribute("data-loading");
|
|
1253
1255
|
const redirectTo = form.getAttribute("data-redirect");
|
|
1254
1256
|
action = this.processActionParams(action, component);
|
|
1255
1257
|
if (!this.validateForm(form, component)) {
|
|
@@ -1408,12 +1410,7 @@ var FormHandler = class {
|
|
|
1408
1410
|
var ApiHandler = class {
|
|
1409
1411
|
constructor(router, options = {}) {
|
|
1410
1412
|
this.router = router;
|
|
1411
|
-
this.
|
|
1412
|
-
debug: options.debug || false,
|
|
1413
|
-
timeout: options.timeout || 1e4,
|
|
1414
|
-
retries: options.retries || 1,
|
|
1415
|
-
...options
|
|
1416
|
-
};
|
|
1413
|
+
this.apiBaseURL = options.apiBaseURL || "";
|
|
1417
1414
|
this.log("debug", "ApiHandler initialized");
|
|
1418
1415
|
}
|
|
1419
1416
|
/**
|
|
@@ -1430,6 +1427,9 @@ var ApiHandler = class {
|
|
|
1430
1427
|
async fetchData(dataURL, component = null, options = {}) {
|
|
1431
1428
|
try {
|
|
1432
1429
|
let processedURL = this.processURLParameters(dataURL, component);
|
|
1430
|
+
if (this.apiBaseURL && !this.isAbsoluteURL(processedURL)) {
|
|
1431
|
+
processedURL = this.combineURLs(this.apiBaseURL, processedURL);
|
|
1432
|
+
}
|
|
1433
1433
|
const queryString = this.router.queryManager?.buildQueryString(this.router.queryManager?.getQueryParams()) || "";
|
|
1434
1434
|
const fullURL = queryString ? `${processedURL}?${queryString}` : processedURL;
|
|
1435
1435
|
this.log("debug", `Fetching data from: ${fullURL}`);
|
|
@@ -1520,15 +1520,15 @@ var ApiHandler = class {
|
|
|
1520
1520
|
const paramName = match.slice(1, -1);
|
|
1521
1521
|
try {
|
|
1522
1522
|
let paramValue = null;
|
|
1523
|
-
if (component
|
|
1524
|
-
paramValue = component
|
|
1523
|
+
if (component.$options?.computed?.[paramName]) {
|
|
1524
|
+
paramValue = component[paramName];
|
|
1525
1525
|
}
|
|
1526
1526
|
if (paramValue === null || paramValue === void 0) {
|
|
1527
1527
|
paramValue = component[paramName];
|
|
1528
1528
|
}
|
|
1529
1529
|
if (paramValue === null || paramValue === void 0) {
|
|
1530
|
-
if (component
|
|
1531
|
-
paramValue = component
|
|
1530
|
+
if (component.getParam) {
|
|
1531
|
+
paramValue = component.getParam(paramName);
|
|
1532
1532
|
}
|
|
1533
1533
|
}
|
|
1534
1534
|
if (paramValue === null || paramValue === void 0) {
|
|
@@ -1582,6 +1582,20 @@ var ApiHandler = class {
|
|
|
1582
1582
|
fetchMultipleData: (dataConfig) => this.fetchMultipleData(dataConfig, component)
|
|
1583
1583
|
};
|
|
1584
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
|
+
}
|
|
1585
1599
|
/**
|
|
1586
1600
|
* 정리 (메모리 누수 방지)
|
|
1587
1601
|
*/
|
|
@@ -1597,7 +1611,6 @@ var ComponentLoader = class {
|
|
|
1597
1611
|
this.config = {
|
|
1598
1612
|
componentsPath: options.componentsPath || "/components",
|
|
1599
1613
|
// srcPath 기준 상대 경로
|
|
1600
|
-
debug: options.debug || false,
|
|
1601
1614
|
environment: options.environment || "development",
|
|
1602
1615
|
...options
|
|
1603
1616
|
};
|
|
@@ -1868,8 +1881,7 @@ var RouteLoader = class {
|
|
|
1868
1881
|
// 프로덕션 라우트 경로
|
|
1869
1882
|
environment: options.environment || "development",
|
|
1870
1883
|
useLayout: options.useLayout !== false,
|
|
1871
|
-
defaultLayout: options.defaultLayout || "default"
|
|
1872
|
-
debug: options.debug || false
|
|
1884
|
+
defaultLayout: options.defaultLayout || "default"
|
|
1873
1885
|
};
|
|
1874
1886
|
this.router = router;
|
|
1875
1887
|
this.formHandler = new FormHandler(router, this.config);
|
|
@@ -2036,6 +2048,7 @@ ${template}`;
|
|
|
2036
2048
|
...originalData,
|
|
2037
2049
|
currentRoute: routeName,
|
|
2038
2050
|
$query: router.queryManager?.getQueryParams() || {},
|
|
2051
|
+
$params: router.queryManager?.getRouteParams() || {},
|
|
2039
2052
|
$lang: (() => {
|
|
2040
2053
|
try {
|
|
2041
2054
|
return router.i18nManager?.getCurrentLanguage() || router.config.i18nDefaultLanguage || router.config.defaultLanguage || "ko";
|
|
@@ -2057,11 +2070,12 @@ ${template}`;
|
|
|
2057
2070
|
},
|
|
2058
2071
|
async mounted() {
|
|
2059
2072
|
this.$api = router.routeLoader.apiHandler.bindToComponent(this);
|
|
2073
|
+
this.$state = router.stateHandler;
|
|
2060
2074
|
if (script.mounted) {
|
|
2061
2075
|
await script.mounted.call(this);
|
|
2062
2076
|
}
|
|
2063
2077
|
if (script.dataURL) {
|
|
2064
|
-
await this
|
|
2078
|
+
await this.fetchData();
|
|
2065
2079
|
}
|
|
2066
2080
|
await this.$nextTick();
|
|
2067
2081
|
router.routeLoader.formHandler.bindAutoForms(this);
|
|
@@ -2083,29 +2097,22 @@ ${template}`;
|
|
|
2083
2097
|
return key;
|
|
2084
2098
|
}
|
|
2085
2099
|
},
|
|
2086
|
-
// 인증 관련
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
set: (key, value) => router.stateHandler?.set(key, value),
|
|
2100
|
-
has: (key) => router.stateHandler?.has(key) || false,
|
|
2101
|
-
delete: (key) => router.stateHandler?.delete(key) || false,
|
|
2102
|
-
update: (updates) => router.stateHandler?.update(updates),
|
|
2103
|
-
watch: (key, callback) => router.stateHandler?.watch(key, callback),
|
|
2104
|
-
unwatch: (key, callback) => router.stateHandler?.unwatch(key, callback),
|
|
2105
|
-
getAll: () => router.stateHandler?.getAll() || {}
|
|
2100
|
+
// 인증 관련 (핵심 4개 메소드만)
|
|
2101
|
+
isAuth: () => router.authManager?.isAuthenticated() || false,
|
|
2102
|
+
logout: () => router.authManager ? router.navigateTo(router.authManager.logout()) : null,
|
|
2103
|
+
getToken: () => router.authManager?.getAccessToken() || null,
|
|
2104
|
+
setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
|
|
2105
|
+
// i18n 언어 관리
|
|
2106
|
+
getLanguage: () => router.i18nManager?.getCurrentLanguage() || router.config.defaultLanguage || "ko",
|
|
2107
|
+
setLanguage: (lang) => router.i18nManager?.setLanguage(lang),
|
|
2108
|
+
// 로깅 및 에러 처리
|
|
2109
|
+
log: (level, ...args) => {
|
|
2110
|
+
if (router.errorHandler) {
|
|
2111
|
+
router.errorHandler.log(level, `[${routeName}]`, ...args);
|
|
2112
|
+
}
|
|
2106
2113
|
},
|
|
2107
2114
|
// 데이터 fetch (ApiHandler 래퍼)
|
|
2108
|
-
async
|
|
2115
|
+
async fetchData(dataConfig = null) {
|
|
2109
2116
|
const configToUse = dataConfig || script.dataURL;
|
|
2110
2117
|
if (!configToUse) return null;
|
|
2111
2118
|
this.$dataLoading = true;
|
|
@@ -2189,9 +2196,7 @@ ${template}`;
|
|
|
2189
2196
|
var ErrorHandler = class {
|
|
2190
2197
|
constructor(router, options = {}) {
|
|
2191
2198
|
this.config = {
|
|
2192
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
*
|
|
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
|
-
|
|
2665
|
-
|
|
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.
|
|
2676
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
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
|
}
|