From bc69d279e6f0c190245b3eb848d771314972c1f3 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Thu, 28 Nov 2024 15:16:54 +0300 Subject: [PATCH 01/30] feat: add getTableOfContent parseYAML --- utils/helper.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/utils/helper.js b/utils/helper.js index a34e6a51..13898d2e 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -684,6 +684,69 @@ const transformHref = (href) => { return href } +export const getTableOfContent = async ({ zip, href }) => { + if (!zip || !href) { + console.error('The archive is not provided.') + return {} + } + const transformedHref = transformHref(href) + + const targetFiles = [`${transformedHref}`] + + const results = await Promise.all( + targetFiles.map(async (filePath) => { + const file = zip.files[filePath] + if (!file) { + console.warn(`The ${filePath} file was not found.`) + return { path: filePath, content: null } + } + + const content = await file.async('text') + return { path: filePath, content } + }) + ) + + const fileObject = results.reduce((acc, { path, content }) => { + const key = path.split('/').pop() + acc[key] = content + return acc + }, {}) + return fileObject +} + +export const parseYAML = (yamlData) => { + const parsedData = jsyaml.load(yamlData) + + if (!parsedData || !parsedData.sections || !Array.isArray(parsedData.sections)) { + throw new Error("Invalid YAML structure: 'sections' not found or not an array") + } + + const processSections = (sections, depth = 0) => { + return sections.reduce((acc, section) => { + const { title, link, sections: childSections } = section + + if (title && link) { + acc.push({ title, link, depth }) // Добавляем уровень вложенности + } + if (childSections && Array.isArray(childSections)) { + acc.push(...processSections(childSections, depth + 1)) // Увеличиваем вложенность для дочерних элементов + } + return acc + }, []) + } + + const flattenedSections = processSections(parsedData.sections) + + return { + titleLinkMap: flattenedSections.reduce((acc, { title, link }) => { + acc[title] = link + return acc + }, {}), + sections: flattenedSections, + title: parsedData.title || null, + } +} + export const getWordsAcademy = async ({ zip, href }) => { if (!zip || !href) { console.error('The archive is not provided.') From 55441e3a5c497a69f6ae80f0cde117203df67e84 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Thu, 28 Nov 2024 15:17:51 +0300 Subject: [PATCH 02/30] feat: add base Navigation --- components/CustomComboBox.js | 56 ++++++++++++++++++++++ components/Panel/UI/TAContent.js | 2 +- components/Panel/UI/TaTopics.js | 82 ++++++++++++++++++++++++++++---- 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 components/CustomComboBox.js diff --git a/components/CustomComboBox.js b/components/CustomComboBox.js new file mode 100644 index 00000000..c103043e --- /dev/null +++ b/components/CustomComboBox.js @@ -0,0 +1,56 @@ +import { useState } from 'react' + +function CustomComboBox({ topics, selectedTopic, onChange }) { + const [isOpen, setIsOpen] = useState(false) + const [searchQuery, setSearchQuery] = useState('') + + const handleItemClick = (link) => { + onChange(link) + setIsOpen(false) + } + + const filteredTopics = topics.filter((topic) => + topic.title.toLowerCase().includes(searchQuery.toLowerCase()) + ) + + const renderNestedList = (items) => { + return items.map((item) => ( +
  • handleItemClick(item.link)} + className={`m-2 cursor-pointer p-2 hover:bg-gray-100 ${ + selectedTopic === item.link ? 'bg-gray-200 font-bold' : '' + }`} + > + {item.title} +
  • + )) + } + + return ( +
    + setSearchQuery(e.target.value)} + className="w-full rounded border border-gray-300 p-2" + onClick={() => setIsOpen(!isOpen)} + /> + {isOpen && ( +
    +
      + {filteredTopics.length > 0 ? ( + renderNestedList(filteredTopics) + ) : ( +
    • No topics found
    • + )} +
    +
    + )} +
    + ) +} + +export default CustomComboBox diff --git a/components/Panel/UI/TAContent.js b/components/Panel/UI/TAContent.js index 7c9873af..2694e3fb 100644 --- a/components/Panel/UI/TAContent.js +++ b/components/Panel/UI/TAContent.js @@ -13,7 +13,7 @@ function TAContent({ item, setHref, config, goBack }) { return (
    diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 4587890e..eca628a0 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -2,18 +2,19 @@ import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' +import CustomComboBox from 'components/CustomComboBox' + import TaContentInfo from '../Resources/TAContentInfo' import TAContent from './TAContent' import { getFile } from 'utils/apiHelper' import { academyLinks } from 'utils/config' -import { getWordsAcademy, resolvePath } from 'utils/helper' +import { getTableOfContent, getWordsAcademy, parseYAML, resolvePath } from 'utils/helper' import Loading from 'public/icons/progress.svg' function TaTopics() { const { locale } = useRouter() - const config = locale === 'ru' ? academyLinks['ru'] : academyLinks['en'] const [href, setHref] = useState('intro/ta-intro') @@ -22,6 +23,24 @@ function TaTopics() { const [loading, setLoading] = useState(false) const scrollRef = useRef(null) + const [selectedCategory, setSelectedCategory] = useState('intro') + const [selectedTopic, setSelectedTopic] = useState('') + const [topics, setTopics] = useState([]) + + const processSections = (sections, parentTitle = '', depth = 0) => { + return sections.reduce((acc, section) => { + const { title, link, sections: childSections } = section + if (title && link) { + const fullPath = parentTitle ? `${parentTitle} > ${title}` : title + acc.push({ title: fullPath, link, depth }) + } + if (childSections && Array.isArray(childSections)) { + acc.push(...processSections(childSections, fullPath || title, depth + 1)) + } + return acc + }, []) + } + const updateHref = (newRelativePath) => { const { absolutePath } = resolvePath(config.base, href, newRelativePath) const newHref = absolutePath.replace(config.base + '/', '') @@ -44,8 +63,23 @@ function TaTopics() { }) } + const handleCategoryChange = (event) => { + const newCategory = event.target.value + setSelectedCategory(newCategory) + setSelectedTopic('') + setHref(`${newCategory}/`) + } + + const handleTopicChange = (newTopic) => { + setSelectedTopic(newTopic) + if (selectedCategory && newTopic) { + const newHref = `${selectedCategory}/${newTopic}` + setHref(newHref) + } + } + useEffect(() => { - const getData = async () => { + const fetchData = async () => { setLoading(true) try { const zip = await getFile({ @@ -55,6 +89,19 @@ function TaTopics() { apiUrl: '/api/git/ta', }) + const tableContent = await getTableOfContent({ + zip, + href: `${config.base}/${selectedCategory}/toc.yaml`, + }) + const yamlString = tableContent['toc.yaml'] + if (!yamlString) throw new Error('YAML-файл не найден') + + const parsedYaml = parseYAML(yamlString) + const sections = parsedYaml?.sections || [] + + const processedTopics = processSections(sections) + setTopics(processedTopics) + const fetchedWords = await getWordsAcademy({ zip, href: `${config.base}/${href}`, @@ -62,12 +109,11 @@ function TaTopics() { const title = fetchedWords?.['sub-title'] || href const text = fetchedWords?.['01'] || href - const item = { + setItem({ title, text, type: 'ta', - } - setItem?.(item) + }) } catch (error) { console.error('Error fetching data:', error) } finally { @@ -75,8 +121,8 @@ function TaTopics() { } } - getData() - }, [href, config.base, config.resource]) + fetchData() + }, [href, selectedCategory, config.base, config.resource]) useEffect(() => { if (scrollRef.current) { @@ -94,7 +140,27 @@ function TaTopics() {
    )} +
    +
    + + + +
    + Date: Thu, 28 Nov 2024 16:08:56 +0300 Subject: [PATCH 03/30] feat: add getTitleOfContent --- utils/helper.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/utils/helper.js b/utils/helper.js index 13898d2e..b3c97687 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -684,6 +684,37 @@ const transformHref = (href) => { return href } +export const getTitleOfContent = async ({ zip, href }) => { + if (!zip || !href) { + console.error('The archive is not provided.') + return {} + } + + const parts = href.slice(5).split('/') + const transformedHref = `${parts[0]}_${parts[1]}/${parts[3]}` + + const targetFiles = [`${transformedHref}`] + + const results = await Promise.all( + targetFiles.map(async (filePath) => { + const file = zip.files[filePath] + if (!file) { + console.warn(`The ${filePath} file was not found.`) + return { path: filePath, content: null } + } + + const content = await file.async('text') + return { path: filePath, content } + }) + ) + const fileObject = results.reduce((acc, { path, content }) => { + const key = path.split('/').pop() + acc[key] = content + return acc + }, {}) + return fileObject +} + export const getTableOfContent = async ({ zip, href }) => { if (!zip || !href) { console.error('The archive is not provided.') @@ -705,7 +736,7 @@ export const getTableOfContent = async ({ zip, href }) => { return { path: filePath, content } }) ) - + console.log(results, 708) const fileObject = results.reduce((acc, { path, content }) => { const key = path.split('/').pop() acc[key] = content From b7147e244fa75b622562c50fbb4ed59dd7645437 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 11:01:00 +0300 Subject: [PATCH 04/30] feat: add fill categoryOptions from manifest --- components/CustomComboBox.js | 34 +++--- components/Panel/UI/TaTopics.js | 195 +++++++++++++++++++++++++------- utils/helper.js | 5 +- 3 files changed, 171 insertions(+), 63 deletions(-) diff --git a/components/CustomComboBox.js b/components/CustomComboBox.js index c103043e..625d6a7d 100644 --- a/components/CustomComboBox.js +++ b/components/CustomComboBox.js @@ -2,47 +2,43 @@ import { useState } from 'react' function CustomComboBox({ topics, selectedTopic, onChange }) { const [isOpen, setIsOpen] = useState(false) - const [searchQuery, setSearchQuery] = useState('') const handleItemClick = (link) => { onChange(link) setIsOpen(false) } - const filteredTopics = topics.filter((topic) => - topic.title.toLowerCase().includes(searchQuery.toLowerCase()) - ) - const renderNestedList = (items) => { return items.map((item) => (
  • handleItemClick(item.link)} className={`m-2 cursor-pointer p-2 hover:bg-gray-100 ${ selectedTopic === item.link ? 'bg-gray-200 font-bold' : '' - }`} + } truncate`} > - {item.title} + {item.title}
  • )) } + const selectedTitle = + topics.find((topic) => topic.link === selectedTopic)?.title || 'Select a topic' + return ( -
    - setSearchQuery(e.target.value)} - className="w-full rounded border border-gray-300 p-2" +
    +
    setIsOpen(!isOpen)} - /> + title={selectedTitle} + > + {selectedTitle} +
    {isOpen && ( -
    +
      - {filteredTopics.length > 0 ? ( - renderNestedList(filteredTopics) + {topics.length > 0 ? ( + renderNestedList(topics) ) : (
    • No topics found
    • )} diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index eca628a0..65561b5b 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -1,7 +1,9 @@ -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' +import yaml from 'js-yaml' + import CustomComboBox from 'components/CustomComboBox' import TaContentInfo from '../Resources/TAContentInfo' @@ -9,7 +11,13 @@ import TAContent from './TAContent' import { getFile } from 'utils/apiHelper' import { academyLinks } from 'utils/config' -import { getTableOfContent, getWordsAcademy, parseYAML, resolvePath } from 'utils/helper' +import { + getTableOfContent, + getTitleOfContent, + getWordsAcademy, + parseYAML, + resolvePath, +} from 'utils/helper' import Loading from 'public/icons/progress.svg' @@ -23,11 +31,12 @@ function TaTopics() { const [loading, setLoading] = useState(false) const scrollRef = useRef(null) - const [selectedCategory, setSelectedCategory] = useState('intro') + const [selectedCategory, setSelectedCategory] = useState('') const [selectedTopic, setSelectedTopic] = useState('') const [topics, setTopics] = useState([]) + const [categoryOptions, setCategoryOptions] = useState([]) - const processSections = (sections, parentTitle = '', depth = 0) => { + const processSections = useCallback((sections, parentTitle = '', depth = 0) => { return sections.reduce((acc, section) => { const { title, link, sections: childSections } = section if (title && link) { @@ -39,44 +48,122 @@ function TaTopics() { } return acc }, []) - } - - const updateHref = (newRelativePath) => { - const { absolutePath } = resolvePath(config.base, href, newRelativePath) - const newHref = absolutePath.replace(config.base + '/', '') - - if (newHref === href) { - setHref('') - setTimeout(() => setHref(newHref), 0) - } else { - setHistory((prev) => [...prev, href]) - setHref(newHref) + }, []) + + const handleCategoryChange = useCallback( + (event) => { + const newCategory = event.target.value + setSelectedCategory(newCategory) + setSelectedTopic('') + + const fetchTopicsForCategory = async () => { + try { + setLoading(true) + + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + + const tableContent = await getTableOfContent({ + zip, + href: `${config.base}/${newCategory}/toc.yaml`, + }) + + const yamlString = tableContent['toc.yaml'] + if (!yamlString) throw new Error('YAML-файл не найден') + + const parsedYaml = parseYAML(yamlString) + const sections = parsedYaml?.sections || [] + + const processedTopics = processSections(sections) + setTopics(processedTopics) + } catch (error) { + console.error('Ошибка загрузки тем для категории:', error) + } finally { + setLoading(false) + } + } + + fetchTopicsForCategory() + }, + [config.base, config.resource, processSections] + ) + + const handleTopicChange = useCallback( + async (newTopic) => { + setSelectedTopic(newTopic) + if (selectedCategory && newTopic) { + const newHref = `${selectedCategory}/${newTopic}` + setHref(newHref) + + try { + setLoading(true) + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + + const fetchedWords = await getWordsAcademy({ + zip, + href: `${config.base}/${newHref}`, + }) + + const title = fetchedWords?.['sub-title'] || newHref + const text = fetchedWords?.['01'] || newHref + setItem({ + title, + text, + type: 'ta', + }) + } catch (error) { + console.error('Error fetching topic content:', error) + } finally { + setLoading(false) + } + } + }, + [selectedCategory, config.base, config.resource] + ) + useEffect(() => { + if (topics.length > 0 && selectedCategory) { + const isCurrentTopicValid = topics.some((topic) => topic.link === selectedTopic) + if (!isCurrentTopicValid) { + const firstTopicLink = topics[0].link + setSelectedTopic(firstTopicLink) + setHref(`${selectedCategory}/${firstTopicLink}`) + } } - } + }, [topics, selectedCategory]) + + const updateHref = useCallback( + (newRelativePath) => { + const { absolutePath } = resolvePath(config.base, href, newRelativePath) + const newHref = absolutePath.replace(config.base + '/', '') - const goBack = () => { + if (newHref === href) { + setHref('') + setTimeout(() => setHref(newHref), 0) + } else { + setHistory((prev) => [...prev, href]) + setHref(newHref) + } + }, + [href, config.base] + ) + + const goBack = useCallback(() => { setHistory((prev) => { const newHistory = [...prev] const lastHref = newHistory.pop() if (lastHref) setHref(lastHref) return newHistory }) - } - - const handleCategoryChange = (event) => { - const newCategory = event.target.value - setSelectedCategory(newCategory) - setSelectedTopic('') - setHref(`${newCategory}/`) - } - - const handleTopicChange = (newTopic) => { - setSelectedTopic(newTopic) - if (selectedCategory && newTopic) { - const newHref = `${selectedCategory}/${newTopic}` - setHref(newHref) - } - } + }, []) useEffect(() => { const fetchData = async () => { @@ -89,12 +176,37 @@ function TaTopics() { apiUrl: '/api/git/ta', }) + const titleContent = await getTitleOfContent({ + zip, + href: `${config.base}/manifest.yaml`, + }) + + const titleContentDataString = titleContent['manifest.yaml'] + const titleContentData = yaml.load(titleContentDataString) + + const projects = titleContentData?.projects + if (!projects || projects.length === 0) { + console.error('Projects not found in manifest.yaml') + return + } + + const projectOptions = projects.map((project) => ({ + value: project.identifier, + label: project.title, + })) + setCategoryOptions(projectOptions) + + if (!selectedCategory) { + setSelectedCategory(projectOptions[0]?.value || '') + } + const tableContent = await getTableOfContent({ zip, - href: `${config.base}/${selectedCategory}/toc.yaml`, + href: `${config.base}/${selectedCategory || projectOptions[0]?.value || ''}/toc.yaml`, }) + const yamlString = tableContent['toc.yaml'] - if (!yamlString) throw new Error('YAML-файл не найден') + if (!yamlString) throw new Error('YAML file not found') const parsedYaml = parseYAML(yamlString) const sections = parsedYaml?.sections || [] @@ -122,7 +234,7 @@ function TaTopics() { } fetchData() - }, [href, selectedCategory, config.base, config.resource]) + }, [href, selectedCategory, config.base, config.resource, processSections]) useEffect(() => { if (scrollRef.current) { @@ -148,10 +260,11 @@ function TaTopics() { onChange={handleCategoryChange} className="rounded border border-gray-300 p-2" > - - - - + {categoryOptions.map((option) => ( + + ))} { return { path: filePath, content } }) ) - console.log(results, 708) const fileObject = results.reduce((acc, { path, content }) => { const key = path.split('/').pop() acc[key] = content @@ -757,10 +756,10 @@ export const parseYAML = (yamlData) => { const { title, link, sections: childSections } = section if (title && link) { - acc.push({ title, link, depth }) // Добавляем уровень вложенности + acc.push({ title, link, depth }) } if (childSections && Array.isArray(childSections)) { - acc.push(...processSections(childSections, depth + 1)) // Увеличиваем вложенность для дочерних элементов + acc.push(...processSections(childSections, depth + 1)) } return acc }, []) From 0b317c365a2836acee37fe1aad094418e26645d1 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 11:14:22 +0300 Subject: [PATCH 05/30] fix: delete used CustomComboBox --- components/CustomComboBox.js | 2 +- components/Panel/UI/TAContent.js | 2 +- components/Panel/UI/TaTopics.js | 23 ++++++++++++++--------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/components/CustomComboBox.js b/components/CustomComboBox.js index 625d6a7d..76d7fc52 100644 --- a/components/CustomComboBox.js +++ b/components/CustomComboBox.js @@ -26,7 +26,7 @@ function CustomComboBox({ topics, selectedTopic, onChange }) { topics.find((topic) => topic.link === selectedTopic)?.title || 'Select a topic' return ( -
      +
      setIsOpen(!isOpen)} diff --git a/components/Panel/UI/TAContent.js b/components/Panel/UI/TAContent.js index 2694e3fb..bceb140c 100644 --- a/components/Panel/UI/TAContent.js +++ b/components/Panel/UI/TAContent.js @@ -13,7 +13,7 @@ function TAContent({ item, setHref, config, goBack }) { return (
      diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 65561b5b..5a9136ff 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -4,8 +4,6 @@ import { useRouter } from 'next/router' import yaml from 'js-yaml' -import CustomComboBox from 'components/CustomComboBox' - import TaContentInfo from '../Resources/TAContentInfo' import TAContent from './TAContent' @@ -254,7 +252,7 @@ function TaTopics() { )}
      -
      +
      handleTopicChange(e.target.value)} + className="rounded border border-gray-300 p-2" + > + {topics.map((topic) => ( + + ))} +
      ) From c34dba264f1b30966d7e6ac157e40d78afcc022c Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 11:27:23 +0300 Subject: [PATCH 06/30] feat: update info navigation --- components/Panel/UI/TaTopics.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 5a9136ff..c5e4359e 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -141,7 +141,7 @@ function TaTopics() { const updateHref = useCallback( (newRelativePath) => { const { absolutePath } = resolvePath(config.base, href, newRelativePath) - const newHref = absolutePath.replace(config.base + '/', '') + const newHref = absolutePath.replace(`${config.base}/`, '') if (newHref === href) { setHref('') @@ -150,8 +150,16 @@ function TaTopics() { setHistory((prev) => [...prev, href]) setHref(newHref) } + + const [newCategory, newTopic] = newHref.split('/') + if (newCategory && newCategory !== selectedCategory) { + setSelectedCategory(newCategory) + setSelectedTopic(newTopic || '') + } else if (newTopic && newTopic !== selectedTopic) { + setSelectedTopic(newTopic) + } }, - [href, config.base] + [href, config.base, selectedCategory, selectedTopic] ) const goBack = useCallback(() => { From b1dc555e35e5de0f2cd285fc2dd3f249f73a3f41 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 11:36:52 +0300 Subject: [PATCH 07/30] feat: add save history navigation --- components/Panel/UI/TaTopics.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index c5e4359e..f2afcf9a 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -53,6 +53,7 @@ function TaTopics() { const newCategory = event.target.value setSelectedCategory(newCategory) setSelectedTopic('') + setHistory((prev) => [...prev, href]) const fetchTopicsForCategory = async () => { try { @@ -87,12 +88,14 @@ function TaTopics() { fetchTopicsForCategory() }, - [config.base, config.resource, processSections] + [config.base, config.resource, processSections, href] ) const handleTopicChange = useCallback( async (newTopic) => { setSelectedTopic(newTopic) + setHistory((prev) => [...prev, href]) + if (selectedCategory && newTopic) { const newHref = `${selectedCategory}/${newTopic}` setHref(newHref) @@ -125,8 +128,9 @@ function TaTopics() { } } }, - [selectedCategory, config.base, config.resource] + [selectedCategory, config.base, config.resource, href] ) + useEffect(() => { if (topics.length > 0 && selectedCategory) { const isCurrentTopicValid = topics.some((topic) => topic.link === selectedTopic) @@ -141,32 +145,31 @@ function TaTopics() { const updateHref = useCallback( (newRelativePath) => { const { absolutePath } = resolvePath(config.base, href, newRelativePath) - const newHref = absolutePath.replace(`${config.base}/`, '') + const newHref = absolutePath.replace(config.base + '/', '') - if (newHref === href) { - setHref('') - setTimeout(() => setHref(newHref), 0) - } else { + if (newHref !== href) { setHistory((prev) => [...prev, href]) setHref(newHref) - } - const [newCategory, newTopic] = newHref.split('/') - if (newCategory && newCategory !== selectedCategory) { - setSelectedCategory(newCategory) + const [newCategory, newTopic] = newHref.split('/') + setSelectedCategory(newCategory || '') setSelectedTopic(newTopic || '') - } else if (newTopic && newTopic !== selectedTopic) { - setSelectedTopic(newTopic) } }, - [href, config.base, selectedCategory, selectedTopic] + [href, config.base] ) const goBack = useCallback(() => { setHistory((prev) => { const newHistory = [...prev] const lastHref = newHistory.pop() - if (lastHref) setHref(lastHref) + if (lastHref) { + setHref(lastHref) + + const [category, topic] = lastHref.split('/') + setSelectedCategory(category || '') + setSelectedTopic(topic || '') + } return newHistory }) }, []) From 803c7efe7e17df295345a32522aba71d334bfede Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 12:47:13 +0300 Subject: [PATCH 08/30] fix: edit parseYAML --- utils/helper.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/utils/helper.js b/utils/helper.js index eebd8d4b..97dfd3f3 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -751,27 +751,35 @@ export const parseYAML = (yamlData) => { throw new Error("Invalid YAML structure: 'sections' not found or not an array") } - const processSections = (sections, depth = 0) => { - return sections.reduce((acc, section) => { + const processSections = ( + sections, + depth = 0, + titleLinkMap = {}, + flattenedSections = [] + ) => { + sections.forEach((section) => { const { title, link, sections: childSections } = section - if (title && link) { - acc.push({ title, link, depth }) + if (title) { + const resolvedLink = link || titleLinkMap[title] + + titleLinkMap[title] = resolvedLink + + flattenedSections.push({ title, link: resolvedLink, depth }) } + if (childSections && Array.isArray(childSections)) { - acc.push(...processSections(childSections, depth + 1)) + processSections(childSections, depth + 1, titleLinkMap, flattenedSections) } - return acc - }, []) + }) + + return { titleLinkMap, flattenedSections } } - const flattenedSections = processSections(parsedData.sections) + const { titleLinkMap, flattenedSections } = processSections(parsedData.sections) return { - titleLinkMap: flattenedSections.reduce((acc, { title, link }) => { - acc[title] = link - return acc - }, {}), + titleLinkMap, sections: flattenedSections, title: parsedData.title || null, } From a7a2fda95c37288db735aa3ab64afb1c4ef2faa3 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 12:48:31 +0300 Subject: [PATCH 09/30] feat: used depth for children item --- components/Panel/UI/TaTopics.js | 56 +++------------------------------ 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index f2afcf9a..1595df21 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -34,61 +34,14 @@ function TaTopics() { const [topics, setTopics] = useState([]) const [categoryOptions, setCategoryOptions] = useState([]) - const processSections = useCallback((sections, parentTitle = '', depth = 0) => { - return sections.reduce((acc, section) => { - const { title, link, sections: childSections } = section - if (title && link) { - const fullPath = parentTitle ? `${parentTitle} > ${title}` : title - acc.push({ title: fullPath, link, depth }) - } - if (childSections && Array.isArray(childSections)) { - acc.push(...processSections(childSections, fullPath || title, depth + 1)) - } - return acc - }, []) - }, []) - const handleCategoryChange = useCallback( (event) => { const newCategory = event.target.value setSelectedCategory(newCategory) setSelectedTopic('') setHistory((prev) => [...prev, href]) - - const fetchTopicsForCategory = async () => { - try { - setLoading(true) - - const zip = await getFile({ - owner: config.resource.owner, - repo: config.resource.repo.split('_')[0] + '_ta', - commit: config.resource.commit, - apiUrl: '/api/git/ta', - }) - - const tableContent = await getTableOfContent({ - zip, - href: `${config.base}/${newCategory}/toc.yaml`, - }) - - const yamlString = tableContent['toc.yaml'] - if (!yamlString) throw new Error('YAML-файл не найден') - - const parsedYaml = parseYAML(yamlString) - const sections = parsedYaml?.sections || [] - - const processedTopics = processSections(sections) - setTopics(processedTopics) - } catch (error) { - console.error('Ошибка загрузки тем для категории:', error) - } finally { - setLoading(false) - } - } - - fetchTopicsForCategory() }, - [config.base, config.resource, processSections, href] + [href] ) const handleTopicChange = useCallback( @@ -220,8 +173,7 @@ function TaTopics() { const parsedYaml = parseYAML(yamlString) const sections = parsedYaml?.sections || [] - const processedTopics = processSections(sections) - setTopics(processedTopics) + setTopics(sections) const fetchedWords = await getWordsAcademy({ zip, @@ -243,7 +195,7 @@ function TaTopics() { } fetchData() - }, [href, selectedCategory, config.base, config.resource, processSections]) + }, [href, selectedCategory, config.base, config.resource]) useEffect(() => { if (scrollRef.current) { @@ -283,7 +235,7 @@ function TaTopics() { > {topics.map((topic) => ( ))} From 3993a4f3a555e855ccb8c18c7136198947e2dd83 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 13:55:29 +0300 Subject: [PATCH 10/30] feat: add updateParsedYamlTitles --- components/Panel/UI/TaTopics.js | 80 ++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 1595df21..d427216f 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -39,6 +39,7 @@ function TaTopics() { const newCategory = event.target.value setSelectedCategory(newCategory) setSelectedTopic('') + setTopics([]) setHistory((prev) => [...prev, href]) }, [href] @@ -138,6 +139,70 @@ function TaTopics() { apiUrl: '/api/git/ta', }) + const getFileContent = async (fileName) => { + const file = zip.files[fileName] + if (file) { + const content = await file.async('text') + return content + } + throw new Error(`File not found: ${fileName}`) + } + + const names = Object.values(zip.files).map((item) => item.name) + + const filteredArray = names.filter( + (name) => name.includes('title.md') && !name.includes('sub-title.md') + ) + + const titleFiles = [] + + for (const file of filteredArray) { + const fileRef = file + .replace(/^.*?\/(.*)/, '$1') + .split('/') + .slice(0, -1) + .join('/') + try { + const fileContent = await getFileContent(file) + + titleFiles.push({ + title: fileContent, + ref: fileRef, + }) + } catch (error) { + console.error(`Error reading file ${file}:`, error) + } + } + + const updateParsedYamlTitles = (parsedYaml, titleFiles, selectedCategory) => { + const titleMap = titleFiles.reduce((map, file) => { + map[file.ref] = file.title + return map + }, {}) + + const updateSections = (sections) => { + return sections.map((section) => { + const updatedSection = { ...section } + const tempLink = `${selectedCategory}/${updatedSection.link}` + + if (updatedSection.link && titleMap[tempLink]) { + updatedSection.title = titleMap[tempLink] + } + + if (updatedSection.sections) { + updatedSection.sections = updateSections(updatedSection.sections) + } + + return updatedSection + }) + } + + return { + ...parsedYaml, + sections: updateSections(parsedYaml.sections), + } + } + const titleContent = await getTitleOfContent({ zip, href: `${config.base}/manifest.yaml`, @@ -170,9 +235,10 @@ function TaTopics() { const yamlString = tableContent['toc.yaml'] if (!yamlString) throw new Error('YAML file not found') - const parsedYaml = parseYAML(yamlString) - const sections = parsedYaml?.sections || [] + const tempYAML = parseYAML(yamlString) + const parsedYaml = updateParsedYamlTitles(tempYAML, titleFiles, selectedCategory) + const sections = parsedYaml?.sections || [] setTopics(sections) const fetchedWords = await getWordsAcademy({ @@ -221,8 +287,8 @@ function TaTopics() { onChange={handleCategoryChange} className="rounded border border-gray-300 p-2" > - {categoryOptions.map((option) => ( - ))} @@ -233,9 +299,9 @@ function TaTopics() { onChange={(e) => handleTopicChange(e.target.value)} className="rounded border border-gray-300 p-2" > - {topics.map((topic) => ( - ))} From 9aa5abc09913123400f27dd1d7a81f3c950ff185 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 14:01:00 +0300 Subject: [PATCH 11/30] feat: isCheck ref for children --- components/Panel/UI/TaTopics.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index d427216f..777ec693 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -181,10 +181,14 @@ function TaTopics() { }, {}) const updateSections = (sections) => { - return sections.map((section) => { + return sections.map((section, index, array) => { const updatedSection = { ...section } const tempLink = `${selectedCategory}/${updatedSection.link}` + if (!updatedSection.link && array[index + 1]?.link) { + updatedSection.link = array[index + 1].link + } + if (updatedSection.link && titleMap[tempLink]) { updatedSection.title = titleMap[tempLink] } From 6069cd67d1debf698dc91d345c4b452a92971e04 Mon Sep 17 00:00:00 2001 From: DenisArger Date: Fri, 29 Nov 2024 17:25:09 +0300 Subject: [PATCH 12/30] feat: add searchQuery for selected Category --- components/Panel/UI/TaTopics.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 777ec693..abd0f292 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -33,6 +33,10 @@ function TaTopics() { const [selectedTopic, setSelectedTopic] = useState('') const [topics, setTopics] = useState([]) const [categoryOptions, setCategoryOptions] = useState([]) + const [searchQuery, setSearchQuery] = useState('') + const filteredTopics = topics.filter((topic) => + topic.title.toLowerCase().includes(searchQuery.toLowerCase()) + ) const handleCategoryChange = useCallback( (event) => { @@ -40,6 +44,7 @@ function TaTopics() { setSelectedCategory(newCategory) setSelectedTopic('') setTopics([]) + setSearchQuery('') setHistory((prev) => [...prev, href]) }, [href] @@ -298,12 +303,20 @@ function TaTopics() { ))} + setSearchQuery(e.target.value)} + placeholder="Search topics" + className="rounded border border-gray-300 p-2" + /> + setIsOpen(true)} + placeholder={placeholder} + className="w-full rounded border border-gray-300 p-2" + /> + {isOpen && ( +
      + {options.length > 0 ? ( + options.map((option, index) => ( +
      { + onChange(option.link) + setIsOpen(false) + }} + className={`cursor-pointer px-4 py-2 hover:bg-gray-100 ${ + value === option.link ? 'bg-gray-200' : '' + }`} + > + {option.title} +
      + )) + ) : ( +
      Нет совпадений
      + )} +
      + )} +
      + ) +} + +export default DropdownSearch diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 44b1a632..70c6b5de 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -4,6 +4,8 @@ import { useRouter } from 'next/router' import yaml from 'js-yaml' +import DropdownSearch from 'components/DropdownSearch' + import TaContentInfo from '../Resources/TAContentInfo' import TAContent from './TAContent' @@ -37,15 +39,15 @@ function TaTopics() { const [allTopics, setAllTopics] = useState([]) const filteredTopics = (() => { - if (!searchQuery.trim()) { - return topics.map((topic) => ({ ...topic, category: selectedCategory })) - } else { - const query = searchQuery.toLowerCase() - const result = allTopics - .filter((topic) => topic.title.toLowerCase().includes(query)) - .map((topic) => ({ ...topic, category: topic.category || selectedCategory })) - return result + const query = searchQuery.trim().toLowerCase() + + if (!query) { + return allTopics + .filter((topic) => topic.category === selectedCategory) + .map((topic) => ({ ...topic, category: selectedCategory })) } + + return allTopics.filter((topic) => topic.title.toLowerCase().includes(query)) })() const handleCategoryChange = useCallback( @@ -325,25 +327,16 @@ function TaTopics() { ))} - setSearchQuery(e.target.value)} - placeholder="Search topics" - className="rounded border border-gray-300 p-2" - /> - - +
      + handleTopicChange(newValue)} + placeholder="Search topics" + searchQuery={searchQuery} + setSearchQuery={setSearchQuery} + /> +
      Date: Tue, 3 Dec 2024 17:06:16 +0300 Subject: [PATCH 17/30] feat: add with for ModalInSideBar --- components/DropdownSearch.js | 2 +- components/ModalInSideBar.js | 4 +++- components/Panel/UI/TaTopics.js | 20 +++++++++----------- components/SideBar.js | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/components/DropdownSearch.js b/components/DropdownSearch.js index 9eb9a141..dde17edf 100644 --- a/components/DropdownSearch.js +++ b/components/DropdownSearch.js @@ -57,7 +57,7 @@ function DropdownSearch({
      )) ) : ( -
      Нет совпадений
      +
      There are no matches
      )}
      )} diff --git a/components/ModalInSideBar.js b/components/ModalInSideBar.js index 7f970d3c..81044978 100644 --- a/components/ModalInSideBar.js +++ b/components/ModalInSideBar.js @@ -8,6 +8,7 @@ function ModalInSideBar({ buttonTitle, collapsed, contentClassName = 'p-4', + width = '30rem', }) { return ( <> @@ -23,7 +24,8 @@ function ModalInSideBar({ {isOpen && (
      e.stopPropagation()} >
      diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index 70c6b5de..8abcdead 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -314,7 +314,7 @@ function TaTopics() { )}
      -
      +
      setIsOpen(true)} placeholder={placeholder} - className="w-full rounded border border-gray-300 p-2" + className="w-full rounded border border-gray-300 p-3" /> {isOpen && ( -
      +
      {options.length > 0 ? ( options.map((option, index) => (
      -
      +
      e.stopPropagation()} >
      diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js index c0c82276..a2697f79 100644 --- a/components/Panel/UI/TaTopics.js +++ b/components/Panel/UI/TaTopics.js @@ -310,11 +310,11 @@ function TaTopics() { )}
      -
      +