create gists
This commit is contained in:
parent
79a17290d5
commit
43a8412f06
90 changed files with 1777 additions and 2107 deletions
|
|
@ -21,6 +21,55 @@ import "phoenix_html";
|
||||||
import { Socket } from "phoenix";
|
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 {
|
||||||
|
DropdownAnimation,
|
||||||
|
SearchableDropdown,
|
||||||
|
} from "./hooks/searchable_dropdown";
|
||||||
|
|
||||||
|
let Hooks = {
|
||||||
|
CodeBlockHook,
|
||||||
|
SearchableDropdown,
|
||||||
|
DropdownAnimation,
|
||||||
|
};
|
||||||
|
|
||||||
|
Hooks.ClickOutside = {
|
||||||
|
mounted() {
|
||||||
|
this.handleClick = (e) => {
|
||||||
|
if (!this.el.contains(e.target)) {
|
||||||
|
this.pushEventTo(this.el, "close_dropdown", {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("click", this.handleClick);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.removeEventListener("click", this.handleClick);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Hooks.ClientSearch = {
|
||||||
|
mounted() {
|
||||||
|
this.options = JSON.parse(this.el.dataset.options);
|
||||||
|
this.searchInput = this.el.querySelector("input");
|
||||||
|
this.optionsContainer = this.el.querySelector(
|
||||||
|
"#dropdown-options-container",
|
||||||
|
);
|
||||||
|
|
||||||
|
this.searchInput.addEventListener("input", (e) => {
|
||||||
|
const query = e.target.value.toLowerCase();
|
||||||
|
const options = this.el.querySelectorAll(".dropdown-option");
|
||||||
|
|
||||||
|
options.forEach((option) => {
|
||||||
|
const value = option.dataset.value.toLowerCase();
|
||||||
|
if (value.includes(query)) {
|
||||||
|
option.style.display = "";
|
||||||
|
} else {
|
||||||
|
option.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener("phx:copy", (event) => {
|
window.addEventListener("phx:copy", (event) => {
|
||||||
let button = event.detail.dispatcher;
|
let button = event.detail.dispatcher;
|
||||||
|
|
@ -40,6 +89,7 @@ let csrfToken = document
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 2500,
|
longPollFallbackMs: 2500,
|
||||||
params: { _csrf_token: csrfToken },
|
params: { _csrf_token: csrfToken },
|
||||||
|
hooks: Hooks,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
|
|
|
||||||
59
assets/js/hooks/code_block_hook.js
Normal file
59
assets/js/hooks/code_block_hook.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// assets/js/hooks/code_block_hook.js
|
||||||
|
import { codeToHtml } from "shiki";
|
||||||
|
|
||||||
|
const CodeBlockHook = {
|
||||||
|
mounted() {
|
||||||
|
const code = this.el.dataset.code;
|
||||||
|
const language = this.el.dataset.language;
|
||||||
|
const highlightedLines = JSON.parse(
|
||||||
|
this.el.dataset.highlightedLines || "[]",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(code);
|
||||||
|
console.log("language", language);
|
||||||
|
|
||||||
|
const lines = code.split("\n");
|
||||||
|
|
||||||
|
// Convert line numbers to decorations
|
||||||
|
const decorations = highlightedLines
|
||||||
|
.map((line) => {
|
||||||
|
// Convert to 0-based index and ensure valid line number
|
||||||
|
const lineIndex = line - 1;
|
||||||
|
if (lineIndex < 0 || lineIndex >= lines.length) return null;
|
||||||
|
|
||||||
|
// Get the actual line length
|
||||||
|
const lineLength = lines[lineIndex].length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Line numbers are 0-indexed
|
||||||
|
start: { line: lineIndex, character: 0 },
|
||||||
|
end: { line: lineIndex, character: lineLength },
|
||||||
|
properties: {
|
||||||
|
// Apply both background color and a class for flexibility
|
||||||
|
class: "highlight",
|
||||||
|
style: "background-color: rgba(200,200,255,0.1);",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean); // Remove any null entries from invalid line numbers
|
||||||
|
|
||||||
|
codeToHtml(code, {
|
||||||
|
lang: language,
|
||||||
|
theme: "catppuccin-mocha",
|
||||||
|
decorations,
|
||||||
|
}).then((html) => {
|
||||||
|
console.log(html);
|
||||||
|
|
||||||
|
// Replace the code content while preserving the pre/code structure
|
||||||
|
const tempDiv = document.createElement("div");
|
||||||
|
tempDiv.innerHTML = html;
|
||||||
|
const codeContent = tempDiv.querySelector("code");
|
||||||
|
|
||||||
|
if (codeContent) {
|
||||||
|
this.el.querySelector("code").innerHTML = codeContent.innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeBlockHook;
|
||||||
32
assets/js/hooks/searchable_dropdown.js
Normal file
32
assets/js/hooks/searchable_dropdown.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
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 };
|
||||||
File diff suppressed because it is too large
Load diff
71
assets/package-lock.json
generated
71
assets/package-lock.json
generated
|
|
@ -10,11 +10,13 @@
|
||||||
"@esbuild/linux-x64": "^0.24.0",
|
"@esbuild/linux-x64": "^0.24.0",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
|
"i": "^0.3.7",
|
||||||
"phoenix": "^1.7.14",
|
"phoenix": "^1.7.14",
|
||||||
"phoenix_html": "^3.3.4",
|
"phoenix_html": "^3.3.4",
|
||||||
"phoenix_live_view": "^1.0.0-rc.7",
|
"phoenix_live_view": "^1.0.0-rc.7",
|
||||||
"shiki": "^1.22.0",
|
"shiki": "^1.22.1",
|
||||||
"tailwindcss": "^3.4.14"
|
"tailwindcss": "^3.4.14",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/phoenix": "^1.6.5",
|
"@types/phoenix": "^1.6.5",
|
||||||
|
|
@ -536,44 +538,44 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/core": {
|
"node_modules/@shikijs/core": {
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.1.tgz",
|
||||||
"integrity": "sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==",
|
"integrity": "sha512-bqAhT/Ri5ixV4oYsvJNH8UJjpjbINWlWyXY6tBTsP4OmD6XnFv43nRJ+lTdxd2rmG5pgam/x+zGR6kLRXrpEKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/engine-javascript": "1.22.0",
|
"@shikijs/engine-javascript": "1.22.1",
|
||||||
"@shikijs/engine-oniguruma": "1.22.0",
|
"@shikijs/engine-oniguruma": "1.22.1",
|
||||||
"@shikijs/types": "1.22.0",
|
"@shikijs/types": "1.22.1",
|
||||||
"@shikijs/vscode-textmate": "^9.3.0",
|
"@shikijs/vscode-textmate": "^9.3.0",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"hast-util-to-html": "^9.0.3"
|
"hast-util-to-html": "^9.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-javascript": {
|
"node_modules/@shikijs/engine-javascript": {
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.1.tgz",
|
||||||
"integrity": "sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==",
|
"integrity": "sha512-540pyoy0LWe4jj2BVbgELwOFu1uFvRI7lg4hdsExrSXA9x7gqfzZ/Nnh4RfX86aDAgJ647gx4TCmRwACbnQSvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.22.0",
|
"@shikijs/types": "1.22.1",
|
||||||
"@shikijs/vscode-textmate": "^9.3.0",
|
"@shikijs/vscode-textmate": "^9.3.0",
|
||||||
"oniguruma-to-js": "0.4.3"
|
"oniguruma-to-js": "0.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-oniguruma": {
|
"node_modules/@shikijs/engine-oniguruma": {
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.1.tgz",
|
||||||
"integrity": "sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==",
|
"integrity": "sha512-L+1Vmd+a2kk8HtogUFymQS6BjUfJnzcWoUp1BUgxoDiklbKSMvrsMuLZGevTOP1m0rEjgnC5MsDmsr8lX1lC+Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.22.0",
|
"@shikijs/types": "1.22.1",
|
||||||
"@shikijs/vscode-textmate": "^9.3.0"
|
"@shikijs/vscode-textmate": "^9.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/types": {
|
"node_modules/@shikijs/types": {
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.1.tgz",
|
||||||
"integrity": "sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==",
|
"integrity": "sha512-+45f8mu/Hxqs6Kyhfm98Nld5n7Q7lwhjU8UtdQwrOPs7BnM4VAb929O3IQ2ce+4D7SlNFlZGd8CnKRSnwbQreQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/vscode-textmate": "^9.3.0",
|
"@shikijs/vscode-textmate": "^9.3.0",
|
||||||
|
|
@ -1134,6 +1136,14 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i": {
|
||||||
|
"version": "0.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
|
||||||
|
"integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-binary-path": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
|
|
@ -1858,15 +1868,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/shiki": {
|
"node_modules/shiki": {
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.1.tgz",
|
||||||
"integrity": "sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==",
|
"integrity": "sha512-PbJ6XxrWLMwB2rm3qdjIHNm3zq4SfFnOx0B3rEoi4AN8AUngsdyZ1tRe5slMPtn6jQkbUURLNZPpLR7Do3k78g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/core": "1.22.0",
|
"@shikijs/core": "1.22.1",
|
||||||
"@shikijs/engine-javascript": "1.22.0",
|
"@shikijs/engine-javascript": "1.22.1",
|
||||||
"@shikijs/engine-oniguruma": "1.22.0",
|
"@shikijs/engine-oniguruma": "1.22.1",
|
||||||
"@shikijs/types": "1.22.0",
|
"@shikijs/types": "1.22.1",
|
||||||
"@shikijs/vscode-textmate": "^9.3.0",
|
"@shikijs/vscode-textmate": "^9.3.0",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
}
|
}
|
||||||
|
|
@ -2083,6 +2093,15 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwindcss-animate": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.0.0 || insiders"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thenify": {
|
"node_modules/thenify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@
|
||||||
"@esbuild/linux-x64": "^0.24.0",
|
"@esbuild/linux-x64": "^0.24.0",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
|
"i": "^0.3.7",
|
||||||
"phoenix": "^1.7.14",
|
"phoenix": "^1.7.14",
|
||||||
"phoenix_html": "^3.3.4",
|
"phoenix_html": "^3.3.4",
|
||||||
"phoenix_live_view": "^1.0.0-rc.7",
|
"phoenix_live_view": "^1.0.0-rc.7",
|
||||||
"shiki": "^1.22.0",
|
"shiki": "^1.22.1",
|
||||||
"tailwindcss": "^3.4.14"
|
"tailwindcss": "^3.4.14",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/phoenix": "^1.6.5",
|
"@types/phoenix": "^1.6.5",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,45 @@ module.exports = {
|
||||||
"../lib/zoeyscomputer_web/**/*.*ex",
|
"../lib/zoeyscomputer_web/**/*.*ex",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
|
fontSize: {
|
||||||
|
sm: "1rem",
|
||||||
|
base: "1.2rem",
|
||||||
|
xl: "1.45rem",
|
||||||
|
"2xl": "1.563rem",
|
||||||
|
"3xl": "1.953rem",
|
||||||
|
"4xl": "2.441rem",
|
||||||
|
"5xl": "3.052rem",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Iosevka Web", "monospace"],
|
||||||
|
},
|
||||||
extend: {
|
extend: {
|
||||||
|
keyframes: {
|
||||||
|
"fade-in": {
|
||||||
|
"0%": { opacity: 0 },
|
||||||
|
"100%": { opacity: 1 },
|
||||||
|
},
|
||||||
|
"fade-out": {
|
||||||
|
"0%": { opacity: 1 },
|
||||||
|
"100%": { opacity: 0 },
|
||||||
|
},
|
||||||
|
"zoom-in": {
|
||||||
|
"0%": { transform: "scale(0.95)" },
|
||||||
|
"100%": { transform: "scale(1)" },
|
||||||
|
},
|
||||||
|
"zoom-out": {
|
||||||
|
"0%": { transform: "scale(1)" },
|
||||||
|
"100%": { transform: "scale(0.95)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"fade-in-0": "fade-in 0.2s ease-in-out",
|
||||||
|
"fade-in": "fade-in 0.3s ease-in-out",
|
||||||
|
"fade-out-0": "fade-out 0.2s ease-in-out",
|
||||||
|
"fade-out": "fade-out 0.3s ease-in-out",
|
||||||
|
"zoom-in-95": "zoom-in 0.2s ease-in-out",
|
||||||
|
"zoom-out-95": "zoom-out 0.2s ease-in-out",
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
brand: "#FD4F00",
|
brand: "#FD4F00",
|
||||||
},
|
},
|
||||||
|
|
@ -25,6 +63,7 @@ module.exports = {
|
||||||
//
|
//
|
||||||
// <div class="phx-click-loading:animate-ping">
|
// <div class="phx-click-loading:animate-ping">
|
||||||
//
|
//
|
||||||
|
require("tailwindcss-animate"),
|
||||||
require("@catppuccin/tailwindcss")({
|
require("@catppuccin/tailwindcss")({
|
||||||
prefix: "ctp",
|
prefix: "ctp",
|
||||||
defaultFlavor: "mocha",
|
defaultFlavor: "mocha",
|
||||||
|
|
|
||||||
104
lib/zoeyscomputer/gists.ex
Normal file
104
lib/zoeyscomputer/gists.ex
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
defmodule Zoeyscomputer.Gists do
|
||||||
|
@moduledoc """
|
||||||
|
The Gists context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query, warn: false
|
||||||
|
alias Zoeyscomputer.Repo
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of gists.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_gists()
|
||||||
|
[%Gist{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_gists do
|
||||||
|
Repo.all(Gist)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single gist.
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if the Gist does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_gist!(123)
|
||||||
|
%Gist{}
|
||||||
|
|
||||||
|
iex> get_gist!(456)
|
||||||
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_gist!(id), do: Repo.get!(Gist, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a gist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> create_gist(%{field: value})
|
||||||
|
{:ok, %Gist{}}
|
||||||
|
|
||||||
|
iex> create_gist(%{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create_gist(attrs \\ %{}) do
|
||||||
|
%Gist{}
|
||||||
|
|> Gist.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a gist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_gist(gist, %{field: new_value})
|
||||||
|
{:ok, %Gist{}}
|
||||||
|
|
||||||
|
iex> update_gist(gist, %{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update_gist(%Gist{} = gist, attrs) do
|
||||||
|
gist
|
||||||
|
|> Gist.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a gist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> delete_gist(gist)
|
||||||
|
{:ok, %Gist{}}
|
||||||
|
|
||||||
|
iex> delete_gist(gist)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete_gist(%Gist{} = gist) do
|
||||||
|
Repo.delete(gist)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking gist changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_gist(gist)
|
||||||
|
%Ecto.Changeset{data: %Gist{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_gist(%Gist{} = gist, attrs \\ %{}) do
|
||||||
|
Gist.changeset(gist, attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
18
lib/zoeyscomputer/gists/gist.ex
Normal file
18
lib/zoeyscomputer/gists/gist.ex
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Zoeyscomputer.Gists.Gist do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "gists" do
|
||||||
|
field :code, :string
|
||||||
|
field :lang, :string
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(gist, attrs) do
|
||||||
|
gist
|
||||||
|
|> cast(attrs, [:code, :lang])
|
||||||
|
|> validate_required([:code, :lang])
|
||||||
|
end
|
||||||
|
end
|
||||||
93
lib/zoeyscomputer_web/components/code_block.ex
Normal file
93
lib/zoeyscomputer_web/components/code_block.ex
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
defmodule ZoeyscomputerWeb.CodeBlock do
|
||||||
|
use Phoenix.Component
|
||||||
|
alias Phoenix.LiveView.JS
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
A code block component with syntax highlighting using Shiki.
|
||||||
|
|
||||||
|
## Features:
|
||||||
|
- Syntax highlighting with Shiki
|
||||||
|
- Optional line numbers
|
||||||
|
- Optional highlighted lines
|
||||||
|
- Optional title
|
||||||
|
- Copy button
|
||||||
|
- Catppuccin theme styling
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a code block with syntax highlighting.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<.code_block
|
||||||
|
code="def hello, do: :world"
|
||||||
|
language="elixir"
|
||||||
|
title="Example Code"
|
||||||
|
line_numbers={true}
|
||||||
|
highlighted_lines={[1, 3]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Options
|
||||||
|
* `:code` - Required. The code string to highlight
|
||||||
|
* `:language` - Required. Programming language for syntax highlighting
|
||||||
|
* `:title` - Optional. Title displayed above the code block
|
||||||
|
* `:line_numbers` - Optional. Show line numbers (default: false)
|
||||||
|
* `:highlighted_lines` - Optional. List of line numbers to highlight
|
||||||
|
"""
|
||||||
|
attr :code, :string, required: true
|
||||||
|
attr :language, :string, required: true
|
||||||
|
attr :title, :string, default: nil
|
||||||
|
attr :line_numbers, :boolean, default: false
|
||||||
|
attr :highlighted_lines, :list, default: []
|
||||||
|
|
||||||
|
def code_block(assigns) do
|
||||||
|
# Calculate the number of lines for line numbers
|
||||||
|
assigns = assign(assigns, :num_lines, String.split(assigns.code, "\n") |> length())
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<div class="relative ctp-bg-base rounded-lg overflow-hidden">
|
||||||
|
<%= if @title do %>
|
||||||
|
<div class="ctp-bg-mantle px-4 py-2 border-b ctp-border-surface0">
|
||||||
|
<h3 class="ctp-text-text text-sm font-medium"><%= @title %></h3>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<%= if @line_numbers do %>
|
||||||
|
<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">
|
||||||
|
<%= for line_num <- 1..@num_lines do %>
|
||||||
|
<span class={[
|
||||||
|
"text-sm leading-6",
|
||||||
|
line_num in @highlighted_lines && "ctp-text-mauve font-medium"
|
||||||
|
]}>
|
||||||
|
<%= line_num %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class={["overflow-x-auto", @line_numbers && "pl-12"]}>
|
||||||
|
<pre
|
||||||
|
class="p-4"
|
||||||
|
id={"code-block-#{System.unique_integer()}"}
|
||||||
|
phx-hook="CodeBlockHook"
|
||||||
|
data-code={@code}
|
||||||
|
data-language={@language}
|
||||||
|
data-highlighted-lines={Jason.encode!(@highlighted_lines)}
|
||||||
|
><code class="text-sm"><%= @code %></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ctp-bg-mantle px-4 py-2 border-t ctp-border-surface0 flex justify-end">
|
||||||
|
<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>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,29 +1,3 @@
|
||||||
<%!-- <header class="px-4 sm:px-6 lg:px-8"> --%>
|
|
||||||
<%!-- <div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm"> --%>
|
|
||||||
<%!-- <div class="flex items-center gap-4"> --%>
|
|
||||||
<%!-- <a href="/"> --%>
|
|
||||||
<%!-- <img src={~p"/images/logo.svg"} width="36" /> --%>
|
|
||||||
<%!-- </a> --%>
|
|
||||||
<%!-- <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6"> --%>
|
|
||||||
<%!-- v<%= Application.spec(:phoenix, :vsn) %> --%>
|
|
||||||
<%!-- </p> --%>
|
|
||||||
<%!-- </div> --%>
|
|
||||||
<%!-- <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900"> --%>
|
|
||||||
<%!-- <a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700"> --%>
|
|
||||||
<%!-- @elixirphoenix --%>
|
|
||||||
<%!-- </a> --%>
|
|
||||||
<%!-- <a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700"> --%>
|
|
||||||
<%!-- GitHub --%>
|
|
||||||
<%!-- </a> --%>
|
|
||||||
<%!-- <a --%>
|
|
||||||
<%!-- href="https://hexdocs.pm/phoenix/overview.html" --%>
|
|
||||||
<%!-- class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80" --%>
|
|
||||||
<%!-- > --%>
|
|
||||||
<%!-- Get Started <span aria-hidden="true">→</span> --%>
|
|
||||||
<%!-- </a> --%>
|
|
||||||
<%!-- </div> --%>
|
|
||||||
<%!-- </div> --%>
|
|
||||||
<%!-- </header> --%>
|
|
||||||
<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 max-w-2xl">
|
||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,37 @@
|
||||||
<%= assigns[:page_title] || "" %>
|
<%= assigns[:page_title] || "" %>
|
||||||
</.live_title>
|
</.live_title>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
|
<link phx-track-statuc rel="stylesheet" href={~p"/fonts/Iosevka.css"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="ctp-mocha bg-ctp-base text-ctp-overlay0">
|
<body class="ctp-mocha bg-ctp-base text-base text-ctp-overlay0">
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="p-4 grow">
|
<div class="p-4">
|
||||||
<.link
|
<.link
|
||||||
class="text-ctp-mauve font-bold hover:border-b-2 border-ctp-mauve"
|
class="text-ctp-mauve text-sm font-bold hover:border-b-2 border-ctp-mauve"
|
||||||
navigate={~p"/"}
|
navigate={~p"/"}
|
||||||
>
|
>
|
||||||
zoey
|
zoey
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<.link
|
||||||
|
class="text-ctp-pink text-sm hover:border-b-2 border-ctp-pink"
|
||||||
|
navigate={~p"/images"}
|
||||||
|
>
|
||||||
|
image
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 grow">
|
||||||
|
<.link
|
||||||
|
class="text-ctp-pink text-sm hover:border-b-2 border-ctp-pink"
|
||||||
|
navigate={~p"/gists"}
|
||||||
|
>
|
||||||
|
gists
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
||||||
<li class="text-[0.8125rem] leading-6">
|
<li class="text-[0.8125rem] leading-6">
|
||||||
<%= @current_user.email %>
|
<%= @current_user.email %>
|
||||||
|
|
|
||||||
81
lib/zoeyscomputer_web/components/searchable_dropdown.ex
Normal file
81
lib/zoeyscomputer_web/components/searchable_dropdown.ex
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
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
|
||||||
43
lib/zoeyscomputer_web/controllers/gist_controller.ex
Normal file
43
lib/zoeyscomputer_web/controllers/gist_controller.ex
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistController do
|
||||||
|
use ZoeyscomputerWeb, :controller
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
action_fallback ZoeyscomputerWeb.FallbackController
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
gists = Gists.list_gists()
|
||||||
|
render(conn, :index, gists: gists)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"gist" => gist_params}) do
|
||||||
|
with {:ok, %Gist{} = gist} <- Gists.create_gist(gist_params) do
|
||||||
|
conn
|
||||||
|
|> put_status(:created)
|
||||||
|
|> put_resp_header("location", ~p"/api/gists/#{gist}")
|
||||||
|
|> render(:show, gist: gist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(conn, %{"id" => id}) do
|
||||||
|
gist = Gists.get_gist!(id)
|
||||||
|
render(conn, :show, gist: gist)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "gist" => gist_params}) do
|
||||||
|
gist = Gists.get_gist!(id)
|
||||||
|
|
||||||
|
with {:ok, %Gist{} = gist} <- Gists.update_gist(gist, gist_params) do
|
||||||
|
render(conn, :show, gist: gist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"id" => id}) do
|
||||||
|
gist = Gists.get_gist!(id)
|
||||||
|
|
||||||
|
with {:ok, %Gist{}} <- Gists.delete_gist(gist) do
|
||||||
|
send_resp(conn, :no_content, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
25
lib/zoeyscomputer_web/controllers/gist_json.ex
Normal file
25
lib/zoeyscomputer_web/controllers/gist_json.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistJSON do
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a list of gists.
|
||||||
|
"""
|
||||||
|
def index(%{gists: gists}) do
|
||||||
|
%{data: for(gist <- gists, do: data(gist))}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a single gist.
|
||||||
|
"""
|
||||||
|
def show(%{gist: gist}) do
|
||||||
|
%{data: data(gist)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp data(%Gist{} = gist) do
|
||||||
|
%{
|
||||||
|
id: gist.id,
|
||||||
|
code: gist.code,
|
||||||
|
lang: gist.lang
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<div class="container p-4 flex justify-center align-middle">
|
|
||||||
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
|
|
||||||
<h1 class="font-bold text-ctp-mauve">zoey</h1>
|
|
||||||
<p class="text-ctp-text"><i>Software Engineer 🏳️⚧️</i></p>
|
|
||||||
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
|
|
||||||
Currently cooking this up, stay tuned... in the meantime, you can monitor my server's resources.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
83
lib/zoeyscomputer_web/live/gist_live/form_component.ex
Normal file
83
lib/zoeyscomputer_web/live/gist_live/form_component.ex
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistLive.FormComponent do
|
||||||
|
use ZoeyscomputerWeb, :live_component
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div>
|
||||||
|
<.header>
|
||||||
|
<%= @title %>
|
||||||
|
<:subtitle>Use this form to manage gist records in your database.</:subtitle>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.simple_form
|
||||||
|
for={@form}
|
||||||
|
id="gist-form"
|
||||||
|
phx-target={@myself}
|
||||||
|
phx-change="validate"
|
||||||
|
phx-submit="save"
|
||||||
|
>
|
||||||
|
<.input field={@form[:code]} type="text" label="Code" />
|
||||||
|
<.input field={@form[:lang]} type="text" label="Lang" />
|
||||||
|
<:actions>
|
||||||
|
<.button phx-disable-with="Saving...">Save Gist</.button>
|
||||||
|
</:actions>
|
||||||
|
</.simple_form>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(%{gist: gist} = assigns, socket) do
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign_new(:form, fn ->
|
||||||
|
to_form(Gists.change_gist(gist))
|
||||||
|
end)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("validate", %{"gist" => gist_params}, socket) do
|
||||||
|
changeset = Gists.change_gist(socket.assigns.gist, gist_params)
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save", %{"gist" => gist_params}, socket) do
|
||||||
|
save_gist(socket, socket.assigns.action, gist_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_gist(socket, :edit, gist_params) do
|
||||||
|
case Gists.update_gist(socket.assigns.gist, gist_params) do
|
||||||
|
{:ok, gist} ->
|
||||||
|
notify_parent({:saved, gist})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "Gist updated successfully")
|
||||||
|
|> push_patch(to: socket.assigns.patch)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_gist(socket, :new, gist_params) do
|
||||||
|
case Gists.create_gist(gist_params) do
|
||||||
|
{:ok, gist} ->
|
||||||
|
notify_parent({:saved, gist})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "Gist created successfully")
|
||||||
|
|> push_patch(to: socket.assigns.patch)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||||
|
end
|
||||||
47
lib/zoeyscomputer_web/live/gist_live/index.ex
Normal file
47
lib/zoeyscomputer_web/live/gist_live/index.ex
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistLive.Index do
|
||||||
|
use ZoeyscomputerWeb, :live_view
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, stream(socket, :gists, Gists.list_gists())}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(params, _url, socket) do
|
||||||
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Edit Gist")
|
||||||
|
|> assign(:gist, Gists.get_gist!(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :new, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "New Gist")
|
||||||
|
|> assign(:gist, %Gist{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :index, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Listing Gists")
|
||||||
|
|> assign(:gist, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({ZoeyscomputerWeb.GistLive.FormComponent, {:saved, gist}}, socket) do
|
||||||
|
{:noreply, stream_insert(socket, :gists, gist)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
|
gist = Gists.get_gist!(id)
|
||||||
|
{:ok, _} = Gists.delete_gist(gist)
|
||||||
|
|
||||||
|
{:noreply, stream_delete(socket, :gists, gist)}
|
||||||
|
end
|
||||||
|
end
|
||||||
42
lib/zoeyscomputer_web/live/gist_live/index.html.heex
Normal file
42
lib/zoeyscomputer_web/live/gist_live/index.html.heex
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<.header>
|
||||||
|
Listing Gists
|
||||||
|
<:actions>
|
||||||
|
<.link patch={~p"/gists/new"}>
|
||||||
|
<.button>New Gist</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.table
|
||||||
|
id="gists"
|
||||||
|
rows={@streams.gists}
|
||||||
|
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="Lang"><%= gist.lang %></:col>
|
||||||
|
<:action :let={{_id, gist}}>
|
||||||
|
<div class="sr-only">
|
||||||
|
<.link navigate={~p"/gists/#{gist}"}>Show</.link>
|
||||||
|
</div>
|
||||||
|
<.link patch={~p"/gists/#{gist}/edit"}>Edit</.link>
|
||||||
|
</:action>
|
||||||
|
<:action :let={{id, gist}}>
|
||||||
|
<.link
|
||||||
|
phx-click={JS.push("delete", value: %{id: gist.id}) |> hide("##{id}")}
|
||||||
|
data-confirm="Are you sure?"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
|
||||||
|
<.modal :if={@live_action in [:new, :edit]} id="gist-modal" show on_cancel={JS.patch(~p"/gists")}>
|
||||||
|
<.live_component
|
||||||
|
module={ZoeyscomputerWeb.GistLive.FormComponent}
|
||||||
|
id={@gist.id || :new}
|
||||||
|
title={@page_title}
|
||||||
|
action={@live_action}
|
||||||
|
gist={@gist}
|
||||||
|
patch={~p"/gists"}
|
||||||
|
/>
|
||||||
|
</.modal>
|
||||||
21
lib/zoeyscomputer_web/live/gist_live/show.ex
Normal file
21
lib/zoeyscomputer_web/live/gist_live/show.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistLive.Show do
|
||||||
|
use ZoeyscomputerWeb, :live_view
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|
|> assign(:gist, Gists.get_gist!(id))}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp page_title(:show), do: "Show Gist"
|
||||||
|
defp page_title(:edit), do: "Edit Gist"
|
||||||
|
end
|
||||||
27
lib/zoeyscomputer_web/live/gist_live/show.html.heex
Normal file
27
lib/zoeyscomputer_web/live/gist_live/show.html.heex
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<.header>
|
||||||
|
Gist <%= @gist.id %>
|
||||||
|
<:subtitle>This is a gist record from your database.</:subtitle>
|
||||||
|
<:actions>
|
||||||
|
<.link patch={~p"/gists/#{@gist}/show/edit"} phx-click={JS.push_focus()}>
|
||||||
|
<.button>Edit gist</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.list>
|
||||||
|
<:item title="Code"><%= @gist.code %></:item>
|
||||||
|
<:item title="Lang"><%= @gist.lang %></:item>
|
||||||
|
</.list>
|
||||||
|
|
||||||
|
<.back navigate={~p"/gists"}>Back to gists</.back>
|
||||||
|
|
||||||
|
<.modal :if={@live_action == :edit} id="gist-modal" show on_cancel={JS.patch(~p"/gists/#{@gist}")}>
|
||||||
|
<.live_component
|
||||||
|
module={ZoeyscomputerWeb.GistLive.FormComponent}
|
||||||
|
id={@gist.id}
|
||||||
|
title={@page_title}
|
||||||
|
action={@live_action}
|
||||||
|
gist={@gist}
|
||||||
|
patch={~p"/gists/#{@gist}"}
|
||||||
|
/>
|
||||||
|
</.modal>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
defmodule ZoeyscomputerWeb.HomeLive do
|
defmodule ZoeyscomputerWeb.HomeLive do
|
||||||
alias Hex.API.Key
|
|
||||||
use ZoeyscomputerWeb, :live_view
|
use ZoeyscomputerWeb, :live_view
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
|
|
@ -40,8 +39,8 @@ defmodule ZoeyscomputerWeb.HomeLive do
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="container p-4 flex justify-center align-middle">
|
<div class="container p-8 flex justify-center align-middle">
|
||||||
<div class="border border-ctp-overlay0 rounded-md p-4 flex-col flex items-center">
|
<div class="border border-ctp-overlay0 rounded-md p-8 flex-col flex items-center">
|
||||||
<h1 class="font-bold text-ctp-mauve">zoey</h1>
|
<h1 class="font-bold text-ctp-mauve">zoey</h1>
|
||||||
<p class="text-ctp-text"><i>Software Engineer 🏳️⚧️</i></p>
|
<p class="text-ctp-text"><i>Software Engineer 🏳️⚧️</i></p>
|
||||||
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
|
<p class="max-w-96 text-center mt-4 text-ctp-overlay2">
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
defmodule ZoeyscomputerWeb.LinkLive.FormComponent do
|
|
||||||
use ZoeyscomputerWeb, :live_component
|
|
||||||
|
|
||||||
alias Zoeyscomputer.Links
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div>
|
|
||||||
<.header>
|
|
||||||
<%= @title %>
|
|
||||||
<:subtitle>Use this form to manage link records in your database.</:subtitle>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.simple_form
|
|
||||||
for={@form}
|
|
||||||
id="link-form"
|
|
||||||
phx-target={@myself}
|
|
||||||
phx-change="validate"
|
|
||||||
phx-submit="save"
|
|
||||||
>
|
|
||||||
<.input field={@form[:url]} type="text" label="Url" />
|
|
||||||
<:actions>
|
|
||||||
<.button phx-disable-with="Saving...">Save Link</.button>
|
|
||||||
</:actions>
|
|
||||||
</.simple_form>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def update(%{link: link} = assigns, socket) do
|
|
||||||
{:ok,
|
|
||||||
socket
|
|
||||||
|> assign(assigns)
|
|
||||||
|> assign_new(:form, fn ->
|
|
||||||
to_form(Links.change_link(link))
|
|
||||||
end)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("validate", %{"link" => link_params}, socket) do
|
|
||||||
changeset = Links.change_link(socket.assigns.link, link_params)
|
|
||||||
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("save", %{"link" => link_params}, socket) do
|
|
||||||
save_link(socket, socket.assigns.action, link_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_link(socket, :edit, link_params) do
|
|
||||||
case Links.update_link(socket.assigns.link, link_params) do
|
|
||||||
{:ok, link} ->
|
|
||||||
notify_parent({:saved, link})
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:info, "Link updated successfully")
|
|
||||||
|> push_patch(to: socket.assigns.patch)}
|
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{} = changeset} ->
|
|
||||||
{:noreply, assign(socket, form: to_form(changeset))}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_link(socket, :new, link_params) do
|
|
||||||
case Links.create_link(link_params) do
|
|
||||||
{:ok, link} ->
|
|
||||||
notify_parent({:saved, link})
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> put_flash(:info, "Link created successfully")
|
|
||||||
|> push_patch(to: socket.assigns.patch)}
|
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{} = changeset} ->
|
|
||||||
{:noreply, assign(socket, form: to_form(changeset))}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
|
||||||
end
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
defmodule ZoeyscomputerWeb.LinkLive.Index do
|
|
||||||
use ZoeyscomputerWeb, :live_view
|
|
||||||
|
|
||||||
alias Zoeyscomputer.Links
|
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
user_id = socket.assigns.current_user.id
|
|
||||||
|
|
||||||
changeset = Links.Link.changeset(%Links.Link{})
|
|
||||||
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> assign(:links, Links.list_links(user_id))
|
|
||||||
|> assign(:form, to_form(changeset))
|
|
||||||
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("submit", %{"link" => link_params}, socket) do
|
|
||||||
params =
|
|
||||||
link_params
|
|
||||||
|> Map.put("user_id", socket.assigns.current_user.id)
|
|
||||||
|
|
||||||
case Links.create_link(params) do
|
|
||||||
{:ok, link} ->
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> assign(:links, [link | socket.assigns.links])
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
|
|
||||||
{:error, changeset} ->
|
|
||||||
socket
|
|
||||||
|> assign(:form, to_form(changeset))
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<div class="flex gap-2">
|
|
||||||
<h1 class="text-2xl grow font-bold">Links</h1>
|
|
||||||
|
|
||||||
<.link
|
|
||||||
navigate={~p"/links/new"}
|
|
||||||
class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md"
|
|
||||||
>
|
|
||||||
Add Link
|
|
||||||
</.link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divide-y">
|
|
||||||
<div :for={link <- @links}>
|
|
||||||
<div>
|
|
||||||
<div class="font-bold"><%= link.url %></div>
|
|
||||||
<div class="text-sm"><%= link.inserted_at %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<.form for={@form} phx-submit="submit">
|
|
||||||
<div class="flex gap-2 items-end">
|
|
||||||
<div class="grow">
|
|
||||||
<.input field={@form[:url]} type="text" label="url" />
|
|
||||||
</div>
|
|
||||||
<button class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md">
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</.form>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
defmodule ZoeyscomputerWeb.LinkLive.New do
|
|
||||||
use ZoeyscomputerWeb, :live_view
|
|
||||||
|
|
||||||
alias Zoeyscomputer.Links
|
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
changeset = Links.Link.changeset(%Links.Link{})
|
|
||||||
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> assign(:form, to_form(changeset))
|
|
||||||
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("submit", %{"link" => link_params}, socket) do
|
|
||||||
params =
|
|
||||||
link_params
|
|
||||||
|> Map.put("user_id", socket.assigns.current_user.id)
|
|
||||||
|
|
||||||
case Links.create_link(params) do
|
|
||||||
{:ok, _link} ->
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> put_flash(:info, "Link created successfully")
|
|
||||||
|> push_navigate(to: ~p"/links")
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
|
|
||||||
{:error, changeset} ->
|
|
||||||
socket
|
|
||||||
|> assign(:form, to_form(changeset))
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<h1 class="text-2xl grow font-bold mb-6">Create a new link</h1>
|
|
||||||
|
|
||||||
<.form for={@form} phx-submit="submit">
|
|
||||||
<div class="flex gap-2 items-end">
|
|
||||||
<div class="grow">
|
|
||||||
<.input field={@form[:url]} type="text" label="url" />
|
|
||||||
</div>
|
|
||||||
<button class="bg-black border border-black hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-md">
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</.form>
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule ZoeyscomputerWeb.DiscordHandler do
|
defmodule ZoeyscomputerWeb.DiscordHandler do
|
||||||
require Logger
|
require Logger
|
||||||
alias ElixirSense.Log
|
|
||||||
alias ExAws.S3
|
alias ExAws.S3
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Mogrify
|
import Mogrify
|
||||||
|
|
@ -15,7 +14,7 @@ defmodule ZoeyscomputerWeb.DiscordHandler do
|
||||||
|
|
||||||
def call(%{path_info: ["images", _id | _]} = conn, _opts) do
|
def call(%{path_info: ["images", _id | _]} = conn, _opts) do
|
||||||
user_agent = List.first(get_req_header(conn, "user-agent"))
|
user_agent = List.first(get_req_header(conn, "user-agent"))
|
||||||
request_id = Logger.metadata()[:request_id]
|
Logger.metadata()[:request_id]
|
||||||
|
|
||||||
Logger.info("Processing image request:\n user_agent: #{user_agent}")
|
Logger.info("Processing image request:\n user_agent: #{user_agent}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
defmodule ZoeyscomputerWeb.Router do
|
defmodule ZoeyscomputerWeb.Router do
|
||||||
require Logger
|
require Logger
|
||||||
alias ExAws.S3
|
|
||||||
use ZoeyscomputerWeb, :router
|
use ZoeyscomputerWeb, :router
|
||||||
|
|
||||||
import ZoeyscomputerWeb.UserAuth
|
import ZoeyscomputerWeb.UserAuth
|
||||||
|
|
@ -29,9 +28,11 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
pipe_through :api
|
pipe_through :api
|
||||||
|
|
||||||
resources "/images", ImageController, except: [:create, :edit]
|
resources "/images", ImageController, except: [:create, :edit]
|
||||||
|
resources "/gists", GistController, except: [:new, :edit]
|
||||||
|
|
||||||
pipe_through [:api_authentication]
|
pipe_through [:api_authentication]
|
||||||
post "/images/create", ImageController, :create
|
post "/images/create", ImageController, :create
|
||||||
|
post "/gists/create", GistController, :create
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
|
|
@ -77,15 +78,17 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
on_mount: [{ZoeyscomputerWeb.UserAuth, :ensure_authenticated}] do
|
on_mount: [{ZoeyscomputerWeb.UserAuth, :ensure_authenticated}] do
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
||||||
live "/links", LinkLive.Index
|
|
||||||
live "/links/new", LinkLive.New
|
|
||||||
|
|
||||||
live "/images/new", ImageLive.Index, :new
|
live "/images/new", ImageLive.Index, :new
|
||||||
|
live "/gists/new", GistLive.Index, :new
|
||||||
live "/images/:id/edit", ImageLive.Index, :edit
|
live "/images/:id/edit", ImageLive.Index, :edit
|
||||||
|
live "/gists/:id/edit", GistLive.Index, :edit
|
||||||
|
|
||||||
live "/images", ImageLive.Index, :index
|
live "/images", ImageLive.Index, :index
|
||||||
|
live "/gists", GistLive.Index
|
||||||
|
|
||||||
live "/images/:id/show/edit", ImageLive.Show, :edit
|
live "/images/:id/show/edit", ImageLive.Show, :edit
|
||||||
|
live "/gists/:id/show/edit", GistLive.Show, :edit
|
||||||
|
|
||||||
live "/api-keys", ApiKeyLive.Index, :index
|
live "/api-keys", ApiKeyLive.Index, :index
|
||||||
live "/api-keys/new", ApiKeyLive.Index, :new
|
live "/api-keys/new", ApiKeyLive.Index, :new
|
||||||
|
|
@ -108,27 +111,7 @@ defmodule ZoeyscomputerWeb.Router do
|
||||||
live "/", HomeLive, :index
|
live "/", HomeLive, :index
|
||||||
|
|
||||||
live "/images/:id", ImageLive.Show, :show
|
live "/images/:id", ImageLive.Show, :show
|
||||||
end
|
live "/gists/:id", GistLive.Show, :show
|
||||||
end
|
|
||||||
|
|
||||||
scope "/", ZoeyscomputerWeb do
|
|
||||||
pipe_through [:browser, :require_authenticated_user]
|
|
||||||
end
|
|
||||||
|
|
||||||
defp download_from_s3(bucket, key) do
|
|
||||||
case S3.get_object(bucket, key) |> ExAws.request() do
|
|
||||||
{:ok, %{body: image_binary, headers: headers}} ->
|
|
||||||
content_type =
|
|
||||||
Enum.find_value(headers, fn
|
|
||||||
{"Content-Type", value} -> value
|
|
||||||
{"content-type", value} -> value
|
|
||||||
_ -> nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:ok, image_binary, content_type || "application/octet-stream"}
|
|
||||||
|
|
||||||
error ->
|
|
||||||
error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
12
priv/repo/migrations/20241026033548_create_gists.exs
Normal file
12
priv/repo/migrations/20241026033548_create_gists.exs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule Zoeyscomputer.Repo.Migrations.CreateGists do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:gists) do
|
||||||
|
add :code, :string
|
||||||
|
add :lang, :string
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
7
priv/repo/migrations/20241026035058_remove_links.exs
Normal file
7
priv/repo/migrations/20241026035058_remove_links.exs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Zoeyscomputer.Repo.Migrations.RemoveLinks do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
drop table(:links)
|
||||||
|
end
|
||||||
|
end
|
||||||
630
priv/static/fonts/Iosevka.css
Normal file
630
priv/static/fonts/Iosevka.css
Normal file
|
|
@ -0,0 +1,630 @@
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Thin.woff2') format('woff2'), url('TTF/Iosevka-Thin.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedThin.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThin.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ThinOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedThinOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ThinItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 100;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedThinItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedThinItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtraLight.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLight.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraLightItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 200;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraLightItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Light.woff2') format('woff2'), url('TTF/Iosevka-Light.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedLight.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLight.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-LightOblique.woff2') format('woff2'), url('TTF/Iosevka-LightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedLightOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-LightItalic.woff2') format('woff2'), url('TTF/Iosevka-LightItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedLightItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedLightItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Regular.woff2') format('woff2'), url('TTF/Iosevka-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Extended.woff2') format('woff2'), url('TTF/Iosevka-Extended.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Oblique.woff2') format('woff2'), url('TTF/Iosevka-Oblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-Italic.woff2') format('woff2'), url('TTF/Iosevka-Italic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Medium.woff2') format('woff2'), url('TTF/Iosevka-Medium.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedMedium.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMedium.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-MediumOblique.woff2') format('woff2'), url('TTF/Iosevka-MediumOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedMediumOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-MediumItalic.woff2') format('woff2'), url('TTF/Iosevka-MediumItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedMediumItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedMediumItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-SemiBold.woff2') format('woff2'), url('TTF/Iosevka-SemiBold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedSemiBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-SemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-SemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-SemiBoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedSemiBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedSemiBoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Bold.woff2') format('woff2'), url('TTF/Iosevka-Bold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-BoldOblique.woff2') format('woff2'), url('TTF/Iosevka-BoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-BoldItalic.woff2') format('woff2'), url('TTF/Iosevka-BoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedBoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtraBold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraBold.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBold.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtraBoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 800;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedExtraBoldItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedExtraBoldItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-Heavy.woff2') format('woff2'), url('TTF/Iosevka-Heavy.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedHeavy.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavy.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: normal;
|
||||||
|
src: url('WOFF2/Iosevka-HeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-HeavyOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: oblique;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web Oblique';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: expanded;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedHeavyOblique.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyOblique.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-HeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-HeavyItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Iosevka Web';
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 900;
|
||||||
|
font-stretch: expanded;
|
||||||
|
font-style: italic;
|
||||||
|
src: url('WOFF2/Iosevka-ExtendedHeavyItalic.woff2') format('woff2'), url('TTF/Iosevka-ExtendedHeavyItalic.ttf') format('truetype');
|
||||||
|
}
|
||||||
BIN
priv/static/fonts/WOFF2/Iosevka-Bold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Bold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-BoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-BoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-BoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Extended.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Extended.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedBoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraBoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLight.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLight.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLightItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLightOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedExtraLightOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavy.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavy.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavyItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavyItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavyOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedHeavyOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLight.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLight.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLightItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLightItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLightOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedLightOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMedium.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMedium.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMediumItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMediumItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMediumOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedMediumOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedSemiBoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThin.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThin.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThinItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThinItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThinOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtendedThinOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraBoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLight.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLightItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLightOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ExtraLightOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Heavy.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Heavy.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-HeavyItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-HeavyItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-HeavyOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-HeavyOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Italic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Italic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Light.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Light.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-LightItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-LightItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-LightOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-LightOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Medium.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Medium.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-MediumItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-MediumOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-MediumOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Oblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Oblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Regular.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Regular.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBold.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBold.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBoldItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBoldOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-SemiBoldOblique.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-Thin.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-Thin.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ThinItalic.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ThinItalic.woff2
Normal file
Binary file not shown.
BIN
priv/static/fonts/WOFF2/Iosevka-ThinOblique.woff2
Normal file
BIN
priv/static/fonts/WOFF2/Iosevka-ThinOblique.woff2
Normal file
Binary file not shown.
21
test/support/fixtures/gists_fixtures.ex
Normal file
21
test/support/fixtures/gists_fixtures.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Zoeyscomputer.GistsFixtures do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines test helpers for creating
|
||||||
|
entities via the `Zoeyscomputer.Gists` context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a gist.
|
||||||
|
"""
|
||||||
|
def gist_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, gist} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
code: "some code",
|
||||||
|
lang: "some lang"
|
||||||
|
})
|
||||||
|
|> Zoeyscomputer.Gists.create_gist()
|
||||||
|
|
||||||
|
gist
|
||||||
|
end
|
||||||
|
end
|
||||||
61
test/zoeyscomputer/gists_test.exs
Normal file
61
test/zoeyscomputer/gists_test.exs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
defmodule Zoeyscomputer.GistsTest do
|
||||||
|
use Zoeyscomputer.DataCase
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists
|
||||||
|
|
||||||
|
describe "gists" do
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
import Zoeyscomputer.GistsFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{code: nil, lang: nil}
|
||||||
|
|
||||||
|
test "list_gists/0 returns all gists" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
assert Gists.list_gists() == [gist]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_gist!/1 returns the gist with given id" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
assert Gists.get_gist!(gist.id) == gist
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_gist/1 with valid data creates a gist" do
|
||||||
|
valid_attrs = %{code: "some code", lang: "some lang"}
|
||||||
|
|
||||||
|
assert {:ok, %Gist{} = gist} = Gists.create_gist(valid_attrs)
|
||||||
|
assert gist.code == "some code"
|
||||||
|
assert gist.lang == "some lang"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_gist/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Gists.create_gist(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_gist/2 with valid data updates the gist" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
update_attrs = %{code: "some updated code", lang: "some updated lang"}
|
||||||
|
|
||||||
|
assert {:ok, %Gist{} = gist} = Gists.update_gist(gist, update_attrs)
|
||||||
|
assert gist.code == "some updated code"
|
||||||
|
assert gist.lang == "some updated lang"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_gist/2 with invalid data returns error changeset" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Gists.update_gist(gist, @invalid_attrs)
|
||||||
|
assert gist == Gists.get_gist!(gist.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_gist/1 deletes the gist" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
assert {:ok, %Gist{}} = Gists.delete_gist(gist)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Gists.get_gist!(gist.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_gist/1 returns a gist changeset" do
|
||||||
|
gist = gist_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Gists.change_gist(gist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
88
test/zoeyscomputer_web/controllers/gist_controller_test.exs
Normal file
88
test/zoeyscomputer_web/controllers/gist_controller_test.exs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistControllerTest do
|
||||||
|
use ZoeyscomputerWeb.ConnCase
|
||||||
|
|
||||||
|
import Zoeyscomputer.GistsFixtures
|
||||||
|
|
||||||
|
alias Zoeyscomputer.Gists.Gist
|
||||||
|
|
||||||
|
@create_attrs %{
|
||||||
|
code: "some code",
|
||||||
|
lang: "some lang"
|
||||||
|
}
|
||||||
|
@update_attrs %{
|
||||||
|
code: "some updated code",
|
||||||
|
lang: "some updated lang"
|
||||||
|
}
|
||||||
|
@invalid_attrs %{code: nil, lang: nil}
|
||||||
|
|
||||||
|
setup %{conn: conn} do
|
||||||
|
{:ok, conn: put_req_header(conn, "accept", "application/json")}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all gists", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/api/gists")
|
||||||
|
assert json_response(conn, 200)["data"] == []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create gist" do
|
||||||
|
test "renders gist when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/api/gists", gist: @create_attrs)
|
||||||
|
assert %{"id" => id} = json_response(conn, 201)["data"]
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/api/gists/#{id}")
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"id" => ^id,
|
||||||
|
"code" => "some code",
|
||||||
|
"lang" => "some lang"
|
||||||
|
} = json_response(conn, 200)["data"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/api/gists", gist: @invalid_attrs)
|
||||||
|
assert json_response(conn, 422)["errors"] != %{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update gist" do
|
||||||
|
setup [:create_gist]
|
||||||
|
|
||||||
|
test "renders gist when data is valid", %{conn: conn, gist: %Gist{id: id} = gist} do
|
||||||
|
conn = put(conn, ~p"/api/gists/#{gist}", gist: @update_attrs)
|
||||||
|
assert %{"id" => ^id} = json_response(conn, 200)["data"]
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/api/gists/#{id}")
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"id" => ^id,
|
||||||
|
"code" => "some updated code",
|
||||||
|
"lang" => "some updated lang"
|
||||||
|
} = json_response(conn, 200)["data"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, gist: gist} do
|
||||||
|
conn = put(conn, ~p"/api/gists/#{gist}", gist: @invalid_attrs)
|
||||||
|
assert json_response(conn, 422)["errors"] != %{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete gist" do
|
||||||
|
setup [:create_gist]
|
||||||
|
|
||||||
|
test "deletes chosen gist", %{conn: conn, gist: gist} do
|
||||||
|
conn = delete(conn, ~p"/api/gists/#{gist}")
|
||||||
|
assert response(conn, 204)
|
||||||
|
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, ~p"/api/gists/#{gist}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_gist(_) do
|
||||||
|
gist = gist_fixture()
|
||||||
|
%{gist: gist}
|
||||||
|
end
|
||||||
|
end
|
||||||
113
test/zoeyscomputer_web/live/gist_live_test.exs
Normal file
113
test/zoeyscomputer_web/live/gist_live_test.exs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
defmodule ZoeyscomputerWeb.GistLiveTest do
|
||||||
|
use ZoeyscomputerWeb.ConnCase
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
import Zoeyscomputer.GistsFixtures
|
||||||
|
|
||||||
|
@create_attrs %{code: "some code", lang: "some lang"}
|
||||||
|
@update_attrs %{code: "some updated code", lang: "some updated lang"}
|
||||||
|
@invalid_attrs %{code: nil, lang: nil}
|
||||||
|
|
||||||
|
defp create_gist(_) do
|
||||||
|
gist = gist_fixture()
|
||||||
|
%{gist: gist}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Index" do
|
||||||
|
setup [:create_gist]
|
||||||
|
|
||||||
|
test "lists all gists", %{conn: conn, gist: gist} do
|
||||||
|
{:ok, _index_live, html} = live(conn, ~p"/gists")
|
||||||
|
|
||||||
|
assert html =~ "Listing Gists"
|
||||||
|
assert html =~ gist.code
|
||||||
|
end
|
||||||
|
|
||||||
|
test "saves new gist", %{conn: conn} do
|
||||||
|
{:ok, index_live, _html} = live(conn, ~p"/gists")
|
||||||
|
|
||||||
|
assert index_live |> element("a", "New Gist") |> render_click() =~
|
||||||
|
"New Gist"
|
||||||
|
|
||||||
|
assert_patch(index_live, ~p"/gists/new")
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#gist-form", gist: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#gist-form", gist: @create_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert_patch(index_live, ~p"/gists")
|
||||||
|
|
||||||
|
html = render(index_live)
|
||||||
|
assert html =~ "Gist created successfully"
|
||||||
|
assert html =~ "some code"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates gist in listing", %{conn: conn, gist: gist} do
|
||||||
|
{:ok, index_live, _html} = live(conn, ~p"/gists")
|
||||||
|
|
||||||
|
assert index_live |> element("#gists-#{gist.id} a", "Edit") |> render_click() =~
|
||||||
|
"Edit Gist"
|
||||||
|
|
||||||
|
assert_patch(index_live, ~p"/gists/#{gist}/edit")
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#gist-form", gist: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#gist-form", gist: @update_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert_patch(index_live, ~p"/gists")
|
||||||
|
|
||||||
|
html = render(index_live)
|
||||||
|
assert html =~ "Gist updated successfully"
|
||||||
|
assert html =~ "some updated code"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes gist in listing", %{conn: conn, gist: gist} do
|
||||||
|
{:ok, index_live, _html} = live(conn, ~p"/gists")
|
||||||
|
|
||||||
|
assert index_live |> element("#gists-#{gist.id} a", "Delete") |> render_click()
|
||||||
|
refute has_element?(index_live, "#gists-#{gist.id}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Show" do
|
||||||
|
setup [:create_gist]
|
||||||
|
|
||||||
|
test "displays gist", %{conn: conn, gist: gist} do
|
||||||
|
{:ok, _show_live, html} = live(conn, ~p"/gists/#{gist}")
|
||||||
|
|
||||||
|
assert html =~ "Show Gist"
|
||||||
|
assert html =~ gist.code
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates gist within modal", %{conn: conn, gist: gist} do
|
||||||
|
{:ok, show_live, _html} = live(conn, ~p"/gists/#{gist}")
|
||||||
|
|
||||||
|
assert show_live |> element("a", "Edit") |> render_click() =~
|
||||||
|
"Edit Gist"
|
||||||
|
|
||||||
|
assert_patch(show_live, ~p"/gists/#{gist}/show/edit")
|
||||||
|
|
||||||
|
assert show_live
|
||||||
|
|> form("#gist-form", gist: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
assert show_live
|
||||||
|
|> form("#gist-form", gist: @update_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert_patch(show_live, ~p"/gists/#{gist}")
|
||||||
|
|
||||||
|
html = render(show_live)
|
||||||
|
assert html =~ "Gist updated successfully"
|
||||||
|
assert html =~ "some updated code"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue