├── .gitignore
├── icons
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── site.webmanifest
└── about.txt
├── README.md
├── scss
├── _variables.scss
├── main.scss
└── _global.scss
├── js
├── elements.js
├── utils.js
└── app.js
├── css
├── main.css.map
└── main.css
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | TODO
2 | local/
--------------------------------------------------------------------------------
/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/favicon.ico
--------------------------------------------------------------------------------
/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Links Management
2 |
3 | Link website [https://savelinks.netlify.app/](https://savelinks.netlify.app/).
4 |
--------------------------------------------------------------------------------
/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muhammadpauzi/web-links-management/HEAD/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $backgroundColor: #FFF;
2 | $whiteColor: #FFF;
3 | $textColor: #2C3E50;
4 | $primaryColor: #6B4FBB;
5 | $dangerColor: #ff2f2f;
6 | $grayColor: #EAEAEA;
7 | $successColor: #09C372;
8 |
9 | $bold: 600;
10 |
11 | $small: 500px;
12 | $medium: 800px;
--------------------------------------------------------------------------------
/icons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/icons/about.txt:
--------------------------------------------------------------------------------
1 | This favicon was generated using the following font:
2 |
3 | - Font Title: Leckerli One
4 | - Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de), with Reserved Font Names "Leckerli"
5 | - Font Source: http://fonts.gstatic.com/s/leckerlione/v11/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
6 | - Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL)
7 |
--------------------------------------------------------------------------------
/js/elements.js:
--------------------------------------------------------------------------------
1 | export const listGroup = document.querySelector('.list-group');
2 | export const inputSearch = document.getElementById('inputSearch');
3 | export const btnExport = document.querySelector('.btn-export');
4 | export const btnBackToTop = document.querySelector('.btn-back-to-top');
5 | export const sortLinks = document.getElementById('sortLinks');
6 | export const formAddLink = document.getElementById('formAddLink');
7 | export const formImportLinks = document.getElementById('formImportLinks');
8 | export const linkAlert = document.querySelector('.alert.link-alert');
9 | export const btnCollapse = document.querySelector('.btn-collapse');
10 | export const btnDetail = document.querySelector('.btn-detail');
11 | export const btnCloseModal = document.querySelector('.btn-close-modal');
12 | export const formEditLink = document.getElementById('formEditLink');
13 | export const btnShowModal = document.querySelectorAll('.btn-show-modal');
14 | export const btnDeleteAll = document.querySelector('.btn-delete-all');
15 |
--------------------------------------------------------------------------------
/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'global';
3 |
4 | .navbar{
5 | width: 100%;
6 | padding: .500rem 0;
7 | background-color: darken($whiteColor, 1);
8 | box-shadow: 0 2px 3px rgba(0,0,0,.05);
9 | margin-bottom: 2rem;
10 | }
11 |
12 | .form-create-link{
13 | position: sticky;
14 | height: 100vh;
15 | top: 0;
16 | }
17 |
18 | .my-name{
19 | display: inline-block;
20 | color: $primaryColor !important;
21 |
22 | &:hover{
23 | text-decoration: underline;
24 | }
25 | }
26 |
27 | .link-icon-github{
28 | color: $textColor;
29 | font-size: 2rem;
30 | }
31 |
32 | .grid-layout{
33 | display: grid;
34 | grid-template-columns: 1.2fr 2fr;
35 | gap: 20px;
36 | }
37 |
38 | .grid-features{
39 | display: grid;
40 | grid-template-columns: 1fr 1fr 1fr .3fr;
41 | gap: 5px;
42 | }
43 |
44 | .grid-import-links{
45 | display: grid;
46 | grid-template-columns: 2fr 1fr;
47 | align-items: center;
48 | gap: 5px;
49 | }
50 |
51 | .tab-item{
52 | display: none;
53 | }
54 |
55 | #btnAdd, #btnEdit{
56 | width: 100%;
57 | margin-top: 1.4rem;
58 | }
59 |
60 | .btn-back-to-top{
61 | position: fixed;
62 | bottom: 50px;
63 | right: 50px;
64 | width: 50px;
65 | height: 50px;
66 | -webkit-tap-highlight-color: transparent;
67 | display: none;
68 | align-items: center;
69 | justify-content: center;
70 | border-radius: 50%;
71 | transition: box-shadow .3s;
72 | background-color: $primaryColor;
73 | cursor: pointer;
74 | color: white;
75 |
76 | &.show{
77 | display: flex;
78 | }
79 |
80 | &:hover{
81 | box-shadow: 0 5px 10px rgba($primaryColor, .5);
82 | }
83 | }
84 |
85 | .btn-visit, .btn-delete, .btn-detail, .btn-show-modal-edit, .btn-delete-all{
86 | padding: 1.4rem !important;
87 | background-repeat: no-repeat;
88 | background-position: center;
89 | background-size: 25px 21px;
90 | }
91 |
92 | .btn-visit{
93 | margin-right: .200rem;
94 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-link-2'%3e%3cpath d='M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3'%3e%3c/path%3e%3cline x1='8' y1='12' x2='16' y2='12'%3e%3c/line%3e%3c/svg%3e");
95 | }
96 |
97 | .btn-visit:visited{
98 | color: $whiteColor;
99 | }
100 |
101 | .btn-delete, .btn-delete-all{
102 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-trash-2'%3e%3cpolyline points='3 6 5 6 21 6'%3e%3c/polyline%3e%3cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2'%3e%3c/path%3e%3cline x1='10' y1='11' x2='10' y2='17'%3e%3c/line%3e%3cline x1='14' y1='11' x2='14' y2='17'%3e%3c/line%3e%3c/svg%3e");
103 | }
104 |
105 | .btn-show-modal-edit{
106 | margin-right: .200rem;
107 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-edit'%3e%3cpath d='M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'%3e%3c/path%3e%3cpath d='M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z'%3e%3c/path%3e%3c/svg%3e");
108 | }
109 |
110 | // for dekstop
111 | .btn-collapse{
112 | display: none;
113 | }
114 |
115 | .btn-detail{
116 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
117 | }
118 |
119 | .btn-detail.active{
120 | transform: rotate(180deg);
121 | }
122 |
123 | @media screen and(max-width: $medium){
124 | .container{
125 | width: 600px;
126 | }
127 |
128 | // for mobile or tablet
129 | .btn-collapse{
130 | display: block;
131 | }
132 |
133 | .grid-layout{
134 | grid-template-columns: 1fr;
135 | }
136 |
137 | .form-create-link{
138 | position: static;
139 | height: auto;
140 | }
141 | }
142 |
143 |
144 | @media screen and(max-width: $small){
145 | .btn-visit, .btn-delete, .btn-detail, .btn-show-modal-edit{
146 | padding: 1.1rem !important;
147 | }
148 |
149 | .btn-back-to-top{
150 | bottom: 20px;
151 | right: 20px;
152 | }
153 |
154 | .grid-import-links{
155 | grid-template-columns: 1fr;
156 | }
157 |
158 | .footer p{
159 | font-size: .800rem;
160 | }
161 | }
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | import { sortLinks, formEditLink } from "./elements.js";
2 |
3 | const generateId = () => {
4 | return '000' + Math.floor(Math.random() * 1000) + 1000 + Date.now();
5 | }
6 |
7 | const add = body => {
8 | const data = findAll();
9 | data.push(body);
10 | save(data);
11 | }
12 |
13 | const edit = body => {
14 | const data = findAll().filter(d => d.id !== body.id);
15 | data.push(body);
16 | save(data);
17 | }
18 |
19 | const findAll = (sort = "1") => {
20 | if (localStorage.getItem('data') == null) {
21 | localStorage.setItem('data', '[]');
22 | return [];
23 | } else {
24 | const data = JSON.parse(localStorage.getItem('data'));
25 | data.sort((a, b) => {
26 | switch (sort) {
27 | case "0": // by asc date created
28 | return a.created_at - b.created_at;
29 | case "1": // by desc data created
30 | return b.created_at - a.created_at;
31 | case "2": // by asc title
32 | if (a.title < b.title) {
33 | return -1;
34 | }
35 | if (a.title > b.title) {
36 | return 1;
37 | }
38 | return 0;
39 | case "3": // by desc title
40 | if (a.title > b.title) {
41 | return -1;
42 | }
43 | if (a.title < b.title) {
44 | return 1;
45 | }
46 | return 0;
47 | case "4": // by asc date updated
48 | if (a.updated_at && b.updated_at) {
49 | return 1;
50 | }
51 | if (a.updated_at == null) {
52 | console.log("sampai a null", a.title)
53 | return -1;
54 | }
55 | if (b.updated_at == null) {
56 | console.log("sampai b null", a.title)
57 | return 1;
58 | }
59 | return 0;
60 | case "5": // by desc date updated
61 | if (a.updated_at && b.updated_at) {
62 | console.log("sampai a dan b tidak null")
63 | return -1;
64 | }
65 | if (a.updated_at == null) {
66 | console.log("sampai a null")
67 | return 1;
68 | }
69 | if (b.updated_at == null) {
70 | console.log("sampai b null")
71 | return -1;
72 | }
73 | return 0;
74 | default:
75 | return b.created_at - a.created_at;
76 | }
77 | });
78 | return data;
79 | }
80 | }
81 |
82 | const deleteData = id => {
83 | const newData = findAll(sortLinks.value).filter(d => d.id !== id);
84 | save(newData);
85 | }
86 |
87 | const deleteAll = () => {
88 | save([]);
89 | }
90 |
91 | const save = data => {
92 | localStorage.setItem('data', JSON.stringify(data));
93 | }
94 |
95 | const loadDataLinkEdit = (id) => {
96 | const data = findAll().filter(d => d.id === id)[0];
97 | formEditLink.inputTitleEdit.value = data.title;
98 | formEditLink.inputUrlEdit.value = data.url;
99 | formEditLink.inputIdEdit.value = data.id;
100 | formEditLink.inputCreatedAtEdit.value = data.created_at;
101 | }
102 |
103 | const clearDataLinkEdit = (id) => {
104 | formEditLink.inputTitleEdit.value = '';
105 | formEditLink.inputUrlEdit.value = '';
106 | formEditLink.inputIdEdit.value = '';
107 | formEditLink.inputCreatedAtEdit.value = '';
108 | }
109 |
110 | const createListItem = data => {
111 | return `
112 |
113 |
114 | ${data.title}
115 | ${data.url}
116 |
117 |
121 |
122 |
123 |
124 | created at ${new Date(data.created_at).toLocaleString()}
125 | ${data.updated_at ? 'updated at ' + new Date(data.updated_at).toLocaleString() : 'Not updated yet'}
126 |
127 |
128 |
129 |
130 |
131 |
132 | `;
133 | }
134 |
135 | export {
136 | generateId,
137 | add,
138 | save,
139 | findAll,
140 | createListItem,
141 | deleteData,
142 | loadDataLinkEdit,
143 | edit,
144 | clearDataLinkEdit,
145 | deleteAll
146 | }
--------------------------------------------------------------------------------
/scss/_global.scss:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: -apple-system, BlinkMacSystemFont, 'Poppins', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
6 | }
7 |
8 | body{
9 | min-height: 100vh;
10 | background-color: $backgroundColor;
11 | color: $textColor;
12 | position: relative;
13 | }
14 |
15 | // RESET
16 |
17 | a, a:visited{
18 | display: block;
19 | text-decoration: none;
20 | color: $textColor;
21 | }
22 |
23 | li{
24 | list-style: none;
25 | }
26 |
27 | span{
28 | display: block;
29 | }
30 |
31 | // UTILITIES
32 |
33 | .container{
34 | width: 1200px;
35 | max-width: 100%;
36 | padding: 0 .500rem;
37 | margin: 0 auto;
38 | }
39 |
40 | .wrapper{
41 | padding-top: 1rem;
42 | }
43 |
44 | .devider{
45 | display: block;
46 | height: 1px;
47 | background-color: $grayColor;
48 | border: none;
49 | margin: .200rem 0 1rem 0;
50 | width: 20px;
51 |
52 | &.full{
53 | width: 100%;
54 | }
55 | }
56 |
57 | .error{
58 | color: $dangerColor;
59 | text-align: center;
60 | padding: 2rem 0;
61 | font-weight: $bold;
62 | }
63 |
64 | .logo{
65 | color: $primaryColor !important;
66 | font-weight: $bold;
67 | font-size: 1.5em;
68 | }
69 |
70 | // MARGIN & PADDING
71 | .py-1{
72 | padding-top: .500rem !important;
73 | padding-bottom: .500rem !important;
74 | }
75 |
76 | .px-2{
77 | padding-right: 1rem !important;
78 | padding-left: 1rem !important;
79 | }
80 |
81 | .mr-1{
82 | margin-right: .500rem !important;
83 | }
84 |
85 | .mb-2{
86 | margin-bottom: 1rem !important;
87 | }
88 |
89 | .mt-2{
90 | margin-top: 1rem !important;
91 | }
92 |
93 | // DISPLAY
94 | .d-none{
95 | display: none !important;
96 | }
97 |
98 | // FLEX
99 |
100 | .flex{
101 | display: flex !important;
102 | }
103 |
104 | .ai-c{
105 | align-items: center !important;
106 | }
107 |
108 | .jc-c{
109 | justify-content: center !important;
110 | }
111 |
112 | .jc-sb{
113 | justify-content: space-between !important;
114 | }
115 |
116 | .flex-1{
117 | flex: 1;
118 | }
119 |
120 | // INPUTS
121 |
122 | .input{
123 | display: block;
124 | width: 100%;
125 | padding: .900rem .600rem;
126 | font-weight: $bold;
127 | font-size: .900em;
128 | outline: none;
129 | border: 1px solid $grayColor;
130 | background-color: #FAFAFA;
131 | border-radius: 3px;
132 | }
133 |
134 | .input-group{
135 | display: block;
136 | margin-bottom: .800rem;
137 | }
138 |
139 | // ALERT
140 | .alert{
141 | display: flex;
142 | align-items: center;
143 | justify-content: space-between;
144 | width: 100%;
145 | padding: 1.2rem 1rem;
146 | font-weight: $bold;
147 | border-radius: 4px;
148 | margin-bottom: 1rem;
149 |
150 | .close{
151 | cursor: pointer;
152 | }
153 | }
154 |
155 | .alert-success{
156 | background-color: rgba($successColor, .2);
157 | color: darken($successColor, 2);
158 | border: 1px solid darken($successColor, 2);
159 | }
160 |
161 | .alert-danger{
162 | background-color: rgba($dangerColor, .2);
163 | color: darken($dangerColor, 2);
164 | border: 1px solid darken($dangerColor, 2);
165 | }
166 |
167 | // LABELS
168 |
169 | .label {
170 | display: block;
171 | font-weight: $bold;
172 | margin: .300rem 0;
173 | }
174 |
175 | // BUTTONS
176 |
177 | .btn{
178 | display: block;
179 | padding: .700rem .500rem;
180 | user-select: none;
181 | -webkit-tap-highlight-color: transparent;
182 | cursor: pointer;
183 | font-weight: $bold;
184 | font-size: .900em;
185 | outline: none;
186 | appearance: none;
187 | border: none;
188 | border-radius: 3px;
189 | }
190 |
191 | .btn-primary{
192 | background-color: $primaryColor;
193 | color: $whiteColor;
194 | transition: background-color .3s;
195 |
196 | &:hover{
197 | background-color: darken($primaryColor, 4);
198 | }
199 | }
200 |
201 | .btn-secondary{
202 | background-color: $grayColor;
203 | color: $textColor;
204 | transition: background-color .3s;
205 |
206 | &:hover{
207 | background-color: darken($grayColor, 4);
208 | }
209 | }
210 |
211 | .btn-danger{
212 | background-color: $dangerColor;
213 | color: $whiteColor;
214 | transition: background-color .3s;
215 |
216 | &:hover{
217 | background-color: darken($dangerColor, 4);
218 | }
219 | }
220 |
221 | // LISTS
222 |
223 | .list-group{
224 | padding: .300rem 0;
225 |
226 | .list-item{
227 | width: 100%;
228 | border: 1px solid $grayColor;
229 | font-weight: $bold;
230 | padding: .600rem .900rem;
231 | margin: .200rem 0;
232 | border-radius: 3px;
233 | transition: background-color .3s;
234 | -webkit-tap-highlight-color: transparent;
235 |
236 | .item-title{
237 | overflow-wrap: anywhere;
238 | margin: .300rem 0;
239 | }
240 |
241 | .item-url{
242 | margin: .300rem 0;
243 | font-size: .800em;
244 | color: $primaryColor;
245 | }
246 |
247 | .item-created, .item-updated{
248 | margin: .300rem 0;
249 | background-color: $grayColor;
250 | padding: .300rem .400rem;
251 | font-size: .800em;
252 | display: block;
253 | width: max-content;
254 | color: $textColor;
255 | }
256 |
257 | }
258 | }
259 |
260 | // MODAL
261 | .backdrop{
262 | position: fixed;
263 | top: 0;
264 | left: 0;
265 | right: 0;
266 | bottom: 0;
267 | background-color: rgba(0,0,0,0.4);
268 | padding: 2rem 0 1rem 0;
269 | display: none;
270 | }
271 |
272 | .backdrop.show{
273 | display: block;
274 | }
275 |
276 | .modal{
277 | width: 100%;
278 | max-width: 700px;
279 | background-color: $whiteColor;
280 | padding: 2rem 1rem;
281 | border-radius: 4px;
282 | margin: auto;
283 | }
284 |
285 | .modal-header{
286 | display: flex;
287 | align-items: center;
288 | justify-content: space-between;
289 | }
290 |
291 | // HEADINGS
292 |
293 | .title{
294 | margin-bottom: .500rem;
295 | }
296 |
297 | @media (max-width: $small){
298 | .title{
299 | font-size: 1.5rem;
300 | }
301 |
302 | .title-md{
303 | font-size: 1.3rem;
304 | }
305 | }
306 |
307 | // TEXTS
308 | .text-primary{
309 | color: $primaryColor !important;
310 | }
311 |
312 | // FOOTERS
313 |
314 | .footer{
315 | background-color: $whiteColor;
316 | border-top: 1px solid $grayColor;
317 | padding: 1.2em 0;
318 | text-align: center;
319 | margin-top: 5rem;
320 |
321 | p{
322 | font-weight: $bold;
323 |
324 | span{
325 | display: inline-block;
326 | color: $primaryColor;
327 | }
328 | }
329 | }
330 |
331 | @media screen and(max-width: $small){
332 | .list-group{
333 | .list-item{
334 | padding: .200rem .500rem .400rem .500rem;
335 |
336 | .item-title{
337 | margin: .300rem 0;
338 | font-size: .900em;
339 | }
340 |
341 | .item-url{
342 | font-size: .700em;
343 | }
344 |
345 | .item-created, .item-updated{
346 | font-size: .600em;
347 | }
348 | }
349 | }
350 | }
--------------------------------------------------------------------------------
/css/main.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "mappings": "AEAA,AAAA,CAAC,CAAA;EACG,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,UAAU;EACtB,WAAW,EAAE,sIAAsI;CACtJ;;AAED,AAAA,IAAI,CAAA;EACA,UAAU,EAAE,KAAK;EACjB,gBAAgB,EDTF,IAAI;ECUlB,KAAK,EDRG,OAAO;ECSf,QAAQ,EAAE,QAAQ;CACrB;;AAID,AAAA,CAAC,EAAE,CAAC,AAAA,QAAQ,CAAA;EACR,OAAO,EAAE,KAAK;EACd,eAAe,EAAE,IAAI;EACrB,KAAK,EDjBG,OAAO;CCkBlB;;AAED,AAAA,EAAE,CAAA;EACE,UAAU,EAAE,IAAI;CACnB;;AAED,AAAA,IAAI,CAAA;EACA,OAAO,EAAE,KAAK;CACjB;;AAID,AAAA,UAAU,CAAA;EACN,KAAK,EAAE,MAAM;EACb,SAAS,EAAE,IAAI;EACf,OAAO,EAAE,SAAS;EAClB,MAAM,EAAE,MAAM;CACjB;;AAED,AAAA,QAAQ,CAAA;EACJ,WAAW,EAAE,IAAI;CACpB;;AAED,AAAA,QAAQ,CAAA;EACJ,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,GAAG;EACX,gBAAgB,EDzCR,OAAO;EC0Cf,MAAM,EAAE,IAAI;EACZ,MAAM,EAAE,gBAAgB;EACxB,KAAK,EAAE,IAAI;CAKd;;AAXD,AAQI,QARI,AAQH,KAAK,CAAA;EACF,KAAK,EAAE,IAAI;CACd;;AAGL,AAAA,MAAM,CAAA;EACF,KAAK,EDrDK,OAAO;ECsDjB,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,MAAM;EACf,WAAW,EDpDR,GAAG;CCqDT;;AAED,AAAA,KAAK,CAAA;EACD,KAAK,ED7DM,OAAO,CC6DG,UAAU;EAC/B,WAAW,EDzDR,GAAG;EC0DN,SAAS,EAAE,KAAK;CACnB;;AAGD,AAAA,KAAK,CAAA;EACD,WAAW,EAAE,kBAAkB;EAC/B,cAAc,EAAE,kBAAkB;CACrC;;AAED,AAAA,KAAK,CAAA;EACD,aAAa,EAAE,eAAe;EAC9B,YAAY,EAAE,eAAe;CAChC;;AAED,AAAA,KAAK,CAAA;EACD,YAAY,EAAE,kBAAkB;CACnC;;AAED,AAAA,KAAK,CAAA;EACD,aAAa,EAAE,eAAe;CACjC;;AAED,AAAA,KAAK,CAAA;EACD,UAAU,EAAE,eAAe;CAC9B;;AAGD,AAAA,OAAO,CAAA;EACH,OAAO,EAAE,eAAe;CAC3B;;AAID,AAAA,KAAK,CAAA;EACD,OAAO,EAAE,eAAe;CAC3B;;AAED,AAAA,KAAK,CAAA;EACD,WAAW,EAAE,iBAAiB;CACjC;;AAED,AAAA,KAAK,CAAA;EACD,eAAe,EAAE,iBAAiB;CACrC;;AAED,AAAA,MAAM,CAAA;EACF,eAAe,EAAE,wBAAwB;CAC5C;;AAED,AAAA,OAAO,CAAA;EACH,IAAI,EAAE,CAAC;CACV;;AAID,AAAA,MAAM,CAAA;EACF,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,eAAe;EACxB,WAAW,EDrHR,GAAG;ECsHN,SAAS,EAAE,MAAM;EACjB,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,GAAG,CAAC,KAAK,CD3HT,OAAO;EC4Hf,gBAAgB,EAAE,OAAO;EACzB,aAAa,EAAE,GAAG;CACrB;;AAED,AAAA,YAAY,CAAA;EACR,OAAO,EAAE,KAAK;EACd,aAAa,EAAE,OAAO;CACzB;;AAGD,AAAA,MAAM,CAAA;EACF,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,eAAe,EAAE,aAAa;EAC9B,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,WAAW;EACpB,WAAW,EDzIR,GAAG;EC0IN,aAAa,EAAE,GAAG;EAClB,aAAa,EAAE,IAAI;CAKtB;;AAbD,AAUI,MAVE,CAUF,MAAM,CAAA;EACF,MAAM,EAAE,OAAO;CAClB;;AAGL,AAAA,cAAc,CAAA;EACV,gBAAgB,EDrJL,sBAAO;ECsJlB,KAAK,EAAE,OAAwB;EAC/B,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,OAAwB;CAC7C;;AAED,AAAA,aAAa,CAAA;EACT,gBAAgB,ED7JN,sBAAO;EC8JjB,KAAK,EAAE,OAAuB;EAC9B,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,OAAuB;CAC5C;;AAID,AAAA,MAAM,CAAC;EACH,OAAO,EAAE,KAAK;EACd,WAAW,EDlKR,GAAG;ECmKN,MAAM,EAAE,SAAS;CACpB;;AAID,AAAA,IAAI,CAAA;EACA,OAAO,EAAE,KAAK;EACd,OAAO,EAAE,eAAe;EACxB,WAAW,EAAE,IAAI;EACjB,2BAA2B,EAAE,WAAW;EACxC,MAAM,EAAE,OAAO;EACf,WAAW,ED9KR,GAAG;EC+KN,SAAS,EAAE,MAAM;EACjB,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;CACrB;;AAED,AAAA,YAAY,CAAA;EACR,gBAAgB,ED5LL,OAAO;EC6LlB,KAAK,ED/LI,IAAI;ECgMb,UAAU,EAAE,oBAAoB;CAKnC;;AARD,AAKI,YALQ,AAKP,MAAM,CAAA;EACH,gBAAgB,EAAE,OAAwB;CAC7C;;AAGL,AAAA,cAAc,CAAA;EACV,gBAAgB,EDpMR,OAAO;ECqMf,KAAK,EDxMG,OAAO;ECyMf,UAAU,EAAE,oBAAoB;CAKnC;;AARD,AAKI,cALU,AAKT,MAAM,CAAA;EACH,gBAAgB,EAAE,OAAqB;CAC1C;;AAGL,AAAA,WAAW,CAAA;EACP,gBAAgB,ED/MN,OAAO;ECgNjB,KAAK,EDnNI,IAAI;ECoNb,UAAU,EAAE,oBAAoB;CAKnC;;AARD,AAKI,WALO,AAKN,MAAM,CAAA;EACH,gBAAgB,EAAE,OAAuB;CAC5C;;AAKL,AAAA,WAAW,CAAA;EACP,OAAO,EAAE,SAAS;CAkCrB;;AAnCD,AAGI,WAHO,CAGP,UAAU,CAAA;EACN,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,GAAG,CAAC,KAAK,CD9Nb,OAAO;EC+NX,WAAW,ED5NZ,GAAG;EC6NF,OAAO,EAAE,eAAe;EACxB,MAAM,EAAE,SAAS;EACjB,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,oBAAoB;EAChC,2BAA2B,EAAE,WAAW;CAuB3C;;AAlCL,AAaQ,WAbG,CAGP,UAAU,CAUN,WAAW,CAAA;EACP,aAAa,EAAE,QAAQ;EACvB,MAAM,EAAE,SAAS;CACpB;;AAhBT,AAkBQ,WAlBG,CAGP,UAAU,CAeN,SAAS,CAAA;EACL,MAAM,EAAE,SAAS;EACjB,SAAS,EAAE,MAAM;EACjB,KAAK,EDhPF,OAAO;CCiPb;;AAtBT,AAwBQ,WAxBG,CAGP,UAAU,CAqBN,aAAa,EAxBrB,WAAW,CAGP,UAAU,CAqBS,aAAa,CAAA;EACxB,MAAM,EAAE,SAAS;EACjB,gBAAgB,EDnPhB,OAAO;ECoPP,OAAO,EAAE,eAAe;EACxB,SAAS,EAAE,MAAM;EACjB,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,WAAW;EAClB,KAAK,ED3PL,OAAO;CC4PV;;AAMT,AAAA,SAAS,CAAA;EACL,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,CAAC;EACP,KAAK,EAAE,CAAC;EACR,MAAM,EAAE,CAAC;EACT,gBAAgB,EAAE,kBAAe;EACjC,OAAO,EAAE,aAAa;EACtB,OAAO,EAAE,IAAI;CAChB;;AAED,AAAA,SAAS,AAAA,KAAK,CAAA;EACV,OAAO,EAAE,KAAK;CACjB;;AAED,AAAA,MAAM,CAAA;EACF,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,KAAK;EAChB,gBAAgB,EDrRP,IAAI;ECsRb,OAAO,EAAE,SAAS;EAClB,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,IAAI;CACf;;AAED,AAAA,aAAa,CAAA;EACT,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,eAAe,EAAE,aAAa;CACjC;;AAID,AAAA,MAAM,CAAA;EACF,aAAa,EAAE,OAAO;CACzB;;AAED,MAAM,EAAE,SAAS,EAAE,KAAK;EACpB,AAAA,MAAM,CAAA;IACF,SAAS,EAAE,MAAM;GACpB;EAED,AAAA,SAAS,CAAA;IACL,SAAS,EAAE,MAAM;GACpB;;;AAIL,AAAA,aAAa,CAAA;EACT,KAAK,EDjTM,OAAO,CCiTG,UAAU;CAClC;;AAID,AAAA,OAAO,CAAA;EACH,gBAAgB,EDzTP,IAAI;EC0Tb,UAAU,EAAE,GAAG,CAAC,KAAK,CDtTb,OAAO;ECuTf,OAAO,EAAE,OAAO;EAChB,UAAU,EAAE,MAAM;EAClB,UAAU,EAAE,IAAI;CAUnB;;AAfD,AAOI,OAPG,CAOH,CAAC,CAAA;EACG,WAAW,EDzTZ,GAAG;CC+TL;;AAdL,AAUQ,OAVD,CAOH,CAAC,CAGG,IAAI,CAAA;EACA,OAAO,EAAE,YAAY;EACrB,KAAK,EDlUF,OAAO;CCmUb;;AAIT,MAAM,CAAC,MAAM,MAAM,SAAS,EAAE,KAAK;EAC/B,AACI,WADO,CACP,UAAU,CAAA;IACN,OAAO,EAAE,+BAA+B;GAc3C;EAhBL,AAIQ,WAJG,CACP,UAAU,CAGN,WAAW,CAAA;IACP,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,MAAM;GACpB;EAPT,AASQ,WATG,CACP,UAAU,CAQN,SAAS,CAAA;IACL,SAAS,EAAE,MAAM;GACpB;EAXT,AAaQ,WAbG,CACP,UAAU,CAYN,aAAa,EAbrB,WAAW,CACP,UAAU,CAYS,aAAa,CAAA;IACxB,SAAS,EAAE,MAAM;GACpB;;;AFvVb,AAAA,OAAO,CAAA;EACH,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,SAAS;EAClB,gBAAgB,EAAE,OAAsB;EACxC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,mBAAe;EACrC,aAAa,EAAE,IAAI;CACtB;;AAED,AAAA,iBAAiB,CAAA;EACb,QAAQ,EAAE,MAAM;EAChB,MAAM,EAAE,KAAK;EACb,GAAG,EAAE,CAAC;CACT;;AAED,AAAA,QAAQ,CAAA;EACJ,OAAO,EAAE,YAAY;EACrB,KAAK,EChBM,OAAO,CDgBG,UAAU;CAKlC;;AAPD,AAII,QAJI,AAIH,MAAM,CAAA;EACH,eAAe,EAAE,SAAS;CAC7B;;AAGL,AAAA,iBAAiB,CAAA;EACb,KAAK,ECzBG,OAAO;ED0Bf,SAAS,EAAE,IAAI;CAClB;;AAED,AAAA,YAAY,CAAA;EACR,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,SAAS;EAChC,GAAG,EAAE,IAAI;CACZ;;AAED,AAAA,cAAc,CAAA;EACV,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,gBAAgB;EACvC,GAAG,EAAE,GAAG;CACX;;AAED,AAAA,kBAAkB,CAAA;EACd,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,OAAO;EAC9B,WAAW,EAAE,MAAM;EACnB,GAAG,EAAE,GAAG;CACX;;AAED,AAAA,SAAS,CAAA;EACL,OAAO,EAAE,IAAI;CAChB;;AAED,AAAA,OAAO,EAAE,QAAQ,CAAA;EACb,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,MAAM;CACrB;;AAED,AAAA,gBAAgB,CAAA;EACZ,QAAQ,EAAE,KAAK;EACf,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,IAAI;EACX,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,2BAA2B,EAAE,WAAW;EACxC,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,MAAM;EACnB,eAAe,EAAE,MAAM;EACvB,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,cAAc;EAC1B,gBAAgB,ECpEL,OAAO;EDqElB,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,KAAK;CASf;;AAvBD,AAgBI,gBAhBY,AAgBX,KAAK,CAAA;EACF,OAAO,EAAE,IAAI;CAChB;;AAlBL,AAoBI,gBApBY,AAoBX,MAAM,CAAA;EACH,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CC7Ef,uBAAO;CD8EjB;;AAGL,AAAA,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,CAAA;EACvE,OAAO,EAAE,iBAAiB;EAC1B,iBAAiB,EAAE,SAAS;EAC5B,mBAAmB,EAAE,MAAM;EAC3B,eAAe,EAAE,SAAS;CAC7B;;AAED,AAAA,UAAU,CAAA;EACN,YAAY,EAAE,OAAO;EACrB,gBAAgB,EAAE,iZAAiZ;CACta;;AAED,AAAA,UAAU,AAAA,QAAQ,CAAA;EACd,KAAK,EChGI,IAAI;CDiGhB;;AAED,AAAA,WAAW,EAAE,eAAe,CAAA;EACxB,gBAAgB,EAAE,8fAA8f;CACnhB;;AAED,AAAA,oBAAoB,CAAA;EAChB,YAAY,EAAE,OAAO;EACrB,gBAAgB,EAAE,2ZAA2Z;CAChb;;AAGD,AAAA,aAAa,CAAA;EACT,OAAO,EAAE,IAAI;CAChB;;AAED,AAAA,WAAW,CAAA;EACP,gBAAgB,EAAE,yTAAyT;CAC9U;;AAED,AAAA,WAAW,AAAA,OAAO,CAAA;EACd,SAAS,EAAE,cAAc;CAC5B;;AAED,MAAM,CAAC,MAAM,MAAM,SAAS,EAAE,KAAK;EAC/B,AAAA,UAAU,CAAA;IACN,KAAK,EAAE,KAAK;GACf;EAGD,AAAA,aAAa,CAAA;IACT,OAAO,EAAE,KAAK;GACjB;EAED,AAAA,YAAY,CAAA;IACR,qBAAqB,EAAE,GAAG;GAC7B;EAED,AAAA,iBAAiB,CAAA;IACb,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,IAAI;GACf;;;AAIL,MAAM,CAAC,MAAM,MAAM,SAAS,EAAE,KAAK;EAC/B,AAAA,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,oBAAoB,CAAA;IACtD,OAAO,EAAE,iBAAiB;GAC7B;EAED,AAAA,gBAAgB,CAAA;IACZ,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;GACd;EAED,AAAA,kBAAkB,CAAA;IACd,qBAAqB,EAAE,GAAG;GAC7B;EAED,AAAA,OAAO,CAAC,CAAC,CAAA;IACL,SAAS,EAAE,OAAO;GACrB",
4 | "sources": [
5 | "../scss/main.scss",
6 | "../scss/_variables.scss",
7 | "../scss/_global.scss"
8 | ],
9 | "names": [],
10 | "file": "main.css"
11 | }
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | import { generateId, add, deleteData, findAll, createListItem, save, loadDataLinkEdit, edit, clearDataLinkEdit, deleteAll } from "./utils.js";
2 | import { btnBackToTop, formAddLink, formImportLinks, btnExport, inputSearch, linkAlert, listGroup, sortLinks, btnCollapse, btnCloseModal, formEditLink, btnDeleteAll } from "./elements.js";
3 |
4 | const showAlert = (message, color = 'success') => {
5 | linkAlert.firstElementChild.textContent = message;
6 | linkAlert.setAttribute('class', 'alert alert-' + color);
7 | linkAlert.style.display = 'flex';
8 | }
9 |
10 | const handleAddEvent = function (e) {
11 | e.preventDefault();
12 | const body = {
13 | id: generateId(),
14 | title: this.inputTitle.value.trim(),
15 | url: this.inputUrl.value.trim(),
16 | created_at: Date.now(),
17 | updated_at: null,
18 | }
19 |
20 | add(body);
21 | showList(inputSearch.value, sortLinks.value);
22 | this.inputTitle.value = '';
23 | this.inputUrl.value = '';
24 | showAlert('Link has been added.');
25 | }
26 |
27 | const handleEditEvent = function (e) {
28 | e.preventDefault();
29 | const body = {
30 | id: this.inputIdEdit.value,
31 | title: this.inputTitleEdit.value.trim(),
32 | url: this.inputUrlEdit.value.trim(),
33 | created_at: parseInt(this.inputCreatedAtEdit.value),
34 | updated_at: Date.now(),
35 | }
36 |
37 | edit(body);
38 | showList(inputSearch.value, sortLinks.value);
39 | this.inputIdEdit.value = '';
40 | this.inputTitleEdit.value = '';
41 | this.inputUrlEdit.value = '';
42 | showAlert('Link has been edited.');
43 | document.querySelector('.backdrop').classList.remove('show');
44 | }
45 |
46 | let jsonFile = null;
47 | const handleExportLinks = () => {
48 | if (confirm('Are you sure to download this file?')) {
49 | // get current links
50 | const links = findAll();
51 | // convert links to blob with the type of json
52 | const data = new Blob([JSON.stringify(links)], { type: "application/json" });
53 | // Avoid memory leaks
54 | if (jsonFile !== null) {
55 | URL.revokeObjectURL(jsonFile);
56 | }
57 |
58 | jsonFile = URL.createObjectURL(data);
59 | const link = document.createElement('a');
60 | link.setAttribute('download', 'links.json');
61 | link.href = jsonFile;
62 | document.body.appendChild(link);
63 |
64 | window.requestAnimationFrame(function () {
65 | const event = new MouseEvent('click');
66 | link.dispatchEvent(event);
67 | document.body.removeChild(link);
68 | });
69 | }
70 | }
71 |
72 | const handleImportLinks = function (e) {
73 | e.preventDefault();
74 | const file = this.fileJson.files[0];
75 | if (file.type !== "application/json") {
76 | return showAlert('Import failed, The File type is not valid.', 'danger');
77 | }
78 | const reader = new FileReader();
79 | reader.addEventListener('load', (e) => {
80 | const links = findAll();
81 | let importedLinks = JSON.parse(e.target.result);
82 | let keys;
83 | if (!Array.isArray(importedLinks)) {
84 | keys = Object.keys(importedLinks);
85 | } else {
86 | keys = Object.keys(importedLinks[0]);
87 | }
88 | if (!keys.includes('id') || !keys.includes('title') || !keys.includes('created_at') || !keys.includes('url') || !keys.includes('updated_at')) {
89 | return showAlert('Import failed, The file is wrong.', 'danger');
90 | }
91 | links.map(link => {
92 | importedLinks.map(importedLink => {
93 | if (link.id == importedLink.id) {
94 | importedLink.id = generateId();
95 | }
96 | })
97 | });
98 | links.push(...importedLinks);
99 | save(links);
100 | showList(inputSearch.value, sortLinks.value);
101 | showAlert('Link has been imported.');
102 | });
103 | reader.readAsBinaryString(file);
104 | this.reset();
105 | }
106 |
107 | const showList = (keyword = null, sort) => {
108 | const data = findAll(sort);
109 | let temp = '';
110 | // Search links
111 | if (keyword) {
112 | data.map(d => {
113 | if (d.title.toLowerCase().includes(keyword.toLowerCase()) || d.url.toLowerCase().includes(keyword.toLowerCase())) {
114 | temp += createListItem(d);
115 | }
116 | });
117 | // Message not found
118 | if (temp === '') {
119 | return listGroup.innerHTML = 'Links not found.
';
120 | }
121 | } else {
122 | data.map(d => temp += createListItem(d));
123 | }
124 | // Message not found
125 | if (!data || data.length <= 0) {
126 | return listGroup.innerHTML = 'Links not added yet.
';
127 | }
128 | listGroup.innerHTML = temp;
129 | }
130 |
131 | // close alert
132 | linkAlert.lastElementChild.addEventListener('click', function () {
133 | linkAlert.style.display = 'none';
134 | });
135 |
136 | window.addEventListener('scroll', () => {
137 | if (window.scrollY != 0) {
138 | btnBackToTop.classList.add('show');
139 | } else {
140 | btnBackToTop.classList.remove('show');
141 | }
142 | });
143 | btnExport.addEventListener('click', handleExportLinks);
144 | formAddLink.addEventListener('submit', handleAddEvent);
145 | formImportLinks.addEventListener('submit', handleImportLinks);
146 | btnBackToTop.addEventListener('click', () => {
147 | scrollTo({
148 | behavior: 'smooth',
149 | top: 0
150 | });
151 | });
152 | sortLinks.addEventListener('change', (e) => {
153 | showList(inputSearch.value, e.target.value);
154 | });
155 | inputSearch.addEventListener('keyup', (e) => {
156 | showList(e.target.value);
157 | });
158 | document.addEventListener('click', (e) => {
159 | if (e.target.classList.contains('btn-delete')) {
160 | if (confirm("Are you sure to remove this link?")) {
161 | const id = e.target.dataset.id;
162 | deleteData(id);
163 | showList(inputSearch.value, sortLinks.value);
164 | showAlert('Link has been deleted.');
165 | }
166 | }
167 | if (e.target.classList.contains('btn-detail')) {
168 | // rotate icon / image
169 | e.target.classList.toggle('active');
170 | // get element list-detail
171 | const elementDetail = e.target.parentElement.parentElement.nextElementSibling;
172 | // show element
173 | elementDetail.classList.toggle('flex');
174 | }
175 | if (e.target.classList.contains('btn-show-modal-edit')) {
176 | loadDataLinkEdit(e.target.dataset.id);
177 | const elementModal = document.getElementById(e.target.dataset.target);
178 | elementModal.classList.toggle('show');
179 | }
180 | });
181 | // handle collapse form
182 | btnCollapse.addEventListener('click', function () {
183 | const targetElementCollapse = document.getElementById(this.dataset.target);
184 | if (targetElementCollapse.classList.contains('d-none')) {
185 | this.textContent = "Hide Form";
186 | } else {
187 | this.textContent = "Show Form";
188 | }
189 | targetElementCollapse.classList.toggle('d-none');
190 | });
191 | // hide/close modal
192 | btnCloseModal.addEventListener('click', function () {
193 | const elementModal = document.getElementById(this.dataset.target);
194 | clearDataLinkEdit();
195 | elementModal.classList.remove('show');
196 | });
197 | // handle edit
198 | formEditLink.addEventListener('submit', handleEditEvent);
199 | btnDeleteAll.addEventListener('click', () => {
200 | if (confirm('Are you sure to delete all links?')) {
201 | deleteAll();
202 | showList();
203 | showAlert('All Links have been deleted.');
204 | }
205 | });
206 |
207 | showList();
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 | Web Links Management | savelinks.
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
43 |
44 |
65 |
66 |
67 |
68 |
72 |
Links List
73 |
74 |
75 |
81 |
82 |
83 |
85 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
165 |
166 |
167 |
168 |
169 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | -webkit-box-sizing: border-box;
5 | box-sizing: border-box;
6 | font-family: -apple-system, BlinkMacSystemFont, 'Poppins', 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
7 | }
8 |
9 | body {
10 | min-height: 100vh;
11 | background-color: #FFF;
12 | color: #2C3E50;
13 | position: relative;
14 | }
15 |
16 | a, a:visited {
17 | display: block;
18 | text-decoration: none;
19 | color: #2C3E50;
20 | }
21 |
22 | li {
23 | list-style: none;
24 | }
25 |
26 | span {
27 | display: block;
28 | }
29 |
30 | .container {
31 | width: 1200px;
32 | max-width: 100%;
33 | padding: 0 .500rem;
34 | margin: 0 auto;
35 | }
36 |
37 | .wrapper {
38 | padding-top: 1rem;
39 | }
40 |
41 | .devider {
42 | display: block;
43 | height: 1px;
44 | background-color: #EAEAEA;
45 | border: none;
46 | margin: .200rem 0 1rem 0;
47 | width: 20px;
48 | }
49 |
50 | .devider.full {
51 | width: 100%;
52 | }
53 |
54 | .error {
55 | color: #ff2f2f;
56 | text-align: center;
57 | padding: 2rem 0;
58 | font-weight: 600;
59 | }
60 |
61 | .logo {
62 | color: #6B4FBB !important;
63 | font-weight: 600;
64 | font-size: 1.5em;
65 | }
66 |
67 | .py-1 {
68 | padding-top: .500rem !important;
69 | padding-bottom: .500rem !important;
70 | }
71 |
72 | .px-2 {
73 | padding-right: 1rem !important;
74 | padding-left: 1rem !important;
75 | }
76 |
77 | .mr-1 {
78 | margin-right: .500rem !important;
79 | }
80 |
81 | .mb-2 {
82 | margin-bottom: 1rem !important;
83 | }
84 |
85 | .mt-2 {
86 | margin-top: 1rem !important;
87 | }
88 |
89 | .d-none {
90 | display: none !important;
91 | }
92 |
93 | .flex {
94 | display: -webkit-box !important;
95 | display: -ms-flexbox !important;
96 | display: flex !important;
97 | }
98 |
99 | .ai-c {
100 | -webkit-box-align: center !important;
101 | -ms-flex-align: center !important;
102 | align-items: center !important;
103 | }
104 |
105 | .jc-c {
106 | -webkit-box-pack: center !important;
107 | -ms-flex-pack: center !important;
108 | justify-content: center !important;
109 | }
110 |
111 | .jc-sb {
112 | -webkit-box-pack: justify !important;
113 | -ms-flex-pack: justify !important;
114 | justify-content: space-between !important;
115 | }
116 |
117 | .flex-1 {
118 | -webkit-box-flex: 1;
119 | -ms-flex: 1;
120 | flex: 1;
121 | }
122 |
123 | .input {
124 | display: block;
125 | width: 100%;
126 | padding: .900rem .600rem;
127 | font-weight: 600;
128 | font-size: .900em;
129 | outline: none;
130 | border: 1px solid #EAEAEA;
131 | background-color: #FAFAFA;
132 | border-radius: 3px;
133 | }
134 |
135 | .input-group {
136 | display: block;
137 | margin-bottom: .800rem;
138 | }
139 |
140 | .alert {
141 | display: -webkit-box;
142 | display: -ms-flexbox;
143 | display: flex;
144 | -webkit-box-align: center;
145 | -ms-flex-align: center;
146 | align-items: center;
147 | -webkit-box-pack: justify;
148 | -ms-flex-pack: justify;
149 | justify-content: space-between;
150 | width: 100%;
151 | padding: 1.2rem 1rem;
152 | font-weight: 600;
153 | border-radius: 4px;
154 | margin-bottom: 1rem;
155 | }
156 |
157 | .alert .close {
158 | cursor: pointer;
159 | }
160 |
161 | .alert-success {
162 | background-color: rgba(9, 195, 114, 0.2);
163 | color: #09b96c;
164 | border: 1px solid #09b96c;
165 | }
166 |
167 | .alert-danger {
168 | background-color: rgba(255, 47, 47, 0.2);
169 | color: #ff2525;
170 | border: 1px solid #ff2525;
171 | }
172 |
173 | .label {
174 | display: block;
175 | font-weight: 600;
176 | margin: .300rem 0;
177 | }
178 |
179 | .btn {
180 | display: block;
181 | padding: .700rem .500rem;
182 | -webkit-user-select: none;
183 | -moz-user-select: none;
184 | -ms-user-select: none;
185 | user-select: none;
186 | -webkit-tap-highlight-color: transparent;
187 | cursor: pointer;
188 | font-weight: 600;
189 | font-size: .900em;
190 | outline: none;
191 | -webkit-appearance: none;
192 | -moz-appearance: none;
193 | appearance: none;
194 | border: none;
195 | border-radius: 3px;
196 | }
197 |
198 | .btn-primary {
199 | background-color: #6B4FBB;
200 | color: #FFF;
201 | -webkit-transition: background-color .3s;
202 | transition: background-color .3s;
203 | }
204 |
205 | .btn-primary:hover {
206 | background-color: #6144b1;
207 | }
208 |
209 | .btn-secondary {
210 | background-color: #EAEAEA;
211 | color: #2C3E50;
212 | -webkit-transition: background-color .3s;
213 | transition: background-color .3s;
214 | }
215 |
216 | .btn-secondary:hover {
217 | background-color: #e0e0e0;
218 | }
219 |
220 | .btn-danger {
221 | background-color: #ff2f2f;
222 | color: #FFF;
223 | -webkit-transition: background-color .3s;
224 | transition: background-color .3s;
225 | }
226 |
227 | .btn-danger:hover {
228 | background-color: #ff1b1b;
229 | }
230 |
231 | .list-group {
232 | padding: .300rem 0;
233 | }
234 |
235 | .list-group .list-item {
236 | width: 100%;
237 | border: 1px solid #EAEAEA;
238 | font-weight: 600;
239 | padding: .600rem .900rem;
240 | margin: .200rem 0;
241 | border-radius: 3px;
242 | -webkit-transition: background-color .3s;
243 | transition: background-color .3s;
244 | -webkit-tap-highlight-color: transparent;
245 | }
246 |
247 | .list-group .list-item .item-title {
248 | overflow-wrap: anywhere;
249 | margin: .300rem 0;
250 | }
251 |
252 | .list-group .list-item .item-url {
253 | margin: .300rem 0;
254 | font-size: .800em;
255 | color: #6B4FBB;
256 | }
257 |
258 | .list-group .list-item .item-created, .list-group .list-item .item-updated {
259 | margin: .300rem 0;
260 | background-color: #EAEAEA;
261 | padding: .300rem .400rem;
262 | font-size: .800em;
263 | display: block;
264 | width: -webkit-max-content;
265 | width: -moz-max-content;
266 | width: max-content;
267 | color: #2C3E50;
268 | }
269 |
270 | .backdrop {
271 | position: fixed;
272 | top: 0;
273 | left: 0;
274 | right: 0;
275 | bottom: 0;
276 | background-color: rgba(0, 0, 0, 0.4);
277 | padding: 2rem 0 1rem 0;
278 | display: none;
279 | }
280 |
281 | .backdrop.show {
282 | display: block;
283 | }
284 |
285 | .modal {
286 | width: 100%;
287 | max-width: 700px;
288 | background-color: #FFF;
289 | padding: 2rem 1rem;
290 | border-radius: 4px;
291 | margin: auto;
292 | }
293 |
294 | .modal-header {
295 | display: -webkit-box;
296 | display: -ms-flexbox;
297 | display: flex;
298 | -webkit-box-align: center;
299 | -ms-flex-align: center;
300 | align-items: center;
301 | -webkit-box-pack: justify;
302 | -ms-flex-pack: justify;
303 | justify-content: space-between;
304 | }
305 |
306 | .title {
307 | margin-bottom: .500rem;
308 | }
309 |
310 | @media (max-width: 500px) {
311 | .title {
312 | font-size: 1.5rem;
313 | }
314 | .title-md {
315 | font-size: 1.3rem;
316 | }
317 | }
318 |
319 | .text-primary {
320 | color: #6B4FBB !important;
321 | }
322 |
323 | .footer {
324 | background-color: #FFF;
325 | border-top: 1px solid #EAEAEA;
326 | padding: 1.2em 0;
327 | text-align: center;
328 | margin-top: 5rem;
329 | }
330 |
331 | .footer p {
332 | font-weight: 600;
333 | }
334 |
335 | .footer p span {
336 | display: inline-block;
337 | color: #6B4FBB;
338 | }
339 |
340 | @media screen and (max-width: 500px) {
341 | .list-group .list-item {
342 | padding: .200rem .500rem .400rem .500rem;
343 | }
344 | .list-group .list-item .item-title {
345 | margin: .300rem 0;
346 | font-size: .900em;
347 | }
348 | .list-group .list-item .item-url {
349 | font-size: .700em;
350 | }
351 | .list-group .list-item .item-created, .list-group .list-item .item-updated {
352 | font-size: .600em;
353 | }
354 | }
355 |
356 | .navbar {
357 | width: 100%;
358 | padding: .500rem 0;
359 | background-color: #fcfcfc;
360 | -webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.05);
361 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.05);
362 | margin-bottom: 2rem;
363 | }
364 |
365 | .form-create-link {
366 | position: -webkit-sticky;
367 | position: sticky;
368 | height: 100vh;
369 | top: 0;
370 | }
371 |
372 | .my-name {
373 | display: inline-block;
374 | color: #6B4FBB !important;
375 | }
376 |
377 | .my-name:hover {
378 | text-decoration: underline;
379 | }
380 |
381 | .link-icon-github {
382 | color: #2C3E50;
383 | font-size: 2rem;
384 | }
385 |
386 | .grid-layout {
387 | display: -ms-grid;
388 | display: grid;
389 | -ms-grid-columns: 1.2fr 2fr;
390 | grid-template-columns: 1.2fr 2fr;
391 | gap: 20px;
392 | }
393 |
394 | .grid-features {
395 | display: -ms-grid;
396 | display: grid;
397 | -ms-grid-columns: 1fr 1fr 1fr .3fr;
398 | grid-template-columns: 1fr 1fr 1fr .3fr;
399 | gap: 5px;
400 | }
401 |
402 | .grid-import-links {
403 | display: -ms-grid;
404 | display: grid;
405 | -ms-grid-columns: 2fr 1fr;
406 | grid-template-columns: 2fr 1fr;
407 | -webkit-box-align: center;
408 | -ms-flex-align: center;
409 | align-items: center;
410 | gap: 5px;
411 | }
412 |
413 | .tab-item {
414 | display: none;
415 | }
416 |
417 | #btnAdd, #btnEdit {
418 | width: 100%;
419 | margin-top: 1.4rem;
420 | }
421 |
422 | .btn-back-to-top {
423 | position: fixed;
424 | bottom: 50px;
425 | right: 50px;
426 | width: 50px;
427 | height: 50px;
428 | -webkit-tap-highlight-color: transparent;
429 | display: none;
430 | -webkit-box-align: center;
431 | -ms-flex-align: center;
432 | align-items: center;
433 | -webkit-box-pack: center;
434 | -ms-flex-pack: center;
435 | justify-content: center;
436 | border-radius: 50%;
437 | -webkit-transition: -webkit-box-shadow .3s;
438 | transition: -webkit-box-shadow .3s;
439 | transition: box-shadow .3s;
440 | transition: box-shadow .3s, -webkit-box-shadow .3s;
441 | background-color: #6B4FBB;
442 | cursor: pointer;
443 | color: white;
444 | }
445 |
446 | .btn-back-to-top.show {
447 | display: -webkit-box;
448 | display: -ms-flexbox;
449 | display: flex;
450 | }
451 |
452 | .btn-back-to-top:hover {
453 | -webkit-box-shadow: 0 5px 10px rgba(107, 79, 187, 0.5);
454 | box-shadow: 0 5px 10px rgba(107, 79, 187, 0.5);
455 | }
456 |
457 | .btn-visit, .btn-delete, .btn-detail, .btn-show-modal-edit, .btn-delete-all {
458 | padding: 1.4rem !important;
459 | background-repeat: no-repeat;
460 | background-position: center;
461 | background-size: 25px 21px;
462 | }
463 |
464 | .btn-visit {
465 | margin-right: .200rem;
466 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-link-2'%3e%3cpath d='M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3'%3e%3c/path%3e%3cline x1='8' y1='12' x2='16' y2='12'%3e%3c/line%3e%3c/svg%3e");
467 | }
468 |
469 | .btn-visit:visited {
470 | color: #FFF;
471 | }
472 |
473 | .btn-delete, .btn-delete-all {
474 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-trash-2'%3e%3cpolyline points='3 6 5 6 21 6'%3e%3c/polyline%3e%3cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2'%3e%3c/path%3e%3cline x1='10' y1='11' x2='10' y2='17'%3e%3c/line%3e%3cline x1='14' y1='11' x2='14' y2='17'%3e%3c/line%3e%3c/svg%3e");
475 | }
476 |
477 | .btn-show-modal-edit {
478 | margin-right: .200rem;
479 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23FFF' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-edit'%3e%3cpath d='M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'%3e%3c/path%3e%3cpath d='M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z'%3e%3c/path%3e%3c/svg%3e");
480 | }
481 |
482 | .btn-collapse {
483 | display: none;
484 | }
485 |
486 | .btn-detail {
487 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
488 | }
489 |
490 | .btn-detail.active {
491 | -webkit-transform: rotate(180deg);
492 | transform: rotate(180deg);
493 | }
494 |
495 | @media screen and (max-width: 800px) {
496 | .container {
497 | width: 600px;
498 | }
499 | .btn-collapse {
500 | display: block;
501 | }
502 | .grid-layout {
503 | -ms-grid-columns: 1fr;
504 | grid-template-columns: 1fr;
505 | }
506 | .form-create-link {
507 | position: static;
508 | height: auto;
509 | }
510 | }
511 |
512 | @media screen and (max-width: 500px) {
513 | .btn-visit, .btn-delete, .btn-detail, .btn-show-modal-edit {
514 | padding: 1.1rem !important;
515 | }
516 | .btn-back-to-top {
517 | bottom: 20px;
518 | right: 20px;
519 | }
520 | .grid-import-links {
521 | -ms-grid-columns: 1fr;
522 | grid-template-columns: 1fr;
523 | }
524 | .footer p {
525 | font-size: .800rem;
526 | }
527 | }
528 | /*# sourceMappingURL=main.css.map */
--------------------------------------------------------------------------------