diff --git a/packages/web/src/appobj/SavedFileAppObject.js b/packages/web/src/appobj/SavedFileAppObject.js index db2428fa2..c8877da63 100644 --- a/packages/web/src/appobj/SavedFileAppObject.js +++ b/packages/web/src/appobj/SavedFileAppObject.js @@ -177,7 +177,43 @@ export function SavedChartFileAppObject({ data, commonProps }) { ); } -[SavedSqlFileAppObject, SavedShellFileAppObject, SavedChartFileAppObject].forEach((fn) => { +export function SavedMarkdownFileAppObject({ data, commonProps }) { + const { file, folder } = data; + const setOpenedTabs = useSetOpenedTabs(); + + const showPage = () => { + openNewTab(setOpenedTabs, { + title: file, + icon: 'img markdown', + tabComponent: 'MarkdownViewTab', + props: { + file, + }, + }); + }; + return ( + { + openNewTab( + setOpenedTabs, + { + title: file, + icon: 'img markdown', + tabComponent: 'MarkdownEditorTab', + }, + data + ); + }} + menuExt={Show page} + /> + ); +} + +[SavedSqlFileAppObject, SavedShellFileAppObject, SavedChartFileAppObject, SavedMarkdownFileAppObject].forEach((fn) => { // @ts-ignore fn.extractKey = (data) => data.file; }); diff --git a/packages/web/src/charts/MarkdownToolbar.js b/packages/web/src/charts/MarkdownToolbar.js new file mode 100644 index 000000000..c59c2b9be --- /dev/null +++ b/packages/web/src/charts/MarkdownToolbar.js @@ -0,0 +1,17 @@ +import React from 'react'; +import useHasPermission from '../utility/useHasPermission'; +import ToolbarButton from '../widgets/ToolbarButton'; + +export default function MarkdownToolbar({ save }) { + const hasPermission = useHasPermission(); + + return ( + <> + {hasPermission('files/markdown/write') && ( + + Save + + )} + + ); +} diff --git a/packages/web/src/freetable/MacroDetail.js b/packages/web/src/freetable/MacroDetail.js index 4237eaaf8..fb3b6c9ff 100644 --- a/packages/web/src/freetable/MacroDetail.js +++ b/packages/web/src/freetable/MacroDetail.js @@ -3,7 +3,7 @@ import ToolbarButton from '../widgets/ToolbarButton'; import styled from 'styled-components'; import { TabPage, TabControl } from '../widgets/TabControl'; import dimensions from '../theme/dimensions'; -import JavaScriptEditor from '../sqleditor/JavaScriptEditor'; +import GenericEditor from '../sqleditor/GenericEditor'; import MacroParameters from './MacroParameters'; import { WidgetTitle } from '../widgets/WidgetStyles'; import { FormButton } from '../utility/forms'; @@ -113,7 +113,7 @@ export default function MacroDetail({ selectedMacro, setSelectedMacro, onChangeV - + diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js index 5c647702e..eeadb9242 100644 --- a/packages/web/src/icons.js +++ b/packages/web/src/icons.js @@ -39,6 +39,7 @@ const iconNames = { 'icon theme': 'mdi mdi-brightness-6', 'icon error': 'mdi mdi-close-circle', 'icon ok': 'mdi mdi-check-circle', + 'icon markdown': 'mdi mdi-application', 'icon run': 'mdi mdi-play', 'icon chevron-down': 'mdi mdi-chevron-down', @@ -60,6 +61,7 @@ const iconNames = { 'img sql-file': 'mdi mdi-file', 'img shell': 'mdi mdi-flash color-blue-7', 'img chart': 'mdi mdi-chart-bar color-magenta-7', + 'img markdown': 'mdi mdi-application color-red-7', 'img free-table': 'mdi mdi-table color-green-7', 'img macro': 'mdi mdi-hammer-wrench', diff --git a/packages/web/src/index.js b/packages/web/src/index.js index 2c830d68b..cc57b855c 100644 --- a/packages/web/src/index.js +++ b/packages/web/src/index.js @@ -10,6 +10,7 @@ import 'ace-builds/src-noconflict/mode-mysql'; import 'ace-builds/src-noconflict/mode-pgsql'; import 'ace-builds/src-noconflict/mode-sqlserver'; import 'ace-builds/src-noconflict/mode-javascript'; +import 'ace-builds/src-noconflict/mode-markdown'; import 'ace-builds/src-noconflict/theme-github'; import 'ace-builds/src-noconflict/theme-twilight'; import 'ace-builds/src-noconflict/ext-searchbox'; diff --git a/packages/web/src/sqleditor/JavaScriptEditor.js b/packages/web/src/sqleditor/GenericEditor.js similarity index 96% rename from packages/web/src/sqleditor/JavaScriptEditor.js rename to packages/web/src/sqleditor/GenericEditor.js index 59b2eaecb..d2e18dcea 100644 --- a/packages/web/src/sqleditor/JavaScriptEditor.js +++ b/packages/web/src/sqleditor/GenericEditor.js @@ -12,7 +12,8 @@ const Wrapper = styled.div` bottom: 0; `; -export default function JavaScriptEditor({ +export default function GenericEditor({ + mode, value = undefined, readOnly = false, onChange = undefined, @@ -52,7 +53,7 @@ export default function JavaScriptEditor({ { + if (keyCode == keycodes.f5) { + event.preventDefault(); + // handlePreview(); + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + <> + + {toolbarPortalRef && + toolbarPortalRef.current && + tabVisible && + ReactDOM.createPortal(, toolbarPortalRef.current)} + + + ); +} diff --git a/packages/web/src/tabs/MarkdownViewTab.js b/packages/web/src/tabs/MarkdownViewTab.js new file mode 100644 index 000000000..3774f97e3 --- /dev/null +++ b/packages/web/src/tabs/MarkdownViewTab.js @@ -0,0 +1,41 @@ +import React from 'react'; +import axios from '../utility/axios'; +import LoadingInfo from '../widgets/LoadingInfo'; +import Markdown from 'markdown-to-jsx'; +import styled from 'styled-components'; + +const Wrapper = styled.div` + padding: 10px; + overflow: auto; + flex: 1; +`; + +export default function MarkdownViewTab({ file }) { + const [isLoading, setIsLoading] = React.useState(false); + const [text, setText] = React.useState(null); + + const handleLoad = async () => { + setIsLoading(true); + const resp = await axios.post('files/load', { folder: 'markdown', file, format: 'text' }); + setText(resp.data); + setIsLoading(false); + }; + + React.useEffect(() => { + handleLoad(); + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + + {text || ''} + + ); +} diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index 4e10c6055..66bb4fee7 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -7,7 +7,7 @@ import { VerticalSplitter } from '../widgets/Splitter'; import keycodes from '../utility/keycodes'; import { changeTab } from '../utility/common'; import useSocket from '../utility/SocketProvider'; -import JavaScriptEditor from '../sqleditor/JavaScriptEditor'; +import GenericEditor from '../sqleditor/GenericEditor'; import ShellToolbar from '../query/ShellToolbar'; import RunnerOutputPane from '../query/RunnerOutputPane'; import useShowModal from '../modals/showModal'; @@ -104,12 +104,13 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other return ( <> - diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index 37feccef3..4d3632510 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -8,6 +8,8 @@ import ArchiveFileTab from './ArchiveFileTab'; import FreeTableTab from './FreeTableTab'; import PluginTab from './PluginTab'; import ChartTab from './ChartTab'; +import MarkdownEditorTab from './MarkdownEditorTab'; +import MarkdownViewTab from './MarkdownViewTab'; export default { TableDataTab, @@ -20,4 +22,6 @@ export default { FreeTableTab, PluginTab, ChartTab, + MarkdownEditorTab, + MarkdownViewTab, }; diff --git a/packages/web/src/widgets/FilesWidget.js b/packages/web/src/widgets/FilesWidget.js index 00c15b212..c462782a6 100644 --- a/packages/web/src/widgets/FilesWidget.js +++ b/packages/web/src/widgets/FilesWidget.js @@ -5,7 +5,12 @@ import { AppObjectList } from '../appobj/AppObjectList'; import { useOpenedTabs } from '../utility/globalState'; import ClosedTabAppObject from '../appobj/ClosedTabAppObject'; import { WidgetsInnerContainer } from './WidgetStyles'; -import { SavedSqlFileAppObject, SavedShellFileAppObject, SavedChartFileAppObject } from '../appobj/SavedFileAppObject'; +import { + SavedSqlFileAppObject, + SavedShellFileAppObject, + SavedChartFileAppObject, + SavedMarkdownFileAppObject, +} from '../appobj/SavedFileAppObject'; import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar'; import { useFiles } from '../utility/metadataLoaders'; import useHasPermission from '../utility/useHasPermission'; @@ -64,6 +69,18 @@ function SavedChartFilesList() { ); } +function SavedMarkdownFilesList() { + const files = useFiles({ folder: 'markdown' }); + + return ( + <> + + + + + ); +} + export default function FilesWidget() { const hasPermission = useHasPermission(); return ( @@ -72,20 +89,25 @@ export default function FilesWidget() { {hasPermission('files/sql/read') && ( - + )} {hasPermission('files/shell/read') && ( - + )} {hasPermission('files/charts/read') && ( - + )} + {hasPermission('files/markdown/read') && ( + + + + )} ); } diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js index fbd67750a..312abb736 100644 --- a/packages/web/src/widgets/Toolbar.js +++ b/packages/web/src/widgets/Toolbar.js @@ -66,6 +66,14 @@ export default function ToolBar({ toolbarPortalRef }) { else setCurrentTheme('light'); }; + const newMarkdown = () => { + openNewTab(setOpenedTabs, { + title: 'Page', + tabComponent: 'MarkdownEditorTab', + icon: 'img markdown', + }); + }; + function openTabFromButton(button) { if (openedTabs.find((x) => x.tabComponent == 'InfoPageTab' && x.props && x.props.page == button.page)) { setOpenedTabs((tabs) => @@ -118,6 +126,9 @@ export default function ToolBar({ toolbarPortalRef }) { Free table editor + + New markdown + Import data