render-markdown-codehighlight/hooks.ts (94 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useState, FC } from 'react';
import hljs from 'highlight.js';
import { themeStyles } from './themeStyles';
import { pluginHookProps, Request } from './types';
const get = async (url: string) => {
const response = await fetch(url);
const { data } = await response.json();
return data;
};
const useHighlightCode: FC<pluginHookProps> = (
props:
| HTMLElement
| null
| {
current: HTMLElement | null;
},
request: Request = {
get,
},
) => {
const [selectTheme, setSelectTheme] = useState<string>('stackoverflow');
// Fetch theme from API
useEffect(() => {
request
.get('/answer/api/v1/render/config')
.then((result) => {
if (result.select_theme) {
setSelectTheme(result.select_theme);
}
})
.catch((error) => {
console.error('Error fetching theme:', error);
});
}, []);
useEffect(() => {
let element;
if (props instanceof HTMLElement) {
element = props;
} else if (props && props.current instanceof HTMLElement) {
element = props.current;
} else {
return;
}
const applyThemeCSS = async (theme: string) => {
const existingStyleElement = document.querySelector(
'style[data-theme-style="highlight"]',
);
if (existingStyleElement) existingStyleElement.remove();
const styleElement = document.createElement('style');
styleElement.setAttribute('data-theme-style', 'highlight');
document.head.appendChild(styleElement);
const themeMode = theme === 'dark' ? 'dark' : 'light';
const selectedTheme = themeStyles[selectTheme] || themeStyles.default;
// Dynamically import the corresponding style
const css = await selectedTheme[themeMode]();
styleElement.innerHTML = css.default;
// Apply syntax highlighting
element.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block as HTMLElement);
(block as HTMLElement).style.backgroundColor = 'transparent';
(block as HTMLElement).style.padding = '0';
});
};
// Get and apply the initial theme
const currentTheme =
document.documentElement.getAttribute('data-bs-theme') || 'light';
applyThemeCSS(currentTheme);
// Observe DOM changes (e.g., code block content changes)
const contentObserver = new MutationObserver(() => {
const newTheme =
document.documentElement.getAttribute('data-bs-theme') || 'light';
console.log(
'Detected code content change, reapplying syntax highlighting, current theme:',
newTheme,
);
applyThemeCSS(newTheme);
});
contentObserver.observe(element, {
childList: true, // Observe changes to child elements
subtree: true, // Observe the entire subtree
});
// Observe theme changes
const themeObserver = new MutationObserver(() => {
const newTheme =
document.documentElement.getAttribute('data-bs-theme') || 'light';
console.log('Detected theme change:', newTheme);
applyThemeCSS(newTheme);
});
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-bs-theme'],
});
return () => {
contentObserver.disconnect();
themeObserver.disconnect();
};
}, [props, selectTheme]);
return null;
};
export { useHighlightCode };