Compare commits
No commits in common. "e93917404071a4ff59c3eeb3b0571dc3483a40fe" and "221a6f00076cef274d9bb82c4de7359cc468b2ca" have entirely different histories.
e939174040
...
221a6f0007
@ -7,7 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
README.md = README.md
|
README.md = README.md
|
||||||
package.json = package.json
|
package.json = package.json
|
||||||
Gulpfile.js = Gulpfile.js
|
Gulpfile.js = Gulpfile.js
|
||||||
tsconfig.json = tsconfig.json
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8A323E64-E41E-4780-99FD-17BF58961FB5}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8A323E64-E41E-4780-99FD-17BF58961FB5}"
|
||||||
|
@ -43,7 +43,6 @@ public sealed class BlogApiController : ControllerBase
|
|||||||
author = post.AuthorId,
|
author = post.AuthorId,
|
||||||
title = post.Title,
|
title = post.Title,
|
||||||
published = post.Published.ToUnixTimeSeconds(),
|
published = post.Published.ToUnixTimeSeconds(),
|
||||||
formattedDate = post.Published.ToString("dddd, d MMMM yyyy HH:mm"),
|
|
||||||
updated = post.Updated?.ToUnixTimeSeconds(),
|
updated = post.Updated?.ToUnixTimeSeconds(),
|
||||||
humanizedTimestamp = post.Updated?.Humanize() ?? post.Published.Humanize(),
|
humanizedTimestamp = post.Updated?.Humanize() ?? post.Published.Humanize(),
|
||||||
excerpt = _blogService.GetExcerpt(post, out bool trimmed),
|
excerpt = _blogService.GetExcerpt(post, out bool trimmed),
|
||||||
|
@ -29,14 +29,14 @@
|
|||||||
<img class="blog-author-icon" src="https://gravatar.com/avatar/@author.AvatarHash?s=28" alt="@author.Name">
|
<img class="blog-author-icon" src="https://gravatar.com/avatar/@author.AvatarHash?s=28" alt="@author.Name">
|
||||||
@author.Name •
|
@author.Name •
|
||||||
|
|
||||||
<abbr data-bs-toggle="tooltip" data-bs-title="@published.ToString("dddd, d MMMM yyyy HH:mm")">
|
<abbr data-bs-toggle="tooltip" data-bs-title="@published.ToString("f")">
|
||||||
Published @published.Humanize()
|
Published @published.Humanize()
|
||||||
</abbr>
|
</abbr>
|
||||||
|
|
||||||
@if (post.Updated is { } updated)
|
@if (post.Updated is { } updated)
|
||||||
{
|
{
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<abbr data-bs-toggle="tooltip" data-bs-title="@updated.ToString("dddd, d MMMM yyyy HH:mm")">
|
<abbr data-bs-toggle="tooltip" data-bs-title="@updated.ToString("f")">
|
||||||
Updated @updated.Humanize()
|
Updated @updated.Humanize()
|
||||||
</abbr>
|
</abbr>
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
<img class="blog-author-icon" src="{{author.avatar}}" alt="{{author.name}}">
|
<img class="blog-author-icon" src="{{author.avatar}}" alt="{{author.name}}">
|
||||||
<span>{{author.name}}<span>
|
<span>{{author.name}}<span>
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
<abbr title="{{ post.formattedDate }}">{{ post.date_humanized }}</abbr>
|
<span title="{{ post.date }}">{{ post.date_humanized }}</span>
|
||||||
{{#if post.enable_comments}}
|
{{#if post.enable_comments}}
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
<a href="{{post.url}}#disqus_thread" data-disqus-identifier="{{post.disqus_identifier}}">
|
<a href="{{post.url}}#disqus_thread" data-disqus-identifier="{{post.disqus_identifier}}">
|
||||||
Loading comment count …
|
0 Comments
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</p>
|
</p>
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js" integrity="sha512-aoZChv+8imY/U1O7KIHXvO87EOzCuKO0GhFtpD6G2Cyjo/xPeTgdf3/bchB10iB+AojMTDkMHDPLKNxPJVqDcw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js" integrity="sha512-aoZChv+8imY/U1O7KIHXvO87EOzCuKO0GhFtpD6G2Cyjo/xPeTgdf3/bchB10iB+AojMTDkMHDPLKNxPJVqDcw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js" integrity="sha512-uKQ39gEGiyUJl4AI6L+ekBdGKpGw4xJ55+xyJG7YFlJokPNYegn9KwQ3P8A7aFQAUtUsAQHep+d/lrGqrbPIDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js" integrity="sha512-uKQ39gEGiyUJl4AI6L+ekBdGKpGw4xJ55+xyJG7YFlJokPNYegn9KwQ3P8A7aFQAUtUsAQHep+d/lrGqrbPIDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js" integrity="sha512-E1dSFxg+wsfJ4HKjutk/WaCzK7S2wv1POn1RRPGh8ZK+ag9l244Vqxji3r6wgz9YBf6+vhQEYJZpSjqWFPg9gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js" integrity="sha512-E1dSFxg+wsfJ4HKjutk/WaCzK7S2wv1POn1RRPGh8ZK+ag9l244Vqxji3r6wgz9YBf6+vhQEYJZpSjqWFPg9gg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="~/js/prism.min.js" asp-append-version="true" data-manual></script>
|
<script src="~/js/prism.min.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/app.min.js" asp-append-version="true"></script>
|
<script src="~/js/app.min.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
private readonly _trimmed: boolean;
|
private readonly _trimmed: boolean;
|
||||||
private readonly _identifier: string;
|
private readonly _identifier: string;
|
||||||
private readonly _humanizedTimestamp: string;
|
private readonly _humanizedTimestamp: string;
|
||||||
private readonly _formattedDate: string;
|
|
||||||
|
|
||||||
constructor(json: any) {
|
constructor(json: any) {
|
||||||
this._id = json.id;
|
this._id = json.id;
|
||||||
@ -24,7 +23,6 @@
|
|||||||
this._trimmed = json.trimmed;
|
this._trimmed = json.trimmed;
|
||||||
this._identifier = json.identifier;
|
this._identifier = json.identifier;
|
||||||
this._humanizedTimestamp = json.humanizedTimestamp;
|
this._humanizedTimestamp = json.humanizedTimestamp;
|
||||||
this._formattedDate = json.formattedDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): number {
|
get id(): number {
|
||||||
@ -70,10 +68,6 @@
|
|||||||
get humanizedTimestamp(): string {
|
get humanizedTimestamp(): string {
|
||||||
return this._humanizedTimestamp;
|
return this._humanizedTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
get formattedDate(): string {
|
|
||||||
return this._formattedDate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BlogPost;
|
export default BlogPost;
|
194
src/ts/UI.ts
194
src/ts/UI.ts
@ -1,12 +1,4 @@
|
|||||||
import BlogPost from "./BlogPost";
|
class UI {
|
||||||
import Author from "./Author";
|
|
||||||
import TimeUtility from "./TimeUtility";
|
|
||||||
|
|
||||||
declare const bootstrap: any;
|
|
||||||
declare const katex: any;
|
|
||||||
declare const Prism: any;
|
|
||||||
|
|
||||||
class UI {
|
|
||||||
public static get blogPostContainer(): HTMLDivElement {
|
public static get blogPostContainer(): HTMLDivElement {
|
||||||
return document.querySelector("#all-blog-posts");
|
return document.querySelector("#all-blog-posts");
|
||||||
}
|
}
|
||||||
@ -14,190 +6,6 @@ class UI {
|
|||||||
public static get blogPostTemplate(): HTMLDivElement {
|
public static get blogPostTemplate(): HTMLDivElement {
|
||||||
return document.querySelector("#blog-post-template");
|
return document.querySelector("#blog-post-template");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a <script> element that loads the Disqus comment counter.
|
|
||||||
*/
|
|
||||||
public static createDisqusCounterScript(): HTMLScriptElement {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.id = "dsq-count-scr";
|
|
||||||
script.src = "https://oliverbooth-dev.disqus.com/count.js";
|
|
||||||
script.async = true;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a blog post card from the given template, post, and author.
|
|
||||||
* @param template The Handlebars template to use.
|
|
||||||
* @param post The blog post to use.
|
|
||||||
* @param author The author of the blog post.
|
|
||||||
*/
|
|
||||||
public static createBlogPostCard(template: any, post: BlogPost, author: Author): HTMLDivElement {
|
|
||||||
const card = document.createElement("div") as HTMLDivElement;
|
|
||||||
card.classList.add("card");
|
|
||||||
card.classList.add("blog-card");
|
|
||||||
card.classList.add("animate__animated");
|
|
||||||
card.classList.add("animate__fadeIn");
|
|
||||||
card.style.marginBottom = "50px";
|
|
||||||
|
|
||||||
const body = template({
|
|
||||||
post: {
|
|
||||||
title: post.title,
|
|
||||||
excerpt: post.excerpt,
|
|
||||||
url: post.url,
|
|
||||||
date: TimeUtility.formatRelativeTimestamp(post.published),
|
|
||||||
formattedDate: post.formattedDate,
|
|
||||||
date_humanized: `${post.updated ? "Updated" : "Published"} ${post.humanizedTimestamp}`,
|
|
||||||
enable_comments: post.commentsEnabled,
|
|
||||||
disqus_identifier: post.identifier,
|
|
||||||
trimmed: post.trimmed,
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
name: author.name,
|
|
||||||
avatar: `https://gravatar.com/avatar/${author.avatarHash}?s=28`,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
card.innerHTML = body.trim();
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces all UI elements under the given element to update their rendering.
|
|
||||||
* @param element The element to search for UI elements in.
|
|
||||||
*/
|
|
||||||
public static updateUI(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
UI.addLineNumbers(element);
|
|
||||||
UI.addHighlighting(element);
|
|
||||||
UI.addBootstrapTooltips(element);
|
|
||||||
UI.renderTeX(element);
|
|
||||||
UI.renderTimestamps(element);
|
|
||||||
UI.unescapeMarkTags(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds Bootstrap tooltips to all elements with a title attribute.
|
|
||||||
* @param element The element to search for elements with a title attribute in.
|
|
||||||
*/
|
|
||||||
public static addBootstrapTooltips(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
element.querySelectorAll("[title]").forEach((el) => {
|
|
||||||
el.setAttribute("data-bs-toggle", "tooltip");
|
|
||||||
el.setAttribute("data-bs-placement", "bottom");
|
|
||||||
el.setAttribute("data-bs-html", "true");
|
|
||||||
el.setAttribute("data-bs-title", el.getAttribute("title"));
|
|
||||||
|
|
||||||
new bootstrap.Tooltip(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds line numbers to all <pre> <code> blocks that have more than one line.
|
|
||||||
* @param element The element to search for <pre> <code> blocks in.
|
|
||||||
*/
|
|
||||||
public static addLineNumbers(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
element.querySelectorAll("pre code").forEach((block) => {
|
|
||||||
let content = block.textContent;
|
|
||||||
if (content.trim().split("\n").length > 1) {
|
|
||||||
block.parentElement.classList.add("line-numbers");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds syntax highlighting to all <pre> <code> blocks in the element.
|
|
||||||
* @param element The element to search for <pre> <code> blocks in.
|
|
||||||
*/
|
|
||||||
public static addHighlighting(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
element.querySelectorAll("pre code").forEach((block) => {
|
|
||||||
Prism.highlightAllUnder(block.parentElement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders all TeX in the document.
|
|
||||||
* @param element The element to search for TeX in.
|
|
||||||
*/
|
|
||||||
public static renderTeX(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
const tex = element.getElementsByClassName("math");
|
|
||||||
Array.from(tex).forEach(function (el: Element) {
|
|
||||||
let content = el.textContent.trim();
|
|
||||||
if (content.startsWith("\\[")) content = content.slice(2);
|
|
||||||
if (content.endsWith("\\]")) content = content.slice(0, -2);
|
|
||||||
|
|
||||||
katex.render(content, el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders Discord-style <t:timestamp:format> tags.
|
|
||||||
* @param element The element to search for timestamps in.
|
|
||||||
*/
|
|
||||||
public static renderTimestamps(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
const timestamps = element.querySelectorAll("span[data-timestamp][data-format]");
|
|
||||||
timestamps.forEach((timestamp) => {
|
|
||||||
const seconds = parseInt(timestamp.getAttribute("data-timestamp"));
|
|
||||||
const format = timestamp.getAttribute("data-format");
|
|
||||||
const date = new Date(seconds * 1000);
|
|
||||||
|
|
||||||
const shortTimeString = date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
|
|
||||||
const shortDateString = date.toLocaleDateString([], {day: "2-digit", month: "2-digit", year: "numeric"});
|
|
||||||
const longTimeString = date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
|
||||||
const longDateString = date.toLocaleDateString([], {day: "numeric", month: "long", year: "numeric"});
|
|
||||||
const weekday = date.toLocaleString([], {weekday: "long"});
|
|
||||||
timestamp.setAttribute("title", `${weekday}, ${longDateString} ${shortTimeString}`);
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case "t":
|
|
||||||
timestamp.textContent = shortTimeString;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "T":
|
|
||||||
timestamp.textContent = longTimeString;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "d":
|
|
||||||
timestamp.textContent = shortDateString;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "D":
|
|
||||||
timestamp.textContent = longDateString;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "f":
|
|
||||||
timestamp.textContent = `${longDateString} at ${shortTimeString}`
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "F":
|
|
||||||
timestamp.textContent = `${weekday}, ${longDateString} at ${shortTimeString}`
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "R":
|
|
||||||
setInterval(() => {
|
|
||||||
timestamp.textContent = TimeUtility.formatRelativeTimestamp(date);
|
|
||||||
}, 1000);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unescapes all <mark> tags in <pre> <code> blocks.
|
|
||||||
* @param element The element to search for <pre> <code> blocks in.
|
|
||||||
*/
|
|
||||||
public static unescapeMarkTags(element?: Element) {
|
|
||||||
element = element || document.body;
|
|
||||||
element.querySelectorAll("pre code").forEach((block) => {
|
|
||||||
let content = block.innerHTML;
|
|
||||||
content = content.replaceAll("<mark>", "<mark>");
|
|
||||||
content = content.replaceAll("</mark>", "</mark>");
|
|
||||||
block.innerHTML = content;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UI;
|
export default UI;
|
117
src/ts/app.ts
117
src/ts/app.ts
@ -1,6 +1,9 @@
|
|||||||
import API from "./API";
|
import API from "./API";
|
||||||
|
import TimeUtility from "./TimeUtility";
|
||||||
import UI from "./UI";
|
import UI from "./UI";
|
||||||
|
|
||||||
|
declare const bootstrap: any;
|
||||||
|
declare const katex: any;
|
||||||
declare const Handlebars: any;
|
declare const Handlebars: any;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
@ -12,15 +15,42 @@ declare const Handlebars: any;
|
|||||||
const posts = await API.getBlogPosts(i, 5);
|
const posts = await API.getBlogPosts(i, 5);
|
||||||
for (const post of posts) {
|
for (const post of posts) {
|
||||||
const author = await API.getAuthor(post.authorId);
|
const author = await API.getAuthor(post.authorId);
|
||||||
const card = UI.createBlogPostCard(template, post, author);
|
|
||||||
|
const card = document.createElement("div") as HTMLDivElement;
|
||||||
|
card.classList.add("card");
|
||||||
|
card.classList.add("blog-card");
|
||||||
|
card.classList.add("animate__animated");
|
||||||
|
card.classList.add("animate__fadeIn");
|
||||||
|
card.style.marginBottom = "50px";
|
||||||
|
|
||||||
|
const body = template({
|
||||||
|
post: {
|
||||||
|
title: post.title,
|
||||||
|
excerpt: post.excerpt,
|
||||||
|
url: post.url,
|
||||||
|
date: TimeUtility.formatRelativeTimestamp(post.published),
|
||||||
|
date_humanized: `${post.updated ? "Updated" : "Published"} ${post.humanizedTimestamp}`,
|
||||||
|
enable_comments: post.commentsEnabled,
|
||||||
|
disqus_identifier: post.identifier,
|
||||||
|
trimmed: post.trimmed,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
name: author.name,
|
||||||
|
avatar: `https://gravatar.com/avatar/${author.avatarHash}?s=28`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
card.innerHTML = body.trim();
|
||||||
|
|
||||||
blogPostContainer.appendChild(card);
|
blogPostContainer.appendChild(card);
|
||||||
UI.updateUI(card);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 4;
|
i += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.appendChild(UI.createDisqusCounterScript());
|
const disqusCounter = document.createElement("script");
|
||||||
|
disqusCounter.id = "dsq-count-scr";
|
||||||
|
disqusCounter.src = "https://oliverbooth-dev.disqus.com/count.js";
|
||||||
|
disqusCounter.async = true;
|
||||||
|
|
||||||
const spinner = document.querySelector("#blog-loading-spinner");
|
const spinner = document.querySelector("#blog-loading-spinner");
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
@ -30,5 +60,82 @@ declare const Handlebars: any;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.updateUI();
|
document.querySelectorAll("pre code").forEach((block) => {
|
||||||
|
let content = block.textContent;
|
||||||
|
if (content.trim().split("\n").length > 1) {
|
||||||
|
block.parentElement.classList.add("line-numbers");
|
||||||
|
}
|
||||||
|
|
||||||
|
content = block.innerHTML;
|
||||||
|
// @ts-ignore
|
||||||
|
content = content.replaceAll("<mark>", "<mark>");
|
||||||
|
// @ts-ignore
|
||||||
|
content = content.replaceAll("</mark>", "</mark>");
|
||||||
|
block.innerHTML = content;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tex = document.getElementsByClassName("math");
|
||||||
|
Array.prototype.forEach.call(tex, function (el) {
|
||||||
|
let content = el.textContent.trim();
|
||||||
|
if (content.startsWith("\\[")) content = content.slice(2);
|
||||||
|
if (content.endsWith("\\]")) content = content.slice(0, -2);
|
||||||
|
|
||||||
|
katex.render(content, el);
|
||||||
|
});
|
||||||
|
|
||||||
|
const timestamps = document.querySelectorAll("span[data-timestamp][data-format]");
|
||||||
|
timestamps.forEach((timestamp) => {
|
||||||
|
const seconds = parseInt(timestamp.getAttribute("data-timestamp"));
|
||||||
|
const format = timestamp.getAttribute("data-format");
|
||||||
|
const date = new Date(seconds * 1000);
|
||||||
|
|
||||||
|
const shortTimeString = date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
|
||||||
|
const shortDateString = date.toLocaleDateString([], {day: "2-digit", month: "2-digit", year: "numeric"});
|
||||||
|
const longTimeString = date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||||
|
const longDateString = date.toLocaleDateString([], {day: "numeric", month: "long", year: "numeric"});
|
||||||
|
const weekday = date.toLocaleString([], {weekday: "long"});
|
||||||
|
timestamp.setAttribute("title", `${weekday}, ${longDateString} ${shortTimeString}`);
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case "t":
|
||||||
|
timestamp.textContent = shortTimeString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "T":
|
||||||
|
timestamp.textContent = longTimeString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "d":
|
||||||
|
timestamp.textContent = shortDateString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "D":
|
||||||
|
timestamp.textContent = longDateString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "f":
|
||||||
|
timestamp.textContent = `${longDateString} at ${shortTimeString}`
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "F":
|
||||||
|
timestamp.textContent = `${weekday}, ${longDateString} at ${shortTimeString}`
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "R":
|
||||||
|
setInterval(() => {
|
||||||
|
timestamp.textContent = TimeUtility.formatRelativeTimestamp(date);
|
||||||
|
}, 1000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("[title]").forEach((el) => {
|
||||||
|
el.setAttribute("data-bs-toggle", "tooltip");
|
||||||
|
el.setAttribute("data-bs-placement", "bottom");
|
||||||
|
el.setAttribute("data-bs-html", "true");
|
||||||
|
el.setAttribute("data-bs-title", el.getAttribute("title"));
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
list.forEach((el: Element) => new bootstrap.Tooltip(el));
|
||||||
})();
|
})();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ES2022", "DOM"],
|
"lib": ["ES2020", "DOM"],
|
||||||
"target": "ES2022"
|
"target": "ES2020"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user