i thought ais ace ui but no i still need to intervene

90/100 A
This commit is contained in:
lda committed 2025-05-22 06:54:06 +07:00
1 parent 3330ff1205
commit fbbb044b83
5 files changed
+187 -26

No files matched your search

+32 -9
View File
@@ -2,20 +2,43 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cal app</title>
<link rel="stylesheet" href="/src/style.css" />
<script type="module" src="/src/main.js" defer></script>
</head>
<body>
<div class="flex_input">
<input type="text" id="ics_file">
<input type="button" value="new calendar" id="ics_run">
<input type="button" value="add a calendar" id="ics_add">
</div>
<div id="calendar"></div>
<div class="container">
<header class="app-header">
<h1>🗓️ Calendar Filter App</h1>
<p class="subtitle">Load, filter, and combine calendars with style.</p>
</header>
<div class="flex_input">
<input type="text" id="ics_file" placeholder="Paste .ics URL or API endpoint here" />
<input type="button" value="new calendar" id="ics_run" />
<input type="button" value="add a calendar" id="ics_add" />
</div>
<div class="info-bar" style="text-align: center">
<span>Tip: You can add multiple calendars. Hover events for details.
<a href="https://fullcalendar.io/" target="_blank" rel="noopener noreferrer">Powered by
FullCalendar</a></span>
</div>
<div class="calendar-wrapper">
<div class="placeholder-message" id="placeholder-message">
No calendar loaded yet. Paste a URL to get started!
</div>
<div id="calendar">
<div id="calendar"></div>
</div>
</div>
<footer class="app-footer">
<p>Made with Rust, JS, and ❤️</p>
<p>by
<a href="//ldlda.com" target="_blank" rel="noopener noreferrer">lđa</a></p>
</footer>
</div>
</body>
</html>
+30 -12
View File
@@ -10,20 +10,36 @@ import bootstrap5Plugin from "@fullcalendar/bootstrap5";
import tippy, { followCursor } from "tippy.js";
import "tippy.js/dist/tippy.css";
import { getNextColor } from "./color_generator";
import { formatTooltip } from "./tooltip_parser";
import { create_url } from "./utils";
const text_field = document.getElementById("ics_file");
const confirm_button = document.getElementById("ics_run");
const add_button = document.getElementById("ics_add");
const cal_ele = document.getElementById("calendar");
const placeholder_message = document.getElementById("placeholder-message");
let currentTippy = null;
let calendar = null;
function showPlaceholder() {
placeholder_message.style.display = "block";
cal_ele.style.display = "none";
}
function hidePlaceholder() {
placeholder_message.style.display = "none";
cal_ele.style.display = "block";
}
const new_tippy = (info) => {
if (info.el._tippy === currentTippy) {
currentTippy.show();
} // what does this even MEAN
else { // if (info.event.extendedProps.description)
} // what does this even MEAN
else {
// if (info.event.extendedProps.description)
// Destroy previous tippy if exists
if (currentTippy) {
currentTippy.destroy();
@@ -51,20 +67,18 @@ const new_tippy = (info) => {
};
const tippy_die = (info) => {
// if hovering current tippy then dont die. if not kill kill kill
// if (currentTippy) {
// currentTippy.destroy();
// currentTippy = null;
// }
// fuck it
// only hide. when another comes current will be killed
currentTippy.hideWithInteractivity(info.jsEvent);
};
confirm_button.addEventListener("click", (_e) => {
console.log("nawgh:", text_field.value);
if (text_field.value) {
const url = create_url(text_field.value);
if (url) {
hidePlaceholder(); // Hide placeholder when loading starts
if (calendar) calendar.destroy();
calendar = new Calendar(cal_ele, {
// calendar config
plugins: [
bootstrap5Plugin,
listPlugin,
@@ -72,6 +86,7 @@ confirm_button.addEventListener("click", (_e) => {
timeGridPlugin,
dayGridPlugin,
],
themeSystem: "bootstrap5", // Enable Bootstrap 5 theme
initialView: "listWeek",
// multiMonthMaxColumns: 1,
navLinks: true,
@@ -83,7 +98,8 @@ confirm_button.addEventListener("click", (_e) => {
headerToolbar: {
left: "prev,next",
center: "title",
right: "today dayGridMonth,timeGridWeek,timeGridDay,listMonth", // Add the views you want
right: "today dayGridMonth,timeGridWeek,timeGridDay,listMonth",
// views
},
views: {
dayGridMonth: { buttonText: "Month" },
@@ -93,7 +109,7 @@ confirm_button.addEventListener("click", (_e) => {
},
events: {
url: text_field.value,
url: url,
format: "ics",
color: getNextColor(),
},
@@ -101,8 +117,10 @@ confirm_button.addEventListener("click", (_e) => {
eventMouseEnter: new_tippy,
eventMouseLeave: tippy_die,
});
// end calendar config
calendar.render();
} else if (!calendar) {
showPlaceholder(); // Show placeholder if no URL is provided
}
});
+111
View File
@@ -0,0 +1,111 @@
/*
cal_filter_js/src/style.css
Custom styles for calendar app
*/
body {
font-family: system-ui, sans-serif;
background: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
max-width: 900px;
width: 100%;
padding: 1em;
box-sizing: border-box;
}
.app-header {
text-align: center;
margin-bottom: 1.5em;
}
.flex_input {
display: flex;
gap: 0.5em;
margin: 2em auto 1em auto;
max-width: 900px;
align-items: center;
}
#ics_file {
flex: 1 1 0;
min-width: 200px;
max-width: 100%;
font-size: 1.1em;
padding: 0.5em 0.8em;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
box-sizing: border-box;
}
input[type="button"] {
padding: 0.5em 1.2em;
font-size: 1em;
border: none;
border-radius: 4px;
background: #007bff;
color: #fff;
cursor: pointer;
transition: background 0.2s;
}
input[type="button"]:hover {
background: #0056b3;
}
#calendar {
/* max-width: 900px; */
/* margin: 0 auto 2em auto; */
/* background: #fff; */
/* border-radius: 8px; */
/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07); */
/* padding: 1em 0.5em 2em 0.5em; */
padding-bottom: 1em; /* ultra mega hack */
display: none;
}
.calendar-wrapper {
position: relative;
max-width: 900px;
margin: 0 auto 2em auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
padding: 1em 0.5em 1em 0.5em;
}
/* Placeholder message */
.placeholder-message {
text-align: center;
/* justify-self: center; */
color: #6c757d;
font-size: 1.2em;
margin: 1em 0;
}
@media (max-width: 600px) {
.flex_input {
flex-direction: column;
gap: 0.7em;
max-width: 98vw;
}
#calendar {
max-width: 99vw;
padding: 0.5em 0.1em 1em 0.1em;
}
}
.app-footer {
/* display: flex; */
justify-items: center;
padding: 1em 0;
border-top: 1px solid #eee;
margin-top: 2em;
font-size: 0.95em;
}
+5 -5
View File
@@ -1,11 +1,11 @@
// Escapes HTML special characters to prevent XSS
/** Escapes HTML special characters to prevent XSS */
export function escapeHtml(text) {
const div = document.createElement("div");
div.innerText = text;
return div.innerHTML;
}
// Converts URLs in text to clickable links, after escaping
/** Converts URLs in text to clickable links, after escaping */
export function linkify(text) {
let safe = escapeHtml(text);
return safe.replace(
@@ -15,11 +15,11 @@ export function linkify(text) {
);
}
// Formats tooltip content: escapes, linkifies, and replaces newlines with <br>
/** Formats tooltip content: escapes, linkifies, and replaces newlines with \<br> */
export function formatTooltip(title, description) {
const safeTitle = escapeHtml(title);
const safeDesc = description
? linkify(description).replace(/\n/g, "<br>")
? `<br><br>${linkify(description).replace(/\n/g, "<br>")}`
: "";
return `<strong>${safeTitle}</strong><br><br>${safeDesc}`;
return `<strong>${safeTitle}</strong>${safeDesc}`;
}
+9
View File
@@ -0,0 +1,9 @@
function create_url(url) {
try {
return new URL(url);
} catch {
return null;
}
}
export { create_url }