Compare commits
52 Commits
main
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
e843870afd | |||
74dc1196cd | |||
306396cce1 | |||
a2acc03f7c | |||
6f0f5f1cf7 | |||
278fc640ce | |||
2e07de0e71 | |||
e0ea53a9d3 | |||
632eed6810 | |||
dd4dfdea78 | |||
377f87ced3 | |||
d8d40ddc30 | |||
ee2098e6e6 | |||
d2066087ae | |||
d187ec70c3 | |||
4a734b66f9 | |||
cbdc81c4ce | |||
7bd03be127 | |||
8d5319ac4a | |||
26357e531d | |||
eac1534497 | |||
94de27084c | |||
0a6ede8125 | |||
ebf9a21478 | |||
86652a4f09 | |||
3f171dea3c | |||
6b0af2fb3e | |||
1b33bd398d | |||
f31f180687 | |||
8b9a2ac8d5 | |||
212103ab71 | |||
7d7012eec6 | |||
b2b56480b8 | |||
79795bc060 | |||
6961d9c34d | |||
0797fd9eff | |||
d69078ff26 | |||
ba437de706 | |||
065a6a2d17 | |||
3eb3b6845b | |||
71227d7d78 | |||
52c1abb0b7 | |||
de234ddf8c | |||
3769f3edc3 | |||
87b2e808f7 | |||
189ca5034b | |||
71405fd5a2 | |||
357f0b06f3 | |||
30a5d53bc0 | |||
bd183bf832 | |||
69cfbf1b44 | |||
a75c919929 |
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# .dockerignore
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
.git
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
.eslintignore
|
||||||
|
.eslintrc.cjs
|
||||||
|
|
||||||
|
.prettierrc
|
||||||
|
.pretieriignore
|
||||||
|
|
||||||
|
README.md
|
||||||
|
|
||||||
|
Dockerfile
|
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
.vscode
|
13
.prettierignore
Normal file
13
.prettierignore
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# create-svelte
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 77 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 99 KiB |
@ -1,86 +0,0 @@
|
|||||||
$(function () {
|
|
||||||
$(
|
|
||||||
"#contactForm input,#contactForm textarea,#contactForm button"
|
|
||||||
).jqBootstrapValidation({
|
|
||||||
preventSubmit: true,
|
|
||||||
submitError: function ($form, event, errors) {
|
|
||||||
// additional error messages or events
|
|
||||||
},
|
|
||||||
submitSuccess: function ($form, event) {
|
|
||||||
event.preventDefault(); // prevent default submit behaviour
|
|
||||||
// get values from FORM
|
|
||||||
var name = $("input#name").val();
|
|
||||||
var email = $("input#email").val();
|
|
||||||
var message = $("textarea#message").val();
|
|
||||||
var firstName = name; // For Success/Failure Message
|
|
||||||
// Check for white space in name for Success/Fail message
|
|
||||||
if (firstName.indexOf(" ") >= 0) {
|
|
||||||
firstName = name.split(" ").slice(0, -1).join(" ");
|
|
||||||
}
|
|
||||||
$this = $("#sendMessageButton");
|
|
||||||
$this.prop("disabled", true); // Disable submit button until AJAX call is complete to prevent duplicate messages
|
|
||||||
$.ajax({
|
|
||||||
url: "assets/mail/contact_me.php",
|
|
||||||
type: "POST",
|
|
||||||
cache: false,
|
|
||||||
data: {
|
|
||||||
name: name,
|
|
||||||
email: email,
|
|
||||||
message: message,
|
|
||||||
},
|
|
||||||
success: function () {
|
|
||||||
// Success message
|
|
||||||
$("#success").html("<div class='alert alert-success'>");
|
|
||||||
$("#success > .alert-success")
|
|
||||||
.html(
|
|
||||||
"<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×"
|
|
||||||
)
|
|
||||||
.append("</button>");
|
|
||||||
$("#success > .alert-success").append(
|
|
||||||
"<strong>Your message has been sent. </strong>"
|
|
||||||
);
|
|
||||||
$("#success > .alert-success").append("</div>");
|
|
||||||
//clear all fields
|
|
||||||
$("#contactForm").trigger("reset");
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
// Fail message
|
|
||||||
$("#success").html("<div class='alert alert-danger'>");
|
|
||||||
$("#success > .alert-danger")
|
|
||||||
.html(
|
|
||||||
"<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×"
|
|
||||||
)
|
|
||||||
.append("</button>");
|
|
||||||
$("#success > .alert-danger").append(
|
|
||||||
$("<strong>").text(
|
|
||||||
"Sorry " +
|
|
||||||
firstName +
|
|
||||||
", an error occurred! Please try again or mail contact@luke-else.co.uk directly!"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$("#success > .alert-danger").append("</div>");
|
|
||||||
//clear all fields
|
|
||||||
$("#contactForm").trigger("reset");
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
setTimeout(function () {
|
|
||||||
$this.prop("disabled", false); // Re-enable submit button when AJAX call is complete
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
filter: function () {
|
|
||||||
return $(this).is(":visible");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$('a[data-toggle="tab"]').click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).tab("show");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/*When clicking on Full hide fail/success boxes */
|
|
||||||
$("#name").focus(function () {
|
|
||||||
$("#success").html("");
|
|
||||||
});
|
|
@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Check for empty fields
|
|
||||||
use PHPMailer\PHPMailer\PHPMailer;
|
|
||||||
use PHPMailer\PHPMailer\SMTP;
|
|
||||||
use PHPMailer\PHPMailer\Exception;
|
|
||||||
|
|
||||||
require '../phpmailer/Exception.php';
|
|
||||||
require '../phpmailer/PHPMailer.php';
|
|
||||||
require '../phpmailer/SMTP.php';
|
|
||||||
|
|
||||||
//Instantiation and passing `true` enables exceptions
|
|
||||||
$mail = new PHPMailer(true);
|
|
||||||
|
|
||||||
|
|
||||||
if ($_POST['name'] != null && $_POST['email'] != null && $_POST['message'] != null) {
|
|
||||||
try {
|
|
||||||
//Server settings
|
|
||||||
$mail->SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output
|
|
||||||
$mail->isSMTP(); //Send using SMTP
|
|
||||||
$mail->Host = 'in-v3.mailjet.com'; //Set the SMTP server to send through
|
|
||||||
$mail->SMTPAuth = true;
|
|
||||||
$mail->Username = '';
|
|
||||||
$mail->Password = '';
|
|
||||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; //Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
|
|
||||||
$mail->Port = 587; //TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above
|
|
||||||
|
|
||||||
//Recipients
|
|
||||||
$mail->setFrom('contact@luke-else.co.uk');
|
|
||||||
$mail->addAddress('contact@luke-else.co.uk'); //Add a recipient
|
|
||||||
|
|
||||||
|
|
||||||
//Content
|
|
||||||
$mail->isHTML(true); //Set email format to HTML
|
|
||||||
$mail->Subject = 'Website Contact Form: ' . $_POST['email'];
|
|
||||||
$mail->Body =
|
|
||||||
'<br /><b> Name: </b>' . $_POST['name'] .
|
|
||||||
'<br /> <b>E-Mail: </b>' . $_POST['email'] .
|
|
||||||
'<br /><br /> <b>Message: </b><br />' . $_POST['message'];
|
|
||||||
|
|
||||||
$mail->send();
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// error_log($e);
|
|
||||||
// echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
|
|
||||||
http_response_code(500);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
http_response_code(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
|
@ -1,912 +0,0 @@
|
|||||||
/* jqBootstrapValidation
|
|
||||||
* A plugin for automating validation on Twitter Bootstrap formatted forms.
|
|
||||||
*
|
|
||||||
* v1.3.6
|
|
||||||
*
|
|
||||||
* License: MIT <http://opensource.org/licenses/mit-license.php> - see LICENSE file
|
|
||||||
*
|
|
||||||
* http://ReactiveRaven.github.com/jqBootstrapValidation/
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function( $ ){
|
|
||||||
|
|
||||||
var createdElements = [];
|
|
||||||
|
|
||||||
var defaults = {
|
|
||||||
options: {
|
|
||||||
prependExistingHelpBlock: false,
|
|
||||||
sniffHtml: true, // sniff for 'required', 'maxlength', etc
|
|
||||||
preventSubmit: true, // stop the form submit event from firing if validation fails
|
|
||||||
submitError: false, // function called if there is an error when trying to submit
|
|
||||||
submitSuccess: false, // function called just before a successful submit event is sent to the server
|
|
||||||
semanticallyStrict: false, // set to true to tidy up generated HTML output
|
|
||||||
autoAdd: {
|
|
||||||
helpBlocks: true
|
|
||||||
},
|
|
||||||
filter: function () {
|
|
||||||
// return $(this).is(":visible"); // only validate elements you can see
|
|
||||||
return true; // validate everything
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init : function( options ) {
|
|
||||||
|
|
||||||
var settings = $.extend(true, {}, defaults);
|
|
||||||
|
|
||||||
settings.options = $.extend(true, settings.options, options);
|
|
||||||
|
|
||||||
var $siblingElements = this;
|
|
||||||
|
|
||||||
var uniqueForms = $.unique(
|
|
||||||
$siblingElements.map( function () {
|
|
||||||
return $(this).parents("form")[0];
|
|
||||||
}).toArray()
|
|
||||||
);
|
|
||||||
|
|
||||||
$(uniqueForms).bind("submit", function (e) {
|
|
||||||
var $form = $(this);
|
|
||||||
var warningsFound = 0;
|
|
||||||
var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter);
|
|
||||||
$inputs.trigger("submit.validation").trigger("validationLostFocus.validation");
|
|
||||||
|
|
||||||
$inputs.each(function (i, el) {
|
|
||||||
var $this = $(el),
|
|
||||||
$controlGroup = $this.parents(".control-group").first();
|
|
||||||
if (
|
|
||||||
$controlGroup.hasClass("warning")
|
|
||||||
) {
|
|
||||||
$controlGroup.removeClass("warning").addClass("error");
|
|
||||||
warningsFound++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$inputs.trigger("validationLostFocus.validation");
|
|
||||||
|
|
||||||
if (warningsFound) {
|
|
||||||
if (settings.options.preventSubmit) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
$form.addClass("error");
|
|
||||||
if ($.isFunction(settings.options.submitError)) {
|
|
||||||
settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$form.removeClass("error");
|
|
||||||
if ($.isFunction(settings.options.submitSuccess)) {
|
|
||||||
settings.options.submitSuccess($form, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.each(function(){
|
|
||||||
|
|
||||||
// Get references to everything we're interested in
|
|
||||||
var $this = $(this),
|
|
||||||
$controlGroup = $this.parents(".control-group").first(),
|
|
||||||
$helpBlock = $controlGroup.find(".help-block").first(),
|
|
||||||
$form = $this.parents("form").first(),
|
|
||||||
validatorNames = [];
|
|
||||||
|
|
||||||
// create message container if not exists
|
|
||||||
if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) {
|
|
||||||
$helpBlock = $('<div class="help-block" />');
|
|
||||||
$controlGroup.find('.controls').append($helpBlock);
|
|
||||||
createdElements.push($helpBlock[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// SNIFF HTML FOR VALIDATORS
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
// *snort sniff snuffle*
|
|
||||||
|
|
||||||
if (settings.options.sniffHtml) {
|
|
||||||
var message = "";
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// PATTERN
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("pattern") !== undefined) {
|
|
||||||
message = "Not in the expected format<!-- data-validation-pattern-message to override -->";
|
|
||||||
if ($this.data("validationPatternMessage")) {
|
|
||||||
message = $this.data("validationPatternMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationPatternMessage", message);
|
|
||||||
$this.data("validationPatternRegex", $this.attr("pattern"));
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MAX
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) {
|
|
||||||
var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax"));
|
|
||||||
message = "Too high: Maximum of '" + max + "'<!-- data-validation-max-message to override -->";
|
|
||||||
if ($this.data("validationMaxMessage")) {
|
|
||||||
message = $this.data("validationMaxMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMaxMessage", message);
|
|
||||||
$this.data("validationMaxMax", max);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MIN
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) {
|
|
||||||
var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin"));
|
|
||||||
message = "Too low: Minimum of '" + min + "'<!-- data-validation-min-message to override -->";
|
|
||||||
if ($this.data("validationMinMessage")) {
|
|
||||||
message = $this.data("validationMinMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMinMessage", message);
|
|
||||||
$this.data("validationMinMin", min);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MAXLENGTH
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("maxlength") !== undefined) {
|
|
||||||
message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters<!-- data-validation-maxlength-message to override -->";
|
|
||||||
if ($this.data("validationMaxlengthMessage")) {
|
|
||||||
message = $this.data("validationMaxlengthMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMaxlengthMessage", message);
|
|
||||||
$this.data("validationMaxlengthMaxlength", $this.attr("maxlength"));
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MINLENGTH
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("minlength") !== undefined) {
|
|
||||||
message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters<!-- data-validation-minlength-message to override -->";
|
|
||||||
if ($this.data("validationMinlengthMessage")) {
|
|
||||||
message = $this.data("validationMinlengthMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMinlengthMessage", message);
|
|
||||||
$this.data("validationMinlengthMinlength", $this.attr("minlength"));
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// REQUIRED
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) {
|
|
||||||
message = settings.builtInValidators.required.message;
|
|
||||||
if ($this.data("validationRequiredMessage")) {
|
|
||||||
message = $this.data("validationRequiredMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationRequiredMessage", message);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// NUMBER
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
|
|
||||||
message = settings.builtInValidators.number.message;
|
|
||||||
if ($this.data("validationNumberMessage")) {
|
|
||||||
message = $this.data("validationNumberMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationNumberMessage", message);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// EMAIL
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") {
|
|
||||||
message = "Not a valid email address<!-- data-validator-validemail-message to override -->";
|
|
||||||
if ($this.data("validationValidemailMessage")) {
|
|
||||||
message = $this.data("validationValidemailMessage");
|
|
||||||
} else if ($this.data("validationEmailMessage")) {
|
|
||||||
message = $this.data("validationEmailMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationValidemailMessage", message);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MINCHECKED
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("minchecked") !== undefined) {
|
|
||||||
message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required<!-- data-validation-minchecked-message to override -->";
|
|
||||||
if ($this.data("validationMincheckedMessage")) {
|
|
||||||
message = $this.data("validationMincheckedMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMincheckedMessage", message);
|
|
||||||
$this.data("validationMincheckedMinchecked", $this.attr("minchecked"));
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// MAXCHECKED
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if ($this.attr("maxchecked") !== undefined) {
|
|
||||||
message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required<!-- data-validation-maxchecked-message to override -->";
|
|
||||||
if ($this.data("validationMaxcheckedMessage")) {
|
|
||||||
message = $this.data("validationMaxcheckedMessage");
|
|
||||||
}
|
|
||||||
$this.data("validationMaxcheckedMessage", message);
|
|
||||||
$this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// COLLECT VALIDATOR NAMES
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
// Get named validators
|
|
||||||
if ($this.data("validation") !== undefined) {
|
|
||||||
validatorNames = $this.data("validation").split(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get extra ones defined on the element's data attributes
|
|
||||||
$.each($this.data(), function (i, el) {
|
|
||||||
var parts = i.replace(/([A-Z])/g, ",$1").split(",");
|
|
||||||
if (parts[0] === "validation" && parts[1]) {
|
|
||||||
validatorNames.push(parts[1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// NORMALISE VALIDATOR NAMES
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
var validatorNamesToInspect = validatorNames;
|
|
||||||
var newValidatorNamesToInspect = [];
|
|
||||||
|
|
||||||
do // repeatedly expand 'shortcut' validators into their real validators
|
|
||||||
{
|
|
||||||
// Uppercase only the first letter of each name
|
|
||||||
$.each(validatorNames, function (i, el) {
|
|
||||||
validatorNames[i] = formatValidatorName(el);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove duplicate validator names
|
|
||||||
validatorNames = $.unique(validatorNames);
|
|
||||||
|
|
||||||
// Pull out the new validator names from each shortcut
|
|
||||||
newValidatorNamesToInspect = [];
|
|
||||||
$.each(validatorNamesToInspect, function(i, el) {
|
|
||||||
if ($this.data("validation" + el + "Shortcut") !== undefined) {
|
|
||||||
// Are these custom validators?
|
|
||||||
// Pull them out!
|
|
||||||
$.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) {
|
|
||||||
newValidatorNamesToInspect.push(el2);
|
|
||||||
});
|
|
||||||
} else if (settings.builtInValidators[el.toLowerCase()]) {
|
|
||||||
// Is this a recognised built-in?
|
|
||||||
// Pull it out!
|
|
||||||
var validator = settings.builtInValidators[el.toLowerCase()];
|
|
||||||
if (validator.type.toLowerCase() === "shortcut") {
|
|
||||||
$.each(validator.shortcut.split(","), function (i, el) {
|
|
||||||
el = formatValidatorName(el);
|
|
||||||
newValidatorNamesToInspect.push(el);
|
|
||||||
validatorNames.push(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
validatorNamesToInspect = newValidatorNamesToInspect;
|
|
||||||
|
|
||||||
} while (validatorNamesToInspect.length > 0)
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// SET UP VALIDATOR ARRAYS
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
var validators = {};
|
|
||||||
|
|
||||||
$.each(validatorNames, function (i, el) {
|
|
||||||
// Set up the 'override' message
|
|
||||||
var message = $this.data("validation" + el + "Message");
|
|
||||||
var hasOverrideMessage = (message !== undefined);
|
|
||||||
var foundValidator = false;
|
|
||||||
message =
|
|
||||||
(
|
|
||||||
message
|
|
||||||
? message
|
|
||||||
: "'" + el + "' validation failed <!-- Add attribute 'data-validation-" + el.toLowerCase() + "-message' to input to change this message -->"
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
$.each(
|
|
||||||
settings.validatorTypes,
|
|
||||||
function (validatorType, validatorTemplate) {
|
|
||||||
if (validators[validatorType] === undefined) {
|
|
||||||
validators[validatorType] = [];
|
|
||||||
}
|
|
||||||
if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) {
|
|
||||||
validators[validatorType].push(
|
|
||||||
$.extend(
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
name: formatValidatorName(validatorTemplate.name),
|
|
||||||
message: message
|
|
||||||
},
|
|
||||||
validatorTemplate.init($this, el)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
foundValidator = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) {
|
|
||||||
|
|
||||||
var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]);
|
|
||||||
if (hasOverrideMessage) {
|
|
||||||
validator.message = message;
|
|
||||||
}
|
|
||||||
var validatorType = validator.type.toLowerCase();
|
|
||||||
|
|
||||||
if (validatorType === "shortcut") {
|
|
||||||
foundValidator = true;
|
|
||||||
} else {
|
|
||||||
$.each(
|
|
||||||
settings.validatorTypes,
|
|
||||||
function (validatorTemplateType, validatorTemplate) {
|
|
||||||
if (validators[validatorTemplateType] === undefined) {
|
|
||||||
validators[validatorTemplateType] = [];
|
|
||||||
}
|
|
||||||
if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) {
|
|
||||||
$this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]);
|
|
||||||
validators[validatorType].push(
|
|
||||||
$.extend(
|
|
||||||
validator,
|
|
||||||
validatorTemplate.init($this, el)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
foundValidator = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! foundValidator) {
|
|
||||||
$.error("Cannot find validation info for '" + el + "'");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// STORE FALLBACK VALUES
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
$helpBlock.data(
|
|
||||||
"original-contents",
|
|
||||||
(
|
|
||||||
$helpBlock.data("original-contents")
|
|
||||||
? $helpBlock.data("original-contents")
|
|
||||||
: $helpBlock.html()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$helpBlock.data(
|
|
||||||
"original-role",
|
|
||||||
(
|
|
||||||
$helpBlock.data("original-role")
|
|
||||||
? $helpBlock.data("original-role")
|
|
||||||
: $helpBlock.attr("role")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$controlGroup.data(
|
|
||||||
"original-classes",
|
|
||||||
(
|
|
||||||
$controlGroup.data("original-clases")
|
|
||||||
? $controlGroup.data("original-classes")
|
|
||||||
: $controlGroup.attr("class")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$this.data(
|
|
||||||
"original-aria-invalid",
|
|
||||||
(
|
|
||||||
$this.data("original-aria-invalid")
|
|
||||||
? $this.data("original-aria-invalid")
|
|
||||||
: $this.attr("aria-invalid")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// VALIDATION
|
|
||||||
// =============================================================
|
|
||||||
|
|
||||||
$this.bind(
|
|
||||||
"validation.validation",
|
|
||||||
function (event, params) {
|
|
||||||
|
|
||||||
var value = getValue($this);
|
|
||||||
|
|
||||||
// Get a list of the errors to apply
|
|
||||||
var errorsFound = [];
|
|
||||||
|
|
||||||
$.each(validators, function (validatorType, validatorTypeArray) {
|
|
||||||
if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) {
|
|
||||||
$.each(validatorTypeArray, function (i, validator) {
|
|
||||||
if (settings.validatorTypes[validatorType].validate($this, value, validator)) {
|
|
||||||
errorsFound.push(validator.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errorsFound;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$this.bind(
|
|
||||||
"getValidators.validation",
|
|
||||||
function () {
|
|
||||||
return validators;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// =============================================================
|
|
||||||
// WATCH FOR CHANGES
|
|
||||||
// =============================================================
|
|
||||||
$this.bind(
|
|
||||||
"submit.validation",
|
|
||||||
function () {
|
|
||||||
return $this.triggerHandler("change.validation", {submitting: true});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
$this.bind(
|
|
||||||
[
|
|
||||||
"keyup",
|
|
||||||
"focus",
|
|
||||||
"blur",
|
|
||||||
"click",
|
|
||||||
"keydown",
|
|
||||||
"keypress",
|
|
||||||
"change"
|
|
||||||
].join(".validation ") + ".validation",
|
|
||||||
function (e, params) {
|
|
||||||
|
|
||||||
var value = getValue($this);
|
|
||||||
|
|
||||||
var errorsFound = [];
|
|
||||||
|
|
||||||
$controlGroup.find("input,textarea,select").each(function (i, el) {
|
|
||||||
var oldCount = errorsFound.length;
|
|
||||||
$.each($(el).triggerHandler("validation.validation", params), function (j, message) {
|
|
||||||
errorsFound.push(message);
|
|
||||||
});
|
|
||||||
if (errorsFound.length > oldCount) {
|
|
||||||
$(el).attr("aria-invalid", "true");
|
|
||||||
} else {
|
|
||||||
var original = $this.data("original-aria-invalid");
|
|
||||||
$(el).attr("aria-invalid", (original !== undefined ? original : false));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation");
|
|
||||||
|
|
||||||
errorsFound = $.unique(errorsFound.sort());
|
|
||||||
|
|
||||||
// Were there any errors?
|
|
||||||
if (errorsFound.length) {
|
|
||||||
// Better flag it up as a warning.
|
|
||||||
$controlGroup.removeClass("success error").addClass("warning");
|
|
||||||
|
|
||||||
// How many errors did we find?
|
|
||||||
if (settings.options.semanticallyStrict && errorsFound.length === 1) {
|
|
||||||
// Only one? Being strict? Just output it.
|
|
||||||
$helpBlock.html(errorsFound[0] +
|
|
||||||
( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
|
|
||||||
} else {
|
|
||||||
// Multiple? Being sloppy? Glue them together into an UL.
|
|
||||||
$helpBlock.html("<ul role=\"alert\"><li>" + errorsFound.join("</li><li>") + "</li></ul>" +
|
|
||||||
( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$controlGroup.removeClass("warning error success");
|
|
||||||
if (value.length > 0) {
|
|
||||||
$controlGroup.addClass("success");
|
|
||||||
}
|
|
||||||
$helpBlock.html($helpBlock.data("original-contents"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.type === "blur") {
|
|
||||||
$controlGroup.removeClass("success");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
$this.bind("validationLostFocus.validation", function () {
|
|
||||||
$controlGroup.removeClass("success");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy : function( ) {
|
|
||||||
|
|
||||||
return this.each(
|
|
||||||
function() {
|
|
||||||
|
|
||||||
var
|
|
||||||
$this = $(this),
|
|
||||||
$controlGroup = $this.parents(".control-group").first(),
|
|
||||||
$helpBlock = $controlGroup.find(".help-block").first();
|
|
||||||
|
|
||||||
// remove our events
|
|
||||||
$this.unbind('.validation'); // events are namespaced.
|
|
||||||
// reset help text
|
|
||||||
$helpBlock.html($helpBlock.data("original-contents"));
|
|
||||||
// reset classes
|
|
||||||
$controlGroup.attr("class", $controlGroup.data("original-classes"));
|
|
||||||
// reset aria
|
|
||||||
$this.attr("aria-invalid", $this.data("original-aria-invalid"));
|
|
||||||
// reset role
|
|
||||||
$helpBlock.attr("role", $this.data("original-role"));
|
|
||||||
// remove all elements we created
|
|
||||||
if (createdElements.indexOf($helpBlock[0]) > -1) {
|
|
||||||
$helpBlock.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
},
|
|
||||||
collectErrors : function(includeEmpty) {
|
|
||||||
|
|
||||||
var errorMessages = {};
|
|
||||||
this.each(function (i, el) {
|
|
||||||
var $el = $(el);
|
|
||||||
var name = $el.attr("name");
|
|
||||||
var errors = $el.triggerHandler("validation.validation", {includeEmpty: true});
|
|
||||||
errorMessages[name] = $.extend(true, errors, errorMessages[name]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$.each(errorMessages, function (i, el) {
|
|
||||||
if (el.length === 0) {
|
|
||||||
delete errorMessages[i];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errorMessages;
|
|
||||||
|
|
||||||
},
|
|
||||||
hasErrors: function() {
|
|
||||||
|
|
||||||
var errorMessages = [];
|
|
||||||
|
|
||||||
this.each(function (i, el) {
|
|
||||||
errorMessages = errorMessages.concat(
|
|
||||||
$(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : []
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (errorMessages.length > 0);
|
|
||||||
},
|
|
||||||
override : function (newDefaults) {
|
|
||||||
defaults = $.extend(true, defaults, newDefaults);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validatorTypes: {
|
|
||||||
callback: {
|
|
||||||
name: "callback",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {
|
|
||||||
validatorName: name,
|
|
||||||
callback: $this.data("validation" + name + "Callback"),
|
|
||||||
lastValue: $this.val(),
|
|
||||||
lastValid: true,
|
|
||||||
lastFinished: true
|
|
||||||
};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
if (validator.lastValue === value && validator.lastFinished) {
|
|
||||||
return !validator.lastValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validator.lastFinished === true)
|
|
||||||
{
|
|
||||||
validator.lastValue = value;
|
|
||||||
validator.lastValid = true;
|
|
||||||
validator.lastFinished = false;
|
|
||||||
|
|
||||||
var rrjqbvValidator = validator;
|
|
||||||
var rrjqbvThis = $this;
|
|
||||||
executeFunctionByName(
|
|
||||||
validator.callback,
|
|
||||||
window,
|
|
||||||
$this,
|
|
||||||
value,
|
|
||||||
function (data) {
|
|
||||||
if (rrjqbvValidator.lastValue === data.value) {
|
|
||||||
rrjqbvValidator.lastValid = data.valid;
|
|
||||||
if (data.message) {
|
|
||||||
rrjqbvValidator.message = data.message;
|
|
||||||
}
|
|
||||||
rrjqbvValidator.lastFinished = true;
|
|
||||||
rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message);
|
|
||||||
// Timeout is set to avoid problems with the events being considered 'already fired'
|
|
||||||
setTimeout(function () {
|
|
||||||
rrjqbvThis.trigger("change.validation");
|
|
||||||
}, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ajax: {
|
|
||||||
name: "ajax",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {
|
|
||||||
validatorName: name,
|
|
||||||
url: $this.data("validation" + name + "Ajax"),
|
|
||||||
lastValue: $this.val(),
|
|
||||||
lastValid: true,
|
|
||||||
lastFinished: true
|
|
||||||
};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
if (""+validator.lastValue === ""+value && validator.lastFinished === true) {
|
|
||||||
return validator.lastValid === false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validator.lastFinished === true)
|
|
||||||
{
|
|
||||||
validator.lastValue = value;
|
|
||||||
validator.lastValid = true;
|
|
||||||
validator.lastFinished = false;
|
|
||||||
$.ajax({
|
|
||||||
url: validator.url,
|
|
||||||
data: "value=" + value + "&field=" + $this.attr("name"),
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data) {
|
|
||||||
if (""+validator.lastValue === ""+data.value) {
|
|
||||||
validator.lastValid = !!(data.valid);
|
|
||||||
if (data.message) {
|
|
||||||
validator.message = data.message;
|
|
||||||
}
|
|
||||||
validator.lastFinished = true;
|
|
||||||
$this.data("validation" + validator.validatorName + "Message", validator.message);
|
|
||||||
// Timeout is set to avoid problems with the events being considered 'already fired'
|
|
||||||
setTimeout(function () {
|
|
||||||
$this.trigger("change.validation");
|
|
||||||
}, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
|
|
||||||
}
|
|
||||||
},
|
|
||||||
failure: function () {
|
|
||||||
validator.lastValid = true;
|
|
||||||
validator.message = "ajax call failed";
|
|
||||||
validator.lastFinished = true;
|
|
||||||
$this.data("validation" + validator.validatorName + "Message", validator.message);
|
|
||||||
// Timeout is set to avoid problems with the events being considered 'already fired'
|
|
||||||
setTimeout(function () {
|
|
||||||
$this.trigger("change.validation");
|
|
||||||
}, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
regex: {
|
|
||||||
name: "regex",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {regex: regexFromString($this.data("validation" + name + "Regex"))};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (!validator.regex.test(value) && ! validator.negative)
|
|
||||||
|| (validator.regex.test(value) && validator.negative);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
name: "required",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return !!(value.length === 0 && ! validator.negative)
|
|
||||||
|| !!(value.length > 0 && validator.negative);
|
|
||||||
},
|
|
||||||
blockSubmit: true
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
name: "match",
|
|
||||||
init: function ($this, name) {
|
|
||||||
var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first();
|
|
||||||
element.bind("validation.validation", function () {
|
|
||||||
$this.trigger("change.validation", {submitting: true});
|
|
||||||
});
|
|
||||||
return {"element": element};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (value !== validator.element.val() && ! validator.negative)
|
|
||||||
|| (value === validator.element.val() && validator.negative);
|
|
||||||
},
|
|
||||||
blockSubmit: true
|
|
||||||
},
|
|
||||||
max: {
|
|
||||||
name: "max",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {max: $this.data("validation" + name + "Max")};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (parseFloat(value, 10) > parseFloat(validator.max, 10) && ! validator.negative)
|
|
||||||
|| (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: {
|
|
||||||
name: "min",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {min: $this.data("validation" + name + "Min")};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (parseFloat(value) < parseFloat(validator.min) && ! validator.negative)
|
|
||||||
|| (parseFloat(value) >= parseFloat(validator.min) && validator.negative);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
maxlength: {
|
|
||||||
name: "maxlength",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {maxlength: $this.data("validation" + name + "Maxlength")};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return ((value.length > validator.maxlength) && ! validator.negative)
|
|
||||||
|| ((value.length <= validator.maxlength) && validator.negative);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
minlength: {
|
|
||||||
name: "minlength",
|
|
||||||
init: function ($this, name) {
|
|
||||||
return {minlength: $this.data("validation" + name + "Minlength")};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return ((value.length < validator.minlength) && ! validator.negative)
|
|
||||||
|| ((value.length >= validator.minlength) && validator.negative);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
maxchecked: {
|
|
||||||
name: "maxchecked",
|
|
||||||
init: function ($this, name) {
|
|
||||||
var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
|
|
||||||
elements.bind("click.validation", function () {
|
|
||||||
$this.trigger("change.validation", {includeEmpty: true});
|
|
||||||
});
|
|
||||||
return {maxchecked: $this.data("validation" + name + "Maxchecked"), elements: elements};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (validator.elements.filter(":checked").length > validator.maxchecked && ! validator.negative)
|
|
||||||
|| (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative);
|
|
||||||
},
|
|
||||||
blockSubmit: true
|
|
||||||
},
|
|
||||||
minchecked: {
|
|
||||||
name: "minchecked",
|
|
||||||
init: function ($this, name) {
|
|
||||||
var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
|
|
||||||
elements.bind("click.validation", function () {
|
|
||||||
$this.trigger("change.validation", {includeEmpty: true});
|
|
||||||
});
|
|
||||||
return {minchecked: $this.data("validation" + name + "Minchecked"), elements: elements};
|
|
||||||
},
|
|
||||||
validate: function ($this, value, validator) {
|
|
||||||
return (validator.elements.filter(":checked").length < validator.minchecked && ! validator.negative)
|
|
||||||
|| (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative);
|
|
||||||
},
|
|
||||||
blockSubmit: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
builtInValidators: {
|
|
||||||
email: {
|
|
||||||
name: "Email",
|
|
||||||
type: "shortcut",
|
|
||||||
shortcut: "validemail"
|
|
||||||
},
|
|
||||||
validemail: {
|
|
||||||
name: "Validemail",
|
|
||||||
type: "regex",
|
|
||||||
regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}",
|
|
||||||
message: "Not a valid email address<!-- data-validator-validemail-message to override -->"
|
|
||||||
},
|
|
||||||
passwordagain: {
|
|
||||||
name: "Passwordagain",
|
|
||||||
type: "match",
|
|
||||||
match: "password",
|
|
||||||
message: "Does not match the given password<!-- data-validator-paswordagain-message to override -->"
|
|
||||||
},
|
|
||||||
positive: {
|
|
||||||
name: "Positive",
|
|
||||||
type: "shortcut",
|
|
||||||
shortcut: "number,positivenumber"
|
|
||||||
},
|
|
||||||
negative: {
|
|
||||||
name: "Negative",
|
|
||||||
type: "shortcut",
|
|
||||||
shortcut: "number,negativenumber"
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
name: "Number",
|
|
||||||
type: "regex",
|
|
||||||
regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?",
|
|
||||||
message: "Must be a number<!-- data-validator-number-message to override -->"
|
|
||||||
},
|
|
||||||
integer: {
|
|
||||||
name: "Integer",
|
|
||||||
type: "regex",
|
|
||||||
regex: "[+-]?\\\d+",
|
|
||||||
message: "No decimal places allowed<!-- data-validator-integer-message to override -->"
|
|
||||||
},
|
|
||||||
positivenumber: {
|
|
||||||
name: "Positivenumber",
|
|
||||||
type: "min",
|
|
||||||
min: 0,
|
|
||||||
message: "Must be a positive number<!-- data-validator-positivenumber-message to override -->"
|
|
||||||
},
|
|
||||||
negativenumber: {
|
|
||||||
name: "Negativenumber",
|
|
||||||
type: "max",
|
|
||||||
max: 0,
|
|
||||||
message: "Must be a negative number<!-- data-validator-negativenumber-message to override -->"
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
name: "Required",
|
|
||||||
type: "required",
|
|
||||||
message: "This is required<!-- data-validator-required-message to override -->"
|
|
||||||
},
|
|
||||||
checkone: {
|
|
||||||
name: "Checkone",
|
|
||||||
type: "minchecked",
|
|
||||||
minchecked: 1,
|
|
||||||
message: "Check at least one option<!-- data-validation-checkone-message to override -->"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var formatValidatorName = function (name) {
|
|
||||||
return name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(
|
|
||||||
/(^|\s)([a-z])/g ,
|
|
||||||
function(m,p1,p2) {
|
|
||||||
return p1+p2.toUpperCase();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getValue = function ($this) {
|
|
||||||
// Extract the value we're talking about
|
|
||||||
var value = $this.val();
|
|
||||||
var type = $this.attr("type");
|
|
||||||
if (type === "checkbox") {
|
|
||||||
value = ($this.is(":checked") ? value : "");
|
|
||||||
}
|
|
||||||
if (type === "radio") {
|
|
||||||
value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : "");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
function regexFromString(inputstring) {
|
|
||||||
return new RegExp("^" + inputstring + "$");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thanks to Jason Bunting via StackOverflow.com
|
|
||||||
*
|
|
||||||
* http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910
|
|
||||||
* Short link: http://tinyurl.com/executeFunctionByName
|
|
||||||
**/
|
|
||||||
function executeFunctionByName(functionName, context /*, args*/) {
|
|
||||||
var args = Array.prototype.slice.call(arguments).splice(2);
|
|
||||||
var namespaces = functionName.split(".");
|
|
||||||
var func = namespaces.pop();
|
|
||||||
for(var i = 0; i < namespaces.length; i++) {
|
|
||||||
context = context[namespaces[i]];
|
|
||||||
}
|
|
||||||
return context[func].apply(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
$.fn.jqBootstrapValidation = function( method ) {
|
|
||||||
|
|
||||||
if ( defaults.methods[method] ) {
|
|
||||||
return defaults.methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
|
|
||||||
} else if ( typeof method === 'object' || ! method ) {
|
|
||||||
return defaults.methods.init.apply( this, arguments );
|
|
||||||
} else {
|
|
||||||
$.error( 'Method ' + method + ' does not exist on jQuery.jqBootstrapValidation' );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
$.jqBootstrapValidation = function (options) {
|
|
||||||
$(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
})( jQuery );
|
|
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHPMailer Exception class.
|
|
||||||
* PHP Version 5.5.
|
|
||||||
*
|
|
||||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
|
||||||
*
|
|
||||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
|
||||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
|
||||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
|
||||||
* @author Brent R. Matzelle (original founder)
|
|
||||||
* @copyright 2012 - 2020 Marcus Bointon
|
|
||||||
* @copyright 2010 - 2012 Jim Jagielski
|
|
||||||
* @copyright 2004 - 2009 Andy Prevost
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
|
||||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPMailer\PHPMailer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHPMailer exception handler.
|
|
||||||
*
|
|
||||||
* @author Marcus Bointon <phpmailer@synchromedia.co.uk>
|
|
||||||
*/
|
|
||||||
class Exception extends \Exception
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Prettify error message output.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function errorMessage()
|
|
||||||
{
|
|
||||||
return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHPMailer - PHP email creation and transport class.
|
|
||||||
* PHP Version 5.5.
|
|
||||||
*
|
|
||||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
|
||||||
*
|
|
||||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
|
||||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
|
||||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
|
||||||
* @author Brent R. Matzelle (original founder)
|
|
||||||
* @copyright 2012 - 2020 Marcus Bointon
|
|
||||||
* @copyright 2010 - 2012 Jim Jagielski
|
|
||||||
* @copyright 2004 - 2009 Andy Prevost
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
|
||||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPMailer\PHPMailer;
|
|
||||||
|
|
||||||
use League\OAuth2\Client\Grant\RefreshToken;
|
|
||||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
|
||||||
use League\OAuth2\Client\Token\AccessToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OAuth - OAuth2 authentication wrapper class.
|
|
||||||
* Uses the oauth2-client package from the League of Extraordinary Packages.
|
|
||||||
*
|
|
||||||
* @see http://oauth2-client.thephpleague.com
|
|
||||||
*
|
|
||||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
|
||||||
*/
|
|
||||||
class OAuth
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An instance of the League OAuth Client Provider.
|
|
||||||
*
|
|
||||||
* @var AbstractProvider
|
|
||||||
*/
|
|
||||||
protected $provider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current OAuth access token.
|
|
||||||
*
|
|
||||||
* @var AccessToken
|
|
||||||
*/
|
|
||||||
protected $oauthToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user's email address, usually used as the login ID
|
|
||||||
* and also the from address when sending email.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $oauthUserEmail = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The client secret, generated in the app definition of the service you're connecting to.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $oauthClientSecret = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The client ID, generated in the app definition of the service you're connecting to.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $oauthClientId = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The refresh token, used to obtain new AccessTokens.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $oauthRefreshToken = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OAuth constructor.
|
|
||||||
*
|
|
||||||
* @param array $options Associative array containing
|
|
||||||
* `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements
|
|
||||||
*/
|
|
||||||
public function __construct($options)
|
|
||||||
{
|
|
||||||
$this->provider = $options['provider'];
|
|
||||||
$this->oauthUserEmail = $options['userName'];
|
|
||||||
$this->oauthClientSecret = $options['clientSecret'];
|
|
||||||
$this->oauthClientId = $options['clientId'];
|
|
||||||
$this->oauthRefreshToken = $options['refreshToken'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a new RefreshToken.
|
|
||||||
*
|
|
||||||
* @return RefreshToken
|
|
||||||
*/
|
|
||||||
protected function getGrant()
|
|
||||||
{
|
|
||||||
return new RefreshToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a new AccessToken.
|
|
||||||
*
|
|
||||||
* @return AccessToken
|
|
||||||
*/
|
|
||||||
protected function getToken()
|
|
||||||
{
|
|
||||||
return $this->provider->getAccessToken(
|
|
||||||
$this->getGrant(),
|
|
||||||
['refresh_token' => $this->oauthRefreshToken]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a base64-encoded OAuth token.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getOauth64()
|
|
||||||
{
|
|
||||||
//Get a new token if it's not available or has expired
|
|
||||||
if (null === $this->oauthToken || $this->oauthToken->hasExpired()) {
|
|
||||||
$this->oauthToken = $this->getToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64_encode(
|
|
||||||
'user=' .
|
|
||||||
$this->oauthUserEmail .
|
|
||||||
"\001auth=Bearer " .
|
|
||||||
$this->oauthToken .
|
|
||||||
"\001\001"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,448 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHPMailer POP-Before-SMTP Authentication Class.
|
|
||||||
* PHP Version 5.5.
|
|
||||||
*
|
|
||||||
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
|
|
||||||
*
|
|
||||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
|
||||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
|
||||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
|
||||||
* @author Brent R. Matzelle (original founder)
|
|
||||||
* @copyright 2012 - 2020 Marcus Bointon
|
|
||||||
* @copyright 2010 - 2012 Jim Jagielski
|
|
||||||
* @copyright 2004 - 2009 Andy Prevost
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
|
||||||
* @note This program is distributed in the hope that it will be useful - WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPMailer\PHPMailer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHPMailer POP-Before-SMTP Authentication Class.
|
|
||||||
* Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
|
|
||||||
* 1) This class does not support APOP authentication.
|
|
||||||
* 2) Opening and closing lots of POP3 connections can be quite slow. If you need
|
|
||||||
* to send a batch of emails then just perform the authentication once at the start,
|
|
||||||
* and then loop through your mail sending script. Providing this process doesn't
|
|
||||||
* take longer than the verification period lasts on your POP3 server, you should be fine.
|
|
||||||
* 3) This is really ancient technology; you should only need to use it to talk to very old systems.
|
|
||||||
* 4) This POP3 class is deliberately lightweight and incomplete, implementing just
|
|
||||||
* enough to do authentication.
|
|
||||||
* If you want a more complete class there are other POP3 classes for PHP available.
|
|
||||||
*
|
|
||||||
* @author Richard Davey (original author) <rich@corephp.co.uk>
|
|
||||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
|
||||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
|
||||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
|
||||||
*/
|
|
||||||
class POP3
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The POP3 PHPMailer Version number.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
const VERSION = '6.4.1';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default POP3 port number.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const DEFAULT_PORT = 110;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default timeout in seconds.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const DEFAULT_TIMEOUT = 30;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 class debug output mode.
|
|
||||||
* Debug output level.
|
|
||||||
* Options:
|
|
||||||
* @see POP3::DEBUG_OFF: No output
|
|
||||||
* @see POP3::DEBUG_SERVER: Server messages, connection/server errors
|
|
||||||
* @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $do_debug = self::DEBUG_OFF;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 mail server hostname.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $host;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 port number.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $port;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 Timeout Value in seconds.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $tval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 username.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $username;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 password.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $password;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource handle for the POP3 connection socket.
|
|
||||||
*
|
|
||||||
* @var resource
|
|
||||||
*/
|
|
||||||
protected $pop_conn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are we connected?
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $connected = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error container.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $errors = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Line break constant.
|
|
||||||
*/
|
|
||||||
const LE = "\r\n";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug level for no output.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const DEBUG_OFF = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug level to show server -> client messages
|
|
||||||
* also shows clients connection errors or errors from server
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const DEBUG_SERVER = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug level to show client -> server and server -> client messages.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const DEBUG_CLIENT = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple static wrapper for all-in-one POP before SMTP.
|
|
||||||
*
|
|
||||||
* @param string $host The hostname to connect to
|
|
||||||
* @param int|bool $port The port number to connect to
|
|
||||||
* @param int|bool $timeout The timeout value
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
* @param int $debug_level
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function popBeforeSmtp(
|
|
||||||
$host,
|
|
||||||
$port = false,
|
|
||||||
$timeout = false,
|
|
||||||
$username = '',
|
|
||||||
$password = '',
|
|
||||||
$debug_level = 0
|
|
||||||
) {
|
|
||||||
$pop = new self();
|
|
||||||
|
|
||||||
return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticate with a POP3 server.
|
|
||||||
* A connect, login, disconnect sequence
|
|
||||||
* appropriate for POP-before SMTP authorisation.
|
|
||||||
*
|
|
||||||
* @param string $host The hostname to connect to
|
|
||||||
* @param int|bool $port The port number to connect to
|
|
||||||
* @param int|bool $timeout The timeout value
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
* @param int $debug_level
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
|
|
||||||
{
|
|
||||||
$this->host = $host;
|
|
||||||
//If no port value provided, use default
|
|
||||||
if (false === $port) {
|
|
||||||
$this->port = static::DEFAULT_PORT;
|
|
||||||
} else {
|
|
||||||
$this->port = (int) $port;
|
|
||||||
}
|
|
||||||
//If no timeout value provided, use default
|
|
||||||
if (false === $timeout) {
|
|
||||||
$this->tval = static::DEFAULT_TIMEOUT;
|
|
||||||
} else {
|
|
||||||
$this->tval = (int) $timeout;
|
|
||||||
}
|
|
||||||
$this->do_debug = $debug_level;
|
|
||||||
$this->username = $username;
|
|
||||||
$this->password = $password;
|
|
||||||
//Reset the error log
|
|
||||||
$this->errors = [];
|
|
||||||
//Connect
|
|
||||||
$result = $this->connect($this->host, $this->port, $this->tval);
|
|
||||||
if ($result) {
|
|
||||||
$login_result = $this->login($this->username, $this->password);
|
|
||||||
if ($login_result) {
|
|
||||||
$this->disconnect();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//We need to disconnect regardless of whether the login succeeded
|
|
||||||
$this->disconnect();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a POP3 server.
|
|
||||||
*
|
|
||||||
* @param string $host
|
|
||||||
* @param int|bool $port
|
|
||||||
* @param int $tval
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function connect($host, $port = false, $tval = 30)
|
|
||||||
{
|
|
||||||
//Are we already connected?
|
|
||||||
if ($this->connected) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
|
|
||||||
//Rather than suppress it with @fsockopen, capture it cleanly instead
|
|
||||||
set_error_handler([$this, 'catchWarning']);
|
|
||||||
|
|
||||||
if (false === $port) {
|
|
||||||
$port = static::DEFAULT_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Connect to the POP3 server
|
|
||||||
$errno = 0;
|
|
||||||
$errstr = '';
|
|
||||||
$this->pop_conn = fsockopen(
|
|
||||||
$host, //POP3 Host
|
|
||||||
$port, //Port #
|
|
||||||
$errno, //Error Number
|
|
||||||
$errstr, //Error Message
|
|
||||||
$tval
|
|
||||||
); //Timeout (seconds)
|
|
||||||
//Restore the error handler
|
|
||||||
restore_error_handler();
|
|
||||||
|
|
||||||
//Did we connect?
|
|
||||||
if (false === $this->pop_conn) {
|
|
||||||
//It would appear not...
|
|
||||||
$this->setError(
|
|
||||||
"Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Increase the stream time-out
|
|
||||||
stream_set_timeout($this->pop_conn, $tval, 0);
|
|
||||||
|
|
||||||
//Get the POP3 server response
|
|
||||||
$pop3_response = $this->getResponse();
|
|
||||||
//Check for the +OK
|
|
||||||
if ($this->checkResponse($pop3_response)) {
|
|
||||||
//The connection is established and the POP3 server is talking
|
|
||||||
$this->connected = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log in to the POP3 server.
|
|
||||||
* Does not support APOP (RFC 2828, 4949).
|
|
||||||
*
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function login($username = '', $password = '')
|
|
||||||
{
|
|
||||||
if (!$this->connected) {
|
|
||||||
$this->setError('Not connected to POP3 server');
|
|
||||||
}
|
|
||||||
if (empty($username)) {
|
|
||||||
$username = $this->username;
|
|
||||||
}
|
|
||||||
if (empty($password)) {
|
|
||||||
$password = $this->password;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Send the Username
|
|
||||||
$this->sendString("USER $username" . static::LE);
|
|
||||||
$pop3_response = $this->getResponse();
|
|
||||||
if ($this->checkResponse($pop3_response)) {
|
|
||||||
//Send the Password
|
|
||||||
$this->sendString("PASS $password" . static::LE);
|
|
||||||
$pop3_response = $this->getResponse();
|
|
||||||
if ($this->checkResponse($pop3_response)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnect from the POP3 server.
|
|
||||||
*/
|
|
||||||
public function disconnect()
|
|
||||||
{
|
|
||||||
$this->sendString('QUIT');
|
|
||||||
//The QUIT command may cause the daemon to exit, which will kill our connection
|
|
||||||
//So ignore errors here
|
|
||||||
try {
|
|
||||||
@fclose($this->pop_conn);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
//Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a response from the POP3 server.
|
|
||||||
*
|
|
||||||
* @param int $size The maximum number of bytes to retrieve
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getResponse($size = 128)
|
|
||||||
{
|
|
||||||
$response = fgets($this->pop_conn, $size);
|
|
||||||
if ($this->do_debug >= self::DEBUG_SERVER) {
|
|
||||||
echo 'Server -> Client: ', $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send raw data to the POP3 server.
|
|
||||||
*
|
|
||||||
* @param string $string
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function sendString($string)
|
|
||||||
{
|
|
||||||
if ($this->pop_conn) {
|
|
||||||
if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
|
|
||||||
echo 'Client -> Server: ', $string;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fwrite($this->pop_conn, $string, strlen($string));
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the POP3 server response.
|
|
||||||
* Looks for for +OK or -ERR.
|
|
||||||
*
|
|
||||||
* @param string $string
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function checkResponse($string)
|
|
||||||
{
|
|
||||||
if (strpos($string, '+OK') !== 0) {
|
|
||||||
$this->setError("Server reported an error: $string");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an error to the internal error store.
|
|
||||||
* Also display debug output if it's enabled.
|
|
||||||
*
|
|
||||||
* @param string $error
|
|
||||||
*/
|
|
||||||
protected function setError($error)
|
|
||||||
{
|
|
||||||
$this->errors[] = $error;
|
|
||||||
if ($this->do_debug >= self::DEBUG_SERVER) {
|
|
||||||
echo '<pre>';
|
|
||||||
foreach ($this->errors as $e) {
|
|
||||||
print_r($e);
|
|
||||||
}
|
|
||||||
echo '</pre>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an array of error messages, if any.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getErrors()
|
|
||||||
{
|
|
||||||
return $this->errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POP3 connection error handler.
|
|
||||||
*
|
|
||||||
* @param int $errno
|
|
||||||
* @param string $errstr
|
|
||||||
* @param string $errfile
|
|
||||||
* @param int $errline
|
|
||||||
*/
|
|
||||||
protected function catchWarning($errno, $errstr, $errfile, $errline)
|
|
||||||
{
|
|
||||||
$this->setError(
|
|
||||||
'Connecting to the POP3 server raised a PHP warning:' .
|
|
||||||
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
|||||||
*::-webkit-scrollbar,
|
|
||||||
*::-webkit-scrollbar-thumb {
|
|
||||||
width: 26px;
|
|
||||||
border-radius: 13px;
|
|
||||||
background-clip: padding-box;
|
|
||||||
border: 10px solid transparent;
|
|
||||||
color: rgb(108, 117, 125, 0.7);
|
|
||||||
}
|
|
||||||
*::-webkit-scrollbar-thumb:hover{
|
|
||||||
color: rgb(108, 117, 125, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
*::-webkit-scrollbar-thumb {
|
|
||||||
box-shadow: inset 0 0 0 10px;
|
|
||||||
}
|
|
10145
css/styles.css
10145
css/styles.css
File diff suppressed because it is too large
Load Diff
18
css/type.css
18
css/type.css
@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
#type{
|
|
||||||
display: inline;
|
|
||||||
border-right: 4px solid #6c757d;
|
|
||||||
animation: blink 1s step-end infinite;
|
|
||||||
color: rgba(36, 129, 60, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes blink {
|
|
||||||
50%{
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.75s;
|
|
||||||
}
|
|
20
dockerfile
Normal file
20
dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM node:lts-slim as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN rm -rf node_modules
|
||||||
|
RUN rm -rf build
|
||||||
|
COPY . .
|
||||||
|
RUN npm install
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:lts-slim as run
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/package.json ./package.json
|
||||||
|
COPY --from=build /app/build ./build
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT [ "npm", "run", "start" ]
|
308
index.html
308
index.html
@ -1,308 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" loading="lazy">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
||||||
<meta name="description" content="Luke Else - Junior Software Developer from Kent. I specialise in C# and dotnet development but also have experience in GoLang and Java Script (+ Node). I've been developing Apps and Backend Web-Systems since Early 2020 and have learned a lot of skills and experience on the way!" />
|
|
||||||
<meta name="author" content="" />
|
|
||||||
<title>Luke Else - Junior Software Developer</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
|
|
||||||
<!-- Font Awesome icons-->
|
|
||||||
<script src="https://kit.fontawesome.com/5e5c5ee5fe.js" crossorigin="anonymous"></script>
|
|
||||||
<!-- Dev Icons-->
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@v2.14.0/devicon.min.css">
|
|
||||||
<!-- Google fonts-->
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:500,700" rel="stylesheet" type="text/css" />
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,800,800i" rel="stylesheet" type="text/css" />
|
|
||||||
<!-- Core theme CSS (includes Bootstrap)-->
|
|
||||||
<link href="css/styles.css" rel="stylesheet" />
|
|
||||||
<link href="css/type.css" rel="stylesheet" />
|
|
||||||
<link href="css/scroll.css" rel="stylesheet" />
|
|
||||||
|
|
||||||
<script src="js/age.js"></script>
|
|
||||||
</head>
|
|
||||||
<body id="page-top" onload="document.body.style.opacity='1'">
|
|
||||||
<!-- Navigation-->
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-secondary fixed-top" id="sideNav">
|
|
||||||
<a class="navbar-brand js-scroll-trigger" href="#page-top">
|
|
||||||
<span class="d-block d-lg-none">Luke Else</span>
|
|
||||||
<span class="d-none d-lg-block"><img class="img-fluid img-profile rounded-circle mx-auto mb-2" src="assets/img/profile.jpg" alt="Luke Else" /></span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#about">About</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#experience">Experience</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#projects">Projects</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#education">Education</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#skills">Skills</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#interests">Interests</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#contact">Contact</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<!-- Page Content-->
|
|
||||||
<div class="container-fluid p-0">
|
|
||||||
<!-- About-->
|
|
||||||
<section class="resume-section" id="about">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h1 class="mb-0">
|
|
||||||
<span class="text-secondary">{</span>
|
|
||||||
Luke
|
|
||||||
<span class="text-primary">Else</span>
|
|
||||||
<span class="text-secondary">}</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<h2 class="mb-0">
|
|
||||||
<p id="type"></p>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="subheading mb-5">
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.write("Age: <span class='text-dark'>" + getAge(new Date("2004-01-12")) + "</span>")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
Mobile: <span class='text-dark'> +44 7498 289321 </span>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
E-Mail:
|
|
||||||
<a href="mailto:contact@luke-else.co.uk">contact@luke-else.co.uk</a>
|
|
||||||
</div>
|
|
||||||
<p class="lead mb-5">I am a Junior but well-practised Software Developer. I have a great passion for learning, especially in topics oriented around Software Development, Networks and Backend Engineering. I am constantly striving to learn more whilst pushing myself to explore new technologies!</p>
|
|
||||||
<div class="social-icons">
|
|
||||||
<a target="_blank" class="social-icon" href="https://www.linkedin.com/in/luke-else-a7183a205/"><i class="fab fa-linkedin-in"></i></a>
|
|
||||||
<a target="_blank" class="social-icon" href="https://github.com/luke-else"><i class="fab fa-github"></i></a>
|
|
||||||
<a target="_blank" class="social-icon" href="https://stackoverflow.com/users/16627564/luke-else"><i class="fab fa-stack-overflow"></i></a>
|
|
||||||
<a target="_blank" class="social-icon" href="assets/Luke Else - CV.pdf"><i class="fas fa-file-download"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Experience-->
|
|
||||||
<section class="resume-section" id="experience">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Experience</h2>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">Car Valeter</h3>
|
|
||||||
<div class="subheading mb-3">CSM Valeting</div>
|
|
||||||
<p>This job involved ensuring customer satisfaction was very high! Every car had to be cleaned meticulously to ensure the highest standard was achieved. We often spent a lot of our time in commercial workspace environments and so remaining professional was key to the service we provided. I enjoyed working here a lot because of both the independent and teamworking aspects. On many occasions we would find ourselves working on a car in a group so communication was key to ensure that no part was missed nor any unnecessary work was undertaken!</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary">March 2020 - Present</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">Site Maintenance</h3>
|
|
||||||
<div class="subheading mb-3">Ashford Self Storage</div>
|
|
||||||
<p>Working at Ashford Self Storage was my first taste of working in the real world. I spent a lot of time interacting with the customers throughout the day whilst also maintaining the cleanliness and presentability of the site. I was in charge of making sure the loading bay was clear and safe to use in addition to landscaping out the front and sides of the warehouse building!</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary">July 2018 - September 2020</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Projects-->
|
|
||||||
<section class="resume-section" id="projects">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Projects</h2>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">snexo.co.uk</h3>
|
|
||||||
<div class="subheading mb-3">Snexo</div>
|
|
||||||
<p>This project is a templated website designed for a client that needed a platform to advertise his clearance services. The website is made using Bootstrap and JQuery along with PHP for some of the backend features. I am hosting the site along with its E-Mail accounts on my own web and mail servers which guarantees a near 100% uptime!</p>
|
|
||||||
<!--<img class="img-fluid mx-auto mb-2" src="assets/img/snexo.jpg" alt="snexo.co.uk" /> -->
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary"><a href="https://snexo.co.uk">snexo.co.uk</a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">Hour Logging System</h3>
|
|
||||||
<div class="subheading mb-3">Personal Project</div>
|
|
||||||
<p>This project is a web application designed to make an easy way for employees to log the hours that they are working. All they have to do is clock in at the start of the day and clock back out once they have finished. The app is being developed using ASP.Net 5 with MongoDB acting as a permanent data store for each user's data.</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary"><a href="https://github.com/luke-else/Hour-Logging-System">GitHub</a></span></div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">Flight Simulator EFB</h3>
|
|
||||||
<div class="subheading mb-3">A-Level Project</div>
|
|
||||||
<p>This project is probably one of the largest so far! It is designed for flight sim users to be able to brief and track their flights as well as giving them the ability to fetch relevant routes and charts with just the click of a button. The project is a part of my A-Level course and thus is heavily documented at all stages of the application development life cycle. It has taught me a lot about the agile development ideology, something that will be really useful to apply to some of my next projects!</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary"><a href="https://github.com/Flight-Simulator-EFB">GitHub</a></span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Education-->
|
|
||||||
<section class="resume-section" id="education">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Education</h2>
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">The Norton Knatchbull School</h3>
|
|
||||||
<div class="subheading mb-3">A-Levels</div>
|
|
||||||
<div>Computer Science, Maths and Physics</div>
|
|
||||||
<p>Grades (Predicted): A*, A, A</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary">September 2020 - Present</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h3 class="mb-0">The Norton Knatchbull School</h3>
|
|
||||||
<div class="subheading mb-3">GCSE's</div>
|
|
||||||
<div>FSMQ (C), Maths (8), Geography (<b>9</b>), Biology (<b>9</b>), Chemistry (<b>9</b>), Physics (<b>9</b>), Spanish (7), English (Literature & Language) (7, 7), Computer Science (<b>9</b>)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-shrink-0"><span class="text-primary">September 2015 - August 2020</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Skills-->
|
|
||||||
<section class="resume-section" id="skills">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Skills</h2>
|
|
||||||
<div class="subheading mb-3">Programming Languages & Tools</div>
|
|
||||||
<ul class="list-inline dev-icons">
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="HTML 5" class="fab fa-html5"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="CSS 3" class="fab fa-css3-alt"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Java Script" class="fab fa-js-square"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="GoLang" class="fab fa-golang"></i></li>
|
|
||||||
<!-- <li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Golang" class="fab fa-golang"></i></li> -->
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="C#" class="devicon-csharp-plain"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title=".Net" class="devicon-dotnetcore-plain"></i></li>
|
|
||||||
<!--
|
|
||||||
<li class="list-inline-item"><i class="fab fa-python"></i></li>
|
|
||||||
<li class="list-inline-item"><i class="fab fa-php"></i></li>
|
|
||||||
-->
|
|
||||||
<!-- <li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Network Infrastructure" class="fas fa-network-wired"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Server Infrastructure" class="fas fa-server"></i></li> -->
|
|
||||||
<!--<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Amazon Web Services" class="fab fa-aws"></i></li> -->
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="NGINX" class="devicon-nginx-plain"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="docker" class="devicon-docker-plain"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Linux" class="fab fa-linux"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Bash" class="devicon-bash-plain"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Git" class="fab fa-git-alt"></i></li>
|
|
||||||
<li class="list-inline-item"><i data-toggle="tooltip" data-placement="bottom" title="Stack Overflow" class="fab fa-stack-overflow"></i></li>
|
|
||||||
</ul>
|
|
||||||
<div class="subheading mb-3">Workflow</div>
|
|
||||||
<ul class="fa-ul mb-0">
|
|
||||||
<li>
|
|
||||||
<span class="fa-li"><i class="fas fa-check"></i></span>
|
|
||||||
Basic Website front-end and UI Development
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="fa-li"><i class="fas fa-check"></i></span>
|
|
||||||
Website Client Side and Server Side Development
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="fa-li"><i class="fas fa-check"></i></span>
|
|
||||||
Basic Networking and Server Infrastructure
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="fa-li"><i class="fas fa-check"></i></span>
|
|
||||||
Software Development
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Interests-->
|
|
||||||
<section class="resume-section" id="interests">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Interests</h2>
|
|
||||||
<p>In my spare time, I will often find myself working on some of my smaller projects such as websites for my friends which I host on my own rented cloud servers. As well as this, I am quite interested in reading about system exploits as it is something that I can apply to my own work in order to keep it safe and secure.</p>
|
|
||||||
<p>I am also an avid member of the VATSIM flight simulation community and spend my evenings interacting with the many members that it holds. I operate as a virtual air traffic controller and will also find myself flying on the network in some of the many events they host each week.</p>
|
|
||||||
<p class="mb-0">From 2015 to 2020 I was also in the 'Royal Air Force Air Cadets' which taught me a lot of leadership, communication and problem solving skills. Whilst there, I partook in the Bronze Duke Of Edinburgh award and joined the squadron band, both of which were tough but rewarding experiences. Throughout the year we would attend many parades for which our drill and discipline standard had to be impeccable.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
<!-- Interests-->
|
|
||||||
<section class="resume-section" id="contact">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<h2 class="mb-5">Contact</h2>
|
|
||||||
|
|
||||||
<form id="contactForm" name="sentMessage" novalidate="novalidate">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="control-group col-sm-6">
|
|
||||||
<div class="form-group floating-label-form-group controls mb-0 pb-2">
|
|
||||||
<label>Name</label>
|
|
||||||
<input class="form-control" id="name" type="text" placeholder="Name" required="required" data-validation-required-message="Please enter your name." />
|
|
||||||
<p class="help-block text-danger"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-group col-sm-6">
|
|
||||||
<div class="form-group floating-label-form-group controls mb-0 pb-2">
|
|
||||||
<label>E-Mail</label>
|
|
||||||
<input class="form-control" id="email" type="email" placeholder="E-Mail Address" required="required" data-validation-required-message="Please enter your email address." />
|
|
||||||
<p class="help-block text-danger"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="form-group floating-label-form-group controls mb-0 pb-2">
|
|
||||||
<label>Message</label>
|
|
||||||
<textarea class="form-control" id="message" rows="5" placeholder="Message" required="required" data-validation-required-message="Please enter a message."></textarea>
|
|
||||||
<p class="help-block text-danger"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div id="success"></div>
|
|
||||||
<div class="form-group">
|
|
||||||
|
|
||||||
<button class="btn btn-success btn-xl" id="sendMessageButton" type="submit">
|
|
||||||
<i class="fa fa-paper-plane " aria-hidden="true"></i> Send →
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
|
|
||||||
<!-- Code
|
|
||||||
|
|
||||||
<section class="d-flex flex-wrap justify-content-center" id="code">
|
|
||||||
<div class="resume-section-content">
|
|
||||||
<img src="/assets/img/code.png" width="100%" height="auto"/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr class="m-0" />
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- Bootstrap core JS-->
|
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<!-- Third party plugin JS-->
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
|
||||||
<!-- Core theme JS-->
|
|
||||||
<script src="js/scripts.js"></script>
|
|
||||||
<script src="js/type.js"></script>
|
|
||||||
<script src="assets/mail/contact_me.js"></script>
|
|
||||||
<script src="assets/mail/jqBootstrapValidation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
10
js/age.js
10
js/age.js
@ -1,10 +0,0 @@
|
|||||||
function getAge(dateString) {
|
|
||||||
var today = new Date();
|
|
||||||
var birthDate = dateString;
|
|
||||||
var age = today.getFullYear() - birthDate.getFullYear();
|
|
||||||
var m = today.getMonth() - birthDate.getMonth();
|
|
||||||
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
||||||
age--;
|
|
||||||
}
|
|
||||||
return age;
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
class carouseltext{
|
|
||||||
|
|
||||||
constructor(elements, carousel, period){
|
|
||||||
this.elements = elements;
|
|
||||||
this.carousel = carousel;
|
|
||||||
this.period = parseInt(period, 10) || 2000;
|
|
||||||
this.loop = 0;
|
|
||||||
this.isDeleting = true;
|
|
||||||
this.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick(){
|
|
||||||
var i = this.loop % this.carousel.length;
|
|
||||||
var fullTxt = this.toRotate[i];
|
|
||||||
|
|
||||||
if (this.isDeleting == true) {
|
|
||||||
this.txt = fullTxt.substring(0, this.txt.length - 1);
|
|
||||||
}else{
|
|
||||||
this.txt = fullTxt.substring(0, this.txt.length + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.innerHTML = '<span class="wrap">'+this.txt+'</span>';
|
|
||||||
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
var delta = 300 - Math.random() * 100;
|
|
||||||
|
|
||||||
if (this.isDeleting) { delta /= 2; }
|
|
||||||
|
|
||||||
if (!this.isDeleting && this.txt === fullTxt) {
|
|
||||||
delta = this.period;
|
|
||||||
this.isDeleting = true;
|
|
||||||
} else if (this.isDeleting && this.txt === '') {
|
|
||||||
this.isDeleting = false;
|
|
||||||
this.loopNum++;
|
|
||||||
delta = 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Hold the function from executing again until delta time has passed.
|
|
||||||
setTimeout(function() {
|
|
||||||
that.tick();
|
|
||||||
}, delta);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
textLoad() {
|
|
||||||
var elements = document.getElementsByClassName('txt-rotate');
|
|
||||||
for (var i=0; i<elements.length; i++) {
|
|
||||||
var carousel = elements[i].getAttribute('data-rotate');
|
|
||||||
var period = elements[i].getAttribute('data-period');
|
|
||||||
if (toRotate) {
|
|
||||||
new Text(elements[i], JSON.parse(carousel), period);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
(function ($) {
|
|
||||||
"use strict"; // Start of use strict
|
|
||||||
|
|
||||||
// Smooth scrolling using anime.js
|
|
||||||
$('a.js-scroll-trigger[href*="#"]:not([href="#"])').on('click', function () {
|
|
||||||
if (
|
|
||||||
location.pathname.replace(/^\//, "") ==
|
|
||||||
this.pathname.replace(/^\//, "") &&
|
|
||||||
location.hostname == this.hostname
|
|
||||||
) {
|
|
||||||
var target = $(this.hash);
|
|
||||||
target = target.length ?
|
|
||||||
target :
|
|
||||||
$("[name=" + this.hash.slice(1) + "]");
|
|
||||||
if (target.length) {
|
|
||||||
anime({
|
|
||||||
targets: 'html, body',
|
|
||||||
scrollTop: target.offset().top,
|
|
||||||
duration: 1000,
|
|
||||||
easing: 'easeInOutExpo'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Closes responsive menu when a scroll trigger link is clicked
|
|
||||||
$(".js-scroll-trigger").on('click', function () {
|
|
||||||
$(".navbar-collapse").collapse("hide");
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Activate scrollspy to add active class to navbar items on scroll
|
|
||||||
$('body').scrollspy({
|
|
||||||
target: "#sideNav"
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})(jQuery); // End of use strict
|
|
52
js/type.js
52
js/type.js
@ -1,52 +0,0 @@
|
|||||||
class typer{
|
|
||||||
|
|
||||||
constructor(text, retypeText){
|
|
||||||
this.i = 0;
|
|
||||||
this.x = 0;
|
|
||||||
this.item = document.getElementById("type");
|
|
||||||
this.text = text;
|
|
||||||
this.retypeText = retypeText;
|
|
||||||
}
|
|
||||||
|
|
||||||
type(){
|
|
||||||
this.item.innerHTML += this.text[this.x].charAt(this.i);
|
|
||||||
this.i++;
|
|
||||||
|
|
||||||
if (this.i < this.text[this.x].length) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.type();
|
|
||||||
}, 150);
|
|
||||||
}else{
|
|
||||||
if(this.retypeText == true){
|
|
||||||
this.i = 0;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.retype();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retype() {
|
|
||||||
this.item.innerHTML = this.item.innerHTML.substring(0, this.item.innerHTML.length-1);
|
|
||||||
|
|
||||||
if (this.item.innerHTML.length > 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.retype();
|
|
||||||
}, 100);
|
|
||||||
}else{
|
|
||||||
this.x++;
|
|
||||||
//this.x > this.text.length -1 || loop
|
|
||||||
if(this.x >= this.text.length - 1){
|
|
||||||
//this.x = 0;
|
|
||||||
this.retypeText = false;
|
|
||||||
}
|
|
||||||
this.type();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pagetyper = new typer(["//Junior Software Developer", "//Aspiring Backend Engineer"], true);
|
|
||||||
|
|
||||||
setTimeout(() =>{
|
|
||||||
pagetyper.type();
|
|
||||||
}, 750)
|
|
2088
package-lock.json
generated
Normal file
2088
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "luke-else.co.uk",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"start": "export PORT=3000 && node ./build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "prettier --plugin-search-dir . --check .",
|
||||||
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
|
"@sveltejs/kit": "^1.20.4",
|
||||||
|
"prettier": "^2.8.0",
|
||||||
|
"prettier-plugin-svelte": "^2.10.1",
|
||||||
|
"svelte": "^4.0.5",
|
||||||
|
"svelte-check": "^3.4.3",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^4.4.2"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
12
src/app.d.ts
vendored
Normal file
12
src/app.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
129
src/app.html
Normal file
129
src/app.html
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="description" content="Luke Else - Software Developer at Thales UK. I specialise in developing distributed systems in C++ using highly scalable internal frameworks. I also develop backend and system applications in my spare time using both Svelte, Rust and C++. Feel free to check my work out at https://git.luke-else.co.uk." />
|
||||||
|
<meta name="author" content="Luke Else (mail@luke-else.co.uk)" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@v2.15.1/devicon.min.css">
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
%sveltekit.head%
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--font: Consolas, 'Cascadia Code', Monaco, 'SF Mono', 'DejaVu Sans Mono', 'Roboto Mono';
|
||||||
|
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 110%;
|
||||||
|
margin: 2rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: var(--header);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: .2em solid var(--bg-grad-3);
|
||||||
|
border-radius: 5em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar,
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
width: 26px;
|
||||||
|
border-radius: 13px;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 10px solid transparent;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar-thumb:hover{
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
box-shadow: inset 0 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:600px) {
|
||||||
|
.not-required {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
color: var(--link);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0%;
|
||||||
|
border-bottom: 2px solid var(--fg);
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover:after {
|
||||||
|
width: 100%;
|
||||||
|
color: var(--glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active {
|
||||||
|
color: var(--header);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: var(--fg);
|
||||||
|
background-color: var(--bg-grad-1);
|
||||||
|
box-shadow: .1em .1em .1em var(--link);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
box-shadow: .3em .3em .3em var(--header);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animationName {
|
||||||
|
0% { opacity:0; }
|
||||||
|
50% { opacity:1; }
|
||||||
|
100% { opacity:0; }
|
||||||
|
}
|
||||||
|
@-o-keyframes animationName{
|
||||||
|
0% { opacity:0; }
|
||||||
|
50% { opacity:1; }
|
||||||
|
100% { opacity:0; }
|
||||||
|
}
|
||||||
|
@-moz-keyframes animationName{
|
||||||
|
0% { opacity:0; }
|
||||||
|
50% { opacity:1; }
|
||||||
|
100% { opacity:0; }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes animationName{
|
||||||
|
0% { opacity:0; }
|
||||||
|
50% { opacity:1; }
|
||||||
|
100% { opacity:0; }
|
||||||
|
}
|
||||||
|
.elementToFadeInAndOut {
|
||||||
|
-webkit-animation: animationName 1.5s infinite;
|
||||||
|
-moz-animation: animationName 1.5s infinite;
|
||||||
|
-o-animation: animationName 1.5s infinite;
|
||||||
|
animation: animationName 1.5s infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
66
src/lib/components/Card.svelte
Normal file
66
src/lib/components/Card.svelte
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
dispatch('click');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 2 1 15em;
|
||||||
|
padding: .5em 2.5em 2em 2.5em;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: .5em;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: .5em .5em .5em var(--hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-header :global(div) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-content :global(div) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-footer :global(div){
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
max-width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div class="card" on:click={onClick}>
|
||||||
|
<div class="card-header">
|
||||||
|
<slot name="header"></slot>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="card-content">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
<hr class="not-required"/>
|
||||||
|
<div class="card-footer">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
82
src/lib/components/Modal.svelte
Normal file
82
src/lib/components/Modal.svelte
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let showModal: boolean;
|
||||||
|
|
||||||
|
let dialog: HTMLDialogElement;
|
||||||
|
|
||||||
|
$: if (dialog && showModal) dialog.showModal();
|
||||||
|
|
||||||
|
import CloseIcon from "./Toasts/CloseIcon.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
|
||||||
|
<dialog
|
||||||
|
bind:this={dialog}
|
||||||
|
on:close={() => (showModal = false)}
|
||||||
|
on:click|self={() => dialog.close()}
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div on:click|stopPropagation>
|
||||||
|
<slot name="header" />
|
||||||
|
<hr />
|
||||||
|
<slot />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<button class="close" on:click={() => dialog.close()}>
|
||||||
|
<CloseIcon width="0.8em" />
|
||||||
|
</button>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
dialog {
|
||||||
|
max-width: 70%;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
border: none;
|
||||||
|
padding: 0em 2em 2em 2em;
|
||||||
|
border-left: 2em;
|
||||||
|
border-right: 2em;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
box-shadow: .5em .5em .5em var(--header);
|
||||||
|
}
|
||||||
|
dialog::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
dialog > div {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
dialog[open] {
|
||||||
|
animation: zoom 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
}
|
||||||
|
@keyframes zoom {
|
||||||
|
from {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog[open]::backdrop {
|
||||||
|
animation: fade 0.3s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 1.5em;
|
||||||
|
color: var(--fg);
|
||||||
|
background: transparent;
|
||||||
|
border: 0 none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
</style>
|
102
src/lib/components/ThemeSwitcher.svelte
Normal file
102
src/lib/components/ThemeSwitcher.svelte
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
export let darkMode: boolean = true;
|
||||||
|
|
||||||
|
function onThemeSwitch() {
|
||||||
|
darkMode = !darkMode;
|
||||||
|
|
||||||
|
localStorage.setItem('theme', darkMode ? 'dark' : 'light');
|
||||||
|
|
||||||
|
darkMode
|
||||||
|
? document.documentElement.classList.add('dark')
|
||||||
|
: document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
if (
|
||||||
|
localStorage.theme === 'dark' ||
|
||||||
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
darkMode = true;
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
darkMode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
position: absolute;
|
||||||
|
top: 0em;
|
||||||
|
right: 0em;
|
||||||
|
display: inline-block;
|
||||||
|
width: 3.75em;
|
||||||
|
height: 2.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--bg-grad-4);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 80%;
|
||||||
|
width: 45%;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: var(--bg-grad-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
-webkit-transform: translateX(1.625em);
|
||||||
|
-ms-transform: translateX(1.625em);
|
||||||
|
transform: translateX(1.625em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 2.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<link rel="stylesheet" href={`/themes/${darkMode ? 'dark' : 'light'}.css`} />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="toggle-wrapper not-required">
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" checked={darkMode} on:click={onThemeSwitch}>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
18
src/lib/components/Toasts/CloseIcon.svelte
Normal file
18
src/lib/components/Toasts/CloseIcon.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let width = "1em"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
style="text-align: center; display: inline-block;"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 352 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
|
||||||
|
/>
|
||||||
|
</svg>
|
19
src/lib/components/Toasts/ErrorIcon.svelte
Normal file
19
src/lib/components/Toasts/ErrorIcon.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
export let width = "1em"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
style="text-align: center; display: inline-block;"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M256 40c118.621 0 216 96.075 216 216 0 119.291-96.61 216-216 216-119.244 0-216-96.562-216-216 0-119.203 96.602-216 216-216m0-32C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm-11.49 120h22.979c6.823 0 12.274 5.682 11.99 12.5l-7 168c-.268 6.428-5.556 11.5-11.99 11.5h-8.979c-6.433 0-11.722-5.073-11.99-11.5l-7-168c-.283-6.818 5.167-12.5 11.99-12.5zM256 340c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28z"
|
||||||
|
class=""
|
||||||
|
></path>
|
||||||
|
</svg>
|
18
src/lib/components/Toasts/InfoIcon.svelte
Normal file
18
src/lib/components/Toasts/InfoIcon.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let width = "1em"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
style="text-align: center; display: inline-block;"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M256 40c118.621 0 216 96.075 216 216 0 119.291-96.61 216-216 216-119.244 0-216-96.562-216-216 0-119.203 96.602-216 216-216m0-32C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm-36 344h12V232h-12c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12h48c6.627 0 12 5.373 12 12v140h12c6.627 0 12 5.373 12 12v8c0 6.627-5.373 12-12 12h-72c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12zm36-240c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32z"
|
||||||
|
/>
|
||||||
|
</svg>
|
18
src/lib/components/Toasts/SuccessIcon.svelte
Normal file
18
src/lib/components/Toasts/SuccessIcon.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let width = "1em"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
style="text-align: center; display: inline-block;"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 464c-118.664 0-216-96.055-216-216 0-118.663 96.055-216 216-216 118.664 0 216 96.055 216 216 0 118.663-96.055 216-216 216zm141.63-274.961L217.15 376.071c-4.705 4.667-12.303 4.637-16.97-.068l-85.878-86.572c-4.667-4.705-4.637-12.303.068-16.97l8.52-8.451c4.705-4.667 12.303-4.637 16.97.068l68.976 69.533 163.441-162.13c4.705-4.667 12.303-4.637 16.97.068l8.451 8.52c4.668 4.705 4.637 12.303-.068 16.97z"
|
||||||
|
/>
|
||||||
|
</svg>
|
67
src/lib/components/Toasts/Toast.svelte
Normal file
67
src/lib/components/Toasts/Toast.svelte
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { Toast, ToastType } from "$lib/toast";
|
||||||
|
import SuccessIcon from "./SuccessIcon.svelte";
|
||||||
|
import ErrorIcon from "./ErrorIcon.svelte";
|
||||||
|
import InfoIcon from "./InfoIcon.svelte";
|
||||||
|
import CloseIcon from "./CloseIcon.svelte";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let toastData: Toast;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<article class={toastData.type.toString().toLowerCase()} role="alert" transition:fade>
|
||||||
|
{#if toastData.type === ToastType.Success}
|
||||||
|
<SuccessIcon width="1.1em" />
|
||||||
|
{:else if toastData.type === ToastType.Error}
|
||||||
|
<ErrorIcon width="1.1em" />
|
||||||
|
{:else}
|
||||||
|
<InfoIcon width="1.1em" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="text">
|
||||||
|
{toastData.text}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if toastData.dismissable}
|
||||||
|
<button class="close" on:click={() => dispatch("dismiss")}>
|
||||||
|
<CloseIcon width="0.8em" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
article {
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto 0.5rem auto;
|
||||||
|
width: 20rem;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background: IndianRed;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background: MediumSeaGreen;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
background: SkyBlue;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
color: white;
|
||||||
|
background: transparent;
|
||||||
|
border: 0 none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
28
src/lib/components/Toasts/Toasts.svelte
Normal file
28
src/lib/components/Toasts/Toasts.svelte
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Toast from "./Toast.svelte";
|
||||||
|
|
||||||
|
import { dismissToast, toasts } from "$lib/store";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $toasts}
|
||||||
|
<section>
|
||||||
|
{#each $toasts as toast (toast.id)}
|
||||||
|
<Toast toastData = {toast} on:dismiss={() => dismissToast(toast.id)} />
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
section {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
21
src/lib/components/timeline/Timeline.svelte
Normal file
21
src/lib/components/timeline/Timeline.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
import type { TimelinePosition, TimelineConfig } from '../types';
|
||||||
|
export let position: TimelinePosition = 'right';
|
||||||
|
export let style: string = null;
|
||||||
|
|
||||||
|
setContext<TimelineConfig>('TimelineConfig', { rootPosition: position });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="timeline" {style}>
|
||||||
|
<slot />
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 6px 16px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
13
src/lib/components/timeline/TimelineConnector.svelte
Normal file
13
src/lib/components/timeline/TimelineConnector.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let style: string = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="timeline-connector" {style} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-connector {
|
||||||
|
width: 2px;
|
||||||
|
background-color: #bdbdbd;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
30
src/lib/components/timeline/TimelineContent.svelte
Normal file
30
src/lib/components/timeline/TimelineContent.svelte
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
import type { TimelineConfig, TimelinePosition } from '../types';
|
||||||
|
export let style: string = null;
|
||||||
|
|
||||||
|
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||||
|
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
||||||
|
|
||||||
|
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={`timeline-content ${itemPosition}`} {style}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-content {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
margin: 6px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
19
src/lib/components/timeline/TimelineDot.svelte
Normal file
19
src/lib/components/timeline/TimelineDot.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let style: string = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="timeline-dot" {style}>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-dot {
|
||||||
|
background-color: #121212;
|
||||||
|
border: solid 2px #121212;
|
||||||
|
display: flex;
|
||||||
|
align-self: baseline;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 11.5px 0;
|
||||||
|
}
|
||||||
|
</style>
|
58
src/lib/components/timeline/TimelineItem.svelte
Normal file
58
src/lib/components/timeline/TimelineItem.svelte
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext, setContext } from 'svelte';
|
||||||
|
import type { TimelinePosition, ParentPosition, TimelineConfig } from '../types';
|
||||||
|
|
||||||
|
export let position: ParentPosition | null = null;
|
||||||
|
export let style: string = null;
|
||||||
|
|
||||||
|
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||||
|
const itemPosition = position ? position : config.rootPosition;
|
||||||
|
setContext<TimelinePosition>('ParentPosition', itemPosition);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li class={`timeline-item ${itemPosition}`} {style}>
|
||||||
|
{#if !$$slots['opposite-content']}
|
||||||
|
<div class="opposite-block" />
|
||||||
|
{:else}
|
||||||
|
<slot name="opposite-content" />
|
||||||
|
{/if}
|
||||||
|
<slot />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.alternate:nth-of-type(even) > .timeline-content) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.alternate:nth-of-type(odd) > .timeline-opposite-content) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opposite-block {
|
||||||
|
flex: 1;
|
||||||
|
margin: 6px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
min-height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alternate:nth-of-type(even) {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alternate:nth-of-type(odd) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
</style>
|
31
src/lib/components/timeline/TimelineOppositeContent.svelte
Normal file
31
src/lib/components/timeline/TimelineOppositeContent.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
import type { TimelineConfig, TimelinePosition } from '../types';
|
||||||
|
export let style: string = null;
|
||||||
|
|
||||||
|
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||||
|
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
||||||
|
|
||||||
|
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={`timeline-opposite-content ${itemPosition}`} {style}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-opposite-content {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: auto;
|
||||||
|
margin: 6px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
16
src/lib/components/timeline/TimelineSeparator.svelte
Normal file
16
src/lib/components/timeline/TimelineSeparator.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let style: string = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="timeline-separator" {style}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-separator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
5
src/lib/data.ts
Normal file
5
src/lib/data.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export async function getJson(path: string) {
|
||||||
|
let response = await fetch(path);
|
||||||
|
let users = await response.json();
|
||||||
|
return users;
|
||||||
|
}
|
39
src/lib/index.ts
Normal file
39
src/lib/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
|
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||||
|
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
||||||
|
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
||||||
|
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
||||||
|
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
||||||
|
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
||||||
|
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
||||||
|
|
||||||
|
import Toasts from '$lib/components/Toasts/Toasts.svelte';
|
||||||
|
import Toast from '$lib/components/Toasts/Toast.svelte';
|
||||||
|
import CloseIcon from '$lib/components/Toasts/CloseIcon.svelte';
|
||||||
|
import InfoIcon from '$lib/components/Toasts/InfoIcon.svelte';
|
||||||
|
import SuccessIcon from '$lib/components/Toasts/SuccessIcon.svelte';
|
||||||
|
import ErrorIcon from '$lib/components/Toasts/ErrorIcon.svelte';
|
||||||
|
|
||||||
|
import Card from '$lib/components/Card.svelte';
|
||||||
|
import Modal from '$lib/components/Modal.svelte';
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
Timeline,
|
||||||
|
TimelineItem,
|
||||||
|
TimelineSeparator,
|
||||||
|
TimelineDot,
|
||||||
|
TimelineConnector,
|
||||||
|
TimelineContent,
|
||||||
|
TimelineOppositeContent,
|
||||||
|
|
||||||
|
Toasts,
|
||||||
|
Toast,
|
||||||
|
CloseIcon,
|
||||||
|
InfoIcon,
|
||||||
|
SuccessIcon,
|
||||||
|
ErrorIcon,
|
||||||
|
|
||||||
|
Card,
|
||||||
|
Modal
|
||||||
|
};
|
28
src/lib/store.ts
Normal file
28
src/lib/store.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ToastType, type Toast } from "$lib/toast";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const toasts: Writable<Toast[]> = writable([]);
|
||||||
|
|
||||||
|
export const addToast = (toast: Toast) => {
|
||||||
|
// Create a unique ID so we can easily find/remove it
|
||||||
|
// if it is dismissible/has a timeout.
|
||||||
|
toast.id = Math.floor(Math.random() * 10000);
|
||||||
|
|
||||||
|
// Setup some sensible defaults for a toast.
|
||||||
|
const defaults = {
|
||||||
|
id: toast.id,
|
||||||
|
type: ToastType.Info,
|
||||||
|
dismissible: true,
|
||||||
|
timeout: 3000,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push the toast to the top of the list of toasts
|
||||||
|
toasts.update((all) => [{ ...defaults, ...toast }, ...all]);
|
||||||
|
|
||||||
|
// If toast is dismissible, dismiss it after "timeout" amount of time.
|
||||||
|
if (toast.timeout) setTimeout(() => dismissToast(toast.id), toast.timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dismissToast = (id: number) => {
|
||||||
|
toasts.update((all) => all.filter((t) => t.id !== id));
|
||||||
|
};
|
26
src/lib/toast.ts
Normal file
26
src/lib/toast.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @enum Used to refer to the type of toast being displayed
|
||||||
|
*/
|
||||||
|
export enum ToastType {
|
||||||
|
Info = "info",
|
||||||
|
Success = "success",
|
||||||
|
Error = "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Toast Notification
|
||||||
|
*/
|
||||||
|
export class Toast {
|
||||||
|
constructor(text: String, type: ToastType, dismissable: boolean, timeout: number ) {
|
||||||
|
this.text = text;
|
||||||
|
this.type = type;
|
||||||
|
this.dismissable = dismissable;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
id: number = 0;
|
||||||
|
text: String;
|
||||||
|
type: ToastType;
|
||||||
|
dismissable: Boolean;
|
||||||
|
timeout: number;
|
||||||
|
}
|
9
src/lib/types.d.ts
vendored
Normal file
9
src/lib/types.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type TimelinePosition = 'right' | 'left' | 'alternate';
|
||||||
|
|
||||||
|
type ParentPosition = 'right' | 'left';
|
||||||
|
|
||||||
|
type TimelineConfig = {
|
||||||
|
rootPosition: TimelinePosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TimelinePosition, ParentPosition, TimelineConfig };
|
125
src/main.svelte
Normal file
125
src/main.svelte
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getJson } from "$lib/data";
|
||||||
|
import { Toast, ToastType } from "$lib/toast";
|
||||||
|
import { addToast } from "$lib/store";
|
||||||
|
|
||||||
|
import Skills from './skills.svelte';
|
||||||
|
|
||||||
|
import Timeline from "./timeline.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.main-card {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border-radius: 1em;
|
||||||
|
padding: .2em 2em 2em 2em;
|
||||||
|
box-shadow: .5em .5em .5em var(--glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 8em;
|
||||||
|
width: 8em;
|
||||||
|
padding: 1em 1em 1em 1em;
|
||||||
|
border: .5em solid var(--bg-grad-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
padding: 0em 5% 0em 5%;
|
||||||
|
font-size: 125%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.flex-container {
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h3 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skills Cards CSS */
|
||||||
|
.container {
|
||||||
|
padding-top: 3em;
|
||||||
|
max-width: 90%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 3em 3em;
|
||||||
|
padding: 2em 0em 2em 0em;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#await getJson('/json/me.json')}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h1>Loading...</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:then info}
|
||||||
|
<div class="main-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h1>{info.name}</h1>
|
||||||
|
<h3 class="not-required">{info.job_title}</h3>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="flex-container">
|
||||||
|
<img class="profile not-required" src={info.profile_photo} alt="{info.name}'s Profile Photo">
|
||||||
|
<p class="about">{@html info.about}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Skills</h1>
|
||||||
|
<hr />
|
||||||
|
<div class="cards">
|
||||||
|
<Skills skills="{info.skills}"></Skills>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Experience</h1>
|
||||||
|
<hr />
|
||||||
|
<!-- https://github.com/K-Sato1995/svelte-vertical-timeline -->
|
||||||
|
<Timeline timelineData="{info.timeline}"></Timeline>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: none;">{addToast(new Toast("Click on a skill to open a prompt", ToastType.Info, true, 8_000))}</div>
|
||||||
|
<div style="display: none;">{addToast(new Toast("Welcome!", ToastType.Success, true, 7_000))}</div>
|
||||||
|
{:catch}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h1>Unable to load portfolio overview data</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: none;">{addToast(new Toast("Unable to load me.json", ToastType.Error, true, 3000))}</div>
|
||||||
|
{/await}
|
60
src/routes/+layout.svelte
Normal file
60
src/routes/+layout.svelte
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Toasts from "$lib/components/Toasts/Toasts.svelte";
|
||||||
|
import ThemeSwitcher from "$lib/components/ThemeSwitcher.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.main-container {
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-right: 10%;
|
||||||
|
padding-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.main-container {
|
||||||
|
margin: 0em;
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 110%;
|
||||||
|
overflow: visible;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.5em;
|
||||||
|
padding: 0em 0em 1.5em 0em;
|
||||||
|
z-index: 2;
|
||||||
|
height: 1.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade {
|
||||||
|
-webkit-animation: fadeinout 1s linear forwards;
|
||||||
|
animation: fadeinout 1s linear forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fadeinout {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeinout {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<a href = "/">//Profile</a>
|
||||||
|
<a href = "/repos">//Repos</a>
|
||||||
|
<a href = "/contact">//Contact</a>
|
||||||
|
<ThemeSwitcher />
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="main-container fade">
|
||||||
|
<Toasts />
|
||||||
|
<slot />
|
||||||
|
</div>
|
5
src/routes/+page.svelte
Normal file
5
src/routes/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
import Main from '../main.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Main></Main>
|
82
src/routes/contact/+page.svelte
Normal file
82
src/routes/contact/+page.svelte
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Card from '$lib/components/Card.svelte';
|
||||||
|
import { Toast, ToastType } from "$lib/toast";
|
||||||
|
import { addToast } from "$lib/store";
|
||||||
|
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
const sent = $page.url.searchParams.get('sent');
|
||||||
|
|
||||||
|
if (sent == "true") {
|
||||||
|
addToast(new Toast("Thank you! Your E-Mail has been sent. I will reply as soon as possible!", ToastType.Success, true, 5000));
|
||||||
|
}
|
||||||
|
// Can't use else otherwise the warning will display on load
|
||||||
|
if (sent == "false") {
|
||||||
|
addToast(new Toast("Sorry, your E-Mail could not be sent... Please try again later!", ToastType.Error, true, 5000));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 2 1;
|
||||||
|
align-items: center;
|
||||||
|
margin: 1em;
|
||||||
|
gap: 1em 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
padding: 1em 0em 1em 1em;
|
||||||
|
background-color: var(--input);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
resize: none;
|
||||||
|
min-width: 100%;
|
||||||
|
transition: all 0.15s;
|
||||||
|
|
||||||
|
&:focus, &:hover{
|
||||||
|
box-shadow: .2em .2em .2em var(--green);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex: 2 1 5rem;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em 1em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<div slot="header">
|
||||||
|
<h2>Contact</h2>
|
||||||
|
</div>
|
||||||
|
<div slot="content">
|
||||||
|
<form action="https://api.staticforms.xyz/submit" method="post">
|
||||||
|
<div class="container">
|
||||||
|
<input type="hidden" name="accessKey" value="fbb5ec04-506b-448a-a445-a2e47579a966">
|
||||||
|
<input type="text" name="name" placeholder="Name" required>
|
||||||
|
<input type="text" name="email" placeholder="Email address" required>
|
||||||
|
<input type="hidden" name="replyTo" value="@">
|
||||||
|
<input type="text" name="subject" placeholder="Subject" required>
|
||||||
|
<input type="text" name="honeypot" style="display: none;">
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<textarea name="message" id="message" placeholder="Message" required></textarea>
|
||||||
|
</div>
|
||||||
|
<input class="button" type="submit" value="Send" />
|
||||||
|
<input type="hidden" name="redirectTo" value="https://luke-else.co.uk/contact?sent=true">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div slot="footer">
|
||||||
|
<a href="/Luke Else - CV.pdf" target="_blank" rel="noopener noreferrer">Curriculum Vitae</a>
|
||||||
|
<a href="mailto:contact@luke-else.co.uk">E-Mail</a>
|
||||||
|
</div>
|
||||||
|
</Card>
|
3
src/routes/repos/+page.svelte
Normal file
3
src/routes/repos/+page.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<h1>Repos</h1>
|
||||||
|
<p>Stay tuned! This is still in development.</p>
|
||||||
|
<p>Come back later to find out what projects I'm currently working on!</p>
|
59
src/skills.svelte
Normal file
59
src/skills.svelte
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let skills: any;
|
||||||
|
|
||||||
|
import Card from '$lib/components/Card.svelte';
|
||||||
|
import Modal from '$lib/components/Modal.svelte';
|
||||||
|
|
||||||
|
let showModal: boolean = false;
|
||||||
|
let activeModal: any = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card-footer {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5em;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
color: var(--fg);
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#each skills as skill}
|
||||||
|
<Card on:click={() => {showModal = true; activeModal = skill}}>
|
||||||
|
<div slot="header">
|
||||||
|
<h2>{skill.skill}</h2>
|
||||||
|
<i class="{skill.logo} logo"></i>
|
||||||
|
</div>
|
||||||
|
<div slot="content">
|
||||||
|
<p class="not-required">{@html skill.usage}</p>
|
||||||
|
</div>
|
||||||
|
<div slot="footer">
|
||||||
|
<!-- svelte-ignore a11y-invalid-attribute -->
|
||||||
|
<a href="#">View More</a>
|
||||||
|
<a href="/repos">Repos</a>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!--Modal to be displayed on click-->
|
||||||
|
{#if activeModal != null}
|
||||||
|
<Modal bind:showModal>
|
||||||
|
<h2 slot="header" class="card-header">
|
||||||
|
{activeModal.skill}
|
||||||
|
<i class="{activeModal.logo} logo"></i>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{activeModal.about}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="card-footer">
|
||||||
|
<a href="{activeModal.link}" target="_blank" rel="noopener noreferrer">Learn More</a>
|
||||||
|
<a href="/repos">Repos</a>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
63
src/timeline.svelte
Normal file
63
src/timeline.svelte
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let timelineData: any;
|
||||||
|
|
||||||
|
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||||
|
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
||||||
|
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
||||||
|
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
||||||
|
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
||||||
|
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
||||||
|
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Timeline
|
||||||
|
position="alternate"
|
||||||
|
style={`
|
||||||
|
border-radius: 3%;
|
||||||
|
padding: 1rem;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{#each timelineData as item}
|
||||||
|
<TimelineItem>
|
||||||
|
<TimelineOppositeContent slot="opposite-content">
|
||||||
|
<p class="oposite-content-title">{item.duration}</p>
|
||||||
|
</TimelineOppositeContent>
|
||||||
|
|
||||||
|
<TimelineSeparator>
|
||||||
|
{#if item.duration.includes('Present') || !item.duration.includes('-')}
|
||||||
|
<div class="elementToFadeInAndOut">
|
||||||
|
<TimelineDot style={`background-color: var(--link); border-color: var(--bg-grad-2);`} />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<TimelineDot style={`background-color: var(--link); border-color: var(--bg-grad-2);`} />
|
||||||
|
{/if}
|
||||||
|
<TimelineConnector />
|
||||||
|
</TimelineSeparator>
|
||||||
|
<TimelineContent>
|
||||||
|
<h3 class="content-title">{item.title}</h3>
|
||||||
|
<p class="content-description">{@html item.description}</p>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
{/each}
|
||||||
|
</Timeline>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.oposite-content-title {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--bg-grad-2);
|
||||||
|
}
|
||||||
|
.content-title {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-description {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: var(--fg);
|
||||||
|
font-weight: lighter;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
Binary file not shown.
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 738 B |
60
static/json/me.json
Normal file
60
static/json/me.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "Luke Else",
|
||||||
|
"job_title": "Software Engineer",
|
||||||
|
"profile_photo": "/profile.jpg",
|
||||||
|
"skills" : [
|
||||||
|
{
|
||||||
|
"skill": "Rust",
|
||||||
|
"logo": "devicon-rust-plain",
|
||||||
|
"link": "https://rust-lang.org",
|
||||||
|
"usage": "Rust is a memory safe programming language that relies on a borrow checker to keep track of who owns memory. This makes it versatile in embedded software becuase of the zero cost abstraction and lack of requirement for a garbage colllector. Becuase of this, I opted to use Rust for one of my own projects in which I created a <a href='https://git@git.luke-else.co.uk/luke-else/esp32_gps_display'>GPS fueled speedometer</a> and position tracker to act as a form of telematics for my car. I have also used Rust when creating some software to assist with university studies. See here for my <a href='https://git@git.luke-else.co.uk/luke-else/subnet_calculator'>Subnet Calculator</a>.",
|
||||||
|
"about": "Rust is a remarkable programming language that combines the best of both high-level and low-level programming. It prioritizes safety and efficiency through strict memory management and concurrent programming features, all while maintaining clean and readable code. The active Rust community makes it a valuable choice for building reliable software."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skill": "C++",
|
||||||
|
"logo": "devicon-cplusplus-plain",
|
||||||
|
"link": "https://cplusplus.com/",
|
||||||
|
"usage": "Opposing Rust, C++, has been my primary language since joining Thales in late 2022. I have predominantly been working on a distributed simulation system that relies upon <a href='https://en.wikipedia.org/wiki/Inter-process_communication'>IPC</a> through the use of internal tools to orchestrate itself and the platform's sessions. I've had a lot of experience using both <a href='https://www.qt.io'>QT</a>, as well as the lesser known <a href='https://github.com/ocornut/imgui'>ImGui</a>, both of which have given me the platform to build readily deployable apps to customers at Thales UK.",
|
||||||
|
"about": "C++, a versatile and powerful programming language, has stood the test of time as a cornerstone of software development. Its combination of high-level abstractions and low-level control allows developers to tackle a wide range of projects efficiently. With a rich standard library and a massive ecosystem of libraries and frameworks; C++ empowers programmers to create efficient and scalable software solutions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skill": "Git",
|
||||||
|
"logo": "devicon-git-plain",
|
||||||
|
"link": "https://git-scm.com",
|
||||||
|
"usage": "With git being pretty much the 'Defaqto standard' when it comes to version control systems, I have had a lot of experience with its use, including more advanced features such as <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Branching / Merging'</a>, <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Hooks'</a>, <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Stashing'</a> etc... I've further setup my own <a href='https://git.luke-else.co.uk/luke-else/'>services</a> for hosting remote Git reporitories to allow for complete access and control over my work. Appending to this, I have setup my own form of actions to aid in the actioning of <a href='https://www.redhat.com/en/topics/devops/what-is-ci-cd'>CI/CD</a> on my own projects.",
|
||||||
|
"about": "Git, a fundamental tool in version control, streamlines collaboration and code management. Its simplicity and robustness empower developers to track changes, collaborate seamlessly, and maintain code efficiently. With Git, managing and tracking code changes becomes a breeze, making it an essential tool for software development."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skill": "Docker",
|
||||||
|
"logo": "devicon-docker-plain",
|
||||||
|
"link": "https://docker.com",
|
||||||
|
"usage": "With docker being so versatile when it comes to deploying software to end users, I decided to pick it up as a skill in order to improve the quality of applications and services I can offer to individuals. I use docker alongside its child, 'docker-compose' to control the <a href='https://git.luke-else.co.uk/luke-else/server'>provisioning of containers</a> on my main home-lab server which delivers content like <a href='https://git.luke-else.co.uk/luke-else/luke-else.co.uk'>this website</a> as well as database and <a href='https://git.luke-else.co.uk/luke-else/'>remote git services</a>.",
|
||||||
|
"about": "Docker, a transformative containerization platform, simplifies application deployment and management. It encapsulates applications and their dependencies in lightweight containers, providing consistency across different environments. Docker's user-friendly approach makes it accessible to developers and DevOps professionals, streamlining the software development and deployment process. Its efficiency, scalability, and support for microservices architecture have made it a go-to tool for creating, deploying, and scaling applications. Docker has revolutionized the way we think about software packaging and distribution, making it an essential component of modern software development workflows."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skill": "Svelte",
|
||||||
|
"logo": "devicon-svelte-plain",
|
||||||
|
"link": "https://svelte.dev",
|
||||||
|
"usage": "Svelte is a front and backend web framework that allows for stylised components to be reused in an efficient manor. This <a href='https://git.luke-else.co.uk/luke-else/luke-else.co.uk'>website</a> was made using svelte and has given me a great opportunity to learn about Svelte's power and usage. I further want to expand this in the future by using <a href='https://github.com/tauri-apps/tauri'>Tauri</a> to create rendered web application in containers that can be deployed as desktop apps.",
|
||||||
|
"about": "Svelte is an impressive web framework that stands out in the world of front-end development. It's known for its unique approach, compiling components to highly optimized vanilla JavaScript, resulting in blazing-fast web applications. Svelte's simplicity and declarative syntax make it easy to learn and use, and it encourages efficient, maintainable code. "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": "Hello! I'm an enthusiastic, dedicated software engineer passionate about backend development, networking, and embedded systems. I am currently employed at <a href='https://www.thalesgroup.com/en'>Thales UK</a> and thrive on architecting robust backend solutions, optimizing data transmission, and crafting efficient embedded software. I love tackling complex challenges, collaborating with fellow professionals, and staying up-to-date with tech trends such as my current venture in learning <a href='https://rust-lang.org'>Rust-Lang</a>.",
|
||||||
|
"timeline" : [
|
||||||
|
{
|
||||||
|
"duration" : "September 2022 - Present",
|
||||||
|
"title" : "Thales UK - Software Engineer",
|
||||||
|
"description" : "As a software engineering apprentice at Thales UK, I find myself partaking in agile / scrum development methodologies in a strong team of 6 other engineers. The team iterates on a pre-existing system designed for the MOD, written in C++, using internal frameworks to assist. <br /><br /> To extend this, the apprenticeship includes allocated time for studying a Digital and Thechnology Solutions degree with the University of Warwick, including modules relevant to business management, devlopment processes and data integrity etc..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duration" : "September 2020 - July 2022",
|
||||||
|
"title" : "The Norton Knatchbull School (A-Levels)",
|
||||||
|
"description" : "Computer Science (<b>A*</b>) <br /> Mathematics (<b>A</b>) <br /> Physics (<b>A</b>)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"duration" : "September 2015 - July 2020",
|
||||||
|
"title" : "The Norton Knatchbull School (GCSEs)",
|
||||||
|
"description" : "FSMQ (C) <br /> Maths (8) <br /> Geography (<b>9</b>) <br /> Biology (<b>9</b>) <br /> Chemistry (<b>9</b>) <br /> Physics (<b>9</b>) <br /> Spanish (7) <br /> English (Literature & Language) (7, 7) <br /> Computer Science (<b>9</b>)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Before Width: | Height: | Size: 425 KiB After Width: | Height: | Size: 425 KiB |
18
static/themes/dark.css
Normal file
18
static/themes/dark.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #282c34;
|
||||||
|
--bg-secondary: #474d57;
|
||||||
|
--bg-grad-1: #484e58;
|
||||||
|
--bg-grad-2: #4e5560;
|
||||||
|
--bg-grad-3: #59616d;
|
||||||
|
--bg-grad-4: #606a7b;
|
||||||
|
--bg-grad-5: #606978;
|
||||||
|
--input: #4e5560;
|
||||||
|
--fg: #ABB2BF;
|
||||||
|
--header: #E06C75;
|
||||||
|
--link: #98C379;
|
||||||
|
--hover: #56B6C2;
|
||||||
|
--glow: #C678DD;
|
||||||
|
|
||||||
|
--green: #98C379;
|
||||||
|
--red: #E06C75;
|
||||||
|
}
|
18
static/themes/light.css
Normal file
18
static/themes/light.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #fff;
|
||||||
|
--bg-secondary: #ebebeb;
|
||||||
|
--bg-grad-1: #c1c1c1;
|
||||||
|
--bg-grad-2: #a1a1a1;
|
||||||
|
--bg-grad-3: #858585;
|
||||||
|
--bg-grad-4: #616161;
|
||||||
|
--bg-grad-5: #484848;
|
||||||
|
--input: #a9a9a9;
|
||||||
|
--fg: #2f2f2f;
|
||||||
|
--header: #514a4a;
|
||||||
|
--link: #df0000;
|
||||||
|
--hover: #4f4b489b;
|
||||||
|
--glow: #545454;
|
||||||
|
|
||||||
|
--green: #98C379;
|
||||||
|
--red: #E06C75;
|
||||||
|
}
|
19
svelte.config.js
Normal file
19
svelte.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// import adapter from '@sveltejs/adapter-auto';
|
||||||
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user