diff --git a/.development b/.development new file mode 100644 index 0000000..c35624c --- /dev/null +++ b/.development @@ -0,0 +1,19 @@ +# General +NODE_ENV=development +URARA_SITE_DOMAIN=localhost +URARA_SITE_PROTOCOL=http:// +PUBLIC_SITE_URL=localhost:5173 + +# Sentry +PUBLIC_SENTRY_ORG=mattmor +PUBLIC_SENTRY_PROJECT=itspersonal +PUBLIC_SENTRY_KEY=cc0a2e656e0cbbcade519f24627044df +PUBLIC_SENTRY_PROJECT_ID=4506781187899392 +PUBLIC_SENTRY_ORG_ID=o4505828687478784 +SENTRY_AUTH_TOKEN="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" +SENTRT_LOG_LEVEL=debug + +# Cloudinary +PUBLIC_CLOUDINARY_NAME=dbex8wss6 +CLOUDINARY_API_KEY=998888477457534 +CLOUDINARY_API_SECRET=E0-FptJ1rFiyxe9Z9F6SEr9CJns diff --git a/.gitea/workflows/vercel-preview.yaml b/.gitea/workflows/vercel-preview.yaml new file mode 100644 index 0000000..f75c963 --- /dev/null +++ b/.gitea/workflows/vercel-preview.yaml @@ -0,0 +1,21 @@ +name: Vercel Preview Deployment +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +on: + push: + branches-ignore: + - main +jobs: + Deploy-Preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Vercel CLI + run: npm install --global vercel@latest + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + - name: Build Project Artifacts + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + - name: Deploy Project Artifacts to Vercel + run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} diff --git a/src/cspDirectives.ts b/src/cspDirectives.ts index e8cada4..c203208 100644 --- a/src/cspDirectives.ts +++ b/src/cspDirectives.ts @@ -1,15 +1,6 @@ // https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73, Rodney Lab - -import { - URARA_SITE_DOMAIN, - PUBLIC_SENTRY_KEY, - PUBLIC_SENTRY_PROJECT_ID, - PUBLIC_SENTRY_ORG_ID, - PUBLIC_WORKER_URL, - URARA_SITE_PROTOCOL -} from '$env/static/public' - -console.log(`${URARA_SITE_PROTOCOL}${URARA_SITE_DOMAIN}`) +import { site } from '$lib/config/site' +import { PUBLIC_SENTRY_KEY, PUBLIC_SENTRY_PROJECT_ID, PUBLIC_SENTRY_ORG_ID } from '$env/static/public' const directives = { 'base-uri': ["'self'"], @@ -21,10 +12,9 @@ const directives = { 'https://hcaptcha.com', 'https://*.hcaptcha.com', 'https://*.cartocdn.com', - URARA_SITE_DOMAIN, - PUBLIC_WORKER_URL + 'https://*.mattmor.in/**' ], - 'img-src': ["'self'", 'data:', 'https://images.unsplash.com', `${URARA_SITE_PROTOCOL}${URARA_SITE_DOMAIN}`], + 'img-src': ["'self'", 'data:', 'https://images.unsplash.com', `${site.protocol}${site.domain}`], 'font-src': ["'self'", 'data:'], 'form-action': ["'self'"], 'frame-ancestors': ["'self'"], @@ -45,8 +35,8 @@ const directives = { 'style-src': ["'self'", "'unsafe-inline'", 'https://hcaptcha.com', 'https://*.hcaptcha.com'], 'default-src': [ "'self'", - URARA_SITE_DOMAIN, - `ws://${URARA_SITE_DOMAIN}`, + site.domain, + `ws://${site.domain}`, // 'https://*.google.com', // 'https://*.googleapis.com', // 'https://*.firebase.com', diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f8e8112..43984ad 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -4,7 +4,7 @@ import { site } from '$lib/config/site' import { handleErrorWithSentry, sentryHandle } from '@sentry/sveltekit' import * as Sentry from '@sentry/sveltekit' -import { PUBLIC_SENTRY_KEY, PUBLIC_SENTRY_PROJECT_ID, PUBLIC_SENTRY_ORG_ID, URARA_SITE_DOMAIN } from '$env/static/public' +import { PUBLIC_SENTRY_KEY, PUBLIC_SENTRY_PROJECT_ID, PUBLIC_SENTRY_ORG_ID } from '$env/static/public' import { csp } from './cspDirectives' @@ -23,7 +23,7 @@ export const cspHandle: Handle = async ({ event, resolve }) => { const headers = { 'X-Frame-Options': 'SAMEORIGIN', 'Referrer-Policy': 'no-referrer', - 'Permissions-Policy': `accelerometer=(), autoplay=(), camera=(), document-domain=(self, 'js-profiling'), encrypted-media=(), fullscreen=(self ${URARA_SITE_DOMAIN}), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()`, + 'Permissions-Policy': `accelerometer=(), autoplay=(), camera=(), document-domain=(self, 'js-profiling'), encrypted-media=(), fullscreen=(self ${site.domain}), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()`, 'X-Content-Type-Options': 'nosniff', // 'Content-Security-Policy-Report-Only': csp, 'Content-Security-Policy': csp, diff --git a/src/lib/components/main/MainFooter.svelte b/src/lib/components/main/MainFooter.svelte index 3bcab82..5c3da6f 100644 --- a/src/lib/components/main/MainFooter.svelte +++ b/src/lib/components/main/MainFooter.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/components/main/ObfuscatedEmail.svelte b/src/lib/components/main/ObfuscatedEmail.svelte deleted file mode 100644 index b210cc6..0000000 --- a/src/lib/components/main/ObfuscatedEmail.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -
- - Send me a mail! - - - - - - - - - - - -
- - diff --git a/src/lib/config/blog/first-post.md b/src/lib/config/blog/first-post.md new file mode 100644 index 0000000..c8d07a2 --- /dev/null +++ b/src/lib/config/blog/first-post.md @@ -0,0 +1,32 @@ +--- +title: First post +excerpt: First post +date: 2021-01-01 +tags: + - first + - post +published: true +image: Feature.jpg +--- +## Svelte + +Media inside the **Svelte** folder is served from the `static` folder. + +```python + +input_text = ''' "yahooapis.com", + "hotmail.com", + "gfx.ms", + "afx.ms", + "live.com", +''' +# and so on... + +lines = input_text.split('\n') + +formatted_lines = ['* ' + line.strip()[1:-2] + ' * block' for line in lines if line] + +output_text = '\n'.join(formatted_lines) +print(output_text) + +``` diff --git a/src/lib/config/blog/postwa.md b/src/lib/config/blog/postwa.md new file mode 100644 index 0000000..f9e4fa3 --- /dev/null +++ b/src/lib/config/blog/postwa.md @@ -0,0 +1,12 @@ +--- +title: w post +excerpt: w post +date: 2021-01-01 +tags: + - first + - post +published: true +image: Feature.jpg +--- + +## Svelte \ No newline at end of file diff --git a/src/lib/config/blog/second-post.md b/src/lib/config/blog/second-post.md new file mode 100644 index 0000000..930f194 --- /dev/null +++ b/src/lib/config/blog/second-post.md @@ -0,0 +1,27 @@ +--- +title: Second post +excerpt: Second post +date: 2023-01-01 +tags: + - first + - post +published: true +image: Feature.jpg + +--- + + +## Switching to Self-Hosted Git + +You can check it out on git.mattmor.in, it's a simple encrypted gitea instance running on AWS + +### Media + +Media inside the **Svelte** folder is server from the `static` folder. + +### Bye Bye Github with your fancy features + +I am ditching the societal value of having a contributions table on my profile, you should view it on git.mattmor.in + + +--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore. \ No newline at end of file diff --git a/src/lib/config/projects/erant.md b/src/lib/config/projects/erant.md new file mode 100644 index 0000000..7a5e887 --- /dev/null +++ b/src/lib/config/projects/erant.md @@ -0,0 +1,15 @@ +--- +title: Erant +excerpt: A SaaS helping SMEs in the tourism sector with virtualization, customer experience & analytics. It got into the republic finale of Soutěž & Podnikej. +date: 2021-01-01 +published: true +image: Feature.jpg +--- + +## Svelte + +Media inside the **Svelte** folder is served from the `static` folder. + +```python + +``` diff --git a/src/lib/config/projects/seedling.md b/src/lib/config/projects/seedling.md new file mode 100644 index 0000000..a18b88c --- /dev/null +++ b/src/lib/config/projects/seedling.md @@ -0,0 +1,27 @@ +--- +title: Seedling +excerpt: An Iot Project, where we built a sensor system for plant care and accompanying app that alerted the user to what their plant needs. We made PCBs, industrial designs, 3D printed cases as clueless students. +date: 2021-01-01 +published: true +image: Feature.jpg +--- + +This is a recollection of my first "startup" journey. + +In 2020, after the onset of Covid, I remembered a presentation I heard from the founder of "Soutěž & Podnikej" Martin Vítek, on a meeting of the Prague Highschool Assembly. +It was an invitation to join their great program guiding highschoolers to launch an idea to financial fruition. The program, now sadly no longer operating, was inspirational to me as entrepreneurship and tech startups for me was a way to bring about a revolution, to solve a problem. +The journey that I embarked on, however, was not something I thought I signed up for. +I have not yet till that point in my life understood the deep rabbit hole of truly understanding a problem, the markets and people's needs, technical difficulties and the state of current technological developments, having the methodologies, networks of contacts and partners and capital at my disposal. + +However what the program gave me was at least a brief outline of where to begin and how to progress. I began with a problem. As I was engaged in discussions and student organizations centered on ecology, especially thanks to being a part of many MUN and EYP conferences, I remembered a paper of the UN FAO called [2050: A third more mouths to feed](https://www.fao.org/newsroom/detail/2050-A-third-more-mouths-to-feed/), basically amongst the array of possibilities - water scarcity, food demand, population will increase + + +[Ben Einsteins LinkedIn post](https://www.linkedin.com/pulse/heres-why-juiceros-press-so-expensive-ben-einstein/) sums up the problem of Hardware startups + +## Svelte + +Media inside the **Svelte** folder is served from the `static` folder. + +```python + +``` diff --git a/src/lib/utils/helpers.ts b/src/lib/utils/helpers.ts new file mode 100644 index 0000000..ba760fe --- /dev/null +++ b/src/lib/utils/helpers.ts @@ -0,0 +1,163 @@ +import { readable } from 'svelte/store'; +import type { create_ssr_component } from 'svelte/internal'; + +/** + * Parses an object of data that has string keys and values of type `T` that have an optional `date` property. + * @param data - The object of data to parse. + * @param dateSort - A function that sorts an array of objects by their `date` property in descending order. + * @param mdPathToSlug - A function that converts an md file path to a slug. + * @returns An array of objects that have a `slug` property and the properties of the original data objects. + */ +export function parseReadContent(data: Record): T[] { + return Object.entries(data) + .map(([file, data]) => ({ + slug: mdPathToSlug(file), + ...data + })) + .sort(dateSort); +} + +/** + * Sorts an array of objects by their `date` property in descending order. + * @param a - The first object to compare. + * @param b - The second object to compare. + * @returns A number that represents the difference between the parsed dates of the two objects. + */ +export function dateSort(a: T, b: T): number { + return Date.parse(b.date) - Date.parse(a.date); +} + +/** + * Renders an mdsvex component and returns the resulting HTML string. + * @param component - The mdsvex component to render. + * @returns The HTML string that was generated by rendering the component. + * @throws An error if the `render` property of the component is not a function. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function renderMdsvexComponent(component: any): string { + if (typeof component['render'] != 'function') { + throw new Error("Unable to render something that isn't a mdsvex component"); + } + + return (component as ReturnType).render().html; +} + +/** + * Converts an md file path to a slug by removing the last segment of the path and the `.md` extension. + * @param path - The path of the md file. + * @returns The slug of the md file. + */ +export function mdPathToSlug(path: string) { + return path.split('/').at(-1).slice(0, -3); +} + +/** + * Determines if the current timezone is between 0 and +3 hours UTC. + * @returns {boolean} True if the timezone is between 0 and +3 hours UTC, false otherwise. + */ +export const isEurope = () => { + const offset = new Date().getTimezoneOffset(); + return offset <= 0 && offset >= -180; // Returns true if the timezone is between 0 and +3 hours UTC, false otherwise +}; +/** + * Takes a string and returns a beautified version of it. + * @param str The input string to be beautified. + * @returns The beautified string. + */ +export const stringToBeautifiedFragment = (str = '') => + (str || '').toLocaleLowerCase().replace(/\s/g, '-').replace(/\?/g, '').replace(/,/g, ''); +/** + * Toggles the 'overflow-y-hidden' class on the 'html' element of the document. + * @param bool A boolean value indicating whether to show or hide the overflow-y scrollbar. + */ +export const showHideOverflowY = (bool: boolean) => { + const html = document.querySelector('html'); + if (html) { + if (bool) { + html.classList.add('overflow-y-hidden'); + } else { + html.classList.remove('overflow-y-hidden'); + } + } +}; + +/** + * Scrolls to the first element that matches the given selector within the provided element. + * @param element The element to search within. + * @param selector The selector to match against. + */ +export const scrollToElement = async (element: HTMLElement, selector: string) => { + const firstElement: HTMLElement | null = element.querySelector(selector); + if (!firstElement) { + return; + } + firstElement.scrollIntoView({ + behavior: 'smooth' + }); +}; + +/** + * Checks if a given URL is an external link. + * @param href - The URL to check. + * @returns True if the URL is an external link, false otherwise. + */ +export const isAnExternalLink = (href: string) => href.startsWith('http'); + +/** + * Checks if the user agent is running on a Mac or iPad. + * @returns {boolean} Returns true if the user agent is running on a Mac or iPad, false otherwise. + */ +export const isMac = () => + navigator.userAgent.includes('Macintosh') || navigator.userAgent.includes('iPad'); + +/** + * Removes the trailing slash from a given string. + * @param site - The string to remove the trailing slash from. + * @returns The string without the trailing slash. + */ +export const removeTrailingSlash = (site: string) => { + return site.replace(/\/$/, ''); +}; + +/** + * Returns a readable store that tracks whether the media query string matches the current viewport. + * @param mediaQueryString - The media query string to match against the viewport. + * @returns A readable store that tracks whether the media query string matches the current viewport. + */ +export const useMediaQuery = (mediaQueryString: string) => { + const matches = readable(undefined, (set) => { + if (typeof globalThis['window'] === 'undefined') return; + + const match = window.matchMedia(mediaQueryString); + set(match.matches); + const element = (event: MediaQueryListEvent) => set(event.matches); + match.addEventListener('change', element); + return () => { + match.removeEventListener('change', element); + }; + }); + return matches; +}; + +/** + * Scrolls the page to the nearest element matching the given selector. + * @param selector - The CSS selector of the element to scroll to. + */ +export const scrollIntoView = (selector: string) => { + const scrollToElement = document.querySelector(selector); + + if (!scrollToElement) return; + + const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); + + scrollToElement.scrollIntoView({ + block: 'nearest', + inline: 'start', + behavior: mediaQuery.matches ? 'auto' : 'smooth' + }); +}; + +export const generateURL = (href?: string, type: string, slug?: string) => { + if (href) return href; + return `/${type}/${slug}`; +}; diff --git a/src/lib/utils/images.ts b/src/lib/utils/images.ts new file mode 100644 index 0000000..37a1bf1 --- /dev/null +++ b/src/lib/utils/images.ts @@ -0,0 +1,33 @@ +import { v2 as cloudinary } from 'cloudinary'; +import { PUBLIC_CLOUDINARY_NAME } from '$env/static/public'; +import { CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } from '$env/static/private'; + +cloudinary.config({ + cloud_name: PUBLIC_CLOUDINARY_NAME, + api_key: CLOUDINARY_API_KEY, + api_secret: CLOUDINARY_API_SECRET +}); + +type ImageTransformationOptions = { + width?: number; + height?: number; + crop?: string; + format?: string; + quality?: string | number; + [key: string]: string; +}; + +export function getImageUrl(publicId: string, options: ImageTransformationOptions = {}): string { + return cloudinary.url(publicId, { + ...options, + crop: options.crop || 'fill', + format: options.format || 'auto', + quality: options.quality || 'auto' + }); +} + +export const getImagePublicId = (imageKey: string) => { + return images[imageKey] || ''; +}; + +export default cloudinary;