svg preview renderer

This commit is contained in:
zack 2024-10-26 21:41:22 -04:00
parent 43a8412f06
commit faa9599849
No known key found for this signature in database
GPG key ID: 5F873416BCF59F35
29 changed files with 1027 additions and 254 deletions

View file

@ -3,3 +3,12 @@
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
/* This file is for your main application CSS */ /* This file is for your main application CSS */
code .highlighted {
@apply bg-ctp-mauve/10;
transition:background-color .5s;
margin:0 -24px;
padding:0 24px;
width:calc(100% + 48px);
display:inline-block
}

View file

@ -22,16 +22,9 @@ import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view"; import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar"; import topbar from "../vendor/topbar";
import CodeBlockHook from "./hooks/code_block_hook"; import CodeBlockHook from "./hooks/code_block_hook";
import { import { SelectHook } from "./hooks/select";
DropdownAnimation,
SearchableDropdown,
} from "./hooks/searchable_dropdown";
let Hooks = { let Hooks = { CodeBlockHook, SelectHook };
CodeBlockHook,
SearchableDropdown,
DropdownAnimation,
};
Hooks.ClickOutside = { Hooks.ClickOutside = {
mounted() { mounted() {

View file

@ -9,11 +9,10 @@ const CodeBlockHook = {
this.el.dataset.highlightedLines || "[]", this.el.dataset.highlightedLines || "[]",
); );
console.log(code);
console.log("language", language);
const lines = code.split("\n"); const lines = code.split("\n");
console.log(lines.length, lines);
// Convert line numbers to decorations // Convert line numbers to decorations
const decorations = highlightedLines const decorations = highlightedLines
.map((line) => { .map((line) => {
@ -40,7 +39,16 @@ const CodeBlockHook = {
codeToHtml(code, { codeToHtml(code, {
lang: language, lang: language,
theme: "catppuccin-mocha", theme: "catppuccin-mocha",
decorations, transformers: [
{
line(node, line) {
node.properties["data-line"] = line;
if (highlightedLines.includes(line)) {
this.addClassToHast(node, "highlighted");
}
},
},
],
}).then((html) => { }).then((html) => {
console.log(html); console.log(html);

View file

@ -1,32 +0,0 @@
SearchableDropdown = {
mounted() {
this.el.addEventListener("input", (e) => {
const query = e.target.value.toLowerCase();
const dropdownId = this.el.dataset.dropdownId;
const optionsContainer = document.querySelector(`#${dropdownId}-options`);
const options = optionsContainer.querySelectorAll("li button");
options.forEach((option) => {
const text = option.textContent.toLowerCase();
option.parentElement.style.display = text.includes(query)
? "block"
: "none";
});
});
},
};
const DropdownAnimation = {
mounted() {
this.el.addEventListener("transitionend", (e) => {
if (
e.propertyName === "opacity" &&
this.el.classList.contains("fade-out")
) {
this.el.classList.add("hidden");
}
});
},
};
export { SearchableDropdown, DropdownAnimation };

186
assets/js/hooks/select.js Normal file
View file

@ -0,0 +1,186 @@
const SelectHook = {
mounted() {
const container = this.el;
const trigger = container.querySelector('[id$="-trigger"]');
const dropdown = container.querySelector('[id$="-dropdown"]');
const search = container.querySelector('[id$="-search"]');
const options = container.querySelector('[id$="-options"]');
const hiddenInput = container.querySelector('input[type="hidden"]');
const selectedText = container.querySelector('[id$="-selected"]');
const toggleDropdown = (event) => {
// Prevent the event from bubbling up to any parent form
if (event) {
event.preventDefault();
event.stopPropagation();
}
const isExpanded = dropdown.classList.contains("block");
if (!isExpanded) {
dropdown.classList.remove("hidden");
// Add animation classes
dropdown.classList.add(
"block",
"animate-in",
"fade-in",
"duration-200",
);
search.value = "";
filterOptions("");
search.focus();
} else {
// Add animation classes for hiding
dropdown.classList.remove("animate-in", "fade-in");
dropdown.classList.add("animate-out", "fade-out");
setTimeout(() => {
dropdown.classList.remove("block", "animate-out", "fade-out");
dropdown.classList.add("hidden");
}, 200);
}
};
const filterOptions = (query) => {
const items = options.querySelectorAll("li");
items.forEach((item) => {
const text = item.textContent.toLowerCase();
if (text.includes(query.toLowerCase())) {
item.style.display = "";
} else {
item.style.display = "none";
}
});
};
const selectOption = (option, event) => {
// Prevent form submission
if (event) {
event.preventDefault();
event.stopPropagation();
}
const value = option.dataset.value;
const label = option.querySelector("span").textContent;
// Update hidden input
hiddenInput.value = value;
// Update visible text
selectedText.textContent = label;
// Update check mark position
options.querySelectorAll("li").forEach((li) => {
const checkmark = li.querySelector("span:last-child");
if (checkmark) checkmark.remove();
if (li.dataset.value === value) {
li.setAttribute("aria-selected", "true");
const checkMarkSpan = document.createElement("span");
checkMarkSpan.className =
"absolute inset-y-0 right-0 flex items-center pr-4 ctp-text-lavender";
checkMarkSpan.innerHTML = `
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
`;
li.appendChild(checkMarkSpan);
} else {
li.setAttribute("aria-selected", "false");
}
});
toggleDropdown();
// Dispatch change event without bubbling
const changeEvent = new Event("change", { bubbles: false });
hiddenInput.dispatchEvent(changeEvent);
// Push the value to the LiveView
this.pushEventTo(this.el, "change", { value: value });
};
// Event Listeners
trigger.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
toggleDropdown(e);
});
search.addEventListener("input", (e) => {
e.preventDefault();
e.stopPropagation();
filterOptions(e.target.value);
});
options.querySelectorAll("li").forEach((option) => {
option.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
selectOption(option, e);
});
});
// Prevent dropdowns from causing form submission
container.addEventListener("submit", (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
// Close dropdown when clicking outside
document.addEventListener("click", (e) => {
if (!container.contains(e.target)) {
dropdown.classList.add("hidden");
dropdown.classList.remove("block");
}
});
// Keyboard navigation
container.addEventListener("keydown", (e) => {
const items = Array.from(options.querySelectorAll("li")).filter(
(li) => li.style.display !== "none",
);
const currentIdx = items.findIndex((li) => li === document.activeElement);
switch (e.key) {
case "ArrowDown":
e.preventDefault();
if (currentIdx < items.length - 1) {
items[currentIdx + 1].focus();
}
break;
case "ArrowUp":
e.preventDefault();
if (currentIdx > 0) {
items[currentIdx - 1].focus();
}
break;
case "Enter":
e.preventDefault();
if (document.activeElement.tagName === "LI") {
selectOption(document.activeElement, e);
}
break;
case "Escape":
e.preventDefault();
toggleDropdown(e);
break;
}
});
// Prevent the dropdown from closing when clicking inside it
dropdown.addEventListener("click", (e) => {
e.stopPropagation();
});
// Prevent the search input from submitting the form
search.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
return false;
}
});
},
};
export { SelectHook };

134
deps.nix
View file

@ -465,6 +465,108 @@ let
beamDeps = [ decimal ]; beamDeps = [ decimal ];
}; };
makeup =
let
version = "1.1.2";
in
buildMix {
inherit version;
name = "makeup";
src = fetchHex {
inherit version;
pkg = "makeup";
sha256 = "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac";
};
beamDeps = [ nimble_parsec ];
};
makeup_elixir =
let
version = "0.16.2";
in
buildMix {
inherit version;
name = "makeup_elixir";
src = fetchHex {
inherit version;
pkg = "makeup_elixir";
sha256 = "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b";
};
beamDeps = [ makeup nimble_parsec ];
};
makeup_erlang =
let
version = "0.1.5";
in
buildMix {
inherit version;
name = "makeup_erlang";
src = fetchHex {
inherit version;
pkg = "makeup_erlang";
sha256 = "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a";
};
beamDeps = [ makeup ];
};
makeup_html =
let
version = "0.1.1";
in
buildMix {
inherit version;
name = "makeup_html";
src = fetchHex {
inherit version;
pkg = "makeup_html";
sha256 = "44f2a61bc5243645dd7fafeaa6cc28793cd22f3c76b861e066168f9a5b2c26a4";
};
beamDeps = [ makeup ];
};
makeup_js =
let
version = "0.1.0";
in
buildMix {
inherit version;
name = "makeup_js";
src = fetchHex {
inherit version;
pkg = "makeup_js";
sha256 = "3f0c1a5eb52c9737b1679c926574e83bb260ccdedf08b58ee96cca7c685dea75";
};
beamDeps = [ makeup ];
};
makeup_rust =
let
version = "0.3.0";
in
buildMix {
inherit version;
name = "makeup_rust";
src = fetchHex {
inherit version;
pkg = "makeup_rust";
sha256 = "1e79ee1995a3b9df9b7d90af7dc02525989efc41390a52065c23782c94ea94e1";
};
beamDeps = [ makeup nimble_parsec ];
};
metrics = metrics =
let let
version = "1.0.1"; version = "1.0.1";
@ -557,6 +659,21 @@ let
}; };
}; };
nimble_parsec =
let
version = "1.4.0";
in
buildMix {
inherit version;
name = "nimble_parsec";
src = fetchHex {
inherit version;
pkg = "nimble_parsec";
sha256 = "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28";
};
};
nimble_pool = nimble_pool =
let let
version = "1.1.0"; version = "1.1.0";
@ -636,6 +753,23 @@ let
}; };
}; };
phoenix_html_helpers =
let
version = "1.0.1";
in
buildMix {
inherit version;
name = "phoenix_html_helpers";
src = fetchHex {
inherit version;
pkg = "phoenix_html_helpers";
sha256 = "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090";
};
beamDeps = [ phoenix_html plug ];
};
phoenix_live_dashboard = phoenix_live_dashboard =
let let
version = "0.8.4"; version = "0.8.4";

View file

@ -232,7 +232,7 @@
StartLimitBurst = 3; StartLimitBurst = 3;
StartLimitInterval = 10; StartLimitInterval = 10;
}; };
path = [pkgs.bash pkgs.imagemagick]; path = [pkgs.bash pkgs.imagemagick pkgs.librsvg];
}; };
}; };
}; };
@ -318,6 +318,7 @@
nodejs nodejs
mix2nix mix2nix
node2nix node2nix
librsvg
imagemagick imagemagick
# Add the language server of your choice. # Add the language server of your choice.
inputs.lexical.packages.${system}.default inputs.lexical.packages.${system}.default

View file

@ -1,10 +1,16 @@
defmodule Zoeyscomputer.Gists.Gist do defmodule Zoeyscomputer.Gists.Gist do
alias Zoeyscomputer.IdGenerator
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Zoeyscomputer.Users.User
@primary_key {:id, :string, autogenerate: false}
schema "gists" do schema "gists" do
field :code, :string field :code, :string
field :lang, :string field :lang, :string
field :title, :string
field :desc, :string
belongs_to :author, User
timestamps(type: :utc_datetime) timestamps(type: :utc_datetime)
end end
@ -12,7 +18,15 @@ defmodule Zoeyscomputer.Gists.Gist do
@doc false @doc false
def changeset(gist, attrs) do def changeset(gist, attrs) do
gist gist
|> cast(attrs, [:code, :lang]) |> cast(attrs, [:code, :lang, :title, :desc])
|> validate_required([:code, :lang]) |> validate_required([:code, :lang, :title])
|> put_new_id()
end
defp put_new_id(changeset) do
case get_field(changeset, :id) do
nil -> put_change(changeset, :id, IdGenerator.generate(7))
_id -> changeset
end
end end
end end

View file

@ -1,6 +1,7 @@
defmodule ZoeyscomputerWeb.CodeBlock do defmodule ZoeyscomputerWeb.CodeBlock do
use Phoenix.Component use Phoenix.Component
alias Phoenix.LiveView.JS
import ZoeyscomputerWeb.CoreComponents
@moduledoc """ @moduledoc """
A code block component with syntax highlighting using Shiki. A code block component with syntax highlighting using Shiki.
@ -37,6 +38,7 @@ defmodule ZoeyscomputerWeb.CodeBlock do
attr :code, :string, required: true attr :code, :string, required: true
attr :language, :string, required: true attr :language, :string, required: true
attr :title, :string, default: nil attr :title, :string, default: nil
attr :class, :string, default: ""
attr :line_numbers, :boolean, default: false attr :line_numbers, :boolean, default: false
attr :highlighted_lines, :list, default: [] attr :highlighted_lines, :list, default: []
@ -44,48 +46,46 @@ defmodule ZoeyscomputerWeb.CodeBlock do
# Calculate the number of lines for line numbers # Calculate the number of lines for line numbers
assigns = assign(assigns, :num_lines, String.split(assigns.code, "\n") |> length()) assigns = assign(assigns, :num_lines, String.split(assigns.code, "\n") |> length())
id = System.unique_integer()
~H""" ~H"""
<div class="relative ctp-bg-base rounded-lg overflow-hidden"> <div class={"relative rounded-lg overflow-hidden w-full bg-ctp-mantle border border-ctp-surface0 #{@class} "}>
<%= if @title do %> <%= if @title do %>
<div class="ctp-bg-mantle px-4 py-2 border-b ctp-border-surface0"> <div class="bg-ctp-crust px-4 py-2 border-b border-ctp-surface0">
<h3 class="ctp-text-text text-sm font-medium"><%= @title %></h3> <h3 class="ctp-text-text text-[0.75rem] font-medium"><%= @title %></h3>
</div> </div>
<% end %> <% end %>
<div class="relative"> <div class="overflow-x-scroll bg-ctp-mantle">
<%= if @line_numbers do %> <div class="relative bg-ctp-crust">
<div class="absolute left-0 top-0 bottom-0 ctp-bg-crust w-12 flex flex-col items-end pr-2 py-4 ctp-text-surface2 select-none"> <%= if @line_numbers do %>
<%= for line_num <- 1..@num_lines do %> <div class="absolute bg-ctp-crust left-0 top-0 bottom-0 ctp-bg-crust min-w-12 flex flex-col items-end py-4 ctp-text-surface2 select-none">
<span class={[ <%= for line_num <- 1..@num_lines do %>
"text-sm leading-6", <span class={[
line_num in @highlighted_lines && "ctp-text-mauve font-medium" "text-sm leading-6 pr-2",
]}> line_num in @highlighted_lines &&
<%= line_num %> "text-ctp-mauve bg-ctp-mauve/15 w-full text-right font-bold"
</span> ]}>
<% end %> <%= line_num %>
</div> </span>
<% end %> <% end %>
</div>
<% end %>
<div class={["overflow-x-auto", @line_numbers && "pl-12"]}> <div class={["bg-ctp-mantle overflow-x-scroll pb-0.5", @line_numbers && "pl-12"]}>
<pre <pre
class="p-4" class="p-4 leading-6 text-sm mb-4"
id={"code-block-#{System.unique_integer()}"} id={"code-block-#{id}"}
phx-hook="CodeBlockHook" phx-hook="CodeBlockHook"
data-code={@code} data-code={@code}
data-language={@language} data-language={@language}
data-highlighted-lines={Jason.encode!(@highlighted_lines)} data-highlighted-lines={Jason.encode!(@highlighted_lines)}
><code class="text-sm"><%= @code %></code></pre> ><code class="text-sm"><%= @code %></code></pre>
</div>
</div> </div>
</div> </div>
<div class="bg-ctp-mantle px-4 py-2 border-t border-ctp-surface0 flex justify-end">
<div class="ctp-bg-mantle px-4 py-2 border-t ctp-border-surface0 flex justify-end"> <.copy_button id="code-copy-btn" content={"code-block-#{id}"} />
<button
type="button"
class="ctp-text-subtext0 hover:ctp-text-text text-sm"
phx-click={JS.dispatch("clipcopy", detail: %{text: @code})}
>
Copy code
</button>
</div> </div>
</div> </div>
""" """

View file

@ -235,8 +235,9 @@ defmodule ZoeyscomputerWeb.CoreComponents do
<button <button
type={@type} type={@type}
class={[ class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3", "phx-submit-loading:opacity-75 rounded-lg bg-ctp-mauve text-ctp-crust hover:brightness-125 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80", "text-sm font-semibold leading-6",
"transition-all duration-150 ease-in",
@class @class
]} ]}
{@rest} {@rest}
@ -359,7 +360,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
id={@id} id={@id}
name={@name} name={@name}
class={[ class={[
"mt-2 block w-full rounded-lg text-ctp-overlay2 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]", "mt-2 block w-full bg-ctp-base rounded-lg text-ctp-text focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
@errors == [] && "border-ctp-surface2 focus:border-zinc-400", @errors == [] && "border-ctp-surface2 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400" @errors != [] && "border-rose-400 focus:border-rose-400"
]} ]}
@ -400,7 +401,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
def label(assigns) do def label(assigns) do
~H""" ~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-ctp-text"> <label for={@for} class="block text-sm font-semibold leading-6 text-ctp-overlay1">
<%= render_slot(@inner_block) %> <%= render_slot(@inner_block) %>
</label> </label>
""" """
@ -458,7 +459,7 @@ defmodule ZoeyscomputerWeb.CoreComponents do
content={@content} content={@content}
phx-click={JS.dispatch("phx:copy", to: "##{@content}")} phx-click={JS.dispatch("phx:copy", to: "##{@content}")}
type="button" type="button"
class="rounded-md inline-flex items-center bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" class="rounded-md inline-flex items-center bg-ctp-blue px-2.5 py-1.5 text-sm font-semibold text-ctp-crust shadow-sm shadow-ctp-blue/25 ring-ctp-blue ring-1 ring-inset transition-all duration-150 ease-in hover:brightness-125"
> >
Copy Copy
</button> </button>

View file

@ -1,5 +1,5 @@
<main class="bg-ctp-base px-4 py-20 sm:px-6 lg:px-8"> <main class="bg-ctp-base px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl"> <div class="mx-auto lg:max-w-7xl">
<.flash_group flash={@flash} /> <.flash_group flash={@flash} />
<%= @inner_content %> <%= @inner_content %>
</div> </div>

View file

@ -1,81 +0,0 @@
defmodule ZoeyscomputerWeb.SearchableDropdown do
use Phoenix.Component
alias Phoenix.LiveView.JS
attr :id, :string, required: true
attr :options, :list, required: true
attr :selected, :string, default: nil
attr :class, :string, default: nil
attr :name, :string, required: true
attr :form, :any, required: true
def searchable_dropdown(assigns) do
~H"""
<div class={["relative w-full", @class]} id={"#{@id}-container"}>
<div class="relative" phx-click-away={JS.hide(to: "##{@id}-dropdown", transition: "fade-out")}>
<input type="hidden" name={@name} value={@selected} id={"#{@id}-input"} />
<button
type="button"
class="flex w-full items-center justify-between rounded-md border border-ctp-surface0 bg-ctp-base px-3 py-2 text-sm text-ctp-text shadow-sm hover:bg-ctp-surface0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-click={
JS.toggle(to: "##{@id}-dropdown", in: "fade-in", out: "fade-out")
|> JS.focus(to: "##{@id}-search")
}
aria-haspopup="listbox"
aria-expanded="false"
>
<span class="block truncate">
<%= @selected || "Select an option..." %>
</span>
<svg
class="h-5 w-5 text-ctp-overlay0 transform transition-transform duration-200"
class={"#{if @selected, do: "rotate-180", else: ""}"}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
id={"#{@id}-dropdown"}
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md border border-ctp-surface0 bg-ctp-base shadow-lg hidden transition-all duration-200 ease-in-out"
phx-hook="DropdownAnimation"
>
<div class="p-2">
<input
type="text"
id={"#{@id}-search"}
placeholder="Search..."
class="w-full rounded-md border border-ctp-surface0 bg-ctp-mantle px-3 py-2 text-sm text-ctp-text placeholder-ctp-overlay0 focus:outline-none focus:ring-2 focus:ring-ctp-lavender transition-colors duration-200"
phx-hook="SearchableDropdown"
data-dropdown-id={@id}
data-form-id={@form.id}
/>
</div>
<ul class="max-h-48 overflow-y-auto py-1" role="listbox" id={"#{@id}-options"}>
<li :for={option <- @options} class="transition-colors duration-150 ease-in-out">
<button
type="button"
class="w-full px-3 py-2 text-left text-sm hover:bg-ctp-surface0 focus:bg-ctp-surface0 focus:outline-none cursor-pointer transition-colors duration-150 ease-in-out"
phx-click={
JS.push("select_language", value: %{language: option}, target: "##{@form.id}")
|> JS.hide(to: "##{@id}-dropdown", transition: "fade-out")
}
phx-target={"##{@form.id}"}
role="option"
data-option={option}
>
<%= option %>
</button>
</li>
</ul>
</div>
</div>
</div>
"""
end
end

View file

@ -0,0 +1,30 @@
defmodule ZoeyscomputerWeb.GistPreviewController do
use ZoeyscomputerWeb, :controller
require Logger
alias Zoeyscomputer.Gists
def show(conn, %{"id" => id}) do
gist = Gists.get_gist!(id)
{:ok, webp} = ZoeyscomputerWeb.GistLive.OgImage.get_webp(gist)
conn
|> put_resp_content_type("image/webp")
|> put_resp_header("cache-control", "public, max-age=300")
|> send_resp(200, webp)
|> halt()
end
def raw(conn, %{"id" => id}) do
gist = Gists.get_gist!(id)
webp = ZoeyscomputerWeb.GistLive.OgImage.generate_preview(gist)
conn
|> put_resp_content_type("image/svg+xml")
|> put_resp_header("cache-control", "public, max-age=300")
|> send_resp(200, webp)
|> halt()
end
end

View file

@ -1,6 +1,8 @@
defmodule ZoeyscomputerWeb.GistLive.FormComponent do defmodule ZoeyscomputerWeb.GistLive.FormComponent do
use ZoeyscomputerWeb, :live_component use ZoeyscomputerWeb, :live_component
import ZoeyscomputerWeb.GistLive.Select
alias Zoeyscomputer.Gists alias Zoeyscomputer.Gists
@impl true @impl true
@ -11,7 +13,6 @@ defmodule ZoeyscomputerWeb.GistLive.FormComponent do
<%= @title %> <%= @title %>
<:subtitle>Use this form to manage gist records in your database.</:subtitle> <:subtitle>Use this form to manage gist records in your database.</:subtitle>
</.header> </.header>
<.simple_form <.simple_form
for={@form} for={@form}
id="gist-form" id="gist-form"
@ -19,8 +20,22 @@ defmodule ZoeyscomputerWeb.GistLive.FormComponent do
phx-change="validate" phx-change="validate"
phx-submit="save" phx-submit="save"
> >
<.input field={@form[:code]} type="text" label="Code" /> <.input field={@form[:title]} type="text" label="Title" />
<.input field={@form[:lang]} type="text" label="Lang" /> <.input field={@form[:desc]} type="textarea" label="Description" />
<.input field={@form[:code]} type="textarea" label="Code" />
<.select
id="lang-select"
form={@form}
field={:lang}
label="Language"
options={[
{"JavaScript", "javascript"},
{"Python", "python"},
{"Elixir", "elixir"},
{"Ruby", "ruby"},
{"Rust", "rust"}
]}
/>
<:actions> <:actions>
<.button phx-disable-with="Saving...">Save Gist</.button> <.button phx-disable-with="Saving...">Save Gist</.button>
</:actions> </:actions>
@ -49,6 +64,14 @@ defmodule ZoeyscomputerWeb.GistLive.FormComponent do
save_gist(socket, socket.assigns.action, gist_params) save_gist(socket, socket.assigns.action, gist_params)
end end
# Updated to use lang instead of language
def handle_event("change", %{"value" => value}, socket) do
current_params = socket.assigns.form.params || %{}
updated_params = Map.put(current_params, "lang", value)
changeset = Gists.change_gist(socket.assigns.gist, updated_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
defp save_gist(socket, :edit, gist_params) do defp save_gist(socket, :edit, gist_params) do
case Gists.update_gist(socket.assigns.gist, gist_params) do case Gists.update_gist(socket.assigns.gist, gist_params) do
{:ok, gist} -> {:ok, gist} ->

View file

@ -11,7 +11,8 @@ defmodule ZoeyscomputerWeb.GistLive.Index do
@impl true @impl true
def handle_params(params, _url, socket) do def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)} live_action = socket.assigns.live_action || :index
{:noreply, apply_action(socket, live_action, params)}
end end
defp apply_action(socket, :edit, %{"id" => id}) do defp apply_action(socket, :edit, %{"id" => id}) do
@ -23,7 +24,16 @@ defmodule ZoeyscomputerWeb.GistLive.Index do
defp apply_action(socket, :new, _params) do defp apply_action(socket, :new, _params) do
socket socket
|> assign(:page_title, "New Gist") |> assign(:page_title, "New Gist")
|> assign(:gist, %Gist{}) |> assign(:gist, %Gist{
code: "",
lang: nil
})
end
defp apply_action(socket, :show, %{"id" => id}) do
socket
|> assign(:page_title, "Show Gist")
|> assign(:gist, Gists.get_gist!(id))
end end
defp apply_action(socket, :index, _params) do defp apply_action(socket, :index, _params) do
@ -32,6 +42,13 @@ defmodule ZoeyscomputerWeb.GistLive.Index do
|> assign(:gist, nil) |> assign(:gist, nil)
end end
# Catch-all clause for when live_action is nil
defp apply_action(socket, nil, _params) do
socket
|> assign(:page_title, "Listing Gists")
|> assign(:gist, nil)
end
@impl true @impl true
def handle_info({ZoeyscomputerWeb.GistLive.FormComponent, {:saved, gist}}, socket) do def handle_info({ZoeyscomputerWeb.GistLive.FormComponent, {:saved, gist}}, socket) do
{:noreply, stream_insert(socket, :gists, gist)} {:noreply, stream_insert(socket, :gists, gist)}

View file

@ -12,7 +12,9 @@
rows={@streams.gists} rows={@streams.gists}
row_click={fn {_id, gist} -> JS.navigate(~p"/gists/#{gist}") end} row_click={fn {_id, gist} -> JS.navigate(~p"/gists/#{gist}") end}
> >
<:col :let={{_id, gist}} label="Code"><%= gist.code %></:col> <:col :let={{_id, gist}} label="Code">
<p class="truncate max-w-72"><%= gist.title || gist.code %></p>
</:col>
<:col :let={{_id, gist}} label="Lang"><%= gist.lang %></:col> <:col :let={{_id, gist}} label="Lang"><%= gist.lang %></:col>
<:action :let={{_id, gist}}> <:action :let={{_id, gist}}>
<div class="sr-only"> <div class="sr-only">

View file

@ -0,0 +1,15 @@
defmodule ZoeyscomputerWeb.GistLive.Languages do
@languages [
%{name: "JavaScript", val: "javascript"},
%{name: "TypeScript", val: "typescript"},
%{name: "Rust", val: "rust"},
%{name: "Elixir", val: "elixir"}
]
def search_languages(name) do
@languages
|> Enum.filter(fn language ->
String.contains?(String.downcase(language.name), String.downcase(name))
end)
end
end

View file

@ -0,0 +1,243 @@
defmodule ZoeyscomputerWeb.GistLive.OgImage do
@moduledoc """
Generates OpenGraph preview images for Gists using SVG with syntax highlighting.
"""
require Logger
@external_resource "priv/static/fonts/WOFF2/Iosevka-Bold.woff2"
@external_resource "priv/static/fonts/WOFF2/Iosevka-Regular.woff2"
@font_bold File.read!("priv/static/fonts/WOFF2/Iosevka-Bold.woff2")
@font_regular File.read!("priv/static/fonts/WOFF2/Iosevka-Regular.woff2")
@line_height 36
@code_start_y 210
@code_start_x 80
@font_size 24
@char_width 12
defp font_face_styles do
"""
@font-face {
font-family: 'Iosevka';
src: url(data:font/woff2;charset=utf-8;base64,#{Base.encode64(@font_regular)}) format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Iosevka';
src: url(data:font/woff2;charset=utf-8;base64,#{Base.encode64(@font_bold)}) format('woff2');
font-weight: 700;
font-style: normal;
}
"""
end
def generate_preview(gist) do
code_lines =
gist.code
|> preview_code()
|> highlight_code(gist.lang)
|> render_code_lines()
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1200" height="630" viewBox="0 0 1200 630"
xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css">
#{font_face_styles()}
* {
font-family: 'Iosevka' !important;
}
.background { fill: #0D1117; }
.title { fill: #ffffff; font-family: system-ui, -apple-system, sans-serif; }
.description { fill: #8B949E; font-family: system-ui, -apple-system, sans-serif; }
.code-background { fill: #161B22; }
/* Syntax Highlighting Colors */
.c, .cm, .cp, .c1, .cs { fill: #768390; } /* Comment */
.k, .kc, .kd, .kn, .kp, .kr, .kt { fill: #f47067; } /* Keyword */
.s, .sb, .sc, .sd, .s2, .se, .sh, .si, .sx, .sr, .s1, .ss { fill: #96d0ff; } /* String */
.n, .na, .nc, .no, .nd, .ni, .ne, .nf, .nl, .nn, .nx, .py, .nt, .nv, .vc, .vg, .vi { fill: #adbac7; } /* Name */
.nb, .bp { fill: #f69d50; } /* Built-in */
.m, .mf, .mh, .mi, .mo, .il { fill: #6cb6ff; } /* Number */
.o, .ow { fill: #f47067; } /* Operator */
.p { fill: #adbac7; } /* Punctuation */
.w { fill: #E6EDF3; } /* Whitespace */
.l { fill: #6cb6ff; } /* Literal */
.default { fill: #E6EDF3; } /* Default text color */
</style>
</defs>
<!-- Background -->
<rect class="background" width="1200" height="630" />
<!-- Content -->
<text x="60" y="80" class="title" font-size="40" font-weight="bold">
#{escape_text(gist.title || "Gist: #{gist.id}")}
</text>
<text x="60" y="130" class="description" font-size="24">
#{escape_text(gist.desc || "No description")}
</text>
<!-- Code Preview Background -->
<rect x="60" y="160" width="1080" height="420" class="code-background" rx="8"/>
<!-- Code Content -->
<g font-family="Iosevka" font-size="#{@font_size}">
#{code_lines}
</g>
</svg>
"""
end
defp render_code_lines(highlighted_html) do
highlighted_html
|> String.split("\n")
|> Enum.with_index()
|> Enum.map_join("\n", fn {line, index} ->
y_position = @code_start_y + index * @line_height
render_line(String.trim(line), y_position)
end)
end
defp render_line("", _y_position), do: ""
defp render_line(line, y_position) do
spans = parse_spans(line)
spans
|> Enum.reduce({[], 0}, fn {content, class}, {spans, x_offset} ->
span = render_span(content, class, x_offset, y_position)
{[span | spans], x_offset + String.length(content)}
end)
|> elem(0)
|> Enum.reverse()
|> Enum.join("\n")
end
defp parse_spans(""), do: []
defp parse_spans(line) do
# Handle non-span text first
line
|> String.split(~r{(<span.*?>|</span>)}, include_captures: true)
|> Enum.reduce({[], nil}, fn
# Opening span tag
<<"<span class=\"", rest::binary>>, {acc, nil} ->
class = rest |> String.replace_suffix("\">", "")
{acc, class}
# Closing span tag
"</span>", {acc, _class} ->
{acc, nil}
# Content within or outside spans
content, {acc, class} when byte_size(content) > 0 ->
{[{content, class || "default"} | acc], class}
_, acc_class ->
acc_class
end)
|> elem(0)
|> Enum.reverse()
end
defp render_span(content, class, x_offset, y_position) when byte_size(content) > 0 do
x = @code_start_x + x_offset * @char_width
"<text x=\"#{x}\" y=\"#{y_position}\" class=\"#{class}\">#{escape_text(content)}</text>"
end
defp render_span(_, _, _, _), do: ""
defp preview_code(content) do
content
|> String.split("\n")
|> Enum.take(15)
|> Enum.join("\n")
end
defp escape_text(text) do
text
|> String.replace("&", "&amp;")
|> String.replace("<", "&lt;")
|> String.replace(">", "&gt;")
end
defp highlight_code(code, language) when is_binary(language) do
lexer = get_lexer_for_language(language)
String.trim(
code
|> Makeup.highlight_inner_html(
lexer: lexer,
formatter_opts: [
highlight_tag: "span",
css_class: "makeup",
highlight_css_class: "hll"
]
)
|> to_string()
)
end
defp highlight_code(code, nil) do
escape_text(code)
end
defp get_lexer_for_language(language) do
case language do
"elixir" -> Makeup.Lexers.ElixirLexer
"erlang" -> Makeup.Lexers.ErlangLexer
"html" -> Makeup.Lexers.HtmlLexer
"javascript" -> Makeup.Lexers.JsLexer
"python" -> Makeup.Lexers.PythonLexer
"ruby" -> Makeup.Lexers.RubyLexer
"rust" -> Makeup.Lexers.RustLexer
end
end
def get_webp(gist) do
svg = generate_preview(gist)
tmp_svg_path = Path.join(System.tmp_dir(), "temp_#{:rand.uniform(999_999)}.svg")
Logger.debug(tmp_svg_path)
try do
File.write!(tmp_svg_path, svg)
Logger.debug("wrote svg")
try do
image =
tmp_svg_path
|> Mogrify.open()
|> Mogrify.custom("define", "svg:include-fonts=true")
|> Mogrify.format("webp")
|> Mogrify.save()
Logger.debug("wrote image #{image.path}")
{:ok, image_binary} = File.read(image.path)
Logger.debug("returning binary")
{:ok, image_binary}
rescue
e in Mogrify.Error ->
{
:error,
"Conversion failed: #{Exception.message(e)}"
}
end
rescue
e -> {:error, "File operation failed: #{Exception.message(e)}"}
after
File.rm(tmp_svg_path)
end
end
end

View file

@ -0,0 +1,125 @@
defmodule ZoeyscomputerWeb.GistLive.Select do
use Phoenix.Component
import Phoenix.HTML.Form, only: [input_value: 2]
attr :id, :string, required: true
attr :form, :any, required: true
attr :field, :atom, required: true
attr :options, :list, required: true
attr :label, :string, default: nil
attr :prompt, :string, default: "Select an option..."
attr :class, :string, default: nil
attr :disabled, :boolean, default: false
def select(assigns) do
selected_value = input_value(assigns.form, assigns.field)
assigns = assign(assigns, :selected_value, selected_value)
~H"""
<div class="relative" id={@id <> "-container"} phx-hook="SelectHook">
<label :if={@label} for={@id} class="block text-sm font-bold mb-2 text-ctp-overlay1">
<%= @label %>
</label>
<button
type="button"
id={@id <> "-trigger"}
phx-update="ignore"
class={[
"relative w-full cursor-default rounded-md py-1.5 pl-3 pr-10 text-left",
"bg-ctp-base text-ctp-text border-ctp-surface0 border",
"focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ctp-ring-lavender",
"transition-colors duration-200",
"disabled:cursor-not-allowed disabled:opacity-50"
]}
disabled={@disabled}
>
<span class="block truncate" id={@id <> "-selected"}>
<%= selected_option(@options, @selected_value) || @prompt %>
</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<svg class="h-5 w-5 ctp-text-overlay0" viewBox="0 0 20 20" fill="currentColor">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
</span>
</button>
<div
id={@id <> "-dropdown"}
phx-update="ignore"
class={
[
"absolute z-10 mt-1 w-full rounded-md py-1 shadow-lg",
"bg-ctp-base border-ctp-surface0 border",
# Initially hidden, toggled by JS
"hidden"
]
}
>
<div class="px-3 py-2">
<input
type="text"
id={@id <> "-search"}
placeholder="Search..."
class={[
"w-full rounded-md py-1.5 px-3",
"bg-ctp-mantle text-ctp-text placeholder-ctp-overlay0",
"focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-ctp-lavender",
"transition-colors duration-200"
]}
/>
</div>
<ul class="max-h-60 overflow-auto" id={@id <> "-options"} role="listbox" tabindex="-1">
<%= for {label, value} <- @options do %>
<li
class={[
"relative cursor-default select-none py-2 pl-3 pr-9",
"text-ctp-text hover:bg-ctp-surface0",
"transition-colors duration-200"
]}
role="option"
data-value={value}
aria-selected={to_string(value) == to_string(@selected_value)}
>
<span class="block truncate"><%= label %></span>
<%= if to_string(value) == to_string(@selected_value) do %>
<span class="absolute inset-y-0 right-0 flex items-center pr-4 ctp-text-lavender">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</span>
<% end %>
</li>
<% end %>
</ul>
</div>
<input
type="hidden"
name={input_name(@form, @field)}
id={input_id(@form, @field)}
value={@selected_value}
/>
</div>
"""
end
defp selected_option(_options, value) when is_nil(value), do: nil
defp selected_option(options, value) do
case Enum.find(options, fn {_label, val} -> to_string(val) == to_string(value) end) do
{label, _value} -> label
_ -> nil
end
end
defp input_id(%{id: id}, field) when is_atom(field), do: "#{id}_#{field}"
defp input_name(%{name: name}, field), do: "#{name}[#{field}]"
end

View file

@ -1,5 +1,6 @@
defmodule ZoeyscomputerWeb.GistLive.Show do defmodule ZoeyscomputerWeb.GistLive.Show do
use ZoeyscomputerWeb, :live_view use ZoeyscomputerWeb, :live_view
import ZoeyscomputerWeb.CodeBlock
alias Zoeyscomputer.Gists alias Zoeyscomputer.Gists

View file

@ -1,5 +1,5 @@
<.header> <.header>
Gist <%= @gist.id %> <%= @gist.title || "Gist: #{@gist.id}" %>
<:subtitle>This is a gist record from your database.</:subtitle> <:subtitle>This is a gist record from your database.</:subtitle>
<:actions> <:actions>
<.link patch={~p"/gists/#{@gist}/show/edit"} phx-click={JS.push_focus()}> <.link patch={~p"/gists/#{@gist}/show/edit"} phx-click={JS.push_focus()}>
@ -8,10 +8,21 @@
</:actions> </:actions>
</.header> </.header>
<.list> <meta property="og:title" content={@gist.title || "Gist: #{@gist.id}"} />
<:item title="Code"><%= @gist.code %></:item> <meta property="og:description" content={@gist.desc} />
<:item title="Lang"><%= @gist.lang %></:item> <meta property="og:image" content={url(~p"/gists/#{@gist.id}/preview")} />
</.list> <meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<.code_block
class="mt-4"
code={@gist.code}
language={@gist.lang}
title={@gist.title}
line_numbers={true}
highlighted_lines={[118, 119, 120]}
/>
<.back navigate={~p"/gists"}>Back to gists</.back> <.back navigate={~p"/gists"}>Back to gists</.back>

View file

@ -110,6 +110,9 @@ defmodule ZoeyscomputerWeb.Router do
live "/users/confirm", UserConfirmationInstructionsLive, :new live "/users/confirm", UserConfirmationInstructionsLive, :new
live "/", HomeLive, :index live "/", HomeLive, :index
get "/gists/:id/preview", GistPreviewController, :show
get "/gists/:id/preview/raw", GistPreviewController, :raw
live "/images/:id", ImageLive.Show, :show live "/images/:id", ImageLive.Show, :show
live "/gists/:id", GistLive.Show, :show live "/gists/:id", GistLive.Show, :show
end end

View file

@ -47,6 +47,7 @@ defmodule Zoeyscomputer.MixProject do
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
{:mogrify, "~> 0.9.3"}, {:mogrify, "~> 0.9.3"},
{:phoenix_html_helpers, "~> 1.0"},
{:heroicons, {:heroicons,
github: "tailwindlabs/heroicons", github: "tailwindlabs/heroicons",
tag: "v2.1.1", tag: "v2.1.1",
@ -54,6 +55,12 @@ defmodule Zoeyscomputer.MixProject do
app: false, app: false,
compile: false, compile: false,
depth: 1}, depth: 1},
{:makeup, "~> 1.1"},
{:makeup_elixir, "~> 0.16"},
{:makeup_erlang, "~> 0.1"},
{:makeup_html, "~> 0.1"},
{:makeup_js, "~> 0.1"},
{:makeup_rust, "~> 0.3.0"},
{:swoosh, "~> 1.5"}, {:swoosh, "~> 1.5"},
{:ex_aws, "~> 2.1"}, {:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"},

View file

@ -11,7 +11,7 @@
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"},
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"}, "esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
"ex_aws": {:hex, :ex_aws, "2.5.6", "6f642e0f82eff10a9b470044f084b81a791cf15b393d647ea5f3e65da2794e3d", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"}, "ex_aws": {:hex, :ex_aws, "2.5.6", "6f642e0f82eff10a9b470044f084b81a791cf15b393d647ea5f3e65da2794e3d", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.4", "87aaf4a2f24a48f516d7f5aaced9d128dd5d0f655c4431f9037a11a85c71109c", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.4", "87aaf4a2f24a48f516d7f5aaced9d128dd5d0f655c4431f9037a11a85c71109c", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"},
@ -25,6 +25,12 @@
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
"makeup_html": {:hex, :makeup_html, "0.1.1", "c3d4abd39d5f7e925faca72ada6e9cc5c6f5fa7cd5bc0158315832656cf14d7f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "44f2a61bc5243645dd7fafeaa6cc28793cd22f3c76b861e066168f9a5b2c26a4"},
"makeup_js": {:hex, :makeup_js, "0.1.0", "ffa8ce9db95d14dcd09045334539d5992d540d63598c592d4805b7674bdd6675", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "3f0c1a5eb52c9737b1679c926574e83bb260ccdedf08b58ee96cca7c685dea75"},
"makeup_rust": {:hex, :makeup_rust, "0.3.0", "6fc4a6a1508a7fbea35d08f15c19bb3ae3a3d62ffd2aaca7ee4825a8b1204cef", [:mix], [{:makeup, "~> 1.1", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1e79ee1995a3b9df9b7d90af7dc02525989efc41390a52065c23782c94ea94e1"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
@ -32,11 +38,13 @@
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"nanoid": {:hex, :nanoid, "2.1.0", "d192a5bf1d774258bc49762b480fca0e3128178fa6d35a464af2a738526607fd", [:mix], [], "hexpm", "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"}, "nanoid": {:hex, :nanoid, "2.1.0", "d192a5bf1d774258bc49762b480fca0e3128178fa6d35a464af2a738526607fd", [:mix], [], "hexpm", "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.0-rc.7", "d2abca526422adea88896769529addb6443390b1d4f1ff9cbe694312d8875fb2", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82a4575f6f3eb5b97922ec6874b0c52b3ca0cc5dcb4b14ddc478cbfa135dd01"}, "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.0-rc.7", "d2abca526422adea88896769529addb6443390b1d4f1ff9cbe694312d8875fb2", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82a4575f6f3eb5b97922ec6874b0c52b3ca0cc5dcb4b14ddc478cbfa135dd01"},
@ -45,6 +53,7 @@
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
"rsvg": {:git, "https://github.com/teamon/rsvg.git", "fa9505fa332e9d5da15f80c08f616a4136079735", []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
"swoosh": {:hex, :swoosh, "1.17.2", "73611f08fc7cb9fa15f4909db36eeb12b70727d5c8b6a7fa0d4a31c6575db29e", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"}, "swoosh": {:hex, :swoosh, "1.17.2", "73611f08fc7cb9fa15f4909db36eeb12b70727d5c8b6a7fa0d4a31c6575db29e", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"},

View file

@ -0,0 +1,9 @@
defmodule Zoeyscomputer.Repo.Migrations.UpdateGistsCodeType do
use Ecto.Migration
def change do
alter table(:gists) do
modify :code, :text
end
end
end

View file

@ -0,0 +1,10 @@
defmodule Zoeyscomputer.Repo.Migrations.AddGistsInfo do
use Ecto.Migration
def change do
alter table(:gists) do
add :title, :string
add :desc, :text
end
end
end

View file

@ -0,0 +1,26 @@
defmodule Zoeyscomputer.Repo.Migrations.ChangeGistsId do
alias Ecto.Repo
alias Zoeyscomputer.IdGenerator
use Ecto.Migration
import Ecto.Query, only: [from: 2]
def change do
alter table(:gists) do
add(:new_id, :string)
end
flush()
execute """
UPDATE gists SET new_id = substring(md5(random()::text), 0, 8)
"""
alter table(:gists) do
remove(:id)
modify(:new_id, :string, primary_key: true)
end
rename(table(:gists), :new_id, to: :id)
end
end

View file

@ -0,0 +1,9 @@
defmodule Zoeyscomputer.Repo.Migrations.GistsAuthorField do
use Ecto.Migration
def change do
alter table(:gists) do
add :author_id, references(:users, on_delete: :nothing)
end
end
end

View file

@ -5,7 +5,7 @@
font-weight: 100; font-weight: 100;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Thin.woff2') format('woff2'), url('TTF/Iosevka-Thin.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Thin.woff2') format('woff2'), url('TTF/Iosevka-Thin.ttf') format('truetype');
} }
@font-face { @font-face {
@ -14,7 +14,7 @@
font-weight: 100; font-weight: 100;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedThin.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThin.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedThin.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThin.ttf') format('truetype');
} }
@font-face { @font-face {
@ -23,7 +23,7 @@
font-weight: 100; font-weight: 100;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -31,7 +31,7 @@
font-display: swap; font-display: swap;
font-weight: 100; font-weight: 100;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -40,7 +40,7 @@
font-weight: 100; font-weight: 100;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -48,7 +48,7 @@
font-display: swap; font-display: swap;
font-weight: 100; font-weight: 100;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -57,7 +57,7 @@
font-weight: 100; font-weight: 100;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ThinItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ThinItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -66,7 +66,7 @@
font-weight: 100; font-weight: 100;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -75,7 +75,7 @@
font-weight: 200; font-weight: 200;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtraLight.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtraLight.ttf') format('truetype');
} }
@font-face { @font-face {
@ -84,7 +84,7 @@
font-weight: 200; font-weight: 200;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLight.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLight.ttf') format('truetype');
} }
@font-face { @font-face {
@ -93,7 +93,7 @@
font-weight: 200; font-weight: 200;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -101,7 +101,7 @@
font-display: swap; font-display: swap;
font-weight: 200; font-weight: 200;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -110,7 +110,7 @@
font-weight: 200; font-weight: 200;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -118,7 +118,7 @@
font-display: swap; font-display: swap;
font-weight: 200; font-weight: 200;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -127,7 +127,7 @@
font-weight: 200; font-weight: 200;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -136,7 +136,7 @@
font-weight: 200; font-weight: 200;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -145,7 +145,7 @@
font-weight: 300; font-weight: 300;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Light.woff2') format('woff2'), url('TTF/Iosevka-Light.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Light.woff2') format('woff2'), url('TTF/Iosevka-Light.ttf') format('truetype');
} }
@font-face { @font-face {
@ -154,7 +154,7 @@
font-weight: 300; font-weight: 300;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLight.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLight.ttf') format('truetype');
} }
@font-face { @font-face {
@ -163,7 +163,7 @@
font-weight: 300; font-weight: 300;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -171,7 +171,7 @@
font-display: swap; font-display: swap;
font-weight: 300; font-weight: 300;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -180,7 +180,7 @@
font-weight: 300; font-weight: 300;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -188,7 +188,7 @@
font-display: swap; font-display: swap;
font-weight: 300; font-weight: 300;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -197,7 +197,7 @@
font-weight: 300; font-weight: 300;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-LightItalic.woff2') format('woff2'), url('TTF/Iosevka-LightItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-LightItalic.woff2') format('woff2'), url('TTF/Iosevka-LightItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -206,7 +206,7 @@
font-weight: 300; font-weight: 300;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -215,7 +215,7 @@
font-weight: 400; font-weight: 400;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Regular.woff2') format('woff2'), url('TTF/Iosevka-Regular.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Regular.woff2') format('woff2'), url('TTF/Iosevka-Regular.ttf') format('truetype');
} }
@font-face { @font-face {
@ -224,7 +224,7 @@
font-weight: 400; font-weight: 400;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Extended.woff2') format('woff2'), url('TTF/Iosevka-Extended.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Extended.woff2') format('woff2'), url('TTF/Iosevka-Extended.ttf') format('truetype');
} }
@font-face { @font-face {
@ -233,7 +233,7 @@
font-weight: 400; font-weight: 400;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -241,7 +241,7 @@
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -250,7 +250,7 @@
font-weight: 400; font-weight: 400;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -258,7 +258,7 @@
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -267,7 +267,7 @@
font-weight: 400; font-weight: 400;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-Italic.woff2') format('woff2'), url('TTF/Iosevka-Italic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Italic.woff2') format('woff2'), url('TTF/Iosevka-Italic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -276,7 +276,7 @@
font-weight: 400; font-weight: 400;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -285,7 +285,7 @@
font-weight: 500; font-weight: 500;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Medium.woff2') format('woff2'), url('TTF/Iosevka-Medium.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Medium.woff2') format('woff2'), url('TTF/Iosevka-Medium.ttf') format('truetype');
} }
@font-face { @font-face {
@ -294,7 +294,7 @@
font-weight: 500; font-weight: 500;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedMedium.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMedium.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedMedium.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMedium.ttf') format('truetype');
} }
@font-face { @font-face {
@ -303,7 +303,7 @@
font-weight: 500; font-weight: 500;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -311,7 +311,7 @@
font-display: swap; font-display: swap;
font-weight: 500; font-weight: 500;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -320,7 +320,7 @@
font-weight: 500; font-weight: 500;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -328,7 +328,7 @@
font-display: swap; font-display: swap;
font-weight: 500; font-weight: 500;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -337,7 +337,7 @@
font-weight: 500; font-weight: 500;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-MediumItalic.woff2') format('woff2'), url('TTF/Iosevka-MediumItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-MediumItalic.woff2') format('woff2'), url('TTF/Iosevka-MediumItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -346,7 +346,7 @@
font-weight: 500; font-weight: 500;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedMediumItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedMediumItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -355,7 +355,7 @@
font-weight: 600; font-weight: 600;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-SemiBold.woff2') format('woff2'), url('TTF/Iosevka-SemiBold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-SemiBold.woff2') format('woff2'), url('TTF/Iosevka-SemiBold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -364,7 +364,7 @@
font-weight: 600; font-weight: 600;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedSemiBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedSemiBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -373,7 +373,7 @@
font-weight: 600; font-weight: 600;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -381,7 +381,7 @@
font-display: swap; font-display: swap;
font-weight: 600; font-weight: 600;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -390,7 +390,7 @@
font-weight: 600; font-weight: 600;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -398,7 +398,7 @@
font-display: swap; font-display: swap;
font-weight: 600; font-weight: 600;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -407,7 +407,7 @@
font-weight: 600; font-weight: 600;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-SemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-SemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -416,7 +416,7 @@
font-weight: 600; font-weight: 600;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedSemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedSemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -425,7 +425,7 @@
font-weight: 700; font-weight: 700;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Bold.woff2') format('woff2'), url('TTF/Iosevka-Bold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Bold.woff2') format('woff2'), url('TTF/Iosevka-Bold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -434,7 +434,7 @@
font-weight: 700; font-weight: 700;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -443,7 +443,7 @@
font-weight: 700; font-weight: 700;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -451,7 +451,7 @@
font-display: swap; font-display: swap;
font-weight: 700; font-weight: 700;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -460,7 +460,7 @@
font-weight: 700; font-weight: 700;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -468,7 +468,7 @@
font-display: swap; font-display: swap;
font-weight: 700; font-weight: 700;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -477,7 +477,7 @@
font-weight: 700; font-weight: 700;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-BoldItalic.woff2') format('woff2'), url('TTF/Iosevka-BoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-BoldItalic.woff2') format('woff2'), url('TTF/Iosevka-BoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -486,7 +486,7 @@
font-weight: 700; font-weight: 700;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -495,7 +495,7 @@
font-weight: 800; font-weight: 800;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtraBold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtraBold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -504,7 +504,7 @@
font-weight: 800; font-weight: 800;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBold.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBold.ttf') format('truetype');
} }
@font-face { @font-face {
@ -513,7 +513,7 @@
font-weight: 800; font-weight: 800;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -521,7 +521,7 @@
font-display: swap; font-display: swap;
font-weight: 800; font-weight: 800;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -530,7 +530,7 @@
font-weight: 800; font-weight: 800;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -538,7 +538,7 @@
font-display: swap; font-display: swap;
font-weight: 800; font-weight: 800;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -547,7 +547,7 @@
font-weight: 800; font-weight: 800;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -556,7 +556,7 @@
font-weight: 800; font-weight: 800;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -565,7 +565,7 @@
font-weight: 900; font-weight: 900;
font-stretch: normal; font-stretch: normal;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-Heavy.woff2') format('woff2'), url('TTF/Iosevka-Heavy.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-Heavy.woff2') format('woff2'), url('TTF/Iosevka-Heavy.ttf') format('truetype');
} }
@font-face { @font-face {
@ -574,7 +574,7 @@
font-weight: 900; font-weight: 900;
font-stretch: expanded; font-stretch: expanded;
font-style: normal; font-style: normal;
src: url('WOFF2/Iosevka-ExtendedHeavy.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavy.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedHeavy.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavy.ttf') format('truetype');
} }
@font-face { @font-face {
@ -583,7 +583,7 @@
font-weight: 900; font-weight: 900;
font-stretch: normal; font-stretch: normal;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -591,7 +591,7 @@
font-display: swap; font-display: swap;
font-weight: 900; font-weight: 900;
font-stretch: normal; font-stretch: normal;
src: url('WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -600,7 +600,7 @@
font-weight: 900; font-weight: 900;
font-stretch: expanded; font-stretch: expanded;
font-style: oblique; font-style: oblique;
src: url('WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -608,7 +608,7 @@
font-display: swap; font-display: swap;
font-weight: 900; font-weight: 900;
font-stretch: expanded; font-stretch: expanded;
src: url('WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype');
} }
@font-face { @font-face {
@ -617,7 +617,7 @@
font-weight: 900; font-weight: 900;
font-stretch: normal; font-stretch: normal;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-HeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-HeavyItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-HeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-HeavyItalic.ttf') format('truetype');
} }
@font-face { @font-face {
@ -626,5 +626,5 @@
font-weight: 900; font-weight: 900;
font-stretch: expanded; font-stretch: expanded;
font-style: italic; font-style: italic;
src: url('WOFF2/Iosevka-ExtendedHeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyItalic.ttf') format('truetype'); src: url('/fonts/WOFF2/Iosevka-ExtendedHeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyItalic.ttf') format('truetype');
} }