mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
feat: toggle theme for code highlighting in article content
This commit is contained in:
parent
2ea0c75258
commit
c3ac7c6c14
@ -5,14 +5,13 @@
|
||||
* and body markup to prevent FOUC (Flash of unstyled content).
|
||||
*/
|
||||
|
||||
const getTheme = () => {
|
||||
window.theme = (() => {
|
||||
const local = localStorage.getItem('theme');
|
||||
if (local && (local === 'dark' || local === 'light')) return local;
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
return isDark ? 'dark' : 'light';
|
||||
};
|
||||
})();
|
||||
|
||||
window.theme = getTheme();
|
||||
document.documentElement.setAttribute('data-theme', window.theme);
|
||||
|
||||
/* Sync with OS theme */
|
||||
|
||||
48
public/assets/js/load-code-highlight.js
Normal file
48
public/assets/js/load-code-highlight.js
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* This script will load the properly code highlithing theme based on user theme
|
||||
* It is important to insert it after detecting a user theme
|
||||
* and assigning the `window.theme` property
|
||||
*/
|
||||
|
||||
if (window.theme) {
|
||||
/* the {theme} in the path will be dynamically changed */
|
||||
const PATH = `assets/css/prism-one-{theme}.min.css`;
|
||||
const $head = document.getElementsByTagName('head')[0];
|
||||
const getPath = (theme) => PATH.replace(`{theme}`, theme);
|
||||
const createLink = (theme, media) => {
|
||||
const $link = document.createElement('link');
|
||||
$link.rel = 'stylesheet';
|
||||
$link.type = 'text/css';
|
||||
$link.href = getPath(theme);
|
||||
$link.media = media;
|
||||
return $link;
|
||||
};
|
||||
|
||||
/* init links to load them first */
|
||||
const $light = createLink('light', 'none');
|
||||
const $dark = createLink('dark', 'none');
|
||||
|
||||
const toggleMedia = (theme) => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
$light.media = 'all';
|
||||
$dark.media = 'none';
|
||||
break;
|
||||
case 'dark':
|
||||
$light.media = 'none';
|
||||
$dark.media = 'all';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
toggleMedia(window.theme);
|
||||
$head.appendChild($light);
|
||||
$head.appendChild($dark);
|
||||
|
||||
window.addEventListener('onthemetoggled', ({ detail: newTheme }) => toggleMedia(newTheme));
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches: isDark }) => {
|
||||
return isDark ? toggleMedia('dark') : toggleMedia('light');
|
||||
});
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { Component } from 'solid-js';
|
||||
|
||||
import { createSignal, createEffect, For } from 'solid-js';
|
||||
import { search, selectedTags } from '@stores/searchStore';
|
||||
import { findArticlesBySearch, findArticlesByTags, sortArtcilesByDate } from '@utils/articles';
|
||||
import { ArticleItem } from '@components/ArticleItem';
|
||||
import * as utils from '@utils/articles';
|
||||
import SearchInput from '@components/SearchInput';
|
||||
import TagsList from '@components/TagsList';
|
||||
import './ArticlesList.scss';
|
||||
import SearchInput from '../SearchInput';
|
||||
import TagsList from '../TagsList';
|
||||
|
||||
type ArticlesListProps = {
|
||||
data: {
|
||||
@ -39,7 +39,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
|
||||
* Find articles by title
|
||||
*/
|
||||
if (search()) {
|
||||
filtered = utils.findArticlesBySearch(search(), filtered);
|
||||
filtered = findArticlesBySearch(search(), filtered);
|
||||
setArticles(filtered);
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
|
||||
* Find articles by selected tags
|
||||
*/
|
||||
if (selectedTags().length !== 0) {
|
||||
filtered = utils.findArticlesByTags(selectedTags(), filtered);
|
||||
filtered = findArticlesByTags(selectedTags(), filtered);
|
||||
setArticles(filtered);
|
||||
|
||||
return;
|
||||
@ -78,7 +78,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
|
||||
return (
|
||||
<div class="articles-list__wrapper">
|
||||
<h2>{year}</h2>
|
||||
<For each={utils.sortArtcilesByDate(articles()[year])} children={(article) => <ArticleItem data={article} />} />
|
||||
<For each={sortArtcilesByDate(articles()[year])} children={(article) => <ArticleItem data={article} />} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
import { SITE_METADATA } from '@root/config';
|
||||
import { SITE_METADATA } from '@root/config';
|
||||
|
||||
export interface Props {
|
||||
export interface Props {
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const { title = SITE_METADATA.title, description } = Astro.props as Props;
|
||||
const { title = SITE_METADATA.title, description } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<link rel='preconnect' href='https://fonts.googleapis.com' />
|
||||
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin />
|
||||
<link href='https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap' rel='stylesheet' />
|
||||
<link href='https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap'
|
||||
rel='stylesheet' />
|
||||
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import './BaseLayout.scss'
|
||||
import Header from '@components/Header'
|
||||
import Footer from '@components/Footer'
|
||||
import './BaseLayout.scss';
|
||||
import Header from '@components/Header';
|
||||
import Footer from '@components/Footer';
|
||||
---
|
||||
|
||||
<body>
|
||||
|
||||
@ -25,7 +25,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
* Update state and set url param
|
||||
*/
|
||||
const onSearch: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => {
|
||||
const value = e.currentTarget.value;
|
||||
const value = e.currentTarget.value.trim();
|
||||
|
||||
if (value.length === 0) {
|
||||
resetSearch();
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
|
||||
import { SITE_SETTINGS } from '@root/config';
|
||||
import { createSignal, onMount, Switch, Match } from 'solid-js';
|
||||
import './ThemeSwitcher.scss';
|
||||
|
||||
@ -23,14 +22,17 @@ const SunIcon: Component = () => (
|
||||
);
|
||||
|
||||
const ThemeSwitcher: Component = () => {
|
||||
const [theme, setTheme] = createSignal<ThemeModeType>(SITE_SETTINGS.defaultTheme);
|
||||
const [theme, setTheme] = createSignal<ThemeModeType>('light');
|
||||
const [isReady, setIsReady] = createSignal<boolean>(false);
|
||||
|
||||
const toggleTheme: JSX.EventHandler<HTMLButtonElement, MouseEvent> = () => {
|
||||
if (!window.theme) return;
|
||||
|
||||
const newTheme = theme() === 'light' ? 'dark' : 'light';
|
||||
const toggleEvent = new CustomEvent('onthemetoggled', { detail: newTheme });
|
||||
|
||||
setTheme(newTheme);
|
||||
window.dispatchEvent(toggleEvent);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
};
|
||||
@ -39,7 +41,7 @@ const ThemeSwitcher: Component = () => {
|
||||
setIsReady(true);
|
||||
if (window.theme) {
|
||||
setTheme(window.theme);
|
||||
/* Update theme state on changing OS the,e */
|
||||
/* Update theme state on changing OS theme */
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches: isDark }) => {
|
||||
const theme = isDark ? 'dark' : 'light';
|
||||
setTheme(theme);
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
---
|
||||
import type { Props as BaseHeadProps } from '@root/components/BaseHead.astro';
|
||||
import type { Props as BaseHeadProps } from '@root/components/BaseHead.astro';
|
||||
|
||||
import BaseHead from '@components/BaseHead.astro';
|
||||
import BaseLayout from '@components/BaseLayout';
|
||||
import ArticleContent from '@components/ArticleContent';
|
||||
import ArticlePost from '@components/ArticlePost';
|
||||
import '@styles/global.scss';
|
||||
import BaseHead from '@components/BaseHead.astro';
|
||||
import BaseLayout from '@components/BaseLayout';
|
||||
import ArticleContent from '@components/ArticleContent';
|
||||
import ArticlePost from '@components/ArticlePost';
|
||||
import '@styles/global.scss';
|
||||
|
||||
export interface Props extends BaseHeadProps {
|
||||
export interface Props extends BaseHeadProps {
|
||||
lang?: string | undefined;
|
||||
content: ArticleType;
|
||||
}
|
||||
}
|
||||
|
||||
const { lang = 'ru', content } = Astro.props as Props;
|
||||
const { lang = 'ru', content } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<BaseHead title={content.title}>
|
||||
<link rel='stylesheet' href='assets/css/prism-one-dark.min.css' media='(prefers-color-scheme: dark)' />
|
||||
<link rel='stylesheet' href='assets/css/prism-one-light.min.css' media='(prefers-color-scheme: light)' />
|
||||
</BaseHead>
|
||||
</head>
|
||||
|
||||
<BaseLayout>
|
||||
<head>
|
||||
<BaseHead title={content.title}>
|
||||
<script is:inline defer src='assets/js/load-code-highlight.js' />
|
||||
</BaseHead>
|
||||
</head>
|
||||
|
||||
<BaseLayout>
|
||||
<div class='container'>
|
||||
<ArticlePost data={content}>
|
||||
<ArticleContent>
|
||||
@ -31,5 +31,6 @@ const { lang = 'ru', content } = Astro.props as Props;
|
||||
</ArticleContent>
|
||||
</ArticlePost>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
</BaseLayout>
|
||||
|
||||
</html>
|
||||
@ -116,7 +116,7 @@
|
||||
--font-color-grayer: var(--color-gray-200);
|
||||
|
||||
/* Border */
|
||||
--border-color: var(--color-gray-700);
|
||||
--border-color: var(--color-gray-600);
|
||||
--border-color-hover: var(--color-gray-600);
|
||||
|
||||
/* Elements */
|
||||
|
||||
5
src/types/main.d.ts
vendored
5
src/types/main.d.ts
vendored
@ -3,6 +3,11 @@ export {};
|
||||
declare global {
|
||||
interface Window {
|
||||
theme?: ThemeModeType | undefined;
|
||||
addEventListener<K extends keyof CustomEventMap>(type: K, listener: (this: Window, ev: CustomEventMap[K]) => void): void;
|
||||
}
|
||||
|
||||
interface CustomEventMap {
|
||||
onthemetoggled: CustomEvent<ThemeModeType>;
|
||||
}
|
||||
|
||||
type ArticleType = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user