Skip to content

Commit 05d4f9f

Browse files
authored
Live Preview (#41)
* Preview Button * Add preview Icon * Add preview button and block * Add js script for controling preview * Update preview.js * Add base data to preview * Add preview - name - url - photo - description - email * Update preview.png * Add Links to preview * Add links to preview * Add default preview * Add animation * Move preview.js to index.js * Update Icon * Add Preview for theme * Themes Load images * js preview in new html window independent * default theme * Rename css * Final preview Feature * Add meta data * Make preview only for devices min-width: 1000px * Update index.php * Update .gitignore
1 parent 60c41ac commit 05d4f9f

File tree

4 files changed

+219
-4
lines changed

4 files changed

+219
-4
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/dist/
1+
/dist/

src/index.css

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
:root{
2+
--buttonSize: 70px;
3+
--buttonOffset: 10px;
4+
--buttonRadius: 10px;
5+
--previewWidth: 30vw;
6+
--previewHeight: 100vh;
7+
--previewXOffset: 0;
8+
--previewYOffset: 0;
9+
--animationDuration: 500ms;
10+
}
11+
#previewButton {
12+
position: fixed;
13+
width: var(--buttonSize);
14+
height: var(--buttonSize);
15+
bottom: var(--buttonOffset);
16+
right: var(--buttonOffset);
17+
border-radius: var(--buttonRadius);
18+
z-index: 2;
19+
}
20+
21+
#previewButton > *{
22+
width: 100%;
23+
height: 100%;
24+
}
25+
26+
#previewBlock{
27+
position: fixed;
28+
top: var(--previewYOffset);
29+
right: var(--previewXOffset);
30+
width: var(--previewWidth);
31+
height: var(--previewHeight);
32+
z-index: 1;
33+
transition: var(--animationDuration);
34+
right: -100%;
35+
}
36+
37+
@media screen and (max-width: 1000px){
38+
#previewButton{
39+
display: none;
40+
}
41+
#previewBlock{
42+
display: none;
43+
}
44+
}

src/index.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const localStorageIgnore = ["photo"];
2+
const themeUrl = "https://cdn.jsdelivr.net/npm/[email protected]";
23

34
// Elements
45
const addCustomLinkButton = document.querySelector("a.btn");
@@ -218,3 +219,152 @@ checkbox_preview.addEventListener("change", () => {
218219
checkbox_zip.addEventListener("change", () => {
219220
checkbox_preview.disabled = checkbox_zip.checked;
220221
});
222+
223+
/*****************************************************/
224+
/***************** Real Time Preview *****************/
225+
/*****************************************************/
226+
227+
// Get Window Elements
228+
var previewButton = document.getElementById('previewButton');
229+
var previewBlock = document.getElementById('previewBlock');
230+
var additionalLinkButton = document.getElementById("additionalLink");
231+
var formData = document.getElementById('form');
232+
var theme = document.getElementById('theme');
233+
234+
// Real time variables
235+
var preview = false;
236+
var photo = "";
237+
var linkCount = Number(additionalLinkButton.getAttribute("data-index"));
238+
const styleElement = document.createElement('style');
239+
240+
// Preview Button functionality
241+
previewButton.addEventListener('click', () => {
242+
preview = !preview;
243+
if (preview) {
244+
// previewBlock.style.display = 'block';
245+
previewBlock.style.right = '0';
246+
previewButton.style.filter = 'invert(1)';
247+
UpdatePreview();
248+
} else {
249+
// previewBlock.style.display = 'none';
250+
previewBlock.style.right = '-100%';
251+
previewButton.style.filter = 'invert(0)';
252+
}
253+
});
254+
255+
// Update Preview Photo On Input
256+
formData['photo'].addEventListener('input', (e) => {
257+
var photoData = e.target.files[0];
258+
if(photoData) {
259+
const reader = new FileReader();
260+
reader.onload = (e) => {
261+
photo = e.target.result;
262+
UpdatePreview();
263+
}
264+
reader.readAsDataURL(photoData);
265+
}
266+
});
267+
268+
// Add Listner for additionalLinkButton and for form data
269+
additionalLinkButton.addEventListener('click', () => {
270+
linkCount++;
271+
var linkId = `links[${linkCount}]`;
272+
document.getElementById(linkId + "[url]").addEventListener('input', UpdatePreview);
273+
document.getElementById(linkId + "[name]").addEventListener('input', UpdatePreview);
274+
document.getElementById(linkId + "[icon]").addEventListener('input', UpdatePreview);
275+
});
276+
277+
// Update Prview Function
278+
function UpdatePreview() {
279+
var name = formData['name'].value;
280+
var mainUrl= formData['url'].value;
281+
var description = formData['description'].value;
282+
var email = formData['email'].value;
283+
var links = "";
284+
var photoCode = "";
285+
var themePath = "";
286+
287+
// Links
288+
for (var i = 0; i < linkCount; i++) {
289+
var linkId = `links[${i}]`;
290+
var linkUrl = document.getElementById(linkId + "[url]").value;
291+
var linkName = document.getElementById(linkId + "[name]").value;
292+
var linkIcon = document.getElementById(linkId + "[icon]").value;
293+
294+
if(linkUrl !== ""){
295+
if(linkIcon !== ""){
296+
links +=
297+
`<a class="link" href="${linkUrl}" target="_blank">
298+
<ion-icon name="${linkIcon}"></ion-icon>
299+
${linkName} </a>`;
300+
}
301+
else{
302+
links +=
303+
`<a class="link" href="${linkUrl}" target="_blank">
304+
${linkName} </a>`;
305+
}
306+
}
307+
}
308+
309+
// Check if data is added
310+
if(photo !== '') photoCode = `<img id="userPhoto" src="${photo}" alt="User Photo"></img>`;
311+
if(name !== '') name = `<a href="${mainUrl}"><h1 id="userName">${name}</h1></a>`;
312+
if(description !== '') description =`<p id="description">${description}</p>`;
313+
if(email !== '') email = `<a class="link" href="mailto:${email}" target="_blank"><ion-icon name="mail"></ion-icon> Email</a>`;
314+
315+
// Add path to files
316+
if(theme.value !== "")
317+
{
318+
let jsonString = theme.value.match(/{.*}/)[0];
319+
var json = JSON.parse(jsonString);
320+
themePath = "themes/" + json.id;
321+
}
322+
else themePath = "themes/darkmode";
323+
324+
// Add Style
325+
var themeStylePath = themeUrl + "/" + themePath+ "/" + "style.css";
326+
327+
// Add JS
328+
var themeJSPath = themeUrl + "/" + themePath+ "/" + "index.js";
329+
330+
// Update Preview
331+
var previewHTMLCode =
332+
`<html>
333+
<head>
334+
<meta name="viewport" content="width=device-width, initial-scale=1">
335+
<meta charset="UTF-8">
336+
<link rel="stylesheet" href="${themeStylePath}">
337+
</head>
338+
<body>
339+
${photoCode}
340+
${name}
341+
${description}
342+
<div id="links">
343+
${links}
344+
${email}
345+
</div>
346+
</body>
347+
<script src="${themeJSPath}"></script>
348+
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ionicons/ionicons.esm.js"></script>
349+
<script nomodule src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ionicons/ionicons.js"></script>
350+
</html>`;
351+
352+
var blob = new Blob([previewHTMLCode], { type: 'text/html' });
353+
var url = URL.createObjectURL(blob);
354+
previewBlock.innerHTML = `<object style="width: 100%; height: 100%;" type='text/html' data='${url}'></object>`;
355+
};
356+
357+
// Add Listner for all links on file Load
358+
for (var i = 0; i < linkCount; i++) {
359+
var linkId = `links[${i}]`;
360+
document.getElementById(linkId + "[url]").addEventListener('input', UpdatePreview);
361+
document.getElementById(linkId + "[name]").addEventListener('input', UpdatePreview);
362+
document.getElementById(linkId + "[icon]").addEventListener('input', UpdatePreview);
363+
}
364+
365+
// Add Listner for all forms inputs on file Load
366+
formData['name'].addEventListener('input', UpdatePreview);
367+
formData['url'].addEventListener('input', UpdatePreview);
368+
formData['description'].addEventListener('input', UpdatePreview);
369+
formData['email'].addEventListener('input', UpdatePreview);
370+
theme.addEventListener('input', UpdatePreview);

src/index.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,24 @@
4343
<head>
4444
<meta charset="utf-8">
4545
<meta name="viewport" content="width=device-width, initial-scale=1">
46+
47+
<meta name="description" content="Create your own LinkFree and have all your links in one place">
48+
<meta name="author" content="Chris K. Thomas">
49+
<meta name="keywords" content="linkfree, linktree, link in bio, link in bio alternative, linkfree generator, linktree generator, link in bio generator">
50+
<meta property="og:title" content="LinkFree Generator">
51+
<meta property="og:description" content="Create your own LinkFree and have all your links in one place">
52+
<meta property="og:url" content="https://chriskthomas.github.io/linkfree-generator/">
53+
<meta property="og:image" content="https://lh3.googleusercontent.com/p/AF1QipMjTWdGPL1Ch8Q0poYcH5vhl_tvqF-1o1_4slJD=s680-w680-h510">
54+
<meta property="og:type" content="website">
55+
<meta property="og:site_name" content="LinkFree Generator">
56+
<meta property="og:locale" content="en_US">
57+
<meta name="twitter:card" content="summary_large_image">
58+
<meta name="twitter:title" content="LinkFree Generator">
59+
<meta name="twitter:description" content="Create your own LinkFree and have all your links in one place">
60+
<meta name="twitter:image" content="https://lh3.googleusercontent.com/p/AF1QipMjTWdGPL1Ch8Q0poYcH5vhl_tvqF-1o1_4slJD=s680-w680-h510">
61+
4662
<title>LinkFree Generator</title>
63+
<link rel="stylesheet" href="index.css">
4764
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
4865
</head>
4966

@@ -53,7 +70,7 @@
5370
<p>
5471
Fill out this form to generate your own single page website. All fields are optional except for your name. So, don't worry if you don't have all these accounts. The output will be a single <code>index.html</code> file that you can upload to any static hosting provider such as GitHub Pages, Cloudflare Pages, Vercel, Netlify, or DigitalOcean Apps.
5572
</p>
56-
<form class="mb-3" action="api.php" method="post" enctype="multipart/form-data" accept-charset="utf-8">
73+
<form id="form" class="mb-3" action="api.php" method="post" enctype="multipart/form-data" accept-charset="utf-8">
5774
<div class="mb-3">
5875
<label for="name" class="form-label">Name</label>
5976
<input type="text" id="name" name="name" class="form-control" placeholder="Chris K. Thomas" required>
@@ -102,7 +119,7 @@
102119
</div>
103120
<?php } ?>
104121
<?php if ($num_clinks < 50) { ?>
105-
<a class="btn btn-secondary mb-2" data-index="<?= ($lastsite_index + $num_clinks) ?>" role="button">+ Add Additional Link</a>
122+
<a id="additionalLink" class="btn btn-secondary mb-2" data-index="<?= ($lastsite_index + $num_clinks) ?>" role="button">+ Add Additional Link</a>
106123
<?php } ?>
107124
<div class="mb-3">
108125
<div class="form-text">
@@ -154,7 +171,11 @@
154171

155172
</div>
156173
</footer>
174+
175+
<button id="previewButton"><ion-icon name="eye"></ion-icon></button>
176+
<div id="previewBlock"></div>
157177
<script src="./index.js"></script>
158-
</body>
159178

179+
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ionicons/ionicons.esm.js"></script>
180+
</body>
160181
</html>

0 commit comments

Comments
 (0)