svg preview renderer

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

View file

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

View file

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

View file

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

View file

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

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

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