markdown manifest

This commit is contained in:
Jan Prochazka
2020-12-10 18:34:02 +01:00
parent d502dc0dfd
commit ac4aa94976
16 changed files with 187 additions and 74 deletions

View File

@@ -3,16 +3,16 @@ const currentVersion = require('../currentVersion');
module.exports = {
get_meta: 'get',
async get() {
const toolbarButtons = process.env.TOOLBAR;
const toolbar = toolbarButtons
? toolbarButtons.split(',').map((name) => ({
name,
icon: process.env[`ICON_${name}`],
title: process.env[`TITLE_${name}`],
page: process.env[`PAGE_${name}`],
}))
: null;
const startupPages = process.env.STARTUP_PAGES ? process.env.STARTUP_PAGES.split(',') : [];
// const toolbarButtons = process.env.TOOLBAR;
// const toolbar = toolbarButtons
// ? toolbarButtons.split(',').map((name) => ({
// name,
// icon: process.env[`ICON_${name}`],
// title: process.env[`TITLE_${name}`],
// page: process.env[`PAGE_${name}`],
// }))
// : null;
// const startupPages = process.env.STARTUP_PAGES ? process.env.STARTUP_PAGES.split(',') : [];
const permissions = process.env.PERMISSIONS ? process.env.PERMISSIONS.split(',') : null;
const singleDatabase =
process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE
@@ -24,8 +24,8 @@ module.exports = {
return {
runAsPortal: !!process.env.CONNECTIONS,
toolbar,
startupPages,
// toolbar,
// startupPages,
singleDatabase,
permissions,
...currentVersion,

View File

@@ -5,6 +5,10 @@ const hasPermission = require('../utility/hasPermission');
const socket = require('../utility/socket');
const scheduler = require('./scheduler');
const markdownAutorunRegex = /<\!--.*@autorun\s*(\n.*-->|-->)/s;
const markdownButtonRegex = /<\!--.*@button\s+([^\n]+)(\n.*-->|-->)/s;
const markdownIconRegex = /<\!--.*@icon\s+([^\n]+)(\n.*-->|-->)/s;
function serialize(format, data) {
if (format == 'text') return data;
if (format == 'json') return JSON.stringify(data);
@@ -61,4 +65,29 @@ module.exports = {
scheduler.reload();
}
},
markdownManifest_meta: 'get',
async markdownManifest() {
if (!hasPermission(`files/markdown/write`)) return {};
const dir = path.join(filesdir(), 'markdown');
if (!(await fs.exists(dir))) return {};
const files = await fs.readdir(dir);
const res = [];
for (const file of files) {
const filePath = path.join(dir, file);
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
const autorun = text.match(markdownAutorunRegex);
const button = text.match(markdownButtonRegex);
const icon = text.match(markdownIconRegex);
if (autorun || button) {
res.push({
file,
autorun: !!autorun,
button: button ? button[1].trim() : undefined,
icon: icon ? icon[1].trim() : undefined,
});
}
}
return res;
},
};

View File

@@ -72,9 +72,9 @@ function start(argument = null) {
useController(app, '/files', files);
useController(app, '/scheduler', scheduler);
if (process.env.PAGES_DIRECTORY) {
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
}
// if (process.env.PAGES_DIRECTORY) {
// app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
// }
app.use('/runners/data', express.static(rundir()));

View File

@@ -26,6 +26,7 @@ const iconNames = {
'icon account': 'mdi mdi-account',
'icon sql-file': 'mdi mdi-file',
'icon web': 'mdi mdi-web',
'icon home': 'mdi mdi-home',
'icon edit': 'mdi mdi-pencil',
'icon delete': 'mdi mdi-delete',
@@ -40,6 +41,7 @@ const iconNames = {
'icon error': 'mdi mdi-close-circle',
'icon ok': 'mdi mdi-check-circle',
'icon markdown': 'mdi mdi-application',
'icon preview': 'mdi mdi-file-find',
'icon run': 'mdi mdi-play',
'icon chevron-down': 'mdi mdi-chevron-down',
@@ -62,6 +64,7 @@ const iconNames = {
'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 preview': 'mdi mdi-file-find color-red-7',
'img free-table': 'mdi mdi-table color-green-7',
'img macro': 'mdi mdi-hammer-wrench',

View File

@@ -2,6 +2,7 @@ import React from 'react';
import Markdown from 'markdown-to-jsx';
import styled from 'styled-components';
import OpenChartLink from './OpenChartLink';
import MarkdownLink from './MarkdownLink';
const Wrapper = styled.div`
padding: 10px;
@@ -18,6 +19,7 @@ export default function MarkdownExtendedView({ children }) {
OpenChartLink: {
component: OpenChartLink,
},
a: MarkdownLink,
},
}}
>

View File

@@ -0,0 +1,13 @@
import React from 'react';
import useTheme from '../theme/useTheme';
import { StyledThemedLink } from '../widgets/FormStyledButton';
export default function MarkdownLink({ href, title, children }) {
const theme = useTheme();
return (
<StyledThemedLink theme={theme} href={href} title={title} target="_blank">
{children}
</StyledThemedLink>
);
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import useHasPermission from '../utility/useHasPermission';
import ToolbarButton from '../widgets/ToolbarButton';
export default function MarkdownToolbar({ save }) {
export default function MarkdownToolbar({ save, showPreview }) {
const hasPermission = useHasPermission();
return (
@@ -12,6 +12,9 @@ export default function MarkdownToolbar({ save }) {
Save
</ToolbarButton>
)}
<ToolbarButton onClick={showPreview} icon="icon preview">
Preview
</ToolbarButton>
</>
);
}

View File

@@ -1,18 +1,9 @@
import React from 'react';
import { useCurrentDatabase, useSetOpenedTabs } from '../utility/globalState';
import styled from 'styled-components';
import { openNewTab } from '../utility/common';
import axios from '../utility/axios';
import useTheme from '../theme/useTheme';
const StyledLink = styled.a`
text-decoration: none;
cursor: pointer;
color: ${(props) => props.theme.main_background_blue[7]};
&:hover {
text-decoration: underline;
}
`;
import { StyledThemedLink } from '../widgets/FormStyledButton';
export default function OpenChartLink({ file, children }) {
const setOpenedTabs = useSetOpenedTabs();
@@ -37,8 +28,8 @@ export default function OpenChartLink({ file, children }) {
};
return (
<StyledLink theme={theme} onClick={handleClick}>
<StyledThemedLink theme={theme} onClick={handleClick}>
{children}
</StyledLink>
</StyledThemedLink>
);
}

View File

@@ -9,6 +9,7 @@ import moment from 'moment';
import styled from 'styled-components';
import getElectron from '../utility/getElectron';
import useTheme from '../theme/useTheme';
import { StyledThemedLink } from '../widgets/FormStyledButton';
const Container = styled.div`
display: flex;
@@ -24,15 +25,6 @@ const StyledValue = styled.span`
font-weight: bold;
`;
const StyledLink = styled.a`
text-decoration: none;
cursor: pointer;
color: ${(props) => props.theme.main_background_blue[7]};
&:hover {
text-decoration: underline;
}
`;
function Line({ label, children }) {
return (
<StyledLine>
@@ -48,13 +40,13 @@ function Link({ label, children, href }) {
<StyledLine>
{label}:{' '}
{electron ? (
<StyledLink theme={theme} onClick={() => electron.shell.openExternal(href)}>
<StyledThemedLink theme={theme} onClick={() => electron.shell.openExternal(href)}>
{children}
</StyledLink>
</StyledThemedLink>
) : (
<StyledLink theme={theme} href={href} target="_blank" rel="noopener noreferrer">
<StyledThemedLink theme={theme} href={href} target="_blank" rel="noopener noreferrer">
{children}
</StyledLink>
</StyledThemedLink>
)}
</StyledLine>
);

View File

@@ -8,15 +8,42 @@ import useEditorData from '../utility/useEditorData';
import SaveTabModal from '../modals/SaveTabModal';
import useModalState from '../modals/useModalState';
import LoadingInfo from '../widgets/LoadingInfo';
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
import { openNewTab } from '../utility/common';
export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, ...other }) {
const { editorData, setEditorData, isLoading } = useEditorData({ tabid });
const { editorData, setEditorData, isLoading, saveToStorage } = useEditorData({ tabid });
const saveFileModalState = useModalState();
const openedTabs = useOpenedTabs();
const setOpenedTabs = useSetOpenedTabs();
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
if (keyCode == keycodes.f5) {
event.preventDefault();
// handlePreview();
showPreview();
}
};
const showPreview = async () => {
await saveToStorage();
const existing = (openedTabs || []).find((x) => x.props && x.props.sourceTabId == tabid && x.closedTime == null);
if (existing) {
setOpenedTabs((tabs) =>
tabs.map((x) => ({
...x,
selected: x.tabid == existing.tabid,
}))
);
} else {
const thisTab = (openedTabs || []).find((x) => x.tabid == tabid);
openNewTab(setOpenedTabs, {
title: thisTab.title,
icon: 'img preview',
tabComponent: 'MarkdownPreviewTab',
props: {
sourceTabId: tabid,
},
});
}
};
@@ -40,7 +67,10 @@ export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef,
{toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(<MarkdownToolbar save={saveFileModalState.open} />, toolbarPortalRef.current)}
ReactDOM.createPortal(
<MarkdownToolbar save={saveFileModalState.open} showPreview={showPreview} />,
toolbarPortalRef.current
)}
<SaveTabModal
modalState={saveFileModalState}
tabVisible={tabVisible}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import LoadingInfo from '../widgets/LoadingInfo';
import MarkdownExtendedView from '../markdown/MarkdownExtendedView';
import useEditorData from '../utility/useEditorData';
export default function MarkdownPreviewTab({ sourceTabId, tabVisible }) {
const [reloadToken, setReloadToken] = React.useState(0);
const { editorData, isLoading } = useEditorData({ tabid: sourceTabId, reloadToken });
React.useEffect(() => {
if (tabVisible) setReloadToken((x) => x + 1);
}, [tabVisible]);
if (isLoading) {
return (
<div>
<LoadingInfo message="Loading markdown page" />
</div>
);
}
return <MarkdownExtendedView>{editorData || ''}</MarkdownExtendedView>;
}

View File

@@ -10,6 +10,7 @@ import PluginTab from './PluginTab';
import ChartTab from './ChartTab';
import MarkdownEditorTab from './MarkdownEditorTab';
import MarkdownViewTab from './MarkdownViewTab';
import MarkdownPreviewTab from './MarkdownPreviewTab';
export default {
TableDataTab,
@@ -24,4 +25,5 @@ export default {
ChartTab,
MarkdownEditorTab,
MarkdownViewTab,
MarkdownPreviewTab,
};

View File

@@ -46,6 +46,12 @@ const configLoader = () => ({
reloadTrigger: 'config-changed',
});
const markdownManifestLoader = () => ({
url: 'files/markdown-manifest',
params: {},
reloadTrigger: 'files-changed-markdown',
});
// const sqlObjectListLoader = ({ conid, database }) => ({
// url: 'metadata/list-objects',
// params: { conid, database },
@@ -269,3 +275,10 @@ export function getFiles(args) {
export function useFiles(args) {
return useCore(filesLoader, args);
}
export function getMarkdownManifest(args) {
return getCore(markdownManifestLoader, args);
}
export function useMarkdownManifest(args) {
return useCore(markdownManifestLoader, args);
}

View File

@@ -4,7 +4,7 @@ import localforage from 'localforage';
import { changeTab } from './common';
import { useSetOpenedTabs } from './globalState';
export default function useEditorData({ tabid, loadFromArgs = null }) {
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null }) {
const localStorageKey = `tabdata_${tabid}`;
const setOpenedTabs = useSetOpenedTabs();
const changeCounterRef = React.useRef(0);
@@ -57,19 +57,20 @@ export default function useEditorData({ tabid, loadFromArgs = null }) {
React.useEffect(() => {
initialLoad();
}, []);
}, [reloadToken]);
const saveToStorage = React.useCallback(async () => {
if (valueRef.current == null) return;
try {
await localforage.setItem(localStorageKey, valueRef.current);
localStorage.removeItem(localStorageKey);
savedCounterRef.current = changeCounterRef.current;
} catch (err) {
console.error(err);
}
}, [localStorageKey, valueRef]);
const saveToStorageFallback = React.useCallback(() => {
const saveToStorageSync = React.useCallback(() => {
if (valueRef.current == null) return;
if (savedCounterRef.current == changeCounterRef.current) return; // all saved
// on window unload must be synchronous actions, save to local storage instead
@@ -86,10 +87,10 @@ export default function useEditorData({ tabid, loadFromArgs = null }) {
};
React.useEffect(() => {
window.addEventListener('beforeunload', saveToStorageFallback);
window.addEventListener('beforeunload', saveToStorageSync);
return () => {
saveToStorage();
window.removeEventListener('beforeunload', saveToStorageFallback);
window.removeEventListener('beforeunload', saveToStorageSync);
};
}, []);
@@ -99,5 +100,7 @@ export default function useEditorData({ tabid, loadFromArgs = null }) {
isLoading,
initialData: initialDataRef.current,
errorMessage,
saveToStorage,
saveToStorageSync,
};
}

View File

@@ -39,8 +39,7 @@ const makeStyle = (base) => styled(base)`
const ButtonInput = makeStyle(styled.input``);
const FormStyledLabelBase = makeStyle(styled.label``);
export const FormStyledLabel = styled(FormStyledLabelBase)`
`;
export const FormStyledLabel = styled(FormStyledLabelBase)``;
export default function FormStyledButton({
onClick = undefined,
@@ -67,3 +66,12 @@ export default function FormStyledButton({
/>
);
}
export const StyledThemedLink = styled.a`
text-decoration: none;
cursor: pointer;
color: ${(props) => props.theme.main_background_blue[7]};
&:hover {
text-decoration: underline;
}
`;

View File

@@ -4,7 +4,7 @@ import ConnectionModal from '../modals/ConnectionModal';
import styled from 'styled-components';
import ToolbarButton, { ToolbarButtonExternalImage } from './ToolbarButton';
import useNewQuery from '../query/useNewQuery';
import { useConfig } from '../utility/metadataLoaders';
import { useConfig, useMarkdownManifest } from '../utility/metadataLoaders';
import { useSetOpenedTabs, useOpenedTabs, useCurrentTheme, useSetCurrentTheme } from '../utility/globalState';
import { openNewTab } from '../utility/common';
import useNewFreeTable from '../freetable/useNewFreeTable';
@@ -25,7 +25,7 @@ export default function ToolBar({ toolbarPortalRef }) {
const newQuery = useNewQuery();
const newFreeTable = useNewFreeTable();
const config = useConfig();
const toolbar = config.toolbar || [];
// const toolbar = config.toolbar || [];
const setOpenedTabs = useSetOpenedTabs();
const openedTabs = useOpenedTabs();
const showModal = useShowModal();
@@ -33,6 +33,7 @@ export default function ToolBar({ toolbarPortalRef }) {
const setCurrentTheme = useSetCurrentTheme();
const extensions = useExtensions();
const electron = getElectron();
const markdownManifest = useMarkdownManifest();
React.useEffect(() => {
window['dbgate_createNewConnection'] = modalState.open;
@@ -74,47 +75,47 @@ export default function ToolBar({ toolbarPortalRef }) {
});
};
function openTabFromButton(button) {
if (openedTabs.find((x) => x.tabComponent == 'InfoPageTab' && x.props && x.props.page == button.page)) {
function openTabFromButton(page) {
if (
openedTabs.find(
(x) => x.tabComponent == 'MarkdownViewTab' && x.props && x.props.file == page.file && x.closedTime == null
)
) {
setOpenedTabs((tabs) =>
tabs.map((tab) => ({
...tab,
selected: tab.tabComponent == 'InfoPageTab' && tab.props && tab.props.page == button.page,
closedTime: undefined,
selected: tab.tabComponent == 'MarkdownViewTab' && tab.props && tab.props.file == page.file,
}))
);
} else {
openNewTab(setOpenedTabs, {
title: button.title,
tabComponent: 'InfoPageTab',
icon: button.icon,
title: page.button || page.file,
tabComponent: 'MarkdownViewTab',
icon: page.icon || 'img markdown',
props: {
page: button.page,
file: page.file,
},
});
}
}
React.useEffect(() => {
if (config.startupPages) {
for (const page of config.startupPages) {
const button = toolbar.find((x) => x.name == page);
if (button) {
openTabFromButton(button);
}
}
for (const page of (markdownManifest || []).filter((x) => x.autorun)) {
openTabFromButton(page);
}
}, config && config.startupPages);
}, [markdownManifest]);
return (
<ToolbarContainer>
<ConnectionModal modalState={modalState} />
{!electron && <ToolbarButtonExternalImage image="/logo192.png" onClick={showAbout} />}
{toolbar.map((button) => (
<ToolbarButton key={button.name} onClick={() => openTabFromButton(button)} icon={button.icon}>
{button.title}
</ToolbarButton>
))}
{(markdownManifest || [])
.filter((x) => x.button)
.map((x) => (
<ToolbarButton key={x.button} onClick={() => openTabFromButton(x)} icon={x.icon || 'icon markdown'}>
{x.button}
</ToolbarButton>
))}
{config.runAsPortal == false && (
<ToolbarButton onClick={modalState.open} icon="icon new-connection">
Add connection