@doyosi/laraisy 1.0.1 → 1.0.3
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/CodeInput.js +48 -48
- package/src/DSAlert.js +352 -352
- package/src/DSAvatar.js +207 -207
- package/src/DSDelete.js +274 -274
- package/src/DSForm.js +568 -568
- package/src/DSGridOrTable.js +453 -453
- package/src/DSLocaleSwitcher.js +239 -239
- package/src/DSLogout.js +293 -293
- package/src/DSNotifications.js +365 -365
- package/src/DSRestore.js +181 -181
- package/src/DSSelect.js +1071 -1071
- package/src/DSSelectBox.js +563 -563
- package/src/DSSimpleSlider.js +517 -517
- package/src/DSSvgFetch.js +69 -69
- package/src/DSTable/DSTableExport.js +68 -68
- package/src/DSTable/DSTableFilter.js +224 -224
- package/src/DSTable/DSTablePagination.js +136 -136
- package/src/DSTable/DSTableSearch.js +40 -40
- package/src/DSTable/DSTableSelection.js +192 -192
- package/src/DSTable/DSTableSort.js +58 -58
- package/src/DSTable.js +353 -353
- package/src/DSTabs.js +488 -488
- package/src/DSUpload.js +887 -887
- package/dist/CodeInput.d.ts +0 -10
- package/dist/DSAlert.d.ts +0 -112
- package/dist/DSAvatar.d.ts +0 -45
- package/dist/DSDelete.d.ts +0 -61
- package/dist/DSForm.d.ts +0 -151
- package/dist/DSGridOrTable/DSGOTRenderer.d.ts +0 -60
- package/dist/DSGridOrTable/DSGOTViewToggle.d.ts +0 -26
- package/dist/DSGridOrTable.d.ts +0 -296
- package/dist/DSLocaleSwitcher.d.ts +0 -71
- package/dist/DSLogout.d.ts +0 -76
- package/dist/DSNotifications.d.ts +0 -54
- package/dist/DSRestore.d.ts +0 -56
- package/dist/DSSelect.d.ts +0 -221
- package/dist/DSSelectBox.d.ts +0 -123
- package/dist/DSSimpleSlider.d.ts +0 -136
- package/dist/DSSvgFetch.d.ts +0 -17
- package/dist/DSTable/DSTableExport.d.ts +0 -11
- package/dist/DSTable/DSTableFilter.d.ts +0 -40
- package/dist/DSTable/DSTablePagination.d.ts +0 -12
- package/dist/DSTable/DSTableSearch.d.ts +0 -8
- package/dist/DSTable/DSTableSelection.d.ts +0 -46
- package/dist/DSTable/DSTableSort.d.ts +0 -8
- package/dist/DSTable.d.ts +0 -116
- package/dist/DSTabs.d.ts +0 -156
- package/dist/DSUpload.d.ts +0 -220
- package/dist/index.d.ts +0 -17
package/src/DSLogout.js
CHANGED
|
@@ -1,293 +1,293 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DSLogout
|
|
3
|
-
*
|
|
4
|
-
* JS-only, multi-element Laravel logout helper.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Bind to one or many elements (selector/NodeList/Array/Element)
|
|
8
|
-
* - Optional event delegation: { root, match }
|
|
9
|
-
* - Prevent multi-click per element with logical + visual disable
|
|
10
|
-
* - Axios → fetch → XHR fallback; JSON redirect support
|
|
11
|
-
* - CSRF auto-resolution from <meta>, data attribute, window, cookie
|
|
12
|
-
* - i18n messages via `translations`
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* // Blade <head>:
|
|
16
|
-
* // <meta name="csrf-token" content="{{ csrf_token() }}">
|
|
17
|
-
*
|
|
18
|
-
* // HTML:
|
|
19
|
-
* // <a class="js-logout" href="#">Logout</a>
|
|
20
|
-
* // <button class="js-logout">Logout 2</button>
|
|
21
|
-
*
|
|
22
|
-
* // JS:
|
|
23
|
-
* import { DSLogout } from '/js/modules/DSLogout.js';
|
|
24
|
-
* new DSLogout({
|
|
25
|
-
* elements: '.js-logout', // one or many elements
|
|
26
|
-
* // or: element: '#header-logout'
|
|
27
|
-
* // or delegation:
|
|
28
|
-
* // delegate: { root: document, match: '.js-logout' },
|
|
29
|
-
* url: '/logout', // or "{{ route('logout') }}"
|
|
30
|
-
* requestLibrary: 'axios', // 'auto'|'axios'|'fetch'|'xhr'
|
|
31
|
-
* translations: { loading: 'Çıkış yapılıyor...', error: 'Çıkış yapılamadı' },
|
|
32
|
-
* disabledClasses: ['pointer-events-none','opacity-60','cursor-not-allowed']
|
|
33
|
-
* });
|
|
34
|
-
*/
|
|
35
|
-
export class DSLogout {
|
|
36
|
-
constructor({
|
|
37
|
-
element = null, // Element | string
|
|
38
|
-
elements = null, // Element[] | NodeList | string
|
|
39
|
-
delegate = null, // { root: Element|string, match: string }
|
|
40
|
-
url = '/logout',
|
|
41
|
-
redirect = null,
|
|
42
|
-
requestLibrary = 'auto',
|
|
43
|
-
csrfToken = null,
|
|
44
|
-
translations = {},
|
|
45
|
-
disabledClasses = ['pointer-events-none','opacity-60','cursor-not-allowed'],
|
|
46
|
-
eventType = 'click'
|
|
47
|
-
} = {}) {
|
|
48
|
-
this.url = url;
|
|
49
|
-
this.redirect = redirect;
|
|
50
|
-
this.requestLibrary = requestLibrary;
|
|
51
|
-
this.translations = {
|
|
52
|
-
loading: 'Logging out...',
|
|
53
|
-
error: 'Error logging out',
|
|
54
|
-
...translations
|
|
55
|
-
};
|
|
56
|
-
this.disabledClasses = disabledClasses;
|
|
57
|
-
this.eventType = eventType;
|
|
58
|
-
|
|
59
|
-
// Resolve CSRF once (can be overridden)
|
|
60
|
-
this.csrf = csrfToken || this._resolveCsrfToken();
|
|
61
|
-
|
|
62
|
-
// Per-element state & unbind storage
|
|
63
|
-
this._bound = new WeakMap(); // el -> handler
|
|
64
|
-
this._isDelegated = !!delegate;
|
|
65
|
-
this._delegated = null;
|
|
66
|
-
|
|
67
|
-
// Bind either direct elements or delegation
|
|
68
|
-
if (this._isDelegated) {
|
|
69
|
-
const root = typeof delegate.root === 'string'
|
|
70
|
-
? document.querySelector(delegate.root)
|
|
71
|
-
: (delegate.root || document);
|
|
72
|
-
if (!root) throw new Error('DSLogout: delegate.root not found');
|
|
73
|
-
if (!delegate.match) throw new Error('DSLogout: delegate.match selector required');
|
|
74
|
-
this._delegated = { root, match: delegate.match };
|
|
75
|
-
this._bindDelegated();
|
|
76
|
-
} else {
|
|
77
|
-
const list = this._normalizeElements(element, elements);
|
|
78
|
-
if (!list.length) throw new Error('DSLogout: no elements to bind');
|
|
79
|
-
list.forEach((el) => this._bindElement(el));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Public: cleanup
|
|
84
|
-
destroy() {
|
|
85
|
-
if (this._isDelegated && this._delegated) {
|
|
86
|
-
this._delegated.root.removeEventListener(this.eventType, this._delegated._handler);
|
|
87
|
-
this._delegated = null;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// Unbind direct handlers
|
|
91
|
-
for (const [el, handler] of this._entries(this._bound)) {
|
|
92
|
-
el.removeEventListener(this.eventType, handler);
|
|
93
|
-
}
|
|
94
|
-
this._bound = new WeakMap();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// -------------------------------------------------------------------------
|
|
98
|
-
// Internal binding helpers
|
|
99
|
-
_normalizeElements(element, elements) {
|
|
100
|
-
const out = [];
|
|
101
|
-
const pushEl = (el) => { if (el && el.nodeType === 1) out.push(el); };
|
|
102
|
-
|
|
103
|
-
if (element) {
|
|
104
|
-
if (typeof element === 'string') {
|
|
105
|
-
document.querySelectorAll(element).forEach(pushEl);
|
|
106
|
-
} else {
|
|
107
|
-
pushEl(element);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (elements) {
|
|
112
|
-
if (typeof elements === 'string') {
|
|
113
|
-
document.querySelectorAll(elements).forEach(pushEl);
|
|
114
|
-
} else if (elements instanceof NodeList || Array.isArray(elements)) {
|
|
115
|
-
elements.forEach(pushEl);
|
|
116
|
-
} else {
|
|
117
|
-
pushEl(elements);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return out;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
_bindElement(el) {
|
|
124
|
-
if (!el) return;
|
|
125
|
-
const handler = (e) => {
|
|
126
|
-
e.preventDefault();
|
|
127
|
-
this._handleClick(el);
|
|
128
|
-
};
|
|
129
|
-
el.addEventListener(this.eventType, handler);
|
|
130
|
-
this._bound.set(el, handler);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
_bindDelegated() {
|
|
134
|
-
const { root, match } = this._delegated;
|
|
135
|
-
const handler = (e) => {
|
|
136
|
-
const el = e.target.closest(match);
|
|
137
|
-
if (!el || !root.contains(el)) return;
|
|
138
|
-
e.preventDefault();
|
|
139
|
-
this._handleClick(el);
|
|
140
|
-
};
|
|
141
|
-
root.addEventListener(this.eventType, handler);
|
|
142
|
-
this._delegated._handler = handler;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// -------------------------------------------------------------------------
|
|
146
|
-
// Click + request pipeline
|
|
147
|
-
async _handleClick(el) {
|
|
148
|
-
if (this._isDisabled(el)) return;
|
|
149
|
-
|
|
150
|
-
const restore = this._disableWithLoading(el);
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
const data = await this._post(); // axios/fetch/xhr
|
|
154
|
-
const json = data?.data || data; // axios vs fetch/xhr
|
|
155
|
-
const target = this.redirect || json?.redirect || '/';
|
|
156
|
-
window.location.assign(target);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
alert(this.translations.error);
|
|
159
|
-
// eslint-disable-next-line no-console
|
|
160
|
-
console.error('DSLogout error:', err);
|
|
161
|
-
restore();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
_isDisabled(el) {
|
|
166
|
-
if (el instanceof HTMLButtonElement) return el.disabled;
|
|
167
|
-
return el.getAttribute('aria-disabled') === 'true';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
_disableWithLoading(el) {
|
|
171
|
-
const isButton = el instanceof HTMLButtonElement;
|
|
172
|
-
const prevHTML = el.innerHTML;
|
|
173
|
-
const prevTitle = el.getAttribute('title');
|
|
174
|
-
|
|
175
|
-
if (isButton) {
|
|
176
|
-
el.disabled = true;
|
|
177
|
-
} else {
|
|
178
|
-
el.setAttribute('aria-disabled', 'true');
|
|
179
|
-
this.disabledClasses.forEach(c => el.classList.add(c));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
el.setAttribute('title', this.translations.loading);
|
|
183
|
-
el.innerHTML = this.translations.loading;
|
|
184
|
-
|
|
185
|
-
return () => {
|
|
186
|
-
if (isButton) {
|
|
187
|
-
el.disabled = false;
|
|
188
|
-
} else {
|
|
189
|
-
el.setAttribute('aria-disabled', 'false');
|
|
190
|
-
this.disabledClasses.forEach(c => el.classList.remove(c));
|
|
191
|
-
}
|
|
192
|
-
if (prevTitle == null) el.removeAttribute('title');
|
|
193
|
-
else el.setAttribute('title', prevTitle);
|
|
194
|
-
el.innerHTML = prevHTML;
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
_chooseLib() {
|
|
199
|
-
const want = this.requestLibrary?.toLowerCase?.() || 'auto';
|
|
200
|
-
if (want === 'axios') return (window.axios ? 'axios' : (window.fetch ? 'fetch' : 'xhr'));
|
|
201
|
-
if (want === 'fetch') return (window.fetch ? 'fetch' : (window.axios ? 'axios' : 'xhr'));
|
|
202
|
-
if (want === 'xhr') return 'xhr';
|
|
203
|
-
if (window.axios) return 'axios';
|
|
204
|
-
if (window.fetch) return 'fetch';
|
|
205
|
-
return 'xhr';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async _post() {
|
|
209
|
-
const lib = this._chooseLib();
|
|
210
|
-
const headers = {
|
|
211
|
-
'Accept': 'application/json',
|
|
212
|
-
'X-Requested-With': 'XMLHttpRequest',
|
|
213
|
-
'X-CSRF-TOKEN': this.csrf
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
if (lib === 'axios') {
|
|
217
|
-
return window.axios.post(this.url, {}, { headers });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (lib === 'fetch') {
|
|
221
|
-
const res = await fetch(this.url, {
|
|
222
|
-
method: 'POST',
|
|
223
|
-
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
224
|
-
credentials: 'same-origin',
|
|
225
|
-
body: '{}'
|
|
226
|
-
});
|
|
227
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
228
|
-
return res.json();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// xhr fallback
|
|
232
|
-
return new Promise((resolve, reject) => {
|
|
233
|
-
const xhr = new XMLHttpRequest();
|
|
234
|
-
xhr.open('POST', this.url, true);
|
|
235
|
-
xhr.withCredentials = true;
|
|
236
|
-
xhr.setRequestHeader('Accept', 'application/json');
|
|
237
|
-
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
238
|
-
xhr.setRequestHeader('X-CSRF-TOKEN', this.csrf);
|
|
239
|
-
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
240
|
-
xhr.onload = () => {
|
|
241
|
-
try {
|
|
242
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
243
|
-
resolve(JSON.parse(xhr.responseText || '{}'));
|
|
244
|
-
} else {
|
|
245
|
-
reject(new Error(`HTTP ${xhr.status}`));
|
|
246
|
-
}
|
|
247
|
-
} catch (e) { reject(e); }
|
|
248
|
-
};
|
|
249
|
-
xhr.onerror = () => reject(new Error('Network error'));
|
|
250
|
-
xhr.send('{}');
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// -------------------------------------------------------------------------
|
|
255
|
-
// CSRF utilities
|
|
256
|
-
_resolveCsrfToken() {
|
|
257
|
-
// meta
|
|
258
|
-
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
259
|
-
if (meta?.content) return meta.content;
|
|
260
|
-
|
|
261
|
-
// from any bound element (data-csrf)
|
|
262
|
-
for (const [el] of this._entries(this._bound)) {
|
|
263
|
-
const t = el?.dataset?.csrf;
|
|
264
|
-
if (t) return t;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// window exposed
|
|
268
|
-
if (window.Laravel?.csrfToken) return window.Laravel.csrfToken;
|
|
269
|
-
|
|
270
|
-
// cookie (Sanctum style)
|
|
271
|
-
const xsrf = this._readCookie('XSRF-TOKEN');
|
|
272
|
-
if (xsrf) return decodeURIComponent(xsrf);
|
|
273
|
-
|
|
274
|
-
throw new Error(
|
|
275
|
-
'DSLogout: CSRF token not found. Add <meta name="csrf-token" content="{{ csrf_token() }}"> ' +
|
|
276
|
-
'or pass { csrfToken } when constructing DSLogout.'
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
_readCookie(name) {
|
|
281
|
-
const m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([$?*|{}\]\\^])/g, '\\$1') + '=([^;]*)'));
|
|
282
|
-
return m ? m[1] : null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// helper to iterate WeakMap safely
|
|
286
|
-
*_entries(weakMap) {
|
|
287
|
-
// NOTE: WeakMap isn’t iterable in general; we only use this in places
|
|
288
|
-
// where we’ve just added entries and still hold the references via the DOM.
|
|
289
|
-
// As a safe no-op, return empty iterator if not supported.
|
|
290
|
-
// (We avoid relying on this for core logic.)
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DSLogout
|
|
3
|
+
*
|
|
4
|
+
* JS-only, multi-element Laravel logout helper.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Bind to one or many elements (selector/NodeList/Array/Element)
|
|
8
|
+
* - Optional event delegation: { root, match }
|
|
9
|
+
* - Prevent multi-click per element with logical + visual disable
|
|
10
|
+
* - Axios → fetch → XHR fallback; JSON redirect support
|
|
11
|
+
* - CSRF auto-resolution from <meta>, data attribute, window, cookie
|
|
12
|
+
* - i18n messages via `translations`
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Blade <head>:
|
|
16
|
+
* // <meta name="csrf-token" content="{{ csrf_token() }}">
|
|
17
|
+
*
|
|
18
|
+
* // HTML:
|
|
19
|
+
* // <a class="js-logout" href="#">Logout</a>
|
|
20
|
+
* // <button class="js-logout">Logout 2</button>
|
|
21
|
+
*
|
|
22
|
+
* // JS:
|
|
23
|
+
* import { DSLogout } from '/js/modules/DSLogout.js';
|
|
24
|
+
* new DSLogout({
|
|
25
|
+
* elements: '.js-logout', // one or many elements
|
|
26
|
+
* // or: element: '#header-logout'
|
|
27
|
+
* // or delegation:
|
|
28
|
+
* // delegate: { root: document, match: '.js-logout' },
|
|
29
|
+
* url: '/logout', // or "{{ route('logout') }}"
|
|
30
|
+
* requestLibrary: 'axios', // 'auto'|'axios'|'fetch'|'xhr'
|
|
31
|
+
* translations: { loading: 'Çıkış yapılıyor...', error: 'Çıkış yapılamadı' },
|
|
32
|
+
* disabledClasses: ['pointer-events-none','opacity-60','cursor-not-allowed']
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
export class DSLogout {
|
|
36
|
+
constructor({
|
|
37
|
+
element = null, // Element | string
|
|
38
|
+
elements = null, // Element[] | NodeList | string
|
|
39
|
+
delegate = null, // { root: Element|string, match: string }
|
|
40
|
+
url = '/logout',
|
|
41
|
+
redirect = null,
|
|
42
|
+
requestLibrary = 'auto',
|
|
43
|
+
csrfToken = null,
|
|
44
|
+
translations = {},
|
|
45
|
+
disabledClasses = ['pointer-events-none','opacity-60','cursor-not-allowed'],
|
|
46
|
+
eventType = 'click'
|
|
47
|
+
} = {}) {
|
|
48
|
+
this.url = url;
|
|
49
|
+
this.redirect = redirect;
|
|
50
|
+
this.requestLibrary = requestLibrary;
|
|
51
|
+
this.translations = {
|
|
52
|
+
loading: 'Logging out...',
|
|
53
|
+
error: 'Error logging out',
|
|
54
|
+
...translations
|
|
55
|
+
};
|
|
56
|
+
this.disabledClasses = disabledClasses;
|
|
57
|
+
this.eventType = eventType;
|
|
58
|
+
|
|
59
|
+
// Resolve CSRF once (can be overridden)
|
|
60
|
+
this.csrf = csrfToken || this._resolveCsrfToken();
|
|
61
|
+
|
|
62
|
+
// Per-element state & unbind storage
|
|
63
|
+
this._bound = new WeakMap(); // el -> handler
|
|
64
|
+
this._isDelegated = !!delegate;
|
|
65
|
+
this._delegated = null;
|
|
66
|
+
|
|
67
|
+
// Bind either direct elements or delegation
|
|
68
|
+
if (this._isDelegated) {
|
|
69
|
+
const root = typeof delegate.root === 'string'
|
|
70
|
+
? document.querySelector(delegate.root)
|
|
71
|
+
: (delegate.root || document);
|
|
72
|
+
if (!root) throw new Error('DSLogout: delegate.root not found');
|
|
73
|
+
if (!delegate.match) throw new Error('DSLogout: delegate.match selector required');
|
|
74
|
+
this._delegated = { root, match: delegate.match };
|
|
75
|
+
this._bindDelegated();
|
|
76
|
+
} else {
|
|
77
|
+
const list = this._normalizeElements(element, elements);
|
|
78
|
+
if (!list.length) throw new Error('DSLogout: no elements to bind');
|
|
79
|
+
list.forEach((el) => this._bindElement(el));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Public: cleanup
|
|
84
|
+
destroy() {
|
|
85
|
+
if (this._isDelegated && this._delegated) {
|
|
86
|
+
this._delegated.root.removeEventListener(this.eventType, this._delegated._handler);
|
|
87
|
+
this._delegated = null;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Unbind direct handlers
|
|
91
|
+
for (const [el, handler] of this._entries(this._bound)) {
|
|
92
|
+
el.removeEventListener(this.eventType, handler);
|
|
93
|
+
}
|
|
94
|
+
this._bound = new WeakMap();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// -------------------------------------------------------------------------
|
|
98
|
+
// Internal binding helpers
|
|
99
|
+
_normalizeElements(element, elements) {
|
|
100
|
+
const out = [];
|
|
101
|
+
const pushEl = (el) => { if (el && el.nodeType === 1) out.push(el); };
|
|
102
|
+
|
|
103
|
+
if (element) {
|
|
104
|
+
if (typeof element === 'string') {
|
|
105
|
+
document.querySelectorAll(element).forEach(pushEl);
|
|
106
|
+
} else {
|
|
107
|
+
pushEl(element);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (elements) {
|
|
112
|
+
if (typeof elements === 'string') {
|
|
113
|
+
document.querySelectorAll(elements).forEach(pushEl);
|
|
114
|
+
} else if (elements instanceof NodeList || Array.isArray(elements)) {
|
|
115
|
+
elements.forEach(pushEl);
|
|
116
|
+
} else {
|
|
117
|
+
pushEl(elements);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_bindElement(el) {
|
|
124
|
+
if (!el) return;
|
|
125
|
+
const handler = (e) => {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
this._handleClick(el);
|
|
128
|
+
};
|
|
129
|
+
el.addEventListener(this.eventType, handler);
|
|
130
|
+
this._bound.set(el, handler);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_bindDelegated() {
|
|
134
|
+
const { root, match } = this._delegated;
|
|
135
|
+
const handler = (e) => {
|
|
136
|
+
const el = e.target.closest(match);
|
|
137
|
+
if (!el || !root.contains(el)) return;
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
this._handleClick(el);
|
|
140
|
+
};
|
|
141
|
+
root.addEventListener(this.eventType, handler);
|
|
142
|
+
this._delegated._handler = handler;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// Click + request pipeline
|
|
147
|
+
async _handleClick(el) {
|
|
148
|
+
if (this._isDisabled(el)) return;
|
|
149
|
+
|
|
150
|
+
const restore = this._disableWithLoading(el);
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const data = await this._post(); // axios/fetch/xhr
|
|
154
|
+
const json = data?.data || data; // axios vs fetch/xhr
|
|
155
|
+
const target = this.redirect || json?.redirect || '/';
|
|
156
|
+
window.location.assign(target);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
alert(this.translations.error);
|
|
159
|
+
// eslint-disable-next-line no-console
|
|
160
|
+
console.error('DSLogout error:', err);
|
|
161
|
+
restore();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_isDisabled(el) {
|
|
166
|
+
if (el instanceof HTMLButtonElement) return el.disabled;
|
|
167
|
+
return el.getAttribute('aria-disabled') === 'true';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_disableWithLoading(el) {
|
|
171
|
+
const isButton = el instanceof HTMLButtonElement;
|
|
172
|
+
const prevHTML = el.innerHTML;
|
|
173
|
+
const prevTitle = el.getAttribute('title');
|
|
174
|
+
|
|
175
|
+
if (isButton) {
|
|
176
|
+
el.disabled = true;
|
|
177
|
+
} else {
|
|
178
|
+
el.setAttribute('aria-disabled', 'true');
|
|
179
|
+
this.disabledClasses.forEach(c => el.classList.add(c));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
el.setAttribute('title', this.translations.loading);
|
|
183
|
+
el.innerHTML = this.translations.loading;
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
if (isButton) {
|
|
187
|
+
el.disabled = false;
|
|
188
|
+
} else {
|
|
189
|
+
el.setAttribute('aria-disabled', 'false');
|
|
190
|
+
this.disabledClasses.forEach(c => el.classList.remove(c));
|
|
191
|
+
}
|
|
192
|
+
if (prevTitle == null) el.removeAttribute('title');
|
|
193
|
+
else el.setAttribute('title', prevTitle);
|
|
194
|
+
el.innerHTML = prevHTML;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_chooseLib() {
|
|
199
|
+
const want = this.requestLibrary?.toLowerCase?.() || 'auto';
|
|
200
|
+
if (want === 'axios') return (window.axios ? 'axios' : (window.fetch ? 'fetch' : 'xhr'));
|
|
201
|
+
if (want === 'fetch') return (window.fetch ? 'fetch' : (window.axios ? 'axios' : 'xhr'));
|
|
202
|
+
if (want === 'xhr') return 'xhr';
|
|
203
|
+
if (window.axios) return 'axios';
|
|
204
|
+
if (window.fetch) return 'fetch';
|
|
205
|
+
return 'xhr';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async _post() {
|
|
209
|
+
const lib = this._chooseLib();
|
|
210
|
+
const headers = {
|
|
211
|
+
'Accept': 'application/json',
|
|
212
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
213
|
+
'X-CSRF-TOKEN': this.csrf
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (lib === 'axios') {
|
|
217
|
+
return window.axios.post(this.url, {}, { headers });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (lib === 'fetch') {
|
|
221
|
+
const res = await fetch(this.url, {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
224
|
+
credentials: 'same-origin',
|
|
225
|
+
body: '{}'
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
228
|
+
return res.json();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// xhr fallback
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const xhr = new XMLHttpRequest();
|
|
234
|
+
xhr.open('POST', this.url, true);
|
|
235
|
+
xhr.withCredentials = true;
|
|
236
|
+
xhr.setRequestHeader('Accept', 'application/json');
|
|
237
|
+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
238
|
+
xhr.setRequestHeader('X-CSRF-TOKEN', this.csrf);
|
|
239
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
240
|
+
xhr.onload = () => {
|
|
241
|
+
try {
|
|
242
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
243
|
+
resolve(JSON.parse(xhr.responseText || '{}'));
|
|
244
|
+
} else {
|
|
245
|
+
reject(new Error(`HTTP ${xhr.status}`));
|
|
246
|
+
}
|
|
247
|
+
} catch (e) { reject(e); }
|
|
248
|
+
};
|
|
249
|
+
xhr.onerror = () => reject(new Error('Network error'));
|
|
250
|
+
xhr.send('{}');
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
// CSRF utilities
|
|
256
|
+
_resolveCsrfToken() {
|
|
257
|
+
// meta
|
|
258
|
+
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
259
|
+
if (meta?.content) return meta.content;
|
|
260
|
+
|
|
261
|
+
// from any bound element (data-csrf)
|
|
262
|
+
for (const [el] of this._entries(this._bound)) {
|
|
263
|
+
const t = el?.dataset?.csrf;
|
|
264
|
+
if (t) return t;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// window exposed
|
|
268
|
+
if (window.Laravel?.csrfToken) return window.Laravel.csrfToken;
|
|
269
|
+
|
|
270
|
+
// cookie (Sanctum style)
|
|
271
|
+
const xsrf = this._readCookie('XSRF-TOKEN');
|
|
272
|
+
if (xsrf) return decodeURIComponent(xsrf);
|
|
273
|
+
|
|
274
|
+
throw new Error(
|
|
275
|
+
'DSLogout: CSRF token not found. Add <meta name="csrf-token" content="{{ csrf_token() }}"> ' +
|
|
276
|
+
'or pass { csrfToken } when constructing DSLogout.'
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_readCookie(name) {
|
|
281
|
+
const m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([$?*|{}\]\\^])/g, '\\$1') + '=([^;]*)'));
|
|
282
|
+
return m ? m[1] : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// helper to iterate WeakMap safely
|
|
286
|
+
*_entries(weakMap) {
|
|
287
|
+
// NOTE: WeakMap isn’t iterable in general; we only use this in places
|
|
288
|
+
// where we’ve just added entries and still hold the references via the DOM.
|
|
289
|
+
// As a safe no-op, return empty iterator if not supported.
|
|
290
|
+
// (We avoid relying on this for core logic.)
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|