styles; inkify
This commit is contained in:
parent
d5da4b58a7
commit
064da49acd
|
@ -2,21 +2,17 @@ import { defineConfig } from 'astro/config';
|
|||
import icon from "astro-icon";
|
||||
import mdx from '@astrojs/mdx';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
import vue from "@astrojs/vue";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://watzon.tech',
|
||||
integrations: [
|
||||
mdx(),
|
||||
sitemap(),
|
||||
tailwind(),
|
||||
icon({
|
||||
integrations: [mdx(), sitemap(), tailwind(), icon({
|
||||
include: {
|
||||
mdi: ['*'],
|
||||
ic: ['*'],
|
||||
ic: ['*']
|
||||
}
|
||||
}),
|
||||
]
|
||||
}), vue()]
|
||||
});
|
|
@ -15,9 +15,11 @@
|
|||
"@astrojs/rss": "^4.0.1",
|
||||
"@astrojs/sitemap": "^3.0.3",
|
||||
"@astrojs/tailwind": "^5.0.3",
|
||||
"@astrojs/vue": "^4.0.2",
|
||||
"astro": "^4.0.3",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.3.3",
|
||||
"vue": "^3.2.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/arcticons": "^1.1.85",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import HeaderLink from './HeaderLink.astro';
|
||||
import ThemeToggle from './ThemeToggle.astro';
|
||||
---
|
||||
|
||||
<header class="m-0 px-4">
|
||||
|
@ -11,7 +12,7 @@ import HeaderLink from './HeaderLink.astro';
|
|||
<HeaderLink href="/projects">Projects</HeaderLink>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<!-- Theme toggle -->
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
---
|
||||
|
||||
<button id="themeToggle">
|
||||
<Icon name="mdi:weather-sunny" class="sun w-8 h-8 text-gray-800" />
|
||||
<Icon name="mdi:weather-night" class="moon w-8 h-8 text-gray-100" />
|
||||
</button>
|
||||
|
||||
<style>
|
||||
#themeToggle .sun {
|
||||
@apply dark:hidden;
|
||||
}
|
||||
|
||||
#themeToggle .moon {
|
||||
@apply hidden dark:block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
const theme = (() => {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark';
|
||||
}
|
||||
return 'light';
|
||||
})();
|
||||
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
window.localStorage.setItem('theme', theme);
|
||||
|
||||
const handleToggleClick = () => {
|
||||
const element = document.documentElement;
|
||||
element.classList.toggle("dark");
|
||||
|
||||
const isDark = element.classList.contains("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
}
|
||||
|
||||
document.getElementById("themeToggle").addEventListener("click", handleToggleClick);
|
||||
</script>
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="w-full mb-4" v-if="imgUrl">
|
||||
<a :href="imgUrl" target="_blank">
|
||||
<img :src="imgUrl" class="w-full" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-center items-center border-4 border-dashed h-32 w-full mb-4" v-else>
|
||||
Image will appear here
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<textarea v-model="code" class="bg-gray-300 dark:bg-gray-800 w-full h-32 p-2 font-mono" placeholder="// Put some code here"></textarea>
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<select class="bg-gray-300 dark:bg-gray-800 text-black dark:text-white font-bold py-2 px-4 rounded" v-model="theme">
|
||||
<option v-for="theme in themes" :value="theme">{{ theme }}</option>
|
||||
</select>
|
||||
<button @click="inkifyRequest" class="bg-blue-500 hover:bg-blue-700 text-white text-lg py-2 px-4 rounded">
|
||||
Submit Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const code = ref<string>('')
|
||||
const theme = ref<string>('Nord')
|
||||
const imgUrl = ref<string | undefined>(undefined)
|
||||
|
||||
const windowTitle = "Inkify"
|
||||
const pad_horiz = 20;
|
||||
const pad_vert = 20;
|
||||
const font = "Monaspace Neon=32"
|
||||
|
||||
const themes = [
|
||||
"1337",
|
||||
"Coldark-Cold",
|
||||
"Coldark-Dark",
|
||||
"DarkNeon",
|
||||
"Dracula",
|
||||
"GitHub",
|
||||
"Monokai Extended",
|
||||
"Monokai Extended Bright",
|
||||
"Monokai Extended Light",
|
||||
"Monokai Extended Origin",
|
||||
"Nord",
|
||||
"OneHalfDark",
|
||||
"OneHalfLight",
|
||||
"Solarized (dark)",
|
||||
"Solarized (light)",
|
||||
"Sublime Snazzy",
|
||||
"TwoDark",
|
||||
"Visual Studio Dark+",
|
||||
"gruvbox-dark",
|
||||
"gruvbox-light",
|
||||
"zenburn"
|
||||
]
|
||||
|
||||
const inkifyRequest = () => {
|
||||
const encodedCode = encodeURIComponent(code.value)
|
||||
const endpoint = `https://inkify.0x45.st/generate?code=${encodedCode}&theme=${theme.value}&window_title=${windowTitle}&pad_horiz=${pad_horiz}&pad_vert=${pad_vert}&font=${font}`
|
||||
imgUrl.value = endpoint
|
||||
}
|
||||
</script>
|
|
@ -10,5 +10,4 @@ tags:
|
|||
- library
|
||||
heroImage: /images/cadmium-header.png
|
||||
linkOnly: true
|
||||
featured: true
|
||||
---
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
name: Inkify
|
||||
url: https://github.com/watzon/inkify
|
||||
description: Generate images of your code with this open source server
|
||||
tags:
|
||||
- project
|
||||
- rust
|
||||
- server
|
||||
- self-hosted
|
||||
featured: true
|
||||
heroImage: /images/inkify-header.webp
|
||||
---
|
||||
|
||||
import CodeToImg from '../../components/inkify/CodeToImg.vue'
|
||||
|
||||
Many developers are familiar with the likes of [Carbon](https://carbon.now.sh/) and [Codeimg](https://codeimg.io/), which allow you to generate images of your code. They are extremely useful projects, but they are also closed source, don't have APIs, and are not self-hostable. Inkify is an open source alternative that aims to solve these problems.
|
||||
|
||||
Inkify is written in Rust and makes use of a couple of other open source projects, including [Silicon](https://github.com/Aloxaf/silicon) for rendering the code images, [guesslang](https://github.com/yoeo/guesslang) for detecting the programming language, and [Actix Web](https://github.com/actix/actix-web) for the server. Without these projects, Inkify would not be possible. Silicon is really the backbone and inspiration for the whole project, but since it's mostly made to be a CLI tool it took some work to make it into a server.
|
||||
|
||||
## The API
|
||||
|
||||
Inkify has a very simple to use JSON based API, which is entirely documented with a GET request to the server root. You can see it yourself [here](https://inkify.0x45.st), where I host the official instance. The most important route is `/generate`, which takes a JSON body and returns a PNG image, though there are several other routes available including `/detect` which does some language detection on the provided code.
|
||||
|
||||
## Self-hosting
|
||||
|
||||
I host pretty much everything I build with Docker, and Inkify is no exception. As such there is a ready-to-go Docker container available at [https://hub.docker.com/r/watzon/inkify](https://hub.docker.com/r/watzon/inkify). You can also, of course, build it youself with the provided Dockerfile; be warned, the build process takes a while as it has to compile Tensorflow, download fonts, and build the Rust project.
|
||||
|
||||
## Testing
|
||||
|
||||
Want to give Inkify a try without writing your own code? Here's a simple component that make use of the Inkify API:
|
||||
|
||||
<div class="mb-8">
|
||||
<CodeToImg client:load />
|
||||
</div>
|
||||
|
||||
## Conclusion
|
||||
|
||||
Inkify was inspired by my wanting to generate open graph images for my pastebin, [paste69](https://0x45.st), and I'm very happy with how it turned out. It's a simple project, but it's also very useful and I hope others find it useful as well. Be sure to give the project a [star on GitHub](https://github.com/watzon/inkify) if you like it, and feel free to open an issue if you have any questions or suggestions.
|
|
@ -10,4 +10,5 @@ tags:
|
|||
- webdriver
|
||||
linkOnly: true
|
||||
featured: true
|
||||
heroImage: /images/marionette-header.webp
|
||||
---
|
||||
|
|
|
@ -10,5 +10,4 @@ tags:
|
|||
- framework
|
||||
heroImage: /images/tourmaline-header.png
|
||||
linkOnly: true
|
||||
featured: true
|
||||
---
|
||||
|
|
|
@ -13,11 +13,11 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
|
|||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={`${title} - ${SITE_TITLE}`} description={description} />
|
||||
<BaseHead title={`${title} - ${SITE_TITLE}`} description={description} image={heroImage} />
|
||||
<style>
|
||||
main {
|
||||
width: calc(100% - 2em);
|
||||
@apply max-w-full m-0;
|
||||
@apply max-w-full;
|
||||
}
|
||||
.hero-image {
|
||||
@apply w-full;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import BaseHead from '../components/BaseHead.astro';
|
||||
import Header from '../components/Header.astro';
|
||||
|
@ -7,16 +8,16 @@ import { SITE_TITLE } from '../consts';
|
|||
|
||||
type Props = CollectionEntry<'projects'>['data'];
|
||||
|
||||
const { name, description, heroImage } = Astro.props;
|
||||
const { name, description, heroImage, url } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={`${name} - ${SITE_TITLE}`} description={description} />
|
||||
<BaseHead title={`${name} - ${SITE_TITLE}`} description={description} image={heroImage} />
|
||||
<style>
|
||||
main {
|
||||
width: calc(100% - 2em);
|
||||
@apply max-w-full m-0;
|
||||
@apply max-w-full;
|
||||
}
|
||||
.hero-image {
|
||||
@apply w-full;
|
||||
|
@ -33,7 +34,13 @@ const { name, description, heroImage } = Astro.props;
|
|||
@apply mb-4 py-4 text-center leading-8;
|
||||
}
|
||||
.title h1 {
|
||||
@apply m-0 mb-4;
|
||||
@apply flex justify-center m-0 mb-4;
|
||||
}
|
||||
.title h1 a {
|
||||
@apply block relative w-min text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300;
|
||||
}
|
||||
.title h1 svg {
|
||||
@apply absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-[110%] w-12 h-12;
|
||||
}
|
||||
.date {
|
||||
@apply mb-4 text-base text-gray-500;
|
||||
|
@ -53,7 +60,13 @@ const { name, description, heroImage } = Astro.props;
|
|||
</div>
|
||||
<div class="prose">
|
||||
<div class="title">
|
||||
<h1>{name}</h1>
|
||||
<h1>
|
||||
<a target="_blank" href={url}>
|
||||
<Icon name="mdi:link" />
|
||||
{name}
|
||||
</a>
|
||||
</h1>
|
||||
<h5>{description}</h5>
|
||||
<hr />
|
||||
</div>
|
||||
<slot />
|
||||
|
|
|
@ -63,9 +63,21 @@ const latestPosts = (await getCollection('posts'))
|
|||
</html>
|
||||
|
||||
<style lang="postcss">
|
||||
.header {
|
||||
/* Transitioning rainbow gradient */
|
||||
background: linear-gradient(90deg, #ffb3b3, #ffd9b3, #ffffb3, #b3ffcc, #b3ffff, #b3b3ff, #ffb3ff, #ffb3b3, #ffb3b3);
|
||||
:root {
|
||||
--gradient-colors-light: #b36b6b, #d99e85, #e6d9a3, #7fbf8e, #7fbfbf, #7f7fbf, #b37fbf, #b36b6b, #b36b6b;
|
||||
--gradient-colors-dark: #ffb3b3, #ffd9b3, #ffffb3, #b3ffcc, #b3ffff, #b3b3ff, #ffb3ff, #ffb3b3, #ffb3b3;
|
||||
}
|
||||
|
||||
:global(.dark) .header {
|
||||
background: linear-gradient(90deg, var(--gradient-colors-dark));
|
||||
background-size: 400% 400%;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: gradient 15s linear infinite;
|
||||
}
|
||||
|
||||
:global(:not(.dark)) .header {
|
||||
background: linear-gradient(90deg, var(--gradient-colors-light));
|
||||
background-size: 400% 400%;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
|
|
@ -18,7 +18,7 @@ body {
|
|||
}
|
||||
|
||||
main {
|
||||
@apply w-[720px] max-w-[calc(100%-2em)] m-auto px-4 py-12;
|
||||
@apply w-[720px] max-w-[calc(100%-2em)] m-0 mx-auto px-4 py-12;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -31,19 +31,19 @@ h6 {
|
|||
@apply m-0 mb-6 text-4xl font-bold leading-10 text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
h1 {
|
||||
@apply text-6xl
|
||||
@apply text-5xl md:text-6xl;
|
||||
}
|
||||
h2 {
|
||||
@apply text-5xl
|
||||
@apply text-4xl md:text-5xl;
|
||||
}
|
||||
h3 {
|
||||
@apply text-4xl
|
||||
@apply text-3xl md:text-4xl;
|
||||
}
|
||||
h4 {
|
||||
@apply text-3xl
|
||||
@apply text-2xl md:text-3xl;
|
||||
}
|
||||
h5 {
|
||||
@apply text-2xl
|
||||
@apply text-xl md:text-2xl;
|
||||
}
|
||||
|
||||
strong,
|
||||
|
@ -93,7 +93,7 @@ pre {
|
|||
}
|
||||
|
||||
pre > code {
|
||||
all: unset;
|
||||
all: unset !important;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
|
@ -109,7 +109,7 @@ hr {
|
|||
@apply text-lg;
|
||||
}
|
||||
main {
|
||||
@apply p-4;
|
||||
@apply px-0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true
|
||||
"strictNullChecks": true,
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue