diff --git a/reader/index.html b/reader/index.html
index eb48804..acc8ccd 100644
--- a/reader/index.html
+++ b/reader/index.html
@@ -5,7 +5,6 @@
-
Конспект
diff --git a/reader/src/App.jsx b/reader/src/App.jsx
new file mode 100644
index 0000000..6902412
--- /dev/null
+++ b/reader/src/App.jsx
@@ -0,0 +1,514 @@
+import { StrictMode, useLayoutEffect, useRef, useState, useEffect, useMemo } from "react";
+import { createRoot } from "react-dom/client";
+import { useParams, Navigate, BrowserRouter, Link, Outlet, Route, Routes } from "react-router";
+import { useShallow } from "zustand/shallow";
+import { marked } from "marked";
+import { apiInstance, resourcesInstance } from "./api.js";
+import { useStore } from "./store.js";
+import { useSubject, useTopic, useTopicSyncParams, useTopicsAround } from "./hooks.js";
+import {
+ ArrowBackIcon,
+ ArrowForwardIcon,
+ MenuBookIcon,
+ MyLocationIcon,
+ TitleIcon,
+ WidthIcon,
+ EllipsisIcon,
+ CloseIcon,
+ JustifyTextIcon,
+ TextIncreaseIcon,
+ TextDecreaseIcon,
+ VRuleIcon,
+} from "./icons/Icons";
+
+export function App() {
+ return (
+
+
+
+
+
+ }
+ >
+ } />
+ } />
+
+
+
+ );
+}
+
+function LoadingWrapper({ children }) {
+ const isLoading = useStore((state) => state.isLoading);
+
+ if (isLoading) {
+ return "Loading...";
+ }
+
+ return children;
+}
+
+function Layout() {
+ const config = useStore((state) => state.config);
+ const subject = useSubject();
+ const topic = useTopic();
+
+ return (
+
+ {config.displayTitle && (
+
+
+ {topic
+ ? `${topic.sequence}: ${topic.title}`
+ : `${subject.name} - Конспект за Държавен Изпит`}
+
+
+ )}
+
+
+ );
+}
+
+export function TopicListView() {
+ const itemRefs = useRef({});
+
+ const subjects = useStore((state) => state.subjects);
+
+ const selectedSubject = useSubject();
+ const selectSubject = useStore((state) => state.selectSubject);
+ const [isSelectingSubject, setIsSelectingSubject] = useState(false);
+
+ const selectedTopic = useTopic();
+ const selectTopic = useStore((state) => state.selectTopic);
+
+ const config = useStore((state) => state.config);
+ const changeConfig = useStore((state) => state.changeConfig);
+
+ useLayoutEffect(() => {
+ if (selectedTopic) {
+ itemRefs.current?.[Math.max(selectedTopic.index - 3, 0)].scrollIntoView();
+ }
+ }, [selectedTopic]);
+
+ return (
+ <>
+
+ {selectedSubject.topics.map((topic, topicIdx) => (
+
{
+ itemRefs.current[topicIdx] = node;
+ }}
+ to={`/${topic.id}`}
+ onClick={() => selectTopic(topicIdx)}
+ className={`flex px-2 py-1 rounded-md cursor-pointer border-l-4 ${topic.id === selectedTopic?.id ? "bg-blue-100 border-blue-500" : "border-transparent hover:bg-gray-100"}`}
+ >
+
+ {topic.sequence}
+
+
+ {topic.title}
+
+
+ ))}
+
+
+
+
+
+
+
+ {selectedTopic && (
+
+ )}
+
+
+ {isSelectingSubject && (
+ <>
+
+
+ {subjects.map((subject, subjectIdx) => (
+
+ ))}
+
+ >
+ )}
+
+
+ {selectedTopic && (
+
+
Продължи четенето:
+
+
+ {selectedTopic.sequence}. {selectedTopic.title}
+
+
+ )}
+
+ >
+ );
+}
+
+function FileReader() {
+ const topic = useTopicSyncParams();
+ const topicIdx = useStore((state) => state.topicIdx);
+
+ if (!topic) {
+ return ;
+ }
+
+ console.log({ topic, topicIdx });
+
+ return ;
+}
+
+export function Reader({ topic }) {
+ const config = useStore((state) => state.config);
+ const changeConfig = useStore((state) => state.changeConfig);
+
+ const { prevTopic, nextTopic } = useTopicsAround();
+
+ const resourceIdx = useStore((state) => state.resourceIdx);
+ const selectResource = useStore((state) => state.selectResource);
+ const [isSelectingResource, setIsSelectingResource] = useState(false);
+ const selectedResource = topic.resources[resourceIdx];
+
+ console.log("reader!");
+ if (!prevTopic || !nextTopic) {
+ return "balb";
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {config.contentZoomLevel}%
+
+
+
+
+
+
+
+
+
+
+
+
+ {window.innerWidth > 576 && (
+
+ )}
+ {topic.resources.length > 1 && (
+
+
+ {isSelectingResource && (
+
+ )}
+
+ )}
+
+ {isSelectingResource && (
+
+ {topic.resources.map((resource, rIndex) => (
+
+ ))}
+
+ )}
+
+
+
+
+ {prevTopic === null ? (
+
+ ) : (
+
+
+
+ {prevTopic.sequence - 1 - 1}: {prevTopic.title}
+
+
+ )}
+ {nextTopic === null ? (
+
+ ) : (
+
+
+ {nextTopic.sequence - 1 + 1}: {nextTopic.title}
+
+
+
+ )}
+
+
+ >
+ );
+}
+
+export function PDFViewer({ file, compact, zoomFactor, justifyText }) {
+ const iframeRef = useRef(null);
+ const [content, setContent] = useState(null);
+ const htmlContent = useMemo(() => {
+ const fileContent = `
+
+
+
+
+
+
+
+
+
+
+ ${content}
+
+
+ `;
+ return fileContent;
+ }, [content, compact, justifyText, zoomFactor]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const contentZoomLevel = useStore((state) => state.config.contentZoomLevel);
+
+ useEffect(() => {
+ if (iframeRef.current && iframeRef.current.contentDocument) {
+ iframeRef.current.contentDocument.body.style.zoom = `${contentZoomLevel}%`;
+ }
+ }, [contentZoomLevel]);
+
+ useEffect(() => {
+ const fetchFile = async () => {
+ try {
+ setIsLoading(true);
+ const response = await resourcesInstance.get(`/${file.filename}`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to load file: ${response.status}`);
+ }
+
+ let fileContent = await response.text();
+ if (fileContent === "") {
+ fileContent = "**No Data!**";
+ }
+ fileContent = marked.parse(fileContent);
+
+ setContent(fileContent);
+ setError(null);
+ } catch (err) {
+ console.error("Error loading file:", err);
+ setError(err.message);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchFile();
+ }, [file]);
+
+ if (error) {
+ return Error: {error}
;
+ }
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+
+ );
+}
+
+const DelayedLoader = ({
+ delayMs = 2000,
+ className = "p-4 flex justify-center items-center h-40",
+ text = "Loading...",
+}) => {
+ const [showLoader, setShowLoader] = useState(false);
+
+ useEffect(() => {
+ // Set a timeout to show the loader after the specified delay
+ const timer = setTimeout(() => {
+ setShowLoader(true);
+ }, delayMs);
+
+ // Clear the timeout on unmount
+ return () => clearTimeout(timer);
+ }, []); // Empty dependency array - only run on mount
+
+ // Only render if the delay has passed
+ if (showLoader) {
+ return (
+
+ );
+ }
+
+ // Return null before delay threshold
+ return null;
+};
diff --git a/reader/src/hooks.js b/reader/src/hooks.js
new file mode 100644
index 0000000..8fb761f
--- /dev/null
+++ b/reader/src/hooks.js
@@ -0,0 +1,81 @@
+import { useEffect, useMemo } from "react";
+import { useParams } from "react-router";
+import { useShallow } from "zustand/react/shallow";
+import { useStore } from "./store.js";
+import { getIndexFromTopicId } from "./utils.js";
+
+export function useSubject() {
+ return useStore(
+ useShallow((state) => ({
+ index: state.subjectIdx,
+ ...state.subjects[state.subjectIdx],
+ })),
+ );
+}
+
+export function useTopic() {
+ return useStore(
+ useShallow((state) => {
+ if (state.topicIdx === null) {
+ return null;
+ }
+
+ const { topicIdx, subjectIdx } = state;
+ return {
+ index: topicIdx,
+ subjectIdx,
+ ...state.subjects[subjectIdx].topics[topicIdx],
+ };
+ }),
+ );
+}
+
+export function useTopicSyncParams() {
+ const { topicId } = useParams();
+ const subjects = useStore((state) => state.subjects);
+
+ const selectSubject = useStore((state) => state.selectSubject);
+ const selectTopic = useStore((state) => state.selectTopic);
+
+ const currentTopicIdx = useStore((state) => state.topicIdx);
+ const currentSubjectIdx = useStore((state) => state.subjectIdx);
+
+ const topic = useMemo(() => {
+ if (!topicId) return null;
+
+ const indices = getIndexFromTopicId(topicId);
+ if (!indices) return null;
+
+ const [subjectIdx, topicIdx] = indices;
+ const foundTopic = subjects[subjectIdx]?.topics[topicIdx];
+ if (!foundTopic) return null;
+
+ return { ...foundTopic, index: topicIdx, subjectIdx };
+ }, [topicId, subjects]);
+
+ useEffect(() => {
+ if (topic) {
+ selectSubject(topic.subjectIdx);
+ selectTopic(topic.index);
+ }
+ }, [selectSubject, selectTopic, topic]);
+
+ if (currentTopicIdx === null || currentSubjectIdx === null) {
+ return null;
+ }
+ return topic;
+}
+
+export function useTopicsAround() {
+ return useStore(
+ useShallow((state) => {
+ const { subjects, subjectIdx, topicIdx } = state;
+ const { topics } = subjects[subjectIdx];
+ console.log({ topics, topicIdx });
+ const prevTopic = topicIdx === 0 ? null : topics[topicIdx - 1];
+ const nextTopic = topicIdx === topics.length - 1 ? null : topics[topicIdx + 1];
+
+ return { prevTopic, nextTopic };
+ }),
+ );
+}
diff --git a/reader/src/main.jsx b/reader/src/main.jsx
index d3e6119..fd99551 100644
--- a/reader/src/main.jsx
+++ b/reader/src/main.jsx
@@ -1,496 +1,10 @@
import "./index.css";
-
-import { StrictMode, useLayoutEffect, useRef, useState, useEffect, useMemo } from "react";
+import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
-import { useParams, Navigate, BrowserRouter, Link, Outlet, Route, Routes } from "react-router";
-import { useShallow } from "zustand/shallow";
-import { marked } from "marked";
-import { apiInstance, resourcesInstance } from "./api.js";
-import { useStore } from "./store.js";
-import {
- ArrowBackIcon,
- ArrowForwardIcon,
- MenuBookIcon,
- MyLocationIcon,
- TitleIcon,
- WidthIcon,
- EllipsisIcon,
- CloseIcon,
- JustifyTextIcon,
- TextIncreaseIcon,
- TextDecreaseIcon,
- VRuleIcon,
-} from "./icons/Icons";
+import { App } from "./App";
createRoot(document.getElementById("root")).render(
-
-
- }>
- } />
- } />
-
-
-
+
,
);
-
-function Layout() {
- const params = useParams();
- const topic = useStore((state) => params.topicId && state.topics.byId[params.topicId]);
- const config = useStore((state) => state.config);
-
- return (
-
- {config.displayTitle && (
-
- {topic ? `${topic.index + 1}: ${topic.title}` : "Конспект за Държавен Изпит"}
-
- )}
-
-
- );
-}
-
-const SUBJECTS = [
- { id: 0, name: "ВЪТРЕШНИ БОЛЕСТИ" },
- { id: 1, name: "ФАРМАКОЛОГИЯ" },
-];
-export function TopicListView() {
- const itemRefs = useRef({});
- const topics = useStore(
- useShallow((state) => state.topics.allIds.map((id) => state.topics.byId[id])),
- );
- const selectedTopic = useStore((state) => state.selectedTopic);
- const selectTopic = useStore((state) => state.selectTopic);
- const selectedSubjectIndex = useStore((state) => state.subject);
- const setSelectedSubjectIndex = useStore((state) => state.selectSubject);
- const config = useStore((state) => state.config);
- const changeConfig = useStore((state) => state.changeConfig);
-
- const [isSelectingSubject, setIsSelectingSubject] = useState(false);
-
- useLayoutEffect(() => {
- if (selectedTopic && selectedTopic.id !== null) {
- itemRefs.current?.[Math.max(selectedTopic.index - 3, 0)].scrollIntoView();
- }
- }, [selectedTopic]);
-
- return (
- <>
-
- {topics.map((topic, i) => (
-
{
- itemRefs.current[i] = node;
- }}
- to={`/${topic.id}`}
- onClick={() => selectTopic(topic.id)}
- className={`flex px-2 py-1 rounded-md cursor-pointer border-l-4 ${topic.id === selectedTopic?.id ? "bg-blue-100 border-blue-500" : "border-transparent hover:bg-gray-100"}`}
- >
-
- {i + 1}
-
-
- {topic.title}
-
-
- ))}
-
-
-
-
-
-
-
- {selectedTopic !== null && (
-
- )}
-
-
- {isSelectingSubject && (
- <>
-
-
- {SUBJECTS.map((subject) => (
-
- ))}
-
- >
- )}
-
-
- {selectedTopic !== null && (
-
-
Продължи четенето:
-
-
- {selectedTopic.index + 1}. {selectedTopic.title}
-
-
- )}
-
- >
- );
-}
-
-export function FileReader() {
- const params = useParams();
- const topicsById = useStore((state) => state.topics.byId);
- const selectTopic = useStore((state) => state.selectTopic);
-
- const topic = topicsById[params.topicId];
- if (!topic) {
- return ;
- }
-
- selectTopic(topic.id);
-
- return ;
-}
-
-export function Reader({ topic }) {
- const config = useStore((state) => state.config);
- const changeConfig = useStore((state) => state.changeConfig);
- const selectedVersion = useStore((state) => state.topicVersions[topic.id] ?? 0);
- const selectVersion = useStore((state) => state.selectTopicVersion);
- const getTopicAtIndex = useStore((state) => state.getTopicAtIndex);
-
- const prevTopic = getTopicAtIndex(topic.index - 1);
- const nextTopic = getTopicAtIndex(topic.index + 1);
-
- const [isSelectingVersion, setIsSelectingVersion] = useState(false);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- {config.contentZoomLevel}%
-
-
-
-
-
-
-
-
-
-
-
-
- {window.innerWidth > 576 && (
-
- )}
- {topic.files.length > 1 && (
-
-
- {isSelectingVersion && (
-
- )}
-
- )}
-
- {isSelectingVersion && (
-
- {topic.files.map((file, vIndex) => (
-
- ))}
-
- )}
-
-
-
-
- {topic.isFirst ? (
-
- ) : (
-
-
-
- {prevTopic.index + 1}: {prevTopic.title}
-
-
- )}
- {topic.isLast ? (
-
- ) : (
-
-
- {nextTopic.index + 1}: {nextTopic.title}
-
-
-
- )}
-
-
- >
- );
-}
-
-export function PDFViewer({ file, compact, zoomFactor, justifyText }) {
- const iframeRef = useRef(null);
- const [content, setContent] = useState(null);
- const htmlContent = useMemo(() => {
- const fileContent = `
-
-
-
-
-
-
-
-
-
-
- ${content}
-
-
- `;
- return fileContent;
- }, [content, compact, justifyText, zoomFactor]);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
-
- const contentZoomLevel = useStore((state) => state.config.contentZoomLevel);
-
- useEffect(() => {
- if (iframeRef.current && iframeRef.current.contentDocument) {
- iframeRef.current.contentDocument.body.style.zoom = `${contentZoomLevel}%`;
- }
- }, [contentZoomLevel]);
-
- useEffect(() => {
- const fetchFile = async () => {
- try {
- setIsLoading(true);
- const mdPath = `/files_md/${file.filename}`;
- const response = await fetch(mdPath);
-
- if (!response.ok) {
- throw new Error(`Failed to load file: ${response.status}`);
- }
-
- let fileContent = await response.text();
- if (fileContent === "") {
- fileContent = "**No Data!**";
- }
- fileContent = marked.parse(fileContent);
-
- setContent(fileContent);
- setError(null);
- } catch (err) {
- console.error("Error loading file:", err);
- setError(err.message);
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchFile();
- }, [file]);
-
- if (error) {
- return Error: {error}
;
- }
-
- if (isLoading) {
- return ;
- }
-
- return (
-
-
- );
-}
-
-const DelayedLoader = ({
- delayMs = 2000,
- className = "p-4 flex justify-center items-center h-40",
- text = "Loading...",
-}) => {
- const [showLoader, setShowLoader] = useState(false);
-
- useEffect(() => {
- // Set a timeout to show the loader after the specified delay
- const timer = setTimeout(() => {
- setShowLoader(true);
- }, delayMs);
-
- // Clear the timeout on unmount
- return () => clearTimeout(timer);
- }, []); // Empty dependency array - only run on mount
-
- // Only render if the delay has passed
- if (showLoader) {
- return (
-
- );
- }
-
- // Return null before delay threshold
- return null;
-};
diff --git a/reader/src/store.js b/reader/src/store.js
index 0825f59..c5a6694 100644
--- a/reader/src/store.js
+++ b/reader/src/store.js
@@ -1,36 +1,47 @@
import { create } from "zustand";
import { apiInstance } from "./api.js";
-import structure from "./topics.json";
-const localSubject = (() => {
+function getLocalSubjectIdx() {
let subject = localStorage.getItem("subject");
- subject = typeof subject !== "undefined" ? Number(subject) : 0;
- return subject;
-})();
+ if (typeof subject === "undefined") {
+ subject = 0;
+ }
+ subject = parseInt(subject);
+
+ return Math.max(0, subject);
+}
export const useStore = create((set, get) => ({
- subject: localSubject,
- selectSubject: (id) => {
- set({ subject: id, topics: structure[id] });
- localStorage.setItem("subject", id);
+ isLoading: true,
+ subjects: [],
+ subjectIdx: null,
+ topicIdx: null,
+ resourceIdx: null,
+ selectSubject: (subjectIdx) => {
+ set({ subjectIdx, topicIdx: null, resourceIdx: null });
+ localStorage.setItem("subject", subjectIdx);
},
- topics: structure[localSubject],
- topicVersions: {},
- getTopicAtIndex: (index) => {
- const topicId = get().topics.allIds[index];
- if (!topicId) {
- return null;
+ selectTopic: (topicIdx) => {
+ if (topicIdx === null) {
+ set({ topicIdx, resourceIdx: null });
}
- return get().topics.byId[topicId];
+
+ const { subjects, subjectIdx } = get();
+ const resources = subjects[subjectIdx].topics[topicIdx].resources;
+ const resourceIdx = resources.length - 1;
+ set({ topicIdx, resourceIdx });
},
- selectTopicVersion: (id, version) =>
- set({ topicVersions: { ...get().topicVersions, [id]: version } }),
- selectedTopic: null,
- selectTopic: (id) => {
- const topic = get().topics.byId[id];
- if (topic) {
- set({ selectedTopic: topic });
- }
+ selectResource: (resourceIdx) => {
+ set({ resourceIdx });
+ },
+ getStructure: async () => {
+ const { data: subjects } = await apiInstance("/structure");
+ const subjectIdx = get().subjectIdx ?? getLocalSubjectIdx();
+ set({
+ isLoading: false,
+ subjects,
+ subjectIdx,
+ });
},
config: getLocalConfig(),
changeConfig: (config) => {
@@ -46,6 +57,8 @@ export const useStore = create((set, get) => ({
},
}));
+useStore.getState().getStructure().catch(console.error);
+
function getLocalConfig() {
const defaultConfig = {
displayTitle: true,
@@ -59,9 +72,3 @@ function getLocalConfig() {
return { ...defaultConfig, ...config };
}
-
-async function getStructure() {
- const res = await apiInstance.get("/structure");
- console.log(res);
-}
-getStructure().catch(console.error);
diff --git a/reader/src/topics.json b/reader/src/topics.json
deleted file mode 100644
index de2ae9b..0000000
--- a/reader/src/topics.json
+++ /dev/null
@@ -1,1154 +0,0 @@
-[
- {
- "byId": {
- "F0_43": {
- "id": "F0_43",
- "index": 42,
- "isFirst": false,
- "isLast": false,
- "title": "Остра бъбречна недостатъчност. Хронична бъбречна недостатъчност.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_43_1.md",
- "version": 1
- }
- ]
- },
- "F0_45": {
- "id": "F0_45",
- "index": 44,
- "isFirst": false,
- "isLast": false,
- "title": "Ревматоиден артрит.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_45_1.md",
- "version": 1
- }
- ]
- },
- "F0_46": {
- "id": "F0_46",
- "index": 45,
- "isFirst": false,
- "isLast": false,
- "title": "Лупус еритематодес.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_46_1.md",
- "version": 1
- }
- ]
- },
- "F0_55": {
- "id": "F0_55",
- "index": 54,
- "isFirst": false,
- "isLast": true,
- "title": "Алергия. Алергични заболявания. Анафилактичен шок. Поведение на медицинската сестра при спешни алергични състояния.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_55_1.md",
- "version": 1
- }
- ]
- },
- "F0_52": {
- "id": "F0_52",
- "index": 51,
- "isFirst": false,
- "isLast": false,
- "title": "Бластна левкоза. Хронична миелолевкоза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_52_1.md",
- "version": 1
- }
- ]
- },
- "F0_53": {
- "id": "F0_53",
- "index": 52,
- "isFirst": false,
- "isLast": false,
- "title": "Нехочкинови и хочкинови лимфоми.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_53_1.md",
- "version": 1
- }
- ]
- },
- "F0_54": {
- "id": "F0_54",
- "index": 53,
- "isFirst": false,
- "isLast": false,
- "title": "Остри екзогенни интоксикации. Общи принципи и правила в лечението на острите екзогенни отравяния. Поведение на медицинската сестра и грижи за болния с остро отравяне.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_54_1.md",
- "version": 1
- }
- ]
- },
- "F0_51": {
- "id": "F0_51",
- "index": 50,
- "isFirst": false,
- "isLast": false,
- "title": "Хеморагични диатези – хемофилия, есенциална тромбоцитопения, капиляротоксикоза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_51_1.md",
- "version": 1
- }
- ]
- },
- "F0_50": {
- "id": "F0_50",
- "index": 49,
- "isFirst": false,
- "isLast": false,
- "title": "Хемолитични анемии вследствие на вътре- и извънеритроцитни фактори: вродени и придобити.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_50_1.md",
- "version": 1
- }
- ]
- },
- "F0_5": {
- "id": "F0_5",
- "index": 4,
- "isFirst": false,
- "isLast": false,
- "title": "Сегашно състояние – обективен статус на болния. Клинична диагноза и прогноза. Проследяване на болния – декурзус.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_05_1.md",
- "version": 1
- }
- ]
- },
- "F0_4": {
- "id": "F0_4",
- "index": 3,
- "isFirst": false,
- "isLast": false,
- "title": "Основни класически методи на изследване във вътрешната медицина – анамнеза. Физикални методи на изследване на пациентите – оглед, палпация, перкусия, аускултация. Специални методи на изследване на пациентите.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_04_1.md",
- "version": 1
- }
- ]
- },
- "F0_3": {
- "id": "F0_3",
- "index": 2,
- "isFirst": false,
- "isLast": false,
- "title": "Агония. Клинична смърт. Биологична смърт.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_03_1.md",
- "version": 1
- }
- ]
- },
- "F0_2": {
- "id": "F0_2",
- "index": 1,
- "isFirst": false,
- "isLast": false,
- "title": "Болест и здраве. Етиология и патогенеза на болестите. Периоди на болестта.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_02_1.md",
- "version": 1
- }
- ]
- },
- "F0_1": {
- "id": "F0_1",
- "index": 0,
- "isFirst": true,
- "isLast": false,
- "title": "Предмет и задачи на вътрешната медицина. Раздели на вътрешните болести.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_01_1.md",
- "version": 1
- }
- ]
- },
- "F0_6": {
- "id": "F0_6",
- "index": 5,
- "isFirst": false,
- "isLast": false,
- "title": "Изследване на дихателната система. Основни симптоми и синдроми при заболявания на дихателната система. Физикални и специални методи на изследване на дихателната система.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_06_1.md",
- "version": 1
- }
- ]
- },
- "F0_7": {
- "id": "F0_7",
- "index": 6,
- "isFirst": false,
- "isLast": false,
- "title": "Остър и хроничен бронхит. Белодробен емфизем. ХОББ.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_07_1.md",
- "version": 1
- }
- ]
- },
- "F0_8": {
- "id": "F0_8",
- "index": 7,
- "isFirst": false,
- "isLast": false,
- "title": "Пневмонии: класификации, клиника, лечение.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_08_1.md",
- "version": 1
- }
- ]
- },
- "F0_9": {
- "id": "F0_9",
- "index": 8,
- "isFirst": false,
- "isLast": false,
- "title": "Бронхиектазии. Белодробен абсцес.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_09_1.md",
- "version": 1
- }
- ]
- },
- "F0_10": {
- "id": "F0_10",
- "index": 9,
- "isFirst": false,
- "isLast": false,
- "title": "Тумори на белия дроб.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_10_1.md",
- "version": 1
- }
- ]
- },
- "F0_11": {
- "id": "F0_11",
- "index": 10,
- "isFirst": false,
- "isLast": false,
- "title": "Белодробен тромбоемболизъм.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_11_1.md",
- "version": 1
- }
- ]
- },
- "F0_12": {
- "id": "F0_12",
- "index": 11,
- "isFirst": false,
- "isLast": false,
- "title": "Плеврити.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_12_1.md",
- "version": 1
- }
- ]
- },
- "F0_13": {
- "id": "F0_13",
- "index": 12,
- "isFirst": false,
- "isLast": false,
- "title": "Белодробна туберкулоза – етиология, патогенеза и клинична картина. Първична белодробна туберкулоза. Вторична белодробна туберкулоза. Лечение и профилактика на белодробната туберкулоза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_13_1.md",
- "version": 1
- }
- ]
- },
- "F0_14": {
- "id": "F0_14",
- "index": 13,
- "isFirst": false,
- "isLast": false,
- "title": "Дихателна недостатъчност – остра и хронична. Етиология и патогенеза. Степени, клиника и поведение.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_14_1.md",
- "version": 1
- }
- ]
- },
- "F0_15": {
- "id": "F0_15",
- "index": 14,
- "isFirst": false,
- "isLast": false,
- "title": "Основни симптоми и синдроми при заболявания на сърдечно-съдовата система. Физикални и специални методи на изследване на сърдечно-съдовата система.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_15_1.md",
- "version": 1
- }
- ]
- },
- "F0_16": {
- "id": "F0_16",
- "index": 15,
- "isFirst": false,
- "isLast": false,
- "title": "Ревматизъм.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_16_1.md",
- "version": 1
- }
- ]
- },
- "F0_17": {
- "id": "F0_17",
- "index": 16,
- "isFirst": false,
- "isLast": false,
- "title": "Сърдечна недостатъчност – остра и хронична.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_17_1.md",
- "version": 1
- }
- ]
- },
- "F0_18": {
- "id": "F0_18",
- "index": 17,
- "isFirst": false,
- "isLast": false,
- "title": "Лечение на сърдечната недостатъчност. Поведение на медицинската сестра при спешни състояния на остра или обострена хронична сърдечна недостатъчност.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_18_1.md",
- "version": 1
- }
- ]
- },
- "F0_19": {
- "id": "F0_19",
- "index": 18,
- "isFirst": false,
- "isLast": false,
- "title": "Ендокардити, перикардити. Миокардити.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_19_1.md",
- "version": 1
- }
- ]
- },
- "F0_20": {
- "id": "F0_20",
- "index": 19,
- "isFirst": false,
- "isLast": false,
- "title": "Хипертонична болест: рискови фактори, патогенеза, клиника, лечение.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_20_1.md",
- "version": 1
- }
- ]
- },
- "F0_21": {
- "id": "F0_21",
- "index": 20,
- "isFirst": false,
- "isLast": false,
- "title": "Остра периферна сърдечно-съдова недостатъчност. Кардиогенен шок.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_21_1.md",
- "version": 1
- }
- ]
- },
- "F0_22": {
- "id": "F0_22",
- "index": 21,
- "isFirst": false,
- "isLast": false,
- "title": "Ритъмни и проводни нарушения на сърдечната дейност.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_22_1.md",
- "version": 1
- }
- ]
- },
- "F0_24": {
- "id": "F0_24",
- "index": 23,
- "isFirst": false,
- "isLast": false,
- "title": "Исхемична болест на сърцето: етиология и патогенеза, рискови фактори. Инфаркт на миокарда.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_24_1.md",
- "version": 1
- }
- ]
- },
- "F0_23": {
- "id": "F0_23",
- "index": 22,
- "isFirst": false,
- "isLast": false,
- "title": "Исхемична болест на сърцето: етиология и патогенеза, рискови фактори. Стенокардия.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_23_1.md",
- "version": 1
- }
- ]
- },
- "F0_25": {
- "id": "F0_25",
- "index": 24,
- "isFirst": false,
- "isLast": false,
- "title": "Болести на хипофизата: Акромегалия. Безвкусен диабет.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_25_1.md",
- "version": 1
- }
- ]
- },
- "F0_26": {
- "id": "F0_26",
- "index": 25,
- "isFirst": false,
- "isLast": false,
- "title": "Болести на щитовидната жлеза: Тиреотоксикоза. Микседем. Ендемична гуша.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_26_1.md",
- "version": 1
- }
- ]
- },
- "F0_27": {
- "id": "F0_27",
- "index": 26,
- "isFirst": false,
- "isLast": false,
- "title": "Болести на надбъбречните жлези: Хиперкортицизъм. Хипокортицизъм.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_27_1.md",
- "version": 1
- }
- ]
- },
- "F0_28": {
- "id": "F0_28",
- "index": 27,
- "isFirst": false,
- "isLast": false,
- "title": "Захарен диабет – етиология, патогенеза, класификация, клиника. Диабетна кетоацидоза и хипогликемична кома. Поведение на медицинската сестра при диабетно болен в кома.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_28_1.md",
- "version": 1
- }
- ]
- },
- "F0_29": {
- "id": "F0_29",
- "index": 28,
- "isFirst": false,
- "isLast": false,
- "title": "Захарен диабет – късни усложнения. Захарен диабет – диета и медикаментозно лечение.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_29_1.md",
- "version": 1
- }
- ]
- },
- "F0_30": {
- "id": "F0_30",
- "index": 29,
- "isFirst": false,
- "isLast": false,
- "title": "Метаболитен синдром. Затлъстяване. Подагра.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_30_1.md",
- "version": 1
- }
- ]
- },
- "F0_31": {
- "id": "F0_31",
- "index": 30,
- "isFirst": false,
- "isLast": false,
- "title": "Изследване на стомашно-чревния тракт. Анамнеза. Основни симптоми и синдроми при заболявания на стомашно-чревния тракт.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_31_1.md",
- "version": 1
- }
- ]
- },
- "F0_32": {
- "id": "F0_32",
- "index": 31,
- "isFirst": false,
- "isLast": false,
- "title": "Физикални и специални методи на изследване на стомашно-чревния тракт.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_32_1.md",
- "version": 1
- }
- ]
- },
- "F0_33": {
- "id": "F0_33",
- "index": 32,
- "isFirst": false,
- "isLast": false,
- "title": "Гастрити. ГЕРБ.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_33_1.md",
- "version": 1
- }
- ]
- },
- "F0_34": {
- "id": "F0_34",
- "index": 33,
- "isFirst": false,
- "isLast": false,
- "title": "Язвена болест. Рак на стомаха.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_34_1.md",
- "version": 1
- }
- ]
- },
- "F0_35": {
- "id": "F0_35",
- "index": 34,
- "isFirst": false,
- "isLast": false,
- "title": "Ентерити и колити. Рак на дебелото черво.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_35_1.md",
- "version": 1
- }
- ]
- },
- "F0_36": {
- "id": "F0_36",
- "index": 35,
- "isFirst": false,
- "isLast": false,
- "title": "Основни симптоми и синдроми при заболяване на черния дроб и жлъчните пътища. Анамнеза, физикални и специални методи за изследване на черния дроб и жлъчните пътища.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_36_1.md",
- "version": 1
- }
- ]
- },
- "F0_37": {
- "id": "F0_37",
- "index": 36,
- "isFirst": false,
- "isLast": false,
- "title": "Хронични хепатити. Чернодробни цирози.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_37_1.md",
- "version": 1
- }
- ]
- },
- "F0_38": {
- "id": "F0_38",
- "index": 37,
- "isFirst": false,
- "isLast": false,
- "title": "Холелитиаза, холецистити.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_38_1.md",
- "version": 1
- }
- ]
- },
- "F0_39": {
- "id": "F0_39",
- "index": 38,
- "isFirst": false,
- "isLast": false,
- "title": "Основни симптоми и синдроми при заболявания на отделителната система. Функционално изследване на отделителната система.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_39_1.md",
- "version": 1
- }
- ]
- },
- "F0_40": {
- "id": "F0_40",
- "index": 39,
- "isFirst": false,
- "isLast": false,
- "title": "Остър и хроничен гломерулонефрит.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_40_1.md",
- "version": 1
- }
- ]
- },
- "F0_41": {
- "id": "F0_41",
- "index": 40,
- "isFirst": false,
- "isLast": false,
- "title": "Нефролитиаза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_41_1.md",
- "version": 1
- }
- ]
- },
- "F0_42": {
- "id": "F0_42",
- "index": 41,
- "isFirst": false,
- "isLast": false,
- "title": "Пиелонефрити.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_42_1.md",
- "version": 1
- }
- ]
- },
- "F0_44": {
- "id": "F0_44",
- "index": 43,
- "isFirst": false,
- "isLast": false,
- "title": "Балканска ендемична нефропатия. Бъбречна поликистозна болест. Бъбречна туберкулоза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_44_1.md",
- "version": 1
- }
- ]
- },
- "F0_47": {
- "id": "F0_47",
- "index": 46,
- "isFirst": false,
- "isLast": false,
- "title": "Артрозна болест. Остеопороза.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_47_1.md",
- "version": 1
- }
- ]
- },
- "F0_49": {
- "id": "F0_49",
- "index": 48,
- "isFirst": false,
- "isLast": false,
- "title": "Витамин В12-дефицитни анемии.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_49_1.md",
- "version": 1
- }
- ]
- },
- "F0_48": {
- "id": "F0_48",
- "index": 47,
- "isFirst": false,
- "isLast": false,
- "title": "Желязодефицитни анемии.",
- "subject": 0,
- "files": [
- {
- "filename": "F_1_48_1.md",
- "version": 1
- }
- ]
- }
- },
- "allIds": [
- "F0_1",
- "F0_2",
- "F0_3",
- "F0_4",
- "F0_5",
- "F0_6",
- "F0_7",
- "F0_8",
- "F0_9",
- "F0_10",
- "F0_11",
- "F0_12",
- "F0_13",
- "F0_14",
- "F0_15",
- "F0_16",
- "F0_17",
- "F0_18",
- "F0_19",
- "F0_20",
- "F0_21",
- "F0_22",
- "F0_23",
- "F0_24",
- "F0_25",
- "F0_26",
- "F0_27",
- "F0_28",
- "F0_29",
- "F0_30",
- "F0_31",
- "F0_32",
- "F0_33",
- "F0_34",
- "F0_35",
- "F0_36",
- "F0_37",
- "F0_38",
- "F0_39",
- "F0_40",
- "F0_41",
- "F0_42",
- "F0_43",
- "F0_44",
- "F0_45",
- "F0_46",
- "F0_47",
- "F0_48",
- "F0_49",
- "F0_50",
- "F0_51",
- "F0_52",
- "F0_53",
- "F0_54",
- "F0_55"
- ]
- },
- {
- "byId": {
- "F1_2": {
- "id": "F1_2",
- "index": 1,
- "isFirst": false,
- "isLast": false,
- "title": "Видове дози – терапевтична, токсична. Терапевтичен индекс и терапевтична ширина. Дозиране на лекарствата при различните възрастови групи.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_02_1.md",
- "version": 1
- }
- ]
- },
- "F1_3": {
- "id": "F1_3",
- "index": 2,
- "isFirst": false,
- "isLast": false,
- "title": "Въвеждане на лекарството в организма. Явления при многократно и при комбинирано прилагане.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_03_1.md",
- "version": 1
- }
- ]
- },
- "F1_4": {
- "id": "F1_4",
- "index": 3,
- "isFirst": false,
- "isLast": false,
- "title": "Ефективност и потентност на лекарството. Фактори, модификациращи лекарствените действия от страна на организма и на околната среда.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_04_1.md",
- "version": 1
- }
- ]
- },
- "F1_5": {
- "id": "F1_5",
- "index": 4,
- "isFirst": false,
- "isLast": false,
- "title": "Хистамин и антихистаминови лекарствени средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_05_1.md",
- "version": 1
- }
- ]
- },
- "F1_6": {
- "id": "F1_6",
- "index": 5,
- "isFirst": false,
- "isLast": false,
- "title": "Психотропни лекарствени средства – анксиолитици, психостимуланти, ноотропни средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_06_1.md",
- "version": 1
- }
- ]
- },
- "F1_7": {
- "id": "F1_7",
- "index": 6,
- "isFirst": false,
- "isLast": false,
- "title": "Наркотични (опиоидни) аналгетици.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_07_1.md",
- "version": 1
- }
- ]
- },
- "F1_8": {
- "id": "F1_8",
- "index": 7,
- "isFirst": false,
- "isLast": false,
- "title": "Ненаркотични (неопиоидни) аналгетици – пиразолонови, пиразолидиндионови, анилинови, салицилови производни. Нестероидни противовъзпалителни средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_08_1.md",
- "version": 1
- }
- ]
- },
- "F1_9": {
- "id": "F1_9",
- "index": 8,
- "isFirst": false,
- "isLast": false,
- "title": "Лекарствени средства за лечение на сърдечна недостатъчност. Антиаритмични лекарствени средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_09_1.md",
- "version": 1
- }
- ]
- },
- "F1_10": {
- "id": "F1_10",
- "index": 9,
- "isFirst": false,
- "isLast": false,
- "title": "Антистенокардни (антиангинозни) и антиатероматозни лекарствени средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_10_1.md",
- "version": 1
- }
- ]
- },
- "F1_11": {
- "id": "F1_11",
- "index": 10,
- "isFirst": false,
- "isLast": false,
- "title": "Антихипертензивни лекарствени средства – централни и периферни симпатиколитици, миотропни вазодилататори, калциеви антагонисти, инхибитори на ренин-ангиотензин II-алдостероновата система, диуретици.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_11_1.md",
- "version": 1
- }
- ]
- },
- "F1_12": {
- "id": "F1_12",
- "index": 11,
- "isFirst": false,
- "isLast": false,
- "title": "Лекарствени средства, влияещи върху хемопоезата и кръвосъсирването (антианемични средства, хемостатици, коагуланти, антикоагуланти, фибринолитици и инхибитори на стимулирания фибринолитичен процес).",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_12_1.md",
- "version": 1
- }
- ]
- },
- "F1_13": {
- "id": "F1_13",
- "index": 12,
- "isFirst": false,
- "isLast": false,
- "title": "Беталактамни антибиотици – пеницилини, цефалоспорини, карбапенеми, монобактами.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_13_1.md",
- "version": 1
- }
- ]
- },
- "F1_14": {
- "id": "F1_14",
- "index": 13,
- "isFirst": false,
- "isLast": false,
- "title": "Гликопептидни антибиотици (ванкомицин, тейкопланин). Аминогликозидни антибиотици.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_14_1.md",
- "version": 1
- }
- ]
- },
- "F1_15": {
- "id": "F1_15",
- "index": 14,
- "isFirst": false,
- "isLast": false,
- "title": "Макролидни антибиотици. Линкозамиди. Рифамицини.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_15_1.md",
- "version": 1
- }
- ]
- },
- "F1_16": {
- "id": "F1_16",
- "index": 15,
- "isFirst": false,
- "isLast": false,
- "title": "Полипептидни антибиотици. Противотуберкулозни лекарствени средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_16_1.md",
- "version": 1
- }
- ]
- },
- "F1_17": {
- "id": "F1_17",
- "index": 16,
- "isFirst": false,
- "isLast": false,
- "title": "Тетрациклини, сулфонамиди, рифамицини.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_17_1.md",
- "version": 1
- }
- ]
- },
- "F1_18": {
- "id": "F1_18",
- "index": 17,
- "isFirst": false,
- "isLast": false,
- "title": "Флуорохинолони.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_18_1.md",
- "version": 1
- }
- ]
- },
- "F1_19": {
- "id": "F1_19",
- "index": 18,
- "isFirst": false,
- "isLast": false,
- "title": "Антимикотични и антивирусни лекарствени средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_19_1.md",
- "version": 1
- }
- ]
- },
- "F1_20": {
- "id": "F1_20",
- "index": 19,
- "isFirst": false,
- "isLast": false,
- "title": "Лекарствени средства, действащи върху дихателната система – аналептици на дихателния център, противокашлични, отхрачващи и антисептични средства.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_20_1.md",
- "version": 1
- }
- ]
- },
- "F1_21": {
- "id": "F1_21",
- "index": 20,
- "isFirst": false,
- "isLast": true,
- "title": "Лекарствени средства, действащи върху храносмилателната система – апетитостимулиращи, апетитопотискащи (анорексигенни), противоповръщащи (антиеметични), противоязвени (антиулкусни).",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_21_1.md",
- "version": 1
- }
- ]
- },
- "F1_1": {
- "id": "F1_1",
- "index": 0,
- "isFirst": true,
- "isLast": false,
- "title": "Лекарствена форма – определение, видове (твърди, течни, меки, газообразни и галенови лекарствени форми). Рецептурни примери.",
- "subject": 1,
- "files": [
- {
- "filename": "F_2_01_1.md",
- "version": 1
- }
- ]
- }
- },
- "allIds": [
- "F1_1",
- "F1_2",
- "F1_3",
- "F1_4",
- "F1_5",
- "F1_6",
- "F1_7",
- "F1_8",
- "F1_9",
- "F1_10",
- "F1_11",
- "F1_12",
- "F1_13",
- "F1_14",
- "F1_15",
- "F1_16",
- "F1_17",
- "F1_18",
- "F1_19",
- "F1_20",
- "F1_21"
- ]
- }
-]
\ No newline at end of file
diff --git a/reader/src/utils.js b/reader/src/utils.js
new file mode 100644
index 0000000..6e4b511
--- /dev/null
+++ b/reader/src/utils.js
@@ -0,0 +1,9 @@
+export function getIndexFromTopicId(topicId) {
+ const match = topicId?.match(/^S(\d+)_T(\d+)$/);
+ if (!match) {
+ console.warn(`Invalid topic id: ${topicId}`);
+ return null;
+ }
+
+ return [parseInt(match[1]) - 1, parseInt(match[2]) - 1];
+}
diff --git a/resource-provider/server.js b/resource-provider/server.js
index 7b047ef..a1cc4a1 100644
--- a/resource-provider/server.js
+++ b/resource-provider/server.js
@@ -121,7 +121,6 @@ function errorRequestHandler(error, _req, res, next) {
}
function isOriginAllowed(origin) {
- console.log({ origin });
const url = new URL(origin);
if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
diff --git a/static-provider/nginx.conf b/static-provider/nginx.conf
index 9c32476..8105afe 100644
--- a/static-provider/nginx.conf
+++ b/static-provider/nginx.conf
@@ -2,14 +2,25 @@ server {
listen 80;
server_name localhost;
root /static-files;
-
+
# Serve static files
location / {
try_files $uri $uri/ =404;
+ # Add CORS headers for all requests
+ add_header Access-Control-Allow-Origin "*" always;
+ add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
+ add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
+
+ # Handle preflight requests
+ if ($request_method = 'OPTIONS') {
+ return 204;
+ }
+
# Set proper content type for markdown files
location ~* \.md$ {
add_header Content-Type "text/markdown; charset=utf-8";
+ # CORS headers are inherited from parent location
}
}
}