setting and importing blog scripts and imgs
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
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.
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: Second post
|
||||||
|
excerpt: Second post
|
||||||
|
date: 2023-01-01
|
||||||
|
tags:
|
||||||
|
- first
|
||||||
|
- post
|
||||||
|
published: true
|
||||||
|
image: Feature.jpg
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Svelte
|
||||||
|
|
||||||
|
Media inside the **Svelte** folder is server from the `static` folder.
|
|
@ -0,0 +1,16 @@
|
||||||
|
export interface MarkdownHeading {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
level: number;
|
||||||
|
children: MarkdownHeading[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownMetadata {
|
||||||
|
headings: MarkdownHeading[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MdsvexImport<T extends MarkdownMetadata = MarkdownMetadata> {
|
||||||
|
// Technically not correct but needed to make language-tools happy
|
||||||
|
default: ConstructorOfATypedSvelteComponent;
|
||||||
|
metadata: T;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { dev } from '$app/environment';
|
||||||
|
|
||||||
|
export const title = "Matt's Matrix";
|
||||||
|
export const description = 'A blog about SvelteKit, Svelte, and other things.';
|
||||||
|
export const url = dev ? 'http://localhost:5174' : 'https://mattmor.in';
|
||||||
|
export const author = 'Matt Morin';
|
||||||
|
|
||||||
|
export const email = 'mailto:matt.b.morin@pm.me';
|
||||||
|
export const LinkedIn = 'https://www.linkedin.com/in/matthieu-morin/';
|
||||||
|
export const github = 'https://github.com/matthieu42morin';
|
|
@ -0,0 +1,12 @@
|
||||||
|
const skills = {
|
||||||
|
programming: ['Svelte', 'TypeScript', 'React.js', 'Svelte.js', 'Go', 'Electron'],
|
||||||
|
Devops: [],
|
||||||
|
Linux: ['', ''],
|
||||||
|
IoT: ['Raspberry Pi', 'Arduino', 'ESP32', 'ESP8266', 'lots of sensors','MQTT', 'LoRa', 'BLE', 'NFC', 'RFID', 'WiFi', ],
|
||||||
|
databases: ['MongoDB', 'PostgreSQL'],
|
||||||
|
Tools: [],
|
||||||
|
languages: ['Italian', 'English'],
|
||||||
|
other: ['Figma', 'UI design']
|
||||||
|
};
|
||||||
|
|
||||||
|
export default skills;
|
|
@ -0,0 +1,16 @@
|
||||||
|
export interface MarkdownHeading {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
level: number;
|
||||||
|
children: MarkdownHeading[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownMetadata {
|
||||||
|
headings: MarkdownHeading[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MdsvexImport<T extends MarkdownMetadata = MarkdownMetadata> {
|
||||||
|
// Technically not correct but needed to make language-tools happy
|
||||||
|
default: ConstructorOfATypedSvelteComponent;
|
||||||
|
metadata: T;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import type { MarkdownMetadata } from '$lib/contents/types';
|
||||||
|
|
||||||
|
export type BlogTag = 'Projects' | 'Ideas' | 'Updates' | '';
|
||||||
|
|
||||||
|
export interface BlogPost extends MarkdownMetadata {
|
||||||
|
date?: string;
|
||||||
|
excerpt: string;
|
||||||
|
image: string;
|
||||||
|
slug?: string;
|
||||||
|
href?: string;
|
||||||
|
tags?: BlogTag[];
|
||||||
|
subtitle?: string;
|
||||||
|
title: string;
|
||||||
|
published: boolean;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export type categories = 'sveltekit' | 'svelte';
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { toString } from 'mdast-util-to-string';
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
export default () => (tree, vFile) => {
|
||||||
|
let headers = [];
|
||||||
|
visit(tree, (node) => {
|
||||||
|
if (node.type === 'heading') {
|
||||||
|
if (node.depth !== 1) {
|
||||||
|
const sanitizedString = toString(node).replace(
|
||||||
|
/{(.*?)}/,
|
||||||
|
(_, token) => {
|
||||||
|
return vFile.data.fm[token] || token;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
headers.push({
|
||||||
|
title: sanitizedString,
|
||||||
|
level: node.depth,
|
||||||
|
slug: node.data.id,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stack = [];
|
||||||
|
const sortedHeaders = [];
|
||||||
|
|
||||||
|
//const sort Header to tree
|
||||||
|
const sortHeader = (header) => {
|
||||||
|
while (stack.length !== 0 && header.level <= stack[0].level) {
|
||||||
|
stack.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.length === 0) {
|
||||||
|
sortedHeaders.push(header);
|
||||||
|
stack.push(header);
|
||||||
|
} else {
|
||||||
|
(stack[0].children ??= []).push(header);
|
||||||
|
stack.unshift(header);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
headers.forEach((_, index) => {
|
||||||
|
const header = headers[index];
|
||||||
|
sortHeader(header);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!vFile.data.fm) vFile.data.fm = {};
|
||||||
|
vFile.data.fm.headings = sortedHeaders;
|
||||||
|
return tree;
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
import Prism from 'prismjs';
|
||||||
|
import 'prismjs/components/prism-docker.min.js';
|
||||||
|
import 'prismjs/components/prism-bash.min.js';
|
||||||
|
import 'prismjs/components/prism-yaml.min.js';
|
||||||
|
import 'prismjs/components/prism-javascript.min.js';
|
||||||
|
import 'prismjs/components/prism-json.min.js';
|
||||||
|
import 'prismjs/components/prism-markdown.min.js';
|
||||||
|
import 'prismjs/components/prism-sql.min.js';
|
||||||
|
import 'prismjs/components/prism-toml.min.js';
|
||||||
|
import 'prismjs/components/prism-promql.min.js';
|
||||||
|
import 'prismjs/components/prism-go.min.js';
|
||||||
|
import 'prismjs/components/prism-typescript.min.js';
|
||||||
|
import 'prismjs/components/prism-python.min.js';
|
||||||
|
import { escapeSvelte } from 'mdsvex';
|
||||||
|
|
||||||
|
const langMap = {
|
||||||
|
sh: 'bash',
|
||||||
|
Dockerfile: 'dockerfile',
|
||||||
|
YAML: 'yaml',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} code the code that gets parsed
|
||||||
|
* @param {string} lang the language the code is written in
|
||||||
|
* @param {string} meta meta information for the code fence
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function highlightCode(code, lang, meta) {
|
||||||
|
let title = null;
|
||||||
|
const _lang = langMap[lang] || lang || '';
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
title = meta.match(/title="?(.*?)"/)?.[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlighted = _lang
|
||||||
|
? escapeSvelte(Prism.highlight(code, Prism.languages[_lang], _lang))
|
||||||
|
: code;
|
||||||
|
return `<CodeFence code={${JSON.stringify(highlighted)}}
|
||||||
|
rawCode={${JSON.stringify(code)}}
|
||||||
|
lang={"${_lang}"}
|
||||||
|
${title ? `title={"${title}"}` : ''}
|
||||||
|
/>`;
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Credit goes to @Xananax for providing this solution within the gist https://gist.github.com/Xananax/5dca3a1dd7070e4fdebe2927e4aeb55b
|
||||||
|
*/
|
||||||
|
import { join, basename, extname } from 'path';
|
||||||
|
|
||||||
|
export const defaults = {
|
||||||
|
extensions: ['.svelte.md', '.md', '.svx'],
|
||||||
|
dir: `$lib`,
|
||||||
|
list: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects global imports in all your mdsvex files
|
||||||
|
* Specify:
|
||||||
|
* - the root dir (defaults to `src/lib`)
|
||||||
|
* - the array list of components (with extension), like `['Component.svelte']`
|
||||||
|
* - the valid extensions list as an array (defaults to `['.svelte.md', '.md', '.svx']`)
|
||||||
|
*
|
||||||
|
* If you want the component name to be different from the file name, you can specify an array
|
||||||
|
* of arrays: `['Component.svelte', ['Another', 'AnotherComp.svelte'], 'ThirdComp.svelte']`
|
||||||
|
*
|
||||||
|
* @param {Object} options options described above
|
||||||
|
* @returns a preprocessor suitable to plug into the `preprocess` key of the svelte config
|
||||||
|
*/
|
||||||
|
export const mdsvexGlobalComponents = (options = {}) => {
|
||||||
|
const { extensions, dir, list } = { ...defaults, ...options };
|
||||||
|
const extensionsRegex = new RegExp(
|
||||||
|
'(' + extensions.join('|').replace(/\./g, '\\.') + ')$',
|
||||||
|
'i',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!list || !list.length || !Array.isArray(list)) {
|
||||||
|
throw new Error(
|
||||||
|
`"list" option must be an array and contain at least one element`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imports = list
|
||||||
|
.map((entry) => {
|
||||||
|
let name = '';
|
||||||
|
if (Array.isArray(entry)) {
|
||||||
|
name = entry[0];
|
||||||
|
entry = entry[1];
|
||||||
|
}
|
||||||
|
const ext = extname(entry);
|
||||||
|
const path = join(dir, entry);
|
||||||
|
name = name || basename(entry, ext);
|
||||||
|
return `\nimport ${name} from "${path}"`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const preprocessor = {
|
||||||
|
script(thing) {
|
||||||
|
const { content, filename, attributes, markup } = thing;
|
||||||
|
if (!filename.match(extensionsRegex)) {
|
||||||
|
return { code: content };
|
||||||
|
}
|
||||||
|
const hasModuleContext = /^<script context="module">/.test(markup);
|
||||||
|
|
||||||
|
const isModulePass = attributes?.context === 'module';
|
||||||
|
if (!isModulePass || !hasModuleContext) {
|
||||||
|
return { code: content };
|
||||||
|
}
|
||||||
|
const isValidPass =
|
||||||
|
(hasModuleContext && isModulePass) || !hasModuleContext;
|
||||||
|
if (!isValidPass) {
|
||||||
|
return { code: content };
|
||||||
|
}
|
||||||
|
return { code: `${imports}\n${content}` };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return preprocessor;
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
const youtubeEmbedRegex = new RegExp(`\(youtube\):\(\.\*\)`, 'i');
|
||||||
|
|
||||||
|
const embedVideoHtml = (videoId, options) => `
|
||||||
|
<span class="relative mt-medium not-prose block overflow-hidden max-w-full after:block after:pt-[56.26%]"><iframe
|
||||||
|
title="${options.title ? options.title : 'Youtube Video'}"
|
||||||
|
width="${options.width}"
|
||||||
|
height="${options.height}"
|
||||||
|
src="https://www.youtube-nocookie.com/embed/${videoId}?rel=0"
|
||||||
|
class="absolute top-0 left-0 w-full h-full"
|
||||||
|
${options.noIframeBorder ? 'style="border:0"' : ''}
|
||||||
|
allowfullscreen
|
||||||
|
sandbox="allow-same-origin allow-scripts allow-popups">
|
||||||
|
</iframe></span>`;
|
||||||
|
|
||||||
|
const visitor = (options) => (node) => {
|
||||||
|
if (node.type === 'inlineCode') {
|
||||||
|
const regexResult = node.value.match(youtubeEmbedRegex);
|
||||||
|
if (regexResult) {
|
||||||
|
node.type = 'html';
|
||||||
|
node.value = embedVideoHtml(regexResult[2].trim(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (options) => (tree) => {
|
||||||
|
visit(tree, visitor(options));
|
||||||
|
return tree;
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import regexCreator from 'emoji-regex';
|
||||||
|
const emojiRegex = regexCreator();
|
||||||
|
|
||||||
|
const nonAlphanumericsAtTheBeginningRegex = /^\W+/g;
|
||||||
|
const nonAlphanumericsAtTheEndRegex = /\W+$/g;
|
||||||
|
|
||||||
|
const beautifyFragment = (str = '') =>
|
||||||
|
str
|
||||||
|
.replace(emojiRegex, '')
|
||||||
|
.replace(nonAlphanumericsAtTheBeginningRegex, '')
|
||||||
|
.replace(nonAlphanumericsAtTheEndRegex, '');
|
||||||
|
|
||||||
|
const visitor = (node) => {
|
||||||
|
node.data = node.data || {};
|
||||||
|
node.data.hProperties = node.data.hProperties || {};
|
||||||
|
|
||||||
|
if (node.type === 'heading') {
|
||||||
|
let fragment = node.data.id;
|
||||||
|
fragment = beautifyFragment(fragment);
|
||||||
|
|
||||||
|
const lastChildrenIdx = node.children.length - 1;
|
||||||
|
const headingPermalink = node.children[lastChildrenIdx];
|
||||||
|
headingPermalink.url = `#${fragment}`;
|
||||||
|
|
||||||
|
node.data.hProperties.id = fragment;
|
||||||
|
node.data.id = fragment;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => async (tree) => {
|
||||||
|
visit(tree, visitor);
|
||||||
|
return tree;
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
const visitor = (node) => {
|
||||||
|
node.data = node.data || {};
|
||||||
|
node.data.hProperties = node.data.hProperties || {};
|
||||||
|
if (node.type === 'link') {
|
||||||
|
if (
|
||||||
|
node.children &&
|
||||||
|
node.children.length &&
|
||||||
|
node.children.length === 1
|
||||||
|
) {
|
||||||
|
if (node.children[0].type === 'image') {
|
||||||
|
node.data.hProperties.class = 'after:hidden';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => async (tree) => {
|
||||||
|
visit(tree, visitor);
|
||||||
|
return tree;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
|
||||||
|
const imagesRelativeUrlPattern = '/images/';
|
||||||
|
|
||||||
|
const visitor = (node) => {
|
||||||
|
if (
|
||||||
|
node.type === 'image' &&
|
||||||
|
node.url.indexOf(imagesRelativeUrlPattern) > 0
|
||||||
|
) {
|
||||||
|
node.url = node.url.substring(
|
||||||
|
node.url.indexOf(imagesRelativeUrlPattern) + ''.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => async (tree, vFile) => {
|
||||||
|
if (
|
||||||
|
vFile.filename.indexOf('src/routes/docs/') > 0 ||
|
||||||
|
vFile.filename.indexOf('src/routes/blog/') > 0 ||
|
||||||
|
vFile.filename.indexOf('src/routes/guides/') > 0 ||
|
||||||
|
vFile.filename.indexOf('src/routes/customers/') > 0
|
||||||
|
) {
|
||||||
|
visit(tree, visitor);
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
};
|
After Width: | Height: | Size: 555 KiB |
After Width: | Height: | Size: 950 KiB |
After Width: | Height: | Size: 712 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 4.7 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="98" height="96" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 980 B |
After Width: | Height: | Size: 6.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
|
After Width: | Height: | Size: 963 B |