frayerjj-frontend 0.8.33 → 0.8.34
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 +21 -1
- package/package.json +7 -3
- package/src/full.js +1 -0
- package/src/scss/full.scss +4 -0
- package/src/scss/slim.scss +12 -0
- package/src/slim.js +189 -0
package/README.md
CHANGED
|
@@ -4,4 +4,24 @@ These are my basic scripts for extending Bootstrap 5, CKEditor, and the basic th
|
|
|
4
4
|
|
|
5
5
|
You can install them with:
|
|
6
6
|
|
|
7
|
-
npm install frayerjj-frontend
|
|
7
|
+
npm install frayerjj-frontend
|
|
8
|
+
|
|
9
|
+
## Entry Points
|
|
10
|
+
|
|
11
|
+
This package now exposes two frontend presets:
|
|
12
|
+
|
|
13
|
+
- Full (all features):
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { init } from 'frayerjj-frontend/full';
|
|
17
|
+
import 'frayerjj-frontend/styles/full';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- Slim (core-only):
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import { init } from 'frayerjj-frontend/slim';
|
|
24
|
+
import 'frayerjj-frontend/styles/slim';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The default package root export remains available for backward compatibility.
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frayerjj-frontend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.34",
|
|
4
4
|
"description": "My base frontend for various projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
|
9
|
-
"./
|
|
9
|
+
"./full": "./src/full.js",
|
|
10
|
+
"./slim": "./src/slim.js",
|
|
11
|
+
"./styles": "./src/scss/styles.scss",
|
|
12
|
+
"./styles/full": "./src/scss/full.scss",
|
|
13
|
+
"./styles/slim": "./src/scss/slim.scss"
|
|
10
14
|
},
|
|
11
|
-
"style": "src/scss/
|
|
15
|
+
"style": "src/scss/full.scss",
|
|
12
16
|
"sideEffects": [
|
|
13
17
|
"**/*.css",
|
|
14
18
|
"**/*.scss"
|
package/src/full.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { init } from './init';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@import 'bootstrap/scss/bootstrap';
|
|
2
|
+
@import url(//cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css);
|
|
3
|
+
@import url(//use.typekit.net/oox8pkj.css);
|
|
4
|
+
@import 'fonts';
|
|
5
|
+
@import 'colors';
|
|
6
|
+
@import 'dialog';
|
|
7
|
+
@import 'content';
|
|
8
|
+
@import 'nav';
|
|
9
|
+
@import 'header';
|
|
10
|
+
@import 'footer';
|
|
11
|
+
@import 'loading';
|
|
12
|
+
@import 'forms';
|
package/src/slim.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as bootstrap from 'bootstrap';
|
|
2
|
+
import { loading } from './loading';
|
|
3
|
+
import { msg } from './msg';
|
|
4
|
+
import { modal } from './modal';
|
|
5
|
+
import { session } from './session';
|
|
6
|
+
import { validate } from './validate';
|
|
7
|
+
|
|
8
|
+
export const init = (args) => {
|
|
9
|
+
|
|
10
|
+
const excluded = new Set(args?.exclude ?? []);
|
|
11
|
+
const use = (name) => !excluded.has(name);
|
|
12
|
+
|
|
13
|
+
window.bootstrap = bootstrap;
|
|
14
|
+
window.msg = msg;
|
|
15
|
+
window.modal = modal;
|
|
16
|
+
window.session = session;
|
|
17
|
+
window.validate = validate;
|
|
18
|
+
window.loading = loading;
|
|
19
|
+
loading.init(args?.loadingAnimationStyle ?? 'default');
|
|
20
|
+
|
|
21
|
+
window.addEventListener('load', async () => {
|
|
22
|
+
|
|
23
|
+
msg.verbose('Page Loaded, Initializing');
|
|
24
|
+
if (use('modal')) modal.ajax.init();
|
|
25
|
+
if (use('validate')) validate.init();
|
|
26
|
+
|
|
27
|
+
// Dynamic height for container-fixed
|
|
28
|
+
let container = document.querySelectorAll('.container-fixed');
|
|
29
|
+
if (container.length) {
|
|
30
|
+
let setHeigth = () => {
|
|
31
|
+
let sub = document.querySelectorAll('.navbar,header,footer');
|
|
32
|
+
if (container) {
|
|
33
|
+
let height = window.innerHeight;
|
|
34
|
+
sub.forEach((el) => {
|
|
35
|
+
height -= el.offsetHeight;
|
|
36
|
+
});
|
|
37
|
+
container.forEach((el) => {
|
|
38
|
+
el.style.height = height + 'px';
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
setHeigth();
|
|
43
|
+
window.addEventListener('resize', setHeigth);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Update date/time values to browser settings.
|
|
47
|
+
document.querySelectorAll('.dt-localize').forEach(el => {
|
|
48
|
+
msg.verbose('Localizing Date/Time');
|
|
49
|
+
const date = new Date(el.dataset.utc);
|
|
50
|
+
el.innerText = date.toLocaleString();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Updates the id in the form action inside a modal. Used for delete confirm and edit modals.
|
|
54
|
+
document.querySelectorAll('.modal-uuid-update').forEach(el => {
|
|
55
|
+
msg.verbose('Enabling Modal UUID Update');
|
|
56
|
+
el.addEventListener('click', ev => {
|
|
57
|
+
msg.verbose('Updating Modal UUID');
|
|
58
|
+
ev.preventDefault();
|
|
59
|
+
let uuid = el.getAttribute('inst-uuid'),
|
|
60
|
+
modalSelector = el.getAttribute('data-bs-target'),
|
|
61
|
+
form = document.querySelector(`${modalSelector} form`);
|
|
62
|
+
if (uuid.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i) && form) {
|
|
63
|
+
let action = form.getAttribute('action');
|
|
64
|
+
if (action.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i))
|
|
65
|
+
action = action.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i, uuid);
|
|
66
|
+
form.setAttribute('action', action);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
msg.warn('Unable to update modal UUID.');
|
|
70
|
+
modal.close();
|
|
71
|
+
modal.alert('Error', 'Unable to initialize form.');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Automatically submits on change. Used in Page Nav Selects.
|
|
77
|
+
document.querySelectorAll('.change-submit').forEach(el => {
|
|
78
|
+
msg.verbose('Enabling Change Submit');
|
|
79
|
+
el.addEventListener('change', () => {
|
|
80
|
+
msg.verbose('Change Submit Triggered');
|
|
81
|
+
el.closest('form').submit();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Confirm/Delete Modals
|
|
86
|
+
document.querySelectorAll('.confirm-link').forEach(el => {
|
|
87
|
+
msg.verbose('Enabling Confirm Link');
|
|
88
|
+
el.addEventListener('click', ev => {
|
|
89
|
+
msg.verbose('Confirm Link Triggered');
|
|
90
|
+
ev.preventDefault();
|
|
91
|
+
modal.confirm(
|
|
92
|
+
el.getAttribute('confirm-msg') || 'Are you sure?',
|
|
93
|
+
() => {
|
|
94
|
+
const href = el.getAttribute('href');
|
|
95
|
+
if (el.classList.contains('delete-link')) {
|
|
96
|
+
const form = document.createElement('form');
|
|
97
|
+
form.method = 'POST';
|
|
98
|
+
form.action = href;
|
|
99
|
+
const methodInput = document.createElement('input');
|
|
100
|
+
methodInput.type = 'hidden';
|
|
101
|
+
methodInput.name = '_method';
|
|
102
|
+
methodInput.value = 'DELETE';
|
|
103
|
+
form.appendChild(methodInput);
|
|
104
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
105
|
+
if (csrfToken) {
|
|
106
|
+
const csrfInput = document.createElement('input');
|
|
107
|
+
csrfInput.type = 'hidden';
|
|
108
|
+
csrfInput.name = 'csrfmiddlewaretoken';
|
|
109
|
+
csrfInput.value = csrfToken;
|
|
110
|
+
form.appendChild(csrfInput);
|
|
111
|
+
}
|
|
112
|
+
document.body.appendChild(form);
|
|
113
|
+
form.submit();
|
|
114
|
+
} else
|
|
115
|
+
location.href = href;
|
|
116
|
+
},
|
|
117
|
+
() => {},
|
|
118
|
+
el.getAttribute('confirm-title') || 'Confirm',
|
|
119
|
+
el.getAttribute('confirm-yes') || 'Yes',
|
|
120
|
+
el.getAttribute('confirm-no') || 'No');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Filter Selects
|
|
125
|
+
let filterSelects = document.querySelectorAll('.filter-select');
|
|
126
|
+
if (filterSelects.length) filterSelects.forEach(select => {
|
|
127
|
+
msg.verbose('Enabling Filter Select');
|
|
128
|
+
select.addEventListener('change', ev => {
|
|
129
|
+
msg.verbose('Filter Select Triggered');
|
|
130
|
+
document.querySelectorAll('.filter-item').forEach(el => {
|
|
131
|
+
el.style.display = '';
|
|
132
|
+
filterSelects.forEach(filter => {
|
|
133
|
+
if (filter.value) {
|
|
134
|
+
let attr = filter.getAttribute('filter-attr');
|
|
135
|
+
if (el.getAttribute(attr) != filter.value)
|
|
136
|
+
el.style.display = 'none';
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Scroll to top button
|
|
144
|
+
let toTop = document.getElementById('toTopBtn');
|
|
145
|
+
if (toTop) {
|
|
146
|
+
toTop.style.visibility = 'hidden';
|
|
147
|
+
toTop.addEventListener('click', () => {
|
|
148
|
+
msg.verbose('Scrolling to Top');
|
|
149
|
+
scrollTo(0, 0);
|
|
150
|
+
});
|
|
151
|
+
window.addEventListener('scroll', () => {
|
|
152
|
+
if (scrollY > 1500) {
|
|
153
|
+
msg.verbose('Scrolling to Top Button Visible');
|
|
154
|
+
toTop.style.visibility = 'visible';
|
|
155
|
+
} else {
|
|
156
|
+
msg.verbose('Scrolling to Top Button Hidden');
|
|
157
|
+
toTop.style.visibility = 'hidden';
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Tab Session Persistence
|
|
163
|
+
const PERSIST_CLASS = 'tab-session-persist';
|
|
164
|
+
document.querySelectorAll(`.${PERSIST_CLASS}`).forEach(container => {
|
|
165
|
+
const storageKey = `activeTab_${window.location.pathname}_${container.id}`;
|
|
166
|
+
const savedTarget = session.getStrVar(storageKey);
|
|
167
|
+
msg.verbose(`Restoring tab for container #${container.id} with key ${storageKey}:`, savedTarget);
|
|
168
|
+
if (savedTarget) {
|
|
169
|
+
const tabEl = container.querySelector(`[data-bs-target="${savedTarget}"], [href="${savedTarget}"]`);
|
|
170
|
+
if (tabEl) {
|
|
171
|
+
const tab = new bootstrap.Tab(tabEl);
|
|
172
|
+
tab.show();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
document.addEventListener('shown.bs.tab', event => {
|
|
177
|
+
const tabButton = event.target;
|
|
178
|
+
const container = tabButton.closest(`.${PERSIST_CLASS}`);
|
|
179
|
+
if (container) {
|
|
180
|
+
const storageKey = `activeTab_${window.location.pathname}_${container.id}`;
|
|
181
|
+
const target = tabButton.getAttribute('data-bs-target') || tabButton.getAttribute('href');
|
|
182
|
+
session.set(storageKey, target);
|
|
183
|
+
msg.verbose(`Saved active tab for container #${container.id} with key ${storageKey}:`, target);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
};
|