feat: toggle theme for code highlighting in article content

This commit is contained in:
Sergey Yarkov 2022-07-31 05:26:09 +03:00
parent 2ea0c75258
commit c3ac7c6c14
10 changed files with 115 additions and 59 deletions

View File

@ -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 */

View 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');
});
}

View File

@ -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>
);
}}

View File

@ -1,17 +1,18 @@
---
import { SITE_METADATA } from '@root/config';
import { SITE_METADATA } from '@root/config';
export interface Props {
title?: string | undefined;
description?: string | undefined;
}
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' />
@ -21,4 +22,4 @@ const { title = SITE_METADATA.title, description } = Astro.props as Props;
<meta name='title' content={title} />
<meta name='description' content={description || SITE_METADATA.description} />
<script is:inline src='assets/js/detect-theme.js' />
<slot></slot>
<slot></slot>

View File

@ -1,13 +1,13 @@
---
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>
<Header />
<main class='site-content'>
<slot></slot>
</main>
<Footer />
</body>
<Header />
<main class='site-content'>
<slot></slot>
</main>
<Footer />
</body>

View File

@ -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();

View File

@ -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);

View File

@ -1,35 +1,36 @@
---
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 {
lang?: string | undefined;
content: ArticleType;
}
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>
<div class='container'>
<ArticlePost data={content}>
<ArticleContent>
<slot></slot>
</ArticleContent>
</ArticlePost>
</div>
</BaseLayout>
</html>
<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>
<slot></slot>
</ArticleContent>
</ArticlePost>
</div>
</BaseLayout>
</html>

View File

@ -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
View File

@ -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 = {