e.stopPropagation()}
42 | className="mk-cell-text"
43 | ref={ref}
44 | data-ph={props.compactMode ? props.property.name : "Empty"}
45 | onKeyDown={onKeyDown}
46 | onBlur={onBlur}
47 | contentEditable={true}
48 | dangerouslySetInnerHTML={{ __html: initialValue }}
49 | />
50 | ) : (
51 |
{initialValue}
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Contexts/FilterBar/CustomVIewOption.tsx:
--------------------------------------------------------------------------------
1 | import { Sticker } from "core/react/components/UI/Stickers/Sticker";
2 | import { UIManager } from "makemd-core";
3 | import React from "react";
4 | export const CustomViewOption = (props: {
5 | ui: UIManager;
6 | type: string;
7 | icon: string;
8 | selected: boolean;
9 | onSelect: () => void;
10 | onCustomize?: () => void;
11 | hide: () => void;
12 | }) => {
13 | return (
14 |
{
17 | props.onSelect();
18 | props.hide();
19 | }}
20 | >
21 |
22 |
23 | {props.type}
24 |
25 | {props.selected && (
26 |
31 | )}
32 | {props.onCustomize &&
}
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Contexts/FilterBar/SearchBar.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React, { useEffect } from "react";
3 | import i18n from "shared/i18n";
4 |
5 | export const SearchBar = (props: {
6 | superstate: Superstate;
7 | setSearchString: (str: string) => void;
8 | closeSearch?: () => void;
9 | }) => {
10 | const [searchActive, setSearchActive] = React.useState(false);
11 | const clearSearch = () => {
12 | setSearchActive(false);
13 | props.setSearchString("");
14 | };
15 | const ref = React.useRef
(null);
16 | useEffect(() => {
17 | if (searchActive) {
18 | ref.current?.focus();
19 | }
20 | }, [searchActive]);
21 | return (
22 |
23 |
29 | <>
30 | props.setSearchString(e.target.value)}
32 | placeholder={i18n.labels.searchPlaceholder}
33 | className="mk-search-bar"
34 | ref={ref}
35 | >
36 | {props.closeSearch && (
37 |
48 | )}
49 | >
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/EditorNodes/AudioNodeView.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import { FrameNodeViewProps } from "../ViewNodes/FrameView";
3 |
4 | export const AudioNodeView = (props: FrameNodeViewProps) => {
5 | const value = props.state.props.value;
6 | const sourcePath = useMemo(() => {
7 | return props.superstate.ui.getUIPath(value);
8 | }, [value]);
9 |
10 | return props.state?.props.value?.length > 0 ? (
11 |
12 | ) : (
13 | <>>
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/EditorNodes/ContentNodeView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const ContentNodeView = (props: {
4 | children?: React.ReactNode;
5 | editable?: boolean;
6 | }) => {
7 | return <>{props.children}>;
8 | };
9 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/EditorNodes/NewNodeView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FrameNodeViewProps } from "../ViewNodes/FrameView";
3 | export const NewNodeView = (props: FrameNodeViewProps) => {
4 | return ;
5 | };
6 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Overlays/FrameFillOverlay.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FrameNodeState } from "shared/types/frameExec";
3 | import { FrameTreeProp } from "shared/types/mframe";
4 |
5 | export const FrameFill = (props: {
6 | state: FrameNodeState;
7 | clientSize: FrameTreeProp;
8 | saveStyles: (styles: FrameTreeProp) => void;
9 | }) => {
10 | return (
11 | {}}
23 | >
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/AlignmentSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { wrapQuotes } from "core/utils/strings";
2 | import React, { useState } from "react";
3 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
4 |
5 | export const AlignmentEditor = (props: HoverSubmenuProps) => {
6 | const directions = ["nw", "n", "ne", "w", "m", "e", "sw", "s", "se"];
7 | const [layoutAlign, setLayoutAlign] = useState(
8 | props.state?.styles?.layoutAlign
9 | );
10 | return (
11 |
12 | {directions.map((d) => (
13 |
{
19 | setLayoutAlign(d);
20 | props.saveStyleValue("layoutAlign", wrapQuotes(d));
21 | }}
22 | >
23 | ))}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/HoverSubmenuProps.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import { FrameNodeState, FrameRunInstance } from "shared/types/frameExec";
3 | import { SpaceProperty } from "shared/types/mdb";
4 | import { FrameNode, FrameTreeProp } from "shared/types/mframe";
5 | import { PathState } from "shared/types/PathState";
6 |
7 | export type HoverSubmenuProps = {
8 | superstate: Superstate;
9 | exitMenu: () => void;
10 | saveStyleValue: (key: string, value: string) => void;
11 | selectedNode: FrameNode;
12 | setHoverMenu: (menu: number) => void;
13 | savePropValue: (key: string, value: string) => void;
14 | frameProps: FrameTreeProp;
15 | fields: SpaceProperty[];
16 | state: FrameNodeState;
17 | };
18 |
19 | export type PropertySubmenuProps = HoverSubmenuProps & {
20 | pathState: PathState;
21 | frameProperties: SpaceProperty[];
22 | instance: FrameRunInstance;
23 | };
24 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/LayoutSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { defaultMenu } from "core/react/components/UI/Menus/menu/SelectionMenu";
2 | import { SelectOption, i18n } from "makemd-core";
3 | import React from "react";
4 | import { windowFromDocument } from "shared/utils/dom";
5 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
6 | import { SizeSubmenu } from "./SizeSubmenu";
7 | import { SpacingSubmenu } from "./SpacingSubmenu";
8 |
9 | export const LayoutSubmenu = (props: HoverSubmenuProps) => {
10 | const { selectedNode, saveStyleValue } = props;
11 | const showImageSizeMenu = (e: React.MouseEvent, type: string) => {
12 | const prop = type == "icon" ? "iconSize" : "imageSize";
13 | const menuOptions: SelectOption[] = [];
14 | menuOptions.push({
15 | name: i18n.labels.styleSmall,
16 | icon: "type",
17 | onClick: () => {
18 | saveStyleValue(prop, `'s'`);
19 | // saveStyleValue("height", `'auto'`);
20 | },
21 | });
22 | menuOptions.push({
23 | name: i18n.labels.styleMedium,
24 | icon: "type",
25 | onClick: () => {
26 | saveStyleValue(prop, `'m'`);
27 | // saveStyleValue("height", `'auto'`);
28 | },
29 | });
30 | menuOptions.push({
31 | name: i18n.labels.styleLarge,
32 | icon: "type",
33 | onClick: () => {
34 | saveStyleValue(prop, `'l'`);
35 | // saveStyleValue("height", `'auto'`);
36 | },
37 | });
38 | const offset = (e.target as HTMLElement).getBoundingClientRect();
39 | props.superstate.ui.openMenu(
40 | offset,
41 | defaultMenu(props.superstate.ui, menuOptions),
42 | windowFromDocument(e.view.document)
43 | );
44 | };
45 | return (
46 | <>
47 |
48 |
49 |
50 |
51 | >
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/MarginSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { i18n } from "makemd-core";
2 | import React from "react";
3 | import { StepSetter } from "../../Setters/StepSetter";
4 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
5 | export const MarginSubmenu = (props: HoverSubmenuProps) => {
6 | const { selectedNode, saveStyleValue } = props;
7 | return (
8 | <>
9 | saveStyleValue("marginLeft", value)}
14 | units={["px", "em"]}
15 | >
16 | saveStyleValue("marginTop", value)}
21 | units={["px", "em"]}
22 | >
23 | saveStyleValue("marginRight", value)}
28 | units={["px", "em"]}
29 | >
30 | saveStyleValue("marginBottom", value)}
35 | units={["px", "em"]}
36 | >
37 | >
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/ModeSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { i18n } from "makemd-core";
2 | import React from "react";
3 | import { ToggleSetter } from "../../Setters/ToggleSetter";
4 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
5 |
6 | export const ModeSubmenu = (props: HoverSubmenuProps) => {
7 | const { selectedNode, saveStyleValue } = props;
8 |
9 | return (
10 | <>
11 | saveStyleValue("--mk-min-mode", value)}
19 | >
20 | >
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/PaddingSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { i18n } from "makemd-core";
2 | import React from "react";
3 | import { StepSetter } from "../../Setters/StepSetter";
4 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
5 | export const PaddingSubmenu = (props: HoverSubmenuProps) => {
6 | const { selectedNode, saveStyleValue } = props;
7 | return (
8 | <>
9 | saveStyleValue("paddingLeft", value)}
14 | units={["px", "em"]}
15 | >
16 | saveStyleValue("paddingTop", value)}
22 | units={["px", "em"]}
23 | >
24 | saveStyleValue("paddingRight", value)}
30 | units={["px", "em"]}
31 | >
32 | saveStyleValue("paddingBottom", value)}
38 | units={["px", "em"]}
39 | >
40 | >
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/FrameNodeEditor/Submenus/SizeSubmenu.tsx:
--------------------------------------------------------------------------------
1 | import { i18n } from "makemd-core";
2 | import React from "react";
3 | import { StepSetter } from "../../Setters/StepSetter";
4 | import { HoverSubmenuProps } from "./HoverSubmenuProps";
5 | export const SizeSubmenu = (props: HoverSubmenuProps) => {
6 | const { selectedNode, saveStyleValue } = props;
7 | return (
8 | <>
9 |
10 | saveStyleValue("width", value)}
16 | units={["px", "%", "em"]}
17 | >
18 |
19 | saveStyleValue("height", value)}
25 | units={["px", "%", "em"]}
26 | >
27 | >
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/Setters/ColorSetter.tsx:
--------------------------------------------------------------------------------
1 | import { showColorPickerMenu } from "core/react/components/UI/Menus/properties/colorPickerMenu";
2 | import { Superstate } from "makemd-core";
3 | import React from "react";
4 | import { windowFromDocument } from "shared/utils/dom";
5 | export const ColorSetter = (props: {
6 | superstate: Superstate;
7 | value: string;
8 | setValue: (value: string) => void;
9 | }) => {
10 | const showColorMenu = (e: React.MouseEvent, prop: string) => {
11 | const handleChangeComplete = (color: string) => {
12 | props.setValue(color);
13 | };
14 | const offset = (e.target as HTMLElement).getBoundingClientRect();
15 | showColorPickerMenu(
16 | props.superstate,
17 | offset,
18 | windowFromDocument(e.view.document),
19 | props.value,
20 | handleChangeComplete
21 | );
22 | };
23 | return (
24 | {
30 | showColorMenu(e, "--text-normal");
31 | }}
32 | >
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/Setters/SliderSetter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export const Slider = (props: {
4 | value: string;
5 | setValue: (value: string) => void;
6 | }) => {
7 | const [value, setValue] = useState(props.value);
8 | return (
9 | setValue(e.target.value)}
16 | onMouseUp={(e) => props.setValue(value)}
17 | />
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/Setters/ToggleSetter.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 |
4 | export const ToggleSetter = (props: {
5 | superstate: Superstate;
6 | name: string;
7 | setValue: (value: string) => void;
8 | defaultValue: string;
9 | onValue: string;
10 | value: string;
11 | icon: string;
12 | }) => {
13 | return (
14 |
20 | props.value == props.onValue
21 | ? props.setValue(props.defaultValue)
22 | : props.setValue(props.onValue)
23 | }
24 | dangerouslySetInnerHTML={{
25 | __html: props.superstate.ui.getSticker(props.icon),
26 | }}
27 | >
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/Frames/ViewNodes/FrameInstance.tsx:
--------------------------------------------------------------------------------
1 | import { FrameInstanceContext } from "core/react/context/FrameInstanceContext";
2 | import { Superstate } from "makemd-core";
3 | import React, { useContext } from "react";
4 | import { FrameView } from "./FrameView";
5 |
6 | export const FrameInstanceView = (props: {
7 | superstate: Superstate;
8 | source?: string;
9 | children?: React.ReactNode;
10 | }) => {
11 | const { saveState, instance } = useContext(FrameInstanceContext);
12 | return (
13 | instance.exec && (
14 |
21 | {props.children}
22 |
23 | )
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/core/react/components/SpaceView/SpaceInner.tsx:
--------------------------------------------------------------------------------
1 | import { SpaceHeader } from "core/react/components/SpaceView/SpaceHeader";
2 | import SpaceOuter from "core/react/components/SpaceView/SpaceOuter";
3 | import { SpaceContext } from "core/react/context/SpaceContext";
4 | import { Backlinks, Superstate } from "makemd-core";
5 | import React, { useContext, useRef } from "react";
6 |
7 | export const SpaceInner = (props: {
8 | superstate: Superstate;
9 | header: boolean;
10 | }) => {
11 | const ref = useRef(null);
12 | const { spaceState } = useContext(SpaceContext);
13 | return (
14 | <>
15 | {props.header && (
16 |
17 | )}
18 | {spaceState && (
19 |
24 | )}
25 | {props.superstate.settings.inlineBacklinks && spaceState && (
26 |
27 |
31 |
32 | )}
33 | >
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/core/react/components/System/IconsSet.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React, { useEffect } from "react";
3 | import { uiIconSet } from "shared/assets/icons";
4 | type IconSet = {
5 | name: string;
6 | icon: string;
7 | iconSet: string;
8 | };
9 | export const IconSet = (props: { superstate: Superstate }) => {
10 | const [iconSets, setIconSets] = React.useState([]);
11 | const [icons, setIcons] = React.useState([]);
12 | useEffect(() => {
13 | Object.keys(uiIconSet)
14 | .filter((f, i) => i < 10)
15 | .forEach((icon) => {
16 | setIcons((f) => [...f, "ui//" + icon]);
17 | });
18 | }, []);
19 |
20 | return (
21 |
22 |
23 | {icons.map((c, i) => (
24 |
31 | ))}
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/core/react/components/System/ImageSet.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 |
4 | type ImageSet = {
5 | name: string;
6 | };
7 |
8 | export const ImageSet = (props: { superstate: Superstate }) => {
9 | const [images, setImages] = React.useState([]);
10 | const [currentSet, setCurrentSet] = React.useState(null);
11 | const addImage = (image: ImageSet) => {
12 | setImages([...images, image]);
13 | };
14 | return ;
15 | };
16 |
--------------------------------------------------------------------------------
/src/core/react/components/System/MaterialsSet.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 | import { colors, colorsBase } from "shared/utils/color";
4 | export const MaterialsSet = (props: { superstate: Superstate }) => {
5 | const [materials, setMaterials] = React.useState([]);
6 | return (
7 |
8 |
9 | {colors.map((c, i) => (
10 |
{}}
14 | className="mk-color"
15 | style={{ background: c[1] }}
16 | >
17 | ))}
18 |
Add
19 |
20 |
21 | {colorsBase.map((c, i) => (
22 |
{}}
26 | className="mk-color"
27 | style={{ background: c[1] }}
28 | >
29 | ))}
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/core/react/components/System/SystemActions.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React, { useState } from "react";
3 | import { Command } from "shared/types/commands";
4 | import { SpaceCommand } from "../SpaceEditor/Actions/SpaceActions";
5 | export const SystemActionsEditor = (props: { superstate: Superstate }) => {
6 | const [selectedCommand, setSelectedCommand] = useState<{
7 | library: string;
8 | command: Command;
9 | }>();
10 |
11 | const saveCommand = (command: Command) => {
12 | props.superstate.spaceManager.saveSystemCommand(
13 | selectedCommand.library,
14 | command
15 | );
16 | };
17 | const addLibrary = () => {
18 | props.superstate.spaceManager.saveSystemCommand(
19 | selectedCommand.library,
20 | null
21 | );
22 | };
23 | return (
24 |
25 | {selectedCommand && (
26 |
27 |
36 |
37 | )}
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/core/react/components/System/Templates.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 |
4 | export const Templates = (props: { superstate: Superstate }) => {
5 | return (
6 |
7 |
Templates
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Crumbs/ContextViewCrumb.tsx:
--------------------------------------------------------------------------------
1 | import { defaultString } from "core/utils/strings";
2 | import { Superstate } from "makemd-core";
3 | import React from "react";
4 |
5 | import { FrameSchema } from "shared/types/mframe";
6 |
7 | export const ContextViewCrumb = (
8 | props: React.PropsWithChildren<{
9 | superstate: Superstate;
10 | schema: FrameSchema;
11 | active: boolean;
12 | onSelect: (e: React.MouseEvent) => void;
13 | onContextMenu?: (e: React.MouseEvent, schema: FrameSchema) => void;
14 | }>
15 | ) => {
16 | return (
17 | props.onSelect(e)}
19 | onContextMenu={(e) => props.onContextMenu(e, props.schema)}
20 | className={`mk-context ${props.active ? "mk-active" : ""}`}
21 | >
22 | {defaultString(props.schema.name, "Untitled")}
23 | {props.children}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Cues/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | export const EmptyState = (props: {
3 | enabled: boolean;
4 | message: string;
5 | image: string;
6 | }) => {
7 | return ;
8 | };
9 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import { SelectOption, Superstate } from "makemd-core";
2 | import React from "react";
3 | import { windowFromDocument } from "shared/utils/dom";
4 | export const Dropdown = (props: {
5 | superstate: Superstate;
6 | options?: SelectOption[];
7 | triggerMenu?: (e: React.MouseEvent) => void;
8 | value: string;
9 | selectValue?: (value: string) => void;
10 | }) => {
11 | const openMenu = (e: React.MouseEvent) => {
12 | const offset = (e.target as HTMLElement).getBoundingClientRect();
13 | props.superstate.ui.openMenu(
14 | offset,
15 | {
16 | ui: props.superstate.ui,
17 | editable: false,
18 | value: [props.value],
19 | options: props.options,
20 | saveOptions: (options: string[], value: string[]) => {
21 | props.selectValue(value[0]);
22 | },
23 | searchable: false,
24 | },
25 | windowFromDocument(e.view.document)
26 | );
27 | };
28 | return (
29 | (props.triggerMenu ? props.triggerMenu(e) : openMenu(e))}
32 | >
33 |
34 | {props.options
35 | ? props.options.find((f) => f.value == props.value)?.name
36 | : props.value}
37 |
38 |
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/menu/SelectMenuPill.tsx:
--------------------------------------------------------------------------------
1 | import { SelectOption } from "makemd-core";
2 | import React from "react";
3 |
4 | const SelectMenuPillComponent = (props: {
5 | classNames: Record;
6 | onDelete: (e: React.MouseEvent) => void;
7 | tag: SelectOption;
8 | }) => {
9 | return (
10 |
17 | );
18 | };
19 |
20 | export default SelectMenuPillComponent;
21 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/menu/SelectionMenu.tsx:
--------------------------------------------------------------------------------
1 | import { UIManager } from "core/middleware/ui";
2 | import { SelectMenuProps, SelectOption, SelectOptionType } from "makemd-core";
3 | import React from "react";
4 | import { windowFromDocument } from "shared/utils/dom";
5 | import { parseMultiString } from "utils/parsers";
6 |
7 | export const showDisclosureMenu = (
8 | ui: UIManager,
9 | e: React.MouseEvent,
10 | multi: boolean,
11 | editable: boolean,
12 | value: string,
13 | options: SelectOption[],
14 | saveOptions: (options: string[], value: string[]) => void
15 | ) => {
16 | const offset = (e.target as HTMLElement).getBoundingClientRect();
17 | ui.openMenu(
18 | offset,
19 | {
20 | ui: ui,
21 | multi,
22 | editable,
23 | value: parseMultiString(value) ?? [],
24 | options,
25 | saveOptions,
26 | searchable: true,
27 | showAll: true,
28 | isDisclosure: true,
29 | },
30 | windowFromDocument(e.view.document)
31 | );
32 | };
33 |
34 | export const menuInput = (
35 | value: string,
36 | setValue: (value: string) => void
37 | ): SelectOption => ({
38 | name: "",
39 | type: SelectOptionType.Input,
40 | value,
41 | onValueChange: setValue,
42 | });
43 |
44 | export const menuSection = (name: string): SelectOption => ({
45 | name: name,
46 | type: SelectOptionType.Section,
47 | disabled: true,
48 | });
49 |
50 | export const menuSeparator: SelectOption = {
51 | name: "",
52 | type: SelectOptionType.Separator,
53 | disabled: true,
54 | };
55 | export const defaultMenu = (
56 | ui: UIManager,
57 | options: SelectOption[]
58 | ): SelectMenuProps => ({
59 | ui,
60 | multi: false,
61 | value: [],
62 | editable: false,
63 | options,
64 | searchable: false,
65 | showAll: true,
66 | });
67 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/menu/concerns/focusNextElement.js:
--------------------------------------------------------------------------------
1 | export function focusNextElement(scope, currentTarget) {
2 | const interactiveEls = scope.querySelectorAll("a,button,input");
3 |
4 | const currentEl = Array.prototype.findIndex.call(
5 | interactiveEls,
6 | (element) => element === currentTarget
7 | );
8 |
9 | const nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1];
10 |
11 | if (nextEl) {
12 | nextEl.focus();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/menu/concerns/matchers.js:
--------------------------------------------------------------------------------
1 | function escapeForRegExp(string) {
2 | return string.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
3 | }
4 |
5 | export function matchAny(string) {
6 | return new RegExp(escapeForRegExp(string), "gi");
7 | }
8 |
9 | export function matchPartial(string) {
10 | return new RegExp(`(?:^|\\s)${escapeForRegExp(string)}`, "i");
11 | }
12 |
13 | export function matchExact(string) {
14 | return new RegExp(`^${escapeForRegExp(string)}$`, "i");
15 | }
16 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/properties/linkMenu.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import i18n from "shared/i18n";
3 | import { SelectMenuProps, SelectSection } from "shared/types/menu";
4 | import { Rect } from "shared/types/Pos";
5 |
6 | export const showLinkMenu = (
7 | offset: Rect,
8 | win: Window,
9 | superstate: Superstate,
10 | saveLink: (link: string | string[]) => void,
11 | options?: Partial
12 | ) => {
13 | const suggestions = [...superstate.pathsIndex.values()]
14 | .filter((f) => !f.hidden)
15 | .map((f) => ({
16 | name: f.label.name,
17 | value: f.path,
18 | description: f.path,
19 | icon: f.label?.sticker,
20 | section: f.type,
21 | }));
22 | const sections: SelectSection[] = Array.from(
23 | new Set(suggestions.map((f) => f.section))
24 | ).map((f) => {
25 | return {
26 | name: f,
27 | value: f,
28 | };
29 | });
30 | return superstate.ui.openMenu(
31 | offset,
32 | {
33 | ui: superstate.ui,
34 | multi: options?.multi,
35 | editable: true,
36 | value: options?.value ?? [],
37 | options: suggestions,
38 | saveOptions: (_: string[], value: string[]) => {
39 | options?.multi ? saveLink(value) : saveLink(value[0]);
40 | },
41 | placeholder: i18n.labels.linkItemSelectPlaceholder,
42 | detail: true,
43 | searchable: true,
44 | showAll: true,
45 | sections: sections,
46 | showSections: true,
47 | ...(options ?? {}),
48 | },
49 | win
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/properties/propertiesMenu.tsx:
--------------------------------------------------------------------------------
1 | import i18n from "shared/i18n";
2 |
3 | import { SelectOption, Superstate } from "makemd-core";
4 | import { Rect } from "shared/types/Pos";
5 | import { SpaceProperty } from "shared/types/mdb";
6 | import { defaultMenu, menuInput, menuSeparator } from "../menu/SelectionMenu";
7 |
8 | export const showPropertiesMenu = (
9 | superstate: Superstate,
10 | rect: Rect,
11 | win: Window,
12 | property: SpaceProperty,
13 | deleteProperty: (property: SpaceProperty) => void,
14 | syncProperty: (property: SpaceProperty) => void,
15 | renameProperty: (key: string, name: string) => void,
16 | changeType: (e: React.MouseEvent, key: string) => void
17 | ) => {
18 | const menuOptions: SelectOption[] = [];
19 | menuOptions.push(
20 | menuInput(property?.name ?? "", (value) =>
21 | renameProperty(property.name, value)
22 | )
23 | );
24 | menuOptions.push(menuSeparator);
25 | menuOptions.push({
26 | name: i18n.menu.changePropertyType,
27 | icon: "ui//list",
28 | onClick: (e) => {
29 | changeType(e, property.name);
30 | },
31 | });
32 | if (property.type != "object")
33 | menuOptions.push({
34 | name: i18n.menu.syncToContext,
35 | icon: "ui//sync",
36 | onClick: (e) => {
37 | syncProperty(property);
38 | },
39 | });
40 | menuOptions.push({
41 | name: i18n.menu.deleteProperty,
42 | icon: "ui//trash",
43 | onClick: (e) => {
44 | deleteProperty(property);
45 | },
46 | });
47 |
48 | superstate.ui.openMenu(rect, defaultMenu(superstate.ui, menuOptions), win);
49 | };
50 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/properties/selectSpaceMenu.tsx:
--------------------------------------------------------------------------------
1 | import { SelectOption, Superstate } from "makemd-core";
2 | import i18n from "shared/i18n";
3 | import { Rect } from "shared/types/Pos";
4 |
5 | export const showSpacesMenu = (
6 | offset: Rect,
7 | win: Window,
8 | superstate: Superstate,
9 | saveLink: (link: string, isNew?: boolean, type?: string) => void,
10 | includeDefaults?: boolean,
11 | canAdd?: boolean,
12 | onlyTags?: boolean
13 | ) => {
14 | const options = [...superstate.allSpaces(true)]
15 | .filter(
16 | (f) =>
17 | (includeDefaults || f.type != "default") &&
18 | (!onlyTags || f.type == "tag")
19 | )
20 | .map((f) => ({
21 | name: f.name,
22 | value: f.path,
23 | icon: superstate.pathsIndex.get(f.path)?.label?.sticker,
24 | section: f.type == "tag" ? "tag" : f.type == "folder" ? "folder" : "",
25 | description:
26 | f.type == "tag" ? f.name : f.type == "folder" ? f.path : f.path,
27 | }));
28 |
29 | return superstate.ui.openMenu(
30 | offset,
31 | {
32 | ui: superstate.ui,
33 | multi: false,
34 | editable: canAdd,
35 | addKeyword: "Create",
36 | value: [],
37 | options,
38 | sections: onlyTags
39 | ? []
40 | : [
41 | { name: i18n.buttons.tag, value: "tag" },
42 | { name: i18n.menu.folder, value: "folder" },
43 | ],
44 | saveOptions: (
45 | _: string[],
46 | value: string[],
47 | isNew?: boolean,
48 | section?: string
49 | ) => {
50 | saveLink(value[0], isNew, section);
51 | },
52 | placeholder: i18n.labels.spaceSelectPlaceholder,
53 | detail: true,
54 | searchable: true,
55 | showSections: !onlyTags,
56 | showAll: true,
57 | },
58 | win,
59 | "bottom"
60 | );
61 | };
62 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Menus/selectMenu.tsx:
--------------------------------------------------------------------------------
1 | import { SelectMenu, SelectMenuProps } from "makemd-core";
2 | import React from "react";
3 | import { Anchors, Rect } from "shared/types/Pos";
4 | import { showMenu } from "./menu";
5 |
6 | export type Point = { x: number; y: number };
7 |
8 | export const showSelectMenu = (
9 | rect: Rect,
10 | optionProps: SelectMenuProps,
11 | win: Window,
12 | defaultAnchor: Anchors,
13 | onHide?: () => void,
14 | force?: boolean
15 | ) => {
16 | return showMenu({
17 | ui: optionProps.ui,
18 | rect: rect,
19 | anchor: defaultAnchor,
20 | win,
21 | fc: ,
22 | onHide,
23 | className: optionProps.searchable ? "mk-combo-menu" : "mk-select-menu",
24 | force,
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Modals/ConfirmationModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import i18n from "shared/i18n";
3 |
4 | export const ConfirmationModal = (props: {
5 | hide?: () => void;
6 | confirmAction: () => void;
7 | message: string;
8 | confirmLabel: string;
9 | }) => {
10 | const { hide, confirmAction, message, confirmLabel } = props;
11 | const confirm = () => {
12 | confirmAction();
13 | hide();
14 | };
15 | useEffect(() => {
16 | const handleKeyDown = (event: KeyboardEvent) => {
17 | if (event.key === "Enter") {
18 | event.preventDefault();
19 | event.stopPropagation();
20 | event.stopImmediatePropagation();
21 | confirm();
22 | }
23 | };
24 | window.addEventListener("keydown", handleKeyDown);
25 | return () => {
26 | window.removeEventListener("keydown", handleKeyDown);
27 | };
28 | }, []);
29 | return (
30 |
31 |
{message}
32 |
33 |
36 |
39 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Modals/InputModal.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React, { useEffect, useRef, useState } from "react";
3 | import { default as i18n } from "shared/i18n";
4 | type Action = "rename" | "create folder" | "create note";
5 |
6 | export type SectionAction = "rename" | "create";
7 |
8 | export const openInputModal = (
9 | superstate: Superstate,
10 | title: string,
11 | value: string,
12 | saveValue: (val: string) => void,
13 | saveLabel: string,
14 | win: Window
15 | ) => {
16 | superstate.ui.openModal(
17 | title,
18 | ,
19 | win
20 | );
21 | };
22 |
23 | export const InputModal = (props: {
24 | value: string;
25 | saveValue: (value: string) => void;
26 | saveLabel: string;
27 | hide?: () => void;
28 | }) => {
29 | const [value, setValue] = useState(props.value);
30 | const save = () => {
31 | props.saveValue(value);
32 | if (props.hide) props.hide();
33 | };
34 | const ref = useRef(null);
35 | useEffect(() => {
36 | if (ref?.current) {
37 | ref.current.focus();
38 | }
39 | }, [ref]);
40 | return (
41 |
42 |
setValue(e.target.value)}
47 | className="mk-input mk-input-large"
48 | style={{
49 | width: "100%",
50 | }}
51 | onKeyDown={(e) => {
52 | if (e.key === "Enter") save();
53 | }}
54 | >
55 |
56 |
57 |
60 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Stickers/Sticker.tsx:
--------------------------------------------------------------------------------
1 | import { UIManager } from "core/middleware/ui";
2 | import React from "react";
3 |
4 | export const Sticker = (props: { ui: UIManager; sticker: string }) => {
5 | return (
6 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Toggles/CollapseToggle.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 | export const CollapseToggle = (props: {
4 | superstate: Superstate;
5 | collapsed: boolean;
6 | onToggle?: (collapsed: boolean, e: React.MouseEvent) => void;
7 | }) => {
8 | return (
9 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/core/react/components/UI/Toggles/CollapseToggleSmall.tsx:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import React from "react";
3 | export const CollapseToggleSmall = (props: {
4 | superstate: Superstate;
5 | collapsed: boolean;
6 | onToggle?: (collapsed: boolean, e: React.MouseEvent) => void;
7 | }) => {
8 | return (
9 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/core/react/context/WindowContext.tsx:
--------------------------------------------------------------------------------
1 | import { DragOverlay, useDndMonitor } from "@dnd-kit/core";
2 | import React from "react";
3 | import { createPortal } from "react-dom";
4 |
5 | type WindowContextType = {
6 | dragNode: React.ReactNode;
7 | setDragNode: (node: React.ReactNode) => void;
8 | dragActive: boolean;
9 | };
10 | export const WindowContext = React.createContext({
11 | dragNode: null,
12 | setDragNode: () => null,
13 | dragActive: false,
14 | });
15 | export const WindowProvider = (
16 | props: React.PropsWithChildren<{
17 | dragActive: boolean;
18 | }>
19 | ) => {
20 | const [dragNode, setDragNode] = React.useState(null);
21 |
22 | useDndMonitor({
23 | onDragCancel: () => {
24 | setDragNode(null);
25 | },
26 | onDragEnd: () => {
27 | setDragNode(null);
28 | },
29 | });
30 | return (
31 |
38 | {props.children}
39 | {dragNode &&
40 | createPortal(
41 |
46 | {dragNode}
47 | ,
48 | document.body
49 | )}
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/core/react/hooks/ForceUpdate.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | export default function useForceUpdate() {
4 | const [value, setValue] = useState(0);
5 | return () => setValue((value) => value + 1);
6 | }
7 |
--------------------------------------------------------------------------------
/src/core/react/hooks/useCombinedRef.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 |
3 | export function useCombinedRefs(
4 | ...refs: ((node: T) => void)[]
5 | ): (node: T) => void {
6 | return useMemo(
7 | () => (node: T) => {
8 | refs.forEach((ref) => ref(node));
9 | },
10 | // eslint-disable-next-line react-hooks/exhaustive-deps
11 | refs
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/core/react/hooks/useEffectOnce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 |
3 | export default function useEffectOnce(cb: () => void) {
4 | useEffect(cb, [])
5 | }
--------------------------------------------------------------------------------
/src/core/react/hooks/useEventListener.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react"
2 |
3 | export default function useEventListener(
4 | eventType: string,
5 | callback: (e: Event) => void,
6 | element = window
7 | ) {
8 | const callbackRef = useRef(callback)
9 |
10 | useEffect(() => {
11 | callbackRef.current = callback
12 | }, [callback])
13 |
14 | useEffect(() => {
15 | if (element == null) return
16 | const handler = (e: Event) => callbackRef.current(e)
17 | element.addEventListener(eventType, handler)
18 |
19 | return () => element.removeEventListener(eventType, handler)
20 | }, [eventType, element])
21 | }
--------------------------------------------------------------------------------
/src/core/react/hooks/useLongPress.tsx:
--------------------------------------------------------------------------------
1 | import useEffectOnce from "./useEffectOnce";
2 | import useEventListener from "./useEventListener";
3 | import useTimeout from "./useTimeout";
4 |
5 | import React from "react";
6 |
7 | export default function useLongPress(
8 | ref: React.RefObject,
9 | cb: () => void,
10 | { delay = 400 } = {}
11 | ) {
12 | const { reset, clear } = useTimeout(cb, delay);
13 | useEffectOnce(clear);
14 |
15 | useEventListener("mousedown", reset, ref.current);
16 | useEventListener("touchstart", reset, ref.current);
17 |
18 | useEventListener("mouseup", clear, ref.current);
19 | useEventListener("mouseleave", clear, ref.current);
20 | useEventListener("touchend", clear, ref.current);
21 | }
22 |
--------------------------------------------------------------------------------
/src/core/react/hooks/useTimeout.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from "react";
2 |
3 | export default function useTimeout(callback: () => void, delay: number) {
4 | const callbackRef = useRef(callback);
5 | const timeoutRef = useRef(null);
6 |
7 | useEffect(() => {
8 | callbackRef.current = callback;
9 | }, [callback]);
10 |
11 | const set = useCallback(() => {
12 | timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
13 | }, [delay]);
14 |
15 | const clear = useCallback(() => {
16 | timeoutRef.current && clearTimeout(timeoutRef.current);
17 | }, []);
18 |
19 | useEffect(() => {
20 | set();
21 | return clear;
22 | }, [delay, set, clear]);
23 |
24 | const reset = useCallback(() => {
25 | clear();
26 | set();
27 | }, [clear, set]);
28 |
29 | return { reset, clear };
30 | }
31 |
--------------------------------------------------------------------------------
/src/core/schemas/code.ts:
--------------------------------------------------------------------------------
1 | export const commonActionsKeys = [
2 | "onClick", "onRun"
3 | ]
4 | export const commonStyleKeys = [
5 | "alignContent",
6 | "alignItems",
7 | "alignSelf",
8 | "backgroundColor",
9 | "border",
10 | "borderBottom",
11 | "borderBottomColor",
12 | "borderBottomStyle",
13 | "borderBottomWidth",
14 | "borderColor",
15 | "borderLeft",
16 | "borderLeftColor",
17 | "borderLeftStyle",
18 | "borderLeftWidth",
19 | "borderRadius",
20 | "borderRight",
21 | "borderRightColor",
22 | "borderRightStyle",
23 | "borderRightWidth",
24 | "borderStyle",
25 | "borderTop",
26 | "borderTopColor",
27 | "borderTopStyle",
28 | "borderTopWidth",
29 | "borderWidth",
30 | "boxShadow",
31 | "boxSizing",
32 | "color",
33 | "display",
34 | "flex",
35 | "flexBasis",
36 | "flexDirection",
37 | "flexFlow",
38 | "flexGrow",
39 | "flexShrink",
40 | "flexWrap",
41 | "fontFamily",
42 | "fontSize",
43 | "fontWeight",
44 | "height",
45 | "justifyContent",
46 | "letterSpacing",
47 | "lineHeight",
48 | "margin",
49 | "marginBottom",
50 | "marginLeft",
51 | "marginRight",
52 | "marginTop",
53 | "maxHeight",
54 | "maxWidth",
55 | "minHeight",
56 | "minWidth",
57 | "opacity",
58 | "order",
59 | "outline",
60 | "outlineColor",
61 | "outlineStyle",
62 | "outlineWidth",
63 | "overflow",
64 | "overflowX",
65 | "overflowY",
66 | "padding",
67 | "paddingBottom",
68 | "paddingLeft",
69 | "paddingRight",
70 | "paddingTop",
71 | "position",
72 | "textAlign",
73 | "textDecoration",
74 | "textTransform",
75 | "top",
76 | "right",
77 | "bottom",
78 | "left",
79 | "transform",
80 | "transition",
81 | "verticalAlign",
82 | "visibility",
83 | "whiteSpace",
84 | "width",
85 | "wordSpacing",
86 | "zIndex"
87 | ]
88 |
--------------------------------------------------------------------------------
/src/core/spaceManager/filesystemAdapter/spaces.ts:
--------------------------------------------------------------------------------
1 | import { VaultItem } from "shared/types/afile";
2 |
3 | import { Superstate } from "makemd-core";
4 | import { DBRows } from "shared/types/mdb";
5 | import { MakeMDSettings } from "shared/types/settings";
6 | import { folderPathToString } from "utils/path";
7 |
8 |
9 |
10 |
11 | export const onPathCreated = async (
12 | superstate: Superstate,
13 | newPath: string,
14 | ) => {
15 |
16 | superstate.onPathCreated(newPath)
17 |
18 | };
19 |
20 | export const onPathDeleted = async (superstate: Superstate, oldPath: string) => {
21 | superstate.onPathDeleted(oldPath)
22 | };
23 |
24 | export const onPathChanged = async (
25 | superstate: Superstate,
26 | oldPath: string,
27 | newPath: string
28 | ) => {
29 | return superstate.onPathRename(oldPath, newPath)
30 | };
31 |
32 |
33 |
34 |
35 |
36 | export const excludeVaultItemPredicate =
37 | (settings: MakeMDSettings) =>
38 | (f: VaultItem, index: number, folder: VaultItem[]) =>
39 | !(
40 | (f.path.endsWith('/'+settings.spaceSubFolder) || f.path == settings.spaceSubFolder ||
41 | settings.hiddenExtensions.find(
42 | (e) => (f.path).endsWith(e)
43 | ))
44 | ) &&
45 | !settings.hiddenFiles.find((e) => e == f.path) &&
46 | (!settings.enableFolderNote ||
47 | (!settings.folderNoteInsideFolder &&
48 | !folder.some((g) => g.path + ".md" == f.path)) ||
49 | (settings.folderNoteInsideFolder &&
50 | !(f.parent + "/" + folderPathToString(f.parent) + ".md" == f.path)));
51 |
52 |
53 |
54 | export const retrieveAllRecursiveChildren = (
55 | vaultDB: DBRows,
56 | settings: MakeMDSettings,
57 | folder: string
58 | ) => {
59 | return vaultDB.filter(f => f['parent'].startsWith(folder)).filter(
60 | excludeVaultItemPredicate(settings)
61 | ) as VaultItem[];
62 | };
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/core/superstate/utils/label.ts:
--------------------------------------------------------------------------------
1 | import { ensureArray } from "core/utils/strings";
2 | import { Superstate } from "makemd-core";
3 | import { parseMDBStringValue } from "utils/properties";
4 | import { serializeMultiDisplayString } from "utils/serializers";
5 | import { metadataPathForSpace, saveProperties } from "./spaces";
6 |
7 | export const savePathBanner = (superstate: Superstate, path: string, banner: string) => {
8 | if (superstate.spacesIndex.has(path)) {
9 | saveProperties(superstate, metadataPathForSpace(superstate, superstate.spacesIndex.get(path).space), {
10 | [superstate.settings.fmKeyBanner]: banner,
11 | });
12 | return;
13 | }
14 | saveProperties(superstate, path, {
15 | [superstate.settings.fmKeyBanner]: banner,
16 | });
17 | };
18 | export const savePathColor = async (
19 | superstate: Superstate,
20 | path: string,
21 | color: string
22 | ) => {
23 | superstate.spaceManager.saveLabel(path, superstate.settings.fmKeyColor, color);
24 |
25 | };
26 | export const updatePrimaryAlias = (superstate: Superstate,
27 | path: string, aliases: string[],
28 | value: string) => {
29 | const newValue = serializeMultiDisplayString([value, ...ensureArray(aliases).filter(f => f == value)]);
30 | return saveProperties(superstate, path, { [superstate.settings.fmKeyAlias]: parseMDBStringValue("option-multi", newValue, true) });
31 |
32 | };
33 |
34 |
--------------------------------------------------------------------------------
/src/core/superstate/utils/tags.ts:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import { ensureTag } from "utils/tags";
3 | import { metadataPathForSpace } from "./spaces";
4 |
5 | export const deleteTagFromPath = (superstate: Superstate, path: string, tag: string) => {
6 | if (superstate.spacesIndex.has(path)) {
7 | return superstate.spaceManager.deleteTag(metadataPathForSpace(superstate, superstate.spacesIndex.get(path).space), tag);
8 | }
9 | return superstate.spaceManager.deleteTag(path, tag);
10 | };
11 |
12 |
13 | export const addTagToPath = (superstate: Superstate, path: string, tag: string) => {
14 |
15 | if (superstate.spacesIndex.has(path)) {
16 | return superstate.spaceManager.addTag(metadataPathForSpace(superstate, superstate.spacesIndex.get(path).space), tag);
17 | }
18 | return superstate.spaceManager.addTag(path, tag);
19 | };
20 |
21 | export const addTag = (superstate: Superstate, tag: string) => {
22 | return superstate.spaceManager.createSpace(ensureTag(tag), superstate.settings.spacesFolder, null);
23 | }
--------------------------------------------------------------------------------
/src/core/superstate/workers/indexer/indexer.worker.ts:
--------------------------------------------------------------------------------
1 | import { formulas } from "core/utils/formula/formulas";
2 | import * as math from 'mathjs';
3 | import { indexAllPaths, parseAllContexts, parseAllPaths, parseContext, parsePath } from "./impl";
4 | const ctx: Worker = self as any;
5 |
6 | const all = {
7 | ...math.all,
8 | createAdd: math.factory('add', [], () => function add (a: any, b: any) {
9 | return a + b
10 | }),
11 | createEqual: math.factory('equal', [], () => function equal (a: any, b: any) {
12 | return a == b
13 | }),
14 | createUnequal: math.factory('unequal', [], () => function unequal (a: any, b: any) {
15 | return a != b
16 | })
17 |
18 | }
19 | const config :math.ConfigOptions = {
20 | matrix: "Array"
21 | }
22 | const runContext = math.create(all, config)
23 | runContext.import(formulas, { override: true })
24 |
25 | ctx.onmessage = async evt => {
26 | const { payload, job } = evt.data;
27 | let result;
28 | if (job.type == 'path') {
29 | result = parsePath(payload);
30 | } else if (job.type == 'context') {
31 | result = parseContext(payload, runContext)
32 | } else if (job.type == 'contexts') {
33 | result = parseAllContexts(payload, runContext)
34 | } else if (job.type == 'paths') {
35 | result = parseAllPaths(payload)
36 | } else if (job.type == 'index') {
37 | result = indexAllPaths(payload);
38 | }
39 | try {
40 | (postMessage as any)({ job, result });
41 | } catch (error) {
42 | console.log(error);
43 | (postMessage as any)({
44 | job,
45 | result: {
46 | $error: `Failed to index ${job.type} ${job.path}: ${error}`,
47 | },
48 | });
49 | }
50 | };
--------------------------------------------------------------------------------
/src/core/superstate/workers/search/impl.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { pathByDef } from "core/utils/spaces/query";
4 | import Fuse, { FuseIndex } from "fuse.js";
5 | import { PathState } from "shared/types/PathState";
6 | import { FilterGroupDef } from "shared/types/spaceDef";
7 |
8 | export function fastSearch (query: string, pathsIndex: Map, count: number, index: FuseIndex) {
9 |
10 | const paths = [];
11 |
12 | const fuseOptions = {
13 | // isCaseSensitive: false,
14 | // includeScore: false,
15 | shouldSort: true,
16 | // includeMatches: false,
17 | // findAllMatches: false,
18 | // minMatchCharLength: 1,
19 | // location: 0,
20 | threshold: 0,
21 | // distance: 100,
22 | // useExtendedSearch: false,
23 | ignoreLocation: true,
24 | // ignoreFieldNorm: false,
25 | // fieldNormWeight: 1,
26 | keys: [{ name: 'name', weight: 2 }, "path", 'label.preview', { name: 'spaceNames', weight: 0.5 }],
27 | };
28 | const fuse = new Fuse([...pathsIndex.values()].filter(f => f.hidden == false), fuseOptions, index);
29 | return fuse.search(query).map((result) => result.item).slice(0, count);
30 |
31 | }
32 |
33 | export function searchPath (payload: { queries: FilterGroupDef[], pathsIndex: Map, count: number}) {
34 | const { queries, pathsIndex, count } = payload;
35 | const paths = [];
36 |
37 | for (const [k, f] of pathsIndex) {
38 | if (!f.hidden && pathByDef(queries, f, {}, true)) {
39 | paths.push(f);
40 | }
41 | }
42 | return paths.slice(0, count);
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/core/types/make.d.ts:
--------------------------------------------------------------------------------
1 | import MakeMDPlugin from "main";
2 |
3 | export {};
4 |
5 | declare global {
6 | interface Window {
7 | make: MakeMDPlugin;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/core/types/space.ts:
--------------------------------------------------------------------------------
1 | import { fileSystemSpaceInfoFromFolder } from "core/spaceManager/filesystemAdapter/spaceInfo"
2 | import { SpaceManager } from "makemd-core"
3 | import i18n from "shared/i18n"
4 |
5 |
6 | import { PathState, SpaceState } from "shared/types/PathState"
7 | import { MakeMDSettings } from "../../shared/types/settings"
8 |
9 |
10 |
11 | export const FMMetadataKeys = (settings: MakeMDSettings) => [settings.fmKeyBanner, settings.fmKeySticker, settings.fmKeyColor, settings.fmKeyBanner, settings.fmKeyBannerOffset,
12 | spaceContextsKey, spaceJoinsKey, spaceLinksKey, spaceSortKey, spaceTemplateKey, spaceTemplateNameKey
13 | ]
14 | export const createVaultSpace = (manager: SpaceManager) : SpaceState => ({
15 | name: i18n.menu.vault,
16 | path: "/",
17 | space: fileSystemSpaceInfoFromFolder(manager, "/"),
18 | type: "default",
19 | });
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | export const vaultPath: PathState = {
29 | name: i18n.menu.vault,
30 | readOnly: false,
31 | path: "/",
32 | label: {
33 | thumbnail: '',
34 | name: i18n.menu.vault,
35 | sticker: "ui//vault",
36 | color: ''
37 | },
38 | type: "default",
39 | };
40 |
41 |
42 |
43 | export type BuiltinSpace = {
44 | name: string;
45 | icon: string;
46 | readOnly: boolean;
47 | hidden: boolean;
48 | }
49 |
50 | export const builtinSpaces : Record = {
51 | tags: {
52 | name: "Tags",
53 | icon: "ui//tags",
54 | readOnly: false,
55 | hidden: false
56 | },
57 | overview: {
58 | name: "Overview",
59 | icon: "ui//overview",
60 | readOnly: true,
61 | hidden: true
62 | },
63 | };
64 |
65 | export const spaceContextsKey = "_contexts";
66 | export const spaceTemplateKey = "_template";
67 | export const spaceTemplateNameKey = "_templateName";
68 | export const spaceJoinsKey = "_joins";
69 | export const spaceLinksKey = "_links";
70 | export const spaceSortKey = "_sort";
71 | export const spaceRecursiveKey = "_subfolders";
72 |
73 |
--------------------------------------------------------------------------------
/src/core/types/superstate.ts:
--------------------------------------------------------------------------------
1 |
2 | export const pathStateTypes = {
3 | path: "string",
4 | name: "string",
5 | parent: "string",
6 | type: "string",
7 | subtype: "string",
8 | label: {
9 | name: "string",
10 | sticker: "string",
11 | color: "string",
12 | thumbnail: "string",
13 | preview: "string",
14 | },
15 | metadata: {
16 | file: {
17 | ctime: "date",
18 | mtime: "date",
19 | size: "number",
20 | path: "string",
21 | parent: "string",
22 | extension: "string",
23 | },
24 |
25 | },
26 | properties: "object",
27 | hidden: "boolean",
28 | spaces: "string[]",
29 | tags: "string[]",
30 | inlinks: "string[]",
31 | outlinks: "string[]"
32 | }
--------------------------------------------------------------------------------
/src/core/types/types.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | export const eventTypes = {
6 |
7 | frameSelected: "mkmd-active-frame",
8 | frameLayerSelected: "mkmd-frame-layer",
9 | refreshView: "mkmd-refresh-view",
10 | revealPath: "mkmd-reveal-file",
11 | collapseFolders: "mkmd-collapse-folders",
12 | toggleBacklinks: "mkmd-toggle-backlinks",
13 | metadataChange: "mkmd-tags-change",
14 | vaultChange: "mkmd-vault-change",
15 | mdbChange: "mkmd-mdb-change",
16 | spacesChange: "mkmd-spaces-change",
17 | frameChange: "mkmd-frame-change",
18 | updateSections: "mkmd-update-sections",
19 | settingsChanged: "mkmd-settings-changed",
20 | };
21 |
22 | export type VaultChange =
23 | | "create"
24 | | "delete"
25 | | "rename"
26 | | "modify"
27 | | "collapse";
28 | export type SpaceChange = "sticker" | "space" | "vault" | "file" | "context" | 'frames';
29 | export class ActivePathEvent {
30 | path: string;
31 | selection?: string;
32 | selectionType?: 'view' | 'row'
33 | }
34 | export class SelectionEvent {
35 | selection: string[];
36 | }
37 |
38 | export class SpaceChangeEvent extends Event {
39 | detail: {
40 | type: SpaceChange;
41 | action?: string,
42 | name?: string,
43 | newName?: string
44 |
45 | };
46 | }
47 |
48 | export class CustomVaultChangeEvent extends Event {
49 | detail: {
50 | path: string;
51 | changeType: VaultChange;
52 | oldPath: string;
53 | };
54 | }
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/core/types/ui.ts:
--------------------------------------------------------------------------------
1 | export type PointerModifiers = {
2 | metaKey?: boolean;
3 | ctrlKey?: boolean;
4 | altKey?: boolean;
5 | shiftKey?: boolean;
6 | doubleClick?: boolean;
7 | }
--------------------------------------------------------------------------------
/src/core/types/worker.d.ts:
--------------------------------------------------------------------------------
1 | declare module "web-worker:*" {
2 | const WorkerFactory: new (options: any) => Worker;
3 | export default WorkerFactory;
4 | }
--------------------------------------------------------------------------------
/src/core/utils/color.ts:
--------------------------------------------------------------------------------
1 | export const applySat = (sat: number, color: string) => {
2 | const hash = color.substring(0, 1) === '#';
3 |
4 | const hex = (hash ? color.substring(1) : color).split('');
5 |
6 | const long = hex.length > 3;
7 | const rgb = [];
8 | let i = 0;
9 | const len = 3;
10 |
11 | rgb.push(hex.shift() + (long ? hex.shift() : ''));
12 | rgb.push(hex.shift() + (long ? hex.shift() : ''));
13 | rgb.push(hex.shift() + (long ? hex.shift() : ''));
14 |
15 | for (; i < len; i++) {
16 | if (!long) {
17 | rgb[i] += rgb[i];
18 | }
19 |
20 | rgb[i] = Math.round((parseInt(rgb[i], 16) / 100) * sat).toString(16);
21 |
22 | rgb[i] += rgb[i].length === 1 ? rgb[i] : '';
23 | }
24 |
25 | return (hash ? '#' : '') + rgb.join('');
26 | };
27 |
--------------------------------------------------------------------------------
/src/core/utils/commands/commands.ts:
--------------------------------------------------------------------------------
1 | import { fieldSchema } from "shared/schemas/fields";
2 | import { Command, CommandSchema } from "shared/types/commands";
3 | import { DBTables, SpaceProperty, SpaceTableSchema } from "shared/types/mdb";
4 | import { safelyParseJSON } from "shared/utils/json";
5 |
6 | export const commandToDBTables = (tables: Command, fields: SpaceProperty[]): DBTables => {
7 | return ({
8 | m_fields: {
9 | uniques: fieldSchema.uniques,
10 | cols: fieldSchema.cols,
11 | rows: [...fields.filter(f => f.schemaId != tables.schema.id), ...tables.fields, { name: "$function", schemaId: tables.schema.id, value: tables.code, type: "command"} as SpaceProperty],
12 | }
13 | }) as DBTables;
14 |
15 | };
16 | export const mdbSchemaToCommandSchema = (schema: SpaceTableSchema) : CommandSchema => {
17 | if (!schema) return null;
18 | return {
19 | ...schema,
20 | def: safelyParseJSON(schema.def)
21 | }
22 | }
--------------------------------------------------------------------------------
/src/core/utils/commands/filter.ts:
--------------------------------------------------------------------------------
1 | import { FilterDef, FilterGroupDef } from "shared/types/spaceDef";
2 | import { parseProperty } from "utils/parsers";
3 | import { filterFnTypes } from "../contexts/predicate/filterFns/filterFnTypes";
4 |
5 | const filterForAny = (props: {[key: string]: any}, filters: FilterDef[]) : boolean => {
6 | return filters.reduce((p, c) => {
7 | if (p == true) return true;
8 | return props ? filterForProps(props, c) : false;
9 | }, false)
10 | }
11 |
12 | const filterForProps = (props: {[key: string]: any}, def: FilterDef) : boolean => {
13 | const filterFn = filterFnTypes[def.fn];
14 |
15 | let result = true;
16 | if (filterFn) {
17 | result = filterFn.fn(parseProperty(def.field, props[def.field]), def.value);
18 | }
19 | return result;
20 | }
21 |
22 | const filterPathsForAll = ( props: {[key: string]: any}, filters: FilterDef[]) : boolean => {
23 | return filters.reduce((p, c) => {
24 | if (p == false) return false;
25 | return props ? filterForProps(props, c) : false;
26 | }, true)
27 | }
28 |
29 | export const resultForFilters = ( filters: FilterGroupDef[], props: {[key: string] : any}) => {
30 |
31 | const pathInFilter = filters.reduce((p, c) => {
32 | if (!p || c.filters.length == 0) return false;
33 | const result = (c.type == 'any') ? filterForAny(props, c.filters) : filterPathsForAll(props, c.filters)
34 | return result
35 | }, true);
36 | return pathInFilter;
37 | }
--------------------------------------------------------------------------------
/src/core/utils/contexts/lookup.ts:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import { PathState } from "shared/types/PathState";
3 | import { serializeMultiString } from "utils/serializers";
4 | import { parseMultiString, parseProperty } from "../../../utils/parsers";
5 | import { serializeMultiDisplayString } from "../../../utils/serializers";
6 |
7 |
8 | export const appendPathsMetaData = (superstate: Superstate, propType: string, pathsString: string) => {
9 | const paths = parseMultiString(pathsString)
10 | .map((f) => superstate.pathsIndex.get(f))
11 | .filter((f) => f);
12 | return serializeMultiString(paths.map((f) => appendPathMetaData(propType, f)));
13 | };
14 |
15 | export const appendPathMetaData = (propType: string, pathState: PathState) => {
16 | let value = "";
17 | if (pathState) {
18 | if (propType == "folder") {
19 | value = pathState.parent;
20 | } else if (propType == "name") {
21 | value = pathState.name;
22 | } else if (propType == "ctime") {
23 | value = pathState.metadata?.file?.ctime?.toString();
24 | } else if (propType == "mtime") {
25 | value = pathState.metadata?.file?.mtime?.toString();
26 | } else if (propType == "extension") {
27 | value = pathState.metadata.extension;
28 | } else if (propType == "sticker") {
29 | value = pathState.label.sticker;
30 | } else if (propType == "size") {
31 | value = pathState.metadata?.file?.size?.toString();
32 | } else if (propType == "inlinks") {
33 | value = serializeMultiDisplayString(pathState.inlinks);
34 | } else if (propType == "outlinks") {
35 | value = serializeMultiDisplayString(pathState.outlinks);
36 | } else if (propType == "tags") {
37 | value = serializeMultiDisplayString(pathState.tags);
38 | } else if (propType == 'spaces') {
39 | value = serializeMultiDisplayString(pathState.spaces);
40 | } else {
41 | value = parseProperty(null, pathState.metadata?.[propType]);
42 | }
43 | }
44 | return value;
45 | };
46 |
--------------------------------------------------------------------------------
/src/core/utils/contexts/pathUpdates.ts:
--------------------------------------------------------------------------------
1 | import { PathPropertyName } from "shared/types/context";
2 | import { SpaceTable } from "shared/types/mdb";
3 | import { insertMulti } from "shared/utils/array";
4 |
5 | export const renameRowForPath = (
6 | spaceTable: SpaceTable,
7 | paths: string,
8 | newPath: string
9 | ): SpaceTable => {
10 | return {
11 | ...spaceTable,
12 | rows: spaceTable.rows.map((f) =>
13 | f[PathPropertyName] == paths
14 | ? { ...f, [PathPropertyName]: newPath }
15 | : f
16 | ),
17 | };
18 | };
19 |
20 |
21 |
22 | export const removeRowForPath = (spaceTable: SpaceTable, paths: string): SpaceTable => {
23 | return {
24 | ...spaceTable,
25 | rows: spaceTable.rows.filter(
26 | (f) => f[PathPropertyName] != paths
27 | ),
28 | };
29 | };
30 |
31 | export const removeRowsForPath = (spaceTable: SpaceTable, paths: string[]): SpaceTable => {
32 | return {
33 | ...spaceTable,
34 | rows: spaceTable.rows.filter(
35 | (f) => !paths.includes(f[PathPropertyName])
36 | ),
37 | };
38 | };
39 |
40 |
41 |
42 | export const reorderRowsForPath = (spaceTable: SpaceTable, paths: string[], index: number): SpaceTable => {
43 | const rows = spaceTable.rows.filter(
44 | (f) => paths.includes(f[PathPropertyName])
45 | )
46 | return {
47 | ...spaceTable,
48 | rows: insertMulti(spaceTable.rows.filter(
49 | (f) => !paths.includes(f[PathPropertyName])
50 | ), index, rows),
51 | };
52 | };
--------------------------------------------------------------------------------
/src/core/utils/contexts/predicate/filterFns/filterFnLabels.ts:
--------------------------------------------------------------------------------
1 | import i18n from "shared/i18n";
2 |
3 |
4 | export const filterFnLabels : Record = {
5 | isEmpty: i18n.filterTypes.isEmpty,
6 | isNotEmpty: i18n.filterTypes.isNotEmpty,
7 | include: i18n.filterTypes.contains,
8 | notInclude: i18n.filterTypes.notContains,
9 | is: i18n.filterTypes.is,
10 | isNot: i18n.filterTypes.isNot,
11 | isLink: i18n.filterTypes.is,
12 | isNotLink: i18n.filterTypes.isNot,
13 | equal: "=",
14 | isGreatThan: ">",
15 | isLessThan: "<",
16 | isLessThanOrEqual: "≤",
17 | isGreatThanOrEqual: "≥",
18 | dateBefore: i18n.filterTypes.before,
19 | dateAfter: i18n.filterTypes.after,
20 | isSameDate: i18n.filterTypes.isSameDate,
21 | isSameDateAsToday: i18n.filterTypes.isSameDateAsToday,
22 | isExactList: i18n.filterTypes.is,
23 | isAnyInList: i18n.filterTypes.anyOf,
24 | isNoneInList: i18n.filterTypes.noneOf,
25 | isTrue: i18n.filterTypes.checked,
26 | isFalse: i18n.filterTypes.unchecked,
27 | };
28 |
--------------------------------------------------------------------------------
/src/core/utils/contexts/predicate/tableview.ts:
--------------------------------------------------------------------------------
1 | import { FilterFn, SortingFn } from "@tanstack/react-table";
2 | import { SpaceTableColumn } from "shared/types/mdb";
3 | import { Predicate } from "shared/types/predicate";
4 | import { filterFnTypes } from "./filterFns/filterFnTypes";
5 | import { SortFunction, sortFnTypes } from "./sort";
6 |
7 | export const tableViewFilterFn = (
8 | filterFn: (cellValue: any, filterValue: any) => boolean
9 | ): FilterFn => {
10 | return (row, columnId, filterValue, addmeta) => {
11 | return filterFn(row.getValue(columnId), filterValue);
12 | };
13 | };
14 |
15 | export const filterFnForCol = (
16 | predicate: Predicate,
17 | col: SpaceTableColumn
18 | ): FilterFn => {
19 | const { filters } = predicate;
20 | const filterField = filters.find((f) => f.field == col.name + col.table);
21 | if (!filterField) {
22 | return () => true;
23 | }
24 | const filterType = filterFnTypes[filterField.fn];
25 | if (filterType) {
26 | return tableViewFilterFn(filterType.fn);
27 | }
28 | return () => true;
29 | };
30 |
31 | export const tableViewSortFn = (sortFn: SortFunction): SortingFn => {
32 | return (row, row2, columnId) => {
33 | return sortFn(row.getValue(columnId), row2.getValue(columnId));
34 | };
35 | };
36 |
37 | export const sortFnForCol = (
38 | predicate: Predicate,
39 | col: SpaceTableColumn
40 | ): SortingFn => {
41 | const { sort } = predicate;
42 | const sortField = sort.find((f) => f.field == col.name + col.table);
43 | if (!sortField || !sortField.fn) {
44 | return null;
45 | }
46 | const sortType = sortFnTypes[sortField.fn];
47 | if (sortType) {
48 | return tableViewSortFn(sortType.fn);
49 | }
50 | return null;
51 | };
52 |
--------------------------------------------------------------------------------
/src/core/utils/date.ts:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns';
2 | import { isDate, isFinite, isString } from 'lodash';
3 | import { RRule } from 'rrule';
4 | import { MakeMDSettings } from 'shared/types/settings';
5 |
6 | export const isValidDate = (d: Date) => {
7 | return d instanceof Date && !isNaN(d as any);
8 | };
9 |
10 | export const isoDateFormat = `yyyy-MM-dd'T'HH:mm:ss`;
11 |
12 | export const formatDate = (
13 | settings: MakeMDSettings,
14 | date: Date,
15 | dateFormat?: string,
16 | ) => {
17 | let dateString;
18 |
19 | try {
20 | const hasTime =
21 | date.getHours() > 0 || date.getMinutes() > 0 || date.getSeconds() > 0;
22 | dateString = format(
23 | date,
24 | dateFormat?.length > 0
25 | ? dateFormat
26 | : hasTime
27 | ? `${settings.defaultDateFormat} ${settings.defaultTimeFormat}`
28 | : settings.defaultDateFormat,
29 | );
30 | } catch (e) {
31 | dateString = '';
32 | }
33 | return dateString;
34 | };
35 |
36 | export const parseDate = (str: any) => {
37 | if (!str) return null;
38 | if (isFinite(str)) {
39 | return new Date(str);
40 | }
41 | if (isString(str)) {
42 | return parseISO(str);
43 | }
44 | if (isDate(str)) return str;
45 | return null;
46 | };
47 |
48 | export const getFreqValue = (freq: string) => {
49 | if (freq == 'DAILY') return RRule.DAILY;
50 | if (freq == 'WEEKLY') return RRule.WEEKLY;
51 | if (freq == 'MONTHLY') return RRule.MONTHLY;
52 | if (freq == 'YEARLY') return RRule.YEARLY;
53 | if (freq == 'HOURLY') return RRule.HOURLY;
54 | };
55 | export const getWeekdayValue = (weekday: string) => {
56 | if (weekday == 'SU') return 6;
57 | if (weekday == 'MO') return 0;
58 | if (weekday == 'TU') return 1;
59 | if (weekday == 'WE') return 2;
60 | if (weekday == 'TH') return 3;
61 | if (weekday == 'FR') return 4;
62 | if (weekday == 'SA') return 5;
63 | };
64 |
--------------------------------------------------------------------------------
/src/core/utils/emoji.ts:
--------------------------------------------------------------------------------
1 |
2 | import { savePathColor } from "core/superstate/utils/label";
3 | import { Superstate } from "makemd-core";
4 | import { savePathSticker } from "shared/utils/sticker";
5 |
6 |
7 | export const saveIconsForPaths = (
8 | superstate: Superstate,
9 | paths: string[],
10 | icon: string
11 | ) => {
12 | paths.forEach((path) => {
13 | savePathSticker(superstate, path, icon);
14 | });
15 | };
16 |
17 | export const saveColorForPaths = (
18 | superstate: Superstate,
19 | paths: string[],
20 | icon: string
21 | ) => {
22 | paths.forEach((path) => {
23 |
24 | savePathColor(superstate, path, icon);
25 |
26 | });
27 | };
28 |
29 |
30 | export const savePathIcon = (
31 | superstate: Superstate,
32 | path: string,
33 | icon: string
34 | ) => {
35 | savePathSticker(superstate, path, icon);
36 | };
37 |
38 | export const removePathIcon = (superstate: Superstate, path: string) => {
39 | savePathSticker(superstate, path, "");
40 | };
41 |
--------------------------------------------------------------------------------
/src/core/utils/fonts.ts:
--------------------------------------------------------------------------------
1 | export function listFonts(): string[] {
2 | const { fonts } = document;
3 | //@ts-ignore
4 | const it = fonts.entries();
5 |
6 | const arr: string[] = [];
7 | let done = false;
8 |
9 | while (!done) {
10 | const font = it.next();
11 | if (!font.done) {
12 | arr.push(font.value[0].family);
13 | } else {
14 | done = font.done;
15 | }
16 | }
17 |
18 | // converted to set then array to filter repetitive values
19 | return Array.from(new Set(arr));
20 | }
--------------------------------------------------------------------------------
/src/core/utils/frames/nodes.ts:
--------------------------------------------------------------------------------
1 | import { nodeToPropValue, nodeToTypes } from "schemas/frames";
2 | import { FrameNode, FrameTreeProp, MFrame } from "shared/types/mframe";
3 | import { safelyParseJSON } from "shared/utils/json";
4 |
5 | export const frameToNode = (frame: MFrame): FrameNode => {
6 | return {
7 | ...frame,
8 | rank: parseInt(frame.rank),
9 | contexts: safelyParseJSON(frame.contexts),
10 | styles: safelyParseJSON(frame.styles),
11 | actions: safelyParseJSON(frame.actions),
12 | props: safelyParseJSON(frame.props),
13 | types: nodeToTypes(frame.type),
14 | propsValue: nodeToPropValue(frame.type),
15 | };
16 | };
17 | export const nodeToFrame = (node: FrameNode): MFrame => {
18 | const { contexts, styles, props, actions, ...otherProps } = node;
19 | return {
20 | ...otherProps,
21 | rank: node.rank?.toString() ?? "0",
22 | contexts: JSON.stringify(contexts),
23 | styles: JSON.stringify(styles),
24 | actions: JSON.stringify(actions),
25 | props: JSON.stringify(props),
26 | };
27 | };
28 |
29 | export const mergePropObjects = (
30 | obj1: FrameTreeProp,
31 | obj2: FrameTreeProp
32 | ): FrameTreeProp => {
33 | const mergedObject: FrameTreeProp = { ...obj1, ...obj2 };
34 |
35 | for (const key in obj2) {
36 | if (obj2[key] === null) {
37 | delete mergedObject[key];
38 | }
39 | }
40 | return mergedObject;
41 | };
--------------------------------------------------------------------------------
/src/core/utils/frames/renderer.ts:
--------------------------------------------------------------------------------
1 | import { FrameTreeProp } from "shared/types/mframe";
2 |
3 | export const parseStylesToClass = (styles: FrameTreeProp) => {
4 | const classes = [];
5 | if (styles.class) {
6 | classes.push(`${styles.class}`);
7 | }
8 | if (styles.layout) {
9 | classes.push(`mk-layout-${styles.layout}`);
10 | }
11 | if (styles.layoutAlign) {
12 | classes.push(`mk-layout-align-${styles.layoutAlign}`);
13 | }
14 | if (styles.layoutWrap) {
15 | classes.push(`mk-layout-wrap-${styles.layoutWrap}`);
16 | }
17 | if (styles.iconSize) {
18 | classes.push(`mk-icon-size-${styles.iconSize}`)
19 | }
20 | if (styles.imageSize) {
21 | classes.push(`mk-image-size-${styles.imageSize}`)
22 | }
23 | return classes.join(" ");
24 | };
--------------------------------------------------------------------------------
/src/core/utils/hash.ts:
--------------------------------------------------------------------------------
1 | export const hashCode = (str: string) => {
2 | let hash = 0;
3 | for (let i = 0, len = str.length; i < len; i++) {
4 | const chr = str.charCodeAt(i);
5 | hash = (hash << 5) - hash + chr;
6 | hash |= 0; // Convert to 32bit integer
7 | }
8 | return hash;
9 | }
--------------------------------------------------------------------------------
/src/core/utils/json.ts:
--------------------------------------------------------------------------------
1 | export const jsonMapReplacer = (key: string, value: any) => {
2 | if(value instanceof Map) {
3 | return {
4 | dataType: 'Map',
5 | value: Array.from(value.entries()), // or with spread: value: [...value]
6 | };
7 | } else {
8 | return value;
9 | }
10 | }
11 |
12 | export const jsonMapReviver = (key: string, value: any) => {
13 | if(typeof value === 'object' && value !== null) {
14 | if (value.dataType === 'Map') {
15 | return new Map(value.value);
16 | }
17 | }
18 | return value;
19 | }
--------------------------------------------------------------------------------
/src/core/utils/keyboard.ts:
--------------------------------------------------------------------------------
1 | export const normalizedModifier = (e: React.DragEvent) => e.metaKey || e.ctrlKey
2 | export const normalizedModifierName = () => window.navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl'
3 | export const normalizedAltName = () => window.navigator.platform.startsWith('Mac') ? '⌥' : 'Alt'
4 |
--------------------------------------------------------------------------------
/src/core/utils/metadata.ts:
--------------------------------------------------------------------------------
1 | import { Superstate, i18n } from "makemd-core"
2 | import { fieldTypeForField } from "schemas/mdb"
3 | import { Metadata, fileProperties, labelProperties, pathCacheMetadata } from "shared/types/metadata"
4 |
5 | export const allMetadata = (superstate: Superstate) : Record => ({
9 |
10 | file: {
11 | name: i18n.metadataTypes.fileMetadata,
12 | properties: fileProperties
13 | },
14 | path: {
15 | name: i18n.metadataTypes.outlinks,
16 | properties: pathCacheMetadata
17 | },
18 | label: {
19 | name: i18n.metadataTypes.label,
20 | properties: labelProperties
21 | },
22 | frontmatter: {
23 | name: i18n.metadataTypes.frontmatter,
24 | properties: superstate.spaceManager.keysForCacheType("frontmatter").map(f => ({
25 | id: 'frontmatter.' + f,
26 | label: f,
27 | field: f,
28 | vType: 'any',
29 | defaultFilter: 'contains',
30 | type: 'frontmatter',
31 | description: "Frontmatter property"
32 | }))
33 | },
34 | context: {
35 | name: i18n.metadataTypes.contexts,
36 | properties: [...superstate.contextsIndex.values()].flatMap(f => f?.contextTable?.cols.filter(f => f.primary != "true").map(g => ({
37 | id: 'contexts.' + f.path + '.' + g.name,
38 | label: g.name,
39 | field: f.path+ '.' + g.name,
40 | vType: fieldTypeForField(g),
41 | defaultFilter: 'contains',
42 | type: 'context',
43 | description: f.path + " context property"
44 | })))
45 | }
46 | })
--------------------------------------------------------------------------------
/src/core/utils/parser.tsx:
--------------------------------------------------------------------------------
1 | import { SpaceSort } from "shared/types/spaceDef";
2 | import { parseLinkString } from "utils/parsers";
3 | import { pathToString } from "utils/path";
4 |
5 | //named parsers for converting strings to values
6 |
7 | export const parseSortStrat = (str: string): SpaceSort => {
8 | const [a, b] = str.split("_");
9 | return { field: a, asc: b == "asc", group: true, recursive: true };
10 | };
11 |
12 | export const parseLinkDisplayString = (string: string) => {
13 | return pathToString(parseLinkString(string));
14 | };
15 |
--------------------------------------------------------------------------------
/src/core/utils/properties/allProperties.ts:
--------------------------------------------------------------------------------
1 | import { Superstate } from "makemd-core";
2 | import { detectPropertyType } from "utils/properties";
3 |
4 | export type PropertyType = {
5 | name: string;
6 | type: string;
7 | };
8 |
9 |
10 | export const allPropertiesForPaths = (
11 | superstate: Superstate,
12 | paths: string[]
13 | ): PropertyType[] => {
14 | const properties: { [key: string]: string[] } = {};
15 |
16 | for (const path of paths) {
17 | const f = superstate.pathsIndex.get(path)?.metadata?.property;
18 | if (f) {
19 | Object.keys(f).forEach((k) => {
20 | properties[k] = [...(properties[k] ?? []), detectPropertyType(f[k], k)];
21 | });
22 | }
23 | }
24 | return Object.keys(properties).reduce((p, c) => {
25 | return [...p, { name: c, type: properties[c][0] }];
26 | }, [] as PropertyType[]);
27 | };
28 |
29 | const metadatTypeFilterPredicate = (value: any, index: number, self: any[]) => {
30 | return (
31 | self.findIndex(
32 | (v) => value["type"] == v["type"] && value["name"] == v["name"]
33 | ) === index
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/core/utils/serializer.ts:
--------------------------------------------------------------------------------
1 | import { SelectOption } from "makemd-core";
2 | import { ContextDef } from "shared/types/context";
3 |
4 | //named serializers for converting values to string
5 |
6 | export const serializeDefString = (def: ContextDef[]) => JSON.stringify(def);
7 |
8 | export const serializeOptionValue = (
9 | newOptions: SelectOption[],
10 | value: Record
11 | ) => {
12 | return JSON.stringify({
13 | ...value,
14 | options: newOptions.map((f) => ({
15 | name: f.name,
16 | value: f.value,
17 | color: f.color,
18 | })),
19 | });
20 | };
--------------------------------------------------------------------------------
/src/core/utils/superstate/parser.ts:
--------------------------------------------------------------------------------
1 | import { PathState } from "shared/types/PathState";
2 | import { safelyParseJSON } from "shared/utils/json";
3 |
4 | export const parsePathState = (cache: string): PathState => {
5 | return safelyParseJSON(cache);
6 | }
--------------------------------------------------------------------------------
/src/core/utils/superstate/serializer.ts:
--------------------------------------------------------------------------------
1 | import { PathState, WorkerJobType } from "shared/types/PathState";
2 |
3 | export const serializePathState = (pathState: PathState) => {
4 | return JSON.stringify(pathState);
5 | }
6 |
7 | export const stringifyJob = (job: WorkerJobType) => `${job.type}:${job.path}`
--------------------------------------------------------------------------------
/src/core/utils/tree.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export const nodeIsAncestorOfTarget = (path: string, target: string) => {
5 |
6 | return target.startsWith(path);
7 | return false;
8 |
9 | };
10 |
11 | // Helper Function to Create Folder Tree
12 |
13 |
14 | export const compareByFieldDeep =
15 | (field: (obj: Record) => string, dir: boolean) =>
16 | (_a: Record, _b: Record) => {
17 | const a = dir ? _a : _b;
18 | const b = dir ? _b : _a;
19 |
20 | if (field(a) < field(b)) {
21 | return -1;
22 | }
23 | if (field(a) > field(b)) {
24 | return 1;
25 | }
26 | return 0;
27 | };
28 |
29 |
30 | export const compareByField =
31 | (field: string, dir: boolean) =>
32 | (_a: Record, _b: Record) => {
33 | const a = dir ? _a : _b;
34 | const b = dir ? _b : _a;
35 | if (a[field] < b[field]) {
36 | return -1;
37 | }
38 | if (a[field] > b[field]) {
39 | return 1;
40 | }
41 | return 0;
42 | };
43 |
44 | export const compareByFieldCaseInsensitive =
45 | (field: string, dir: boolean) =>
46 | (_a: Record, _b: Record) => {
47 | const a = dir ? _a : _b;
48 | const b = dir ? _b : _a;
49 | return a[field]?.toLowerCase().localeCompare(b[field]?.toLowerCase(), undefined, {
50 | numeric: true,
51 | })
52 | };
53 |
54 |
55 | export const compareByFieldNumerical = (field: string, dir: boolean) => (_a: Record, _b: Record) => {
56 | const a = dir ? _a : _b;
57 | const b = dir ? _b : _a;
58 | return (+a[field]) - (+b[field]);
59 | }
--------------------------------------------------------------------------------
/src/core/utils/ui/menu.ts:
--------------------------------------------------------------------------------
1 | import { Anchors, Rect, Size } from "shared/types/Pos";
2 |
3 | export const calculateBoundsBasedOnPosition = (
4 | targetRect: Rect,
5 | rect: DOMRect,
6 | bounds: Size,
7 | anchor: Anchors
8 | ) => {
9 | const x = anchor === "bottom" ? targetRect.x : targetRect.x + targetRect.width
10 | const y = anchor === "top" ? targetRect.y - rect.height - 10 : anchor == 'right' ? targetRect.y : targetRect.y + targetRect.height + 10;
11 | const overflowX = x+ rect.width - bounds.width;
12 | const overflowY = (y + rect.height) - bounds.height;
13 | let newY = y;
14 | let newX = x;
15 | if (overflowX > 0) {
16 | if (targetRect.x - rect.width < 0) newX = targetRect.x - overflowX;
17 | else {
18 | newX = targetRect.x - rect.width;
19 | }
20 | }
21 | if (overflowY > 0) {
22 | if (targetRect.y - rect.height < 0) newY = targetRect.y - overflowY;
23 | else {
24 | newY = targetRect.y - rect.height - 10;
25 | }
26 | }
27 | return {
28 | x: newX,
29 | y: newY,
30 | width: rect.width,
31 | height: rect.height,
32 | };
33 | };
--------------------------------------------------------------------------------
/src/core/utils/ui/screen.ts:
--------------------------------------------------------------------------------
1 | import { UIManager } from "core/middleware/ui";
2 | import { InteractionType, ScreenType } from "shared/types/ui";
3 |
4 | export const isTouchScreen = (ui: UIManager) => (ui.primaryInteractionType() == InteractionType.Touch)
5 | export const isPhone = (ui: UIManager) => (ui.getScreenType() == ScreenType.Phone)
--------------------------------------------------------------------------------
/src/core/utils/ui/selection.ts:
--------------------------------------------------------------------------------
1 | export const selectNextIndex = (currIndex: string, array: string[]) => {
2 | if (!currIndex && array.length > 0) return array[0];
3 | const pos = array.indexOf(currIndex);
4 | if (pos < array.length - 1) return array[pos + 1];
5 | return currIndex;
6 | };
7 | export const selectPrevIndex = (currIndex: string, array: string[]) => {
8 | const pos = array.indexOf(currIndex);
9 | if (pos > 0) return array[pos - 1];
10 | return array[0];
11 | };
12 |
13 | export const selectRange = (
14 | currSel: string,
15 | newSel: string,
16 | array: string[]
17 | ) => {
18 | const lastIndex = array.findIndex((f) => f == currSel);
19 | const newIndex = array.findIndex((f) => f == newSel);
20 | if (lastIndex < newIndex) {
21 | return array.filter((f, i) => i > lastIndex && i <= newIndex);
22 | }
23 | return array.filter((f, i) => i < lastIndex && i >= newIndex);
24 | };
25 |
--------------------------------------------------------------------------------
/src/css/.space/views.mdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Make-md/makemd/1d6ba937c5c0fef6794a4612acc5af72c61cf450/src/css/.space/views.mdb
--------------------------------------------------------------------------------
/src/css/Editor/Context/ContextList.css:
--------------------------------------------------------------------------------
1 |
2 | .mk-editor-context-selector {
3 | font-size: 14px;
4 | display: flex;
5 | padding: 8px;
6 | gap: 8px;
7 | border-top: thin solid var(--mk-ui-divider);
8 | flex-direction: column;
9 | }
10 | .mk-editor-context {
11 | overflow-x: scroll;
12 | }
13 | .mk-editor-context-groups {
14 | display: flex;
15 | --icon-size: 14px;
16 | padding: 0;
17 | z-index: var(--layer-popover);
18 | background: var(--mk-ui-background);
19 | max-height: unset;
20 | -webkit-app-region: no-drag;
21 | padding: 6px 10px;
22 | border: 1px solid var(--mk-ui-border);
23 | background-color: var(--mk-ui-background-menu);
24 | user-select: none;
25 | border-radius: 8px;
26 | align-items: center;
27 | height: 50px;
28 | white-space: nowrap;
29 | gap: 8px;
30 | }
31 |
32 | .mk-editor-context-group-select {
33 | background: rgba(var(--mono-rgb-100), 0.025);
34 | border-radius: 4px;
35 | overflow: hidden;
36 | display: flex;
37 | align-items: center;
38 | gap: 2px;
39 | white-space: nowrap;
40 | }
41 |
42 | .mk-editor-context-group .mk-path-context-field {
43 | width: auto;
44 | max-width: unset;
45 | min-width: auto;
46 | background: white;
47 | padding: 2px 8px;
48 | gap: 2px;
49 | }
50 | .mk-editor-context-groups span {
51 | flex: 1;
52 | }
53 |
54 | .mk-editor-context-group {
55 | display: flex;
56 | gap: 8px;
57 | align-items: center;
58 | padding: 0 8px;
59 | border: thin solid var(--mk-ui-divider);
60 | border-radius: 4px;
61 | height: 32px;
62 | background: var(--mk-ui-background);
63 |
64 | }
65 | .mk-editor-context-properties {
66 | display: flex;
67 | flex-direction: column;
68 | padding: 8px;
69 | background: var(--mk-ui-background-contrast);
70 | border-radius: 4px;
71 |
72 | }
73 | .mk-editor-context-properties > div {
74 | gap: 4px;
75 | display: flex;
76 | flex-wrap: wrap;
77 | }
--------------------------------------------------------------------------------
/src/css/Editor/Flow/FlowState.css:
--------------------------------------------------------------------------------
1 |
2 | .is-phone.mk-flow-state .workspace-split {
3 | padding-top: 0;
4 | }
5 | .mk-flow-state
6 | .workspace-tab-header-container-inner {
7 | display: none;
8 | }
9 | .mk-flow-state .workspace-ribbon {
10 | display: none;
11 | }
12 | body:not(.is-mobile).mk-flow-state .workspace-split.mod-left-split .workspace-sidedock-vault-profile {
13 | transform: translateY(-100%);
14 | transition: transform 300ms linear;
15 | }
16 | body.mk-flow-state {
17 | --tab-container-background: var(--background-primary) !important;
18 | --titlebar-background-focused: var(--background-primary) !important;
19 |
20 | }
21 |
22 | .mk-flow-state .workspace-tabs .workspace-leaf {
23 | background: var(--background-primary) !important;
24 | }
25 |
26 | .is-phone.mk-flow-state .view-header {
27 | /* display: none; */
28 | }
29 |
30 | .mk-flow-state .view-header {
31 | transform: translateY(-100%);
32 | max-height: 0;
33 | transition: transform 300ms linear;
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/src/css/Editor/Frames/Insert.css:
--------------------------------------------------------------------------------
1 |
2 | .mk-frame-insert {
3 | width: 100%;
4 | bottom: 0px;
5 | display: flex;
6 | position: fixed;
7 | height: 30px;
8 | border-radius: 4px;
9 | margin-top: 4px;
10 | margin-left: 4px;
11 | --max-width: var(--file-line-width);
12 | max-width: calc(min(100%, var(--max-width))) !important;
13 | margin-left: calc((max(100%, var(--max-width)) - var(--max-width)) /2) !important;
14 |
15 | }
16 |
17 | .mk-frame-insert:hover {
18 | background: var(--mk-ui-background-hover) !important;
19 | }
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/css/Editor/Frames/Slides.css:
--------------------------------------------------------------------------------
1 | .mk-frame-slides {
2 | display: flex
3 | }
4 | .mk-frame-slide, .mk-frame-slide-active {
5 | display: flex;
6 | flex-direction: column
7 | }
8 | .mk-frame-slide span:first-child {
9 | border-left: thin solid var(--mk-ui-divider);
10 | font-size: 8px;
11 | }
12 | .mk-frame-slide-active span:first-child {
13 | border-left: thin solid var(--mk-ui-divider);
14 | font-size: 8px;
15 | }
16 | .mk-frame-slide span:last-child {
17 | width: 20px;
18 | height: 20px;
19 | background: var(--background-primary-alt)
20 | }
21 |
22 | .mk-frame-slide-active span:last-child {
23 | width: 20px;
24 | height: 20px;
25 | background: var(--background-secondary)
26 | }
--------------------------------------------------------------------------------
/src/css/Menus/ColorPicker.css:
--------------------------------------------------------------------------------
1 |
2 | .mk-ui-color-picker {
3 | display: flex;
4 | flex-direction: column;
5 | gap: 4px;
6 | }
7 | .mk-ui-color-picker-selector {
8 | border-bottom: thin solid var(--mk-ui-divider);
9 | }
10 |
11 | .mk-ui-color-picker-palette {
12 | padding: 12px;
13 | display: flex;
14 | flex-direction: column;
15 | }
16 | .mk-ui-color-picker .mk-color {
17 | margin: 4px;
18 | }
19 | .mk-ui-color-picker-palette > div {
20 | display: flex;
21 | flex-direction: row;
22 | gap: 4px;
23 | }
--------------------------------------------------------------------------------
/src/css/Menus/MakeMenu.css:
--------------------------------------------------------------------------------
1 | .mk-slash-item {
2 | display: flex;
3 | align-items: center;
4 | }
5 | .mk-slash-icon {
6 | display: flex;
7 | margin-right: 8px;
8 | }
9 | .mk-slash-icon svg {
10 | width: 16px;
11 | height: 16px;
12 | }
13 | .cm-focused .cm-active.mk-placeholder:before {
14 | content: attr(data-ph);
15 | color: var(--mk-ui-text-tertiary);
16 | position: absolute;
17 | }
18 |
19 | .mk-floweditor .cm-active.mk-placeholder:before {
20 | content: attr(data-ph);
21 | color: var(--mk-ui-text-tertiary);
22 | position: absolute;
23 | }
--------------------------------------------------------------------------------
/src/css/Menus/StickerMenu.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: emoji;
3 |
4 | src: local("Apple Color Emoji"), local("Android Emoji"), local("Segoe UI"),
5 | local(EmojiSymbols), local(Symbola);
6 | /* Emoji unicode blocks */
7 | unicode-range: U+1F300-1F5FF, U+1F600-1F64F, U+1F680-1F6FF, U+2600-26FF;
8 | }
9 |
10 | .mk-sticker-menu .suggestion {
11 | width: 240px;
12 | height: 240px;
13 | display: flex;
14 | flex-wrap: wrap;
15 | align-content: flex-start;
16 | flex-direction: row;
17 |
18 | }
19 | .mk-sticker-modal {
20 | display: flex;
21 | flex-wrap: wrap;
22 | }
23 | .mk-sticker-modal .suggestion-item {
24 | width: 30px;
25 | height: 30px;
26 | display: flex;
27 | font-size: 20px;
28 | gap: 4px;
29 | align-items: center;
30 | padding: 0;
31 | text-align: center;
32 | justify-content: center;
33 | font-family: emoji;
34 | }
35 | .mk-sticker-filter {
36 | border: none;
37 | background: none;
38 | border-bottom: thin solid var(--mk-ui-border);
39 | width: 100%;
40 | padding: 8px 12px;
41 | }
42 | .mk-sticker-menu .suggestion-item:hover {
43 | background: var(--mk-ui-background-hover);
44 | }
45 | .mk-image-modal {
46 | display: flex;
47 | flex-wrap: wrap;
48 | }
49 |
50 | .mk-sticker {
51 | display: flex;
52 | height: var(--icon-size);
53 | align-items: center;
54 | }
55 | .mk-sticker svg {
56 | height: var(--icon-size);
57 | width: var(--icon-size);
58 | stroke-width: var(--icon-stroke);
59 | }
--------------------------------------------------------------------------------
/src/css/Obsidian/Mods.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .mobile-toolbar-options-container {
4 | border-top: 1px solid var(--mk-ui-divider);
5 | }
6 |
--------------------------------------------------------------------------------
/src/css/Panels/ContextBuilder.css:
--------------------------------------------------------------------------------
1 |
2 | .mk-property-editor-context-title {
3 | display: flex;
4 | font-size: 13px;
5 | font-weight: var(--font-normal);
6 | padding: 8px 4px
7 | }
8 | .mk-property-editor-context-title span {
9 | flex: 1;
10 | }
11 | .mk-property-editor-context-tag {
12 | display: flex;
13 | gap: 4px;
14 | align-items: center;
15 | }
16 | .mk-property-editor {
17 | padding: 0;
18 | display: flex;
19 | flex-direction: column;
20 | height: 100%;
21 | gap: 8px;
22 | }
23 | .mk-property-editor-property {
24 | display: flex;
25 | padding: 8px;
26 | font-size: 13px;
27 | color: var(--mk-ui-text-secondary);
28 | gap: 6px;
29 | align-items: center;
30 | background: var(--background-modifier-cover);
31 | border-radius: 6px;
32 | }
33 | .mk-property-editor-new:hover {
34 | color: var(--mk-ui-text-primary);
35 | }
36 | .mk-property-editor-new span {
37 | font-size: 12px
38 | }
39 | .mk-property-editor-new {
40 | color: var(--mk-ui-text-tertiary);
41 | display: flex;
42 | flex-direction:column;
43 | padding: 8px 8px;
44 | border: thin solid var(--mk-ui-divider);
45 | border-radius: 8px;
46 | }
47 |
48 |
49 | .mk-property-editor-context-title {
50 | font-size: 15px;
51 | font-weight: var(--font-semibold);
52 | line-height: var(--line-height-tight);
53 | padding:8px 4px;
54 | display: flex;
55 | align-items: center;
56 | gap: 8px;
57 | }
58 | .mk-property-editor-list {
59 | display: flex;
60 | flex-direction: column;
61 | gap: 4px;
62 | }
63 |
64 | .mk-property-editor-context-title span, .mk-property-editor-list span {
65 | font-size: 12px;
66 | color: var(--mk-ui-text-tertiary);
67 | flex: 1;
68 | }
69 |
--------------------------------------------------------------------------------
/src/css/Panels/Navigator/EverView.css:
--------------------------------------------------------------------------------
1 | .mk-ever-view {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100%;
5 | }
6 | .mk-ever-view-header {
7 | display: flex;
8 |
9 | padding: 8px 12px;
10 | gap: 4px;
11 | }
12 | .mk-ever-view-header-title {
13 | display: flex;
14 | flex-direction: column;
15 | flex: 1;
16 | }
17 | .mk-ever-view-title {
18 | font-size: 16px;
19 | font-weight: 500;
20 | color: var(--mk-ui-text-primary);
21 | }
22 |
23 | .mk-ever-view-filters {
24 | padding: 4px 8px;
25 | }
26 |
27 | .mk-ever-view-contents {
28 | flex: 1;
29 | overflow: auto;
30 | }
31 |
32 | .mk-ever-view-count {
33 | font-size: 12px;
34 | color: var(--mk-ui-text-secondary);
35 | }
36 | .mk-ever-view-filter {
37 | padding: 8px;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/css/SpaceViewer/Frame.css:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | .mk-f-edit {
9 | z-index: 28;
10 | /* transition: all 0.2s ease; */
11 | outline: thin solid var(--mk-ui-border-accent);
12 | }
13 |
14 | .mk-f-editable:not(.mk-f-edit):hover {
15 | z-index: 28;
16 | outline: 2px solid var(--mk-ui-border-accent);
17 | outline-offset: -2px;
18 | }
19 |
20 |
21 |
22 | .mk-f-disabled {
23 | opacity: 0.5;
24 |
25 | }
26 |
27 |
28 | .mk-frame-edit[data-type="column"] {
29 | align-items: flex-start;
30 | }
31 | .mk-frame-edit[data-type="new"] {
32 | min-width: 50px;
33 | }
34 |
35 | .mk-frame-edit.mk-selected {
36 | background: var(--mk-ui-background-active);
37 | border-radius: 4px;
38 | }
39 |
40 |
41 |
42 | .mk-frame-edit.mk-layout-row {
43 | overflow: clip;
44 | overflow-clip-margin: 28px;
45 | }
46 |
47 | .mk-frame,.mk-frame-edit {
48 | --line-count: 1;
49 | /* transition: all 0.2s ease; */
50 | }
51 |
52 | .mk-f-root-label {
53 | position: absolute;
54 | padding: 4px;
55 | border-radius: 4px;
56 | font-size: 13px;
57 | z-index: 29;
58 | top: -22px;
59 | background: var(--mk-background-blur);
60 | display: none;
61 | }
62 | .mk-f-root:hover > .mk-f-root-label {
63 | display: flex;
64 | }
65 |
66 | .mk-frame[data-type="icon"] {
67 | flex-grow: 0;
68 | flex-shrink: 0;
69 | }
70 |
71 | .mk-frame[data-type="image"] {
72 | display: flex;
73 | }
74 |
75 | .mk-frame[data-type="listItem"]:empty, .mk-frame[data-type="frame"]:empty, .mk-frame[data-type="icon"]:empty, .mk-frame[data-type="text"]:empty, .mk-frame[data-type="image"]:empty,
76 | .mk-frame-edit[data-type="icon"]:empty, .mk-frame-edit[data-type="text"]:empty, .mk-frame-edit[data-type="image"]:empty {
77 | display: none;
78 | }
79 |
80 | .mk-frame.mk-icon-size-s {
81 | --icon-size: 18px
82 | }
83 | .mk-frame.mk-icon-size-m {
84 | --icon-size: 24px
85 | }
86 | .mk-frame.mk-icon-size-l {
87 | --icon-size: 48px
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/css/SpaceViewer/Text.css:
--------------------------------------------------------------------------------
1 | .mk-t-h1 {
2 | --font-text-size: var(--h1-size);
3 | --text-normal: var(--h1-color);
4 | --font-weight: var(--h1-weight);
5 | }
6 |
7 | .mk-t-h2 {
8 | --font-text-size: var(--h2-size);
9 | --text-normal: var(--h2-color);
10 | --font-weight: var(--h2-weight);
11 | }
12 |
13 | .mk-t-h3 {
14 | --font-text-size: var(--h3-size);
15 | --text-normal: var(--h3-color);
16 | --font-weight: var(--h3-weight);
17 | }
18 |
19 | .mk-t-h4 {
20 | --font-text-size: var(--h4-size);
21 | --text-normal: var(--h4-color);
22 | --font-weight: var(--h4-weight);
23 | }
24 |
25 | .mk-t-h5 {
26 | --font-text-size: var(--h5-size);
27 | --text-normal: var(--h5-color);
28 | --font-weight: var(--h5-weight);
29 | }
30 |
31 | .mk-t-p {
32 | }
--------------------------------------------------------------------------------
/src/css/UI/Buttons.css:
--------------------------------------------------------------------------------
1 | .is-phone .mk-inline-button {
2 | width: unset !important;
3 | }
4 |
5 | .is-tablet .mk-inline-button {
6 | padding: unset !important;
7 | }
8 |
9 | .mk-inline-button {
10 | display: inline-flex;
11 | align-items: center;
12 | justify-content: center;
13 | padding: 4px;
14 | border-radius: var(--clickable-icon-radius);
15 | box-shadow: unset !important;
16 | }
17 |
18 | .mk-inline-button:not(:hover) {
19 | background: unset !important;
20 | }
21 |
22 | .mk-inline-button {
23 | font-size: 13px;
24 |
25 | display: flex;
26 | align-items: center;
27 | gap: 10px;
28 | width: auto !important;
29 | }
30 | .mk-inline-button {
31 | background: none !important;
32 | box-shadow: none !important;
33 | color: var(--mk-ui-text-tertiary) !important;
34 | padding: 0 4px !important;
35 | display: flex;
36 | gap: 4px;
37 | height: 20px;
38 | border: none !important;
39 | }
40 |
41 | .mk-inline-button svg {
42 | color: var(--mk-ui-text-tertiary) !important;
43 | }
44 | body:not(.is-mobile) .mk-inline-button:hover svg,
45 | body:not(.is-mobile) .mk-inline-button:hover {
46 | color: var(--mk-ui-text-primary) !important;
47 | }
48 |
49 |
50 |
51 | .mk-inline-button {
52 | background: none;
53 | border: 0;
54 | box-shadow: none;
55 | margin: 0;
56 | height: 24px;
57 | width: 24px;
58 | padding: 0 !important;
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/makemd-core.ts:
--------------------------------------------------------------------------------
1 | //i18n
2 | export { default as i18n } from "shared/i18n";
3 |
4 | //Superstate
5 | export { SpaceManager } from "core/spaceManager/spaceManager";
6 | export type { IAPI as API } from "shared/types/api";
7 | export { ISuperstate as Superstate } from "shared/types/superstate";
8 |
9 | //Filesystem
10 | export { FileSystemAdapter, FilesystemMiddleware } from "core/middleware/filesystem";
11 | export type { FileCache } from "core/middleware/filesystem";
12 | export { FileTypeAdapter } from "core/middleware/filetypes";
13 | export type { FileTypeCache } from "core/middleware/filetypes";
14 | export { FilesystemSpaceAdapter } from "core/spaceManager/filesystemAdapter/filesystemAdapter";
15 | export type { AFile } from "shared/types/afile";
16 | export type { PathLabel } from "shared/types/caches";
17 |
18 | //UI
19 | export { UIManager } from "core/middleware/ui";
20 | export { default as SelectMenu } from "core/react/components/UI/Menus/menu/SelectMenu";
21 | export { SelectOptionType } from "shared/types/menu";
22 | export type { SelectMenuProps, SelectOption, SelectSection } from "shared/types/menu";
23 | export type { Sticker } from "shared/types/ui";
24 | export type { UIAdapter } from "shared/types/uiManager";
25 |
26 | //Views
27 | export { Explorer as FileContextView } from "core/react/components/Explorer/Explorer";
28 | export { Backlinks } from "core/react/components/MarkdownEditor/Backlinks";
29 | export { MarkdownHeaderView } from "core/react/components/MarkdownEditor/MarkdownHeaderView";
30 | export { MDBViewer } from "core/react/components/MDBView/MDBViewer";
31 | export { Navigator } from "core/react/components/Navigator/Navigator";
32 | export { NoteView } from "core/react/components/PathView/NoteView";
33 | export { SpaceView } from "core/react/components/SpaceView/Contexts/SpaceView";
34 | export { SpaceFragmentViewComponent } from "core/react/components/SpaceView/Editor/EmbedView/SpaceFragmentView";
35 | export { SpaceFragmentWrapper } from "core/react/components/SpaceView/Editor/EmbedView/SpaceFragmentWrapper";
36 |
37 |
--------------------------------------------------------------------------------
/src/schemas/cache.ts:
--------------------------------------------------------------------------------
1 | import { DBTable } from "shared/types/mdb";
2 |
3 | export const CacheDBSchema : DBTable = {uniques: ['path'], cols: ["path", "cache", 'version'], rows: []}
--------------------------------------------------------------------------------
/src/schemas/kits/slides.ts:
--------------------------------------------------------------------------------
1 | import { FrameRoot } from "shared/types/mframe";
2 |
3 |
4 | export const slidesNode: FrameRoot = {
5 | def: {
6 | icon: "ui//gem"
7 | }, node: {
8 | icon: 'ui//gem',
9 | schemaId: "slides",
10 | parentId: "",
11 | name: "Slides",
12 | rank: 0,
13 | id: "slides",
14 | styles: {},
15 | type: "slides",
16 |
17 | props: {
18 | value: ''
19 | },
20 | types: {
21 | value: 'string'
22 | },
23 | }
24 | };
25 |
26 | export const slideNode: FrameRoot = {
27 | def: {
28 | icon: "ui//gem"
29 | }, node: {
30 | icon: 'ui//gem',
31 | schemaId: "slide",
32 | parentId: "",
33 | name: "Slide",
34 | rank: 0,
35 | id: "slide",
36 | styles: {},
37 | type: "slide",
38 |
39 | props: {
40 | value: "",
41 | },
42 | types: {
43 | value: "string",
44 | },
45 | }
46 | };
47 |
48 | export const deltaNode: FrameRoot = {
49 | def: {
50 | icon: "ui//gem"
51 | }, node: {
52 | icon: 'ui//gem',
53 | schemaId: "delta",
54 | parentId: "",
55 | name: "Delta",
56 | rank: 0,
57 | id: "delta",
58 | styles: {},
59 | type: "delta",
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/src/shared/schemas/builtin.ts:
--------------------------------------------------------------------------------
1 | export const builtinSpacePathPrefix = "spaces://$";
2 | export const tagsSpacePath = "spaces://$tags";
3 |
--------------------------------------------------------------------------------
/src/shared/schemas/context.ts:
--------------------------------------------------------------------------------
1 | import { SpaceTableSchema } from "shared/types/mdb";
2 |
3 | export const defaultContextSchemaID = "files";
4 | export const defaultContextDBSchema: SpaceTableSchema = {
5 | id: defaultContextSchemaID,
6 | name: "Items",
7 | type: "db",
8 | primary: "true",
9 | };
10 |
--------------------------------------------------------------------------------
/src/shared/schemas/fields.ts:
--------------------------------------------------------------------------------
1 | import { defaultContextSchemaID } from "shared/schemas/context";
2 | import { PathPropertyName } from "shared/types/context";
3 | import { DBTable, SpaceProperty } from "shared/types/mdb";
4 | import { SpaceInfo } from "shared/types/spaceInfo";
5 |
6 |
7 |
8 | export const fieldSchema = {
9 | uniques: ["name,schemaId"],
10 | cols: [
11 | "name",
12 | "schemaId",
13 | "type",
14 | "value",
15 | "attrs",
16 | "hidden",
17 | "unique",
18 | "primary",
19 | ],
20 | };
21 |
22 | export const defaultContextFields: DBTable = {
23 | ...fieldSchema,
24 | rows: [
25 | {
26 | name: PathPropertyName,
27 | schemaId: defaultContextSchemaID,
28 | type: "file",
29 | primary: "true",
30 | hidden: "",
31 | unique: "",
32 | attrs: "",
33 | value: "",
34 | },
35 | {
36 | name: "Created",
37 | schemaId: defaultContextSchemaID,
38 | type: "fileprop",
39 | value: PathPropertyName + ".ctime",
40 | hidden: "",
41 | unique: "",
42 | attrs: "",
43 | primary: "true",
44 | },
45 | ] as SpaceProperty[],
46 | };
47 |
48 | export const defaultFieldsForContext = (space: SpaceInfo) => {
49 | return defaultContextFields;
50 | };
51 |
52 | export const defaultTableFields: SpaceProperty[] = [
53 | {
54 | name: "Name",
55 | schemaId: "",
56 | type: "text",
57 | primary: "true",
58 | },
59 | ];
60 |
61 | export const defaultTagFields: DBTable = {
62 | ...fieldSchema,
63 | rows: [
64 | {
65 | name: PathPropertyName,
66 | schemaId: defaultContextSchemaID,
67 | type: "file",
68 | primary: "true",
69 | hidden: "",
70 | unique: "",
71 | attrs: "",
72 | value: "",
73 | },
74 | ],
75 | };
76 |
--------------------------------------------------------------------------------
/src/shared/schemas/predicate.tsx:
--------------------------------------------------------------------------------
1 | import { Predicate } from "shared/types/predicate";
2 |
3 | export const defaultPredicate: Predicate = {
4 | view: "list",
5 | filters: [],
6 | listView: "",
7 | listItem: "",
8 | listGroup: "",
9 | listGroupProps: {},
10 | listViewProps: {},
11 | listItemProps: {},
12 | sort: [],
13 | groupBy: [],
14 | colsOrder: [],
15 | colsHidden: [],
16 | colsSize: {},
17 | colsCalc: {},
18 | };
19 |
20 | export const defaultTablePredicate: Predicate = {
21 | view: "table",
22 | filters: [],
23 | listView: "",
24 | listItem: "",
25 | listGroup: "",
26 | listGroupProps: {},
27 | listViewProps: {},
28 | listItemProps: {},
29 | sort: [],
30 | groupBy: [],
31 | colsOrder: [],
32 | colsHidden: [],
33 | colsSize: {},
34 | colsCalc: {},
35 | };
36 |
--------------------------------------------------------------------------------
/src/shared/types/Pos.ts:
--------------------------------------------------------------------------------
1 |
2 | export type Pos = { x: number; y: number; z?: number; };
3 | export type Size = { width: number; height: number; };
4 | export type Rect = { x: number; y: number; width: number; height: number; };
5 |
6 | export type Anchors = "top" | "bottom" | "left" | "right" | 'center'
7 | export type Edges = "top" | "bottom" | "left" | "right" | 'inside'
--------------------------------------------------------------------------------
/src/shared/types/Warning.ts:
--------------------------------------------------------------------------------
1 | export type Warning = {
2 | id: string;
3 | message: string;
4 | description: string;
5 | command: string;
6 | };
7 |
--------------------------------------------------------------------------------
/src/shared/types/actions.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Command, CommandWithPath } from "./commands";
3 | import { URI } from "./path";
4 | import { ISuperstate } from "./superstate";
5 |
6 |
7 |
8 | export interface CLIAdapter {
9 | manager: ICLIManager;
10 | scheme: string;
11 | commandForAction: (action: string) => Command;
12 | runCommand: (command: string, instance: ActionInstance) => Promise;
13 | allCommands: () => CommandWithPath[];
14 |
15 | }
16 |
17 | export interface ICLIManager {
18 | builtinCommands: Command[];
19 | mainTerminal: CLIAdapter;
20 | terminals: CLIAdapter[];
21 | superstate: ISuperstate;
22 | terminalForURI(uri: URI): CLIAdapter | null;
23 | commandForAction(action: string): Command | null;
24 | runCommand(action: string, instance: ActionInstance): Promise | void;
25 | allCommands(): CommandWithPath[];
26 | }
27 | export type ActionTree = {
28 | action: string;
29 | result?: string;
30 | linked?: { [key: string]: string };
31 | props: { [key: string]: any };
32 | propsValue: { [key: string]: any };
33 | children: ActionTree[];
34 | };
35 |
36 | export type ActionInstance = {
37 | props: { [key: string]: any };
38 | instanceProps: { [key: string]: any };
39 | result?: any;
40 | iterations: number;
41 | error?: any;
42 | };
43 |
--------------------------------------------------------------------------------
/src/shared/types/afile.ts:
--------------------------------------------------------------------------------
1 | import { PathLabel } from "./caches";
2 |
3 | export type AFile = {
4 | path: string;
5 | name: string;
6 | filename: string;
7 | parent: string;
8 | isFolder: boolean;
9 | extension?: string;
10 | ctime?: number;
11 | mtime?: number;
12 | size?: number;
13 | }
14 |
15 | export type VaultItem = {
16 | path: string;
17 | parent: string;
18 | created: string;
19 | folder: string;
20 | rank?: string;
21 | } & PathLabel;
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/shared/types/blink.tsx:
--------------------------------------------------------------------------------
1 | export enum BlinkMode {
2 | Search,
3 | Blink,
4 | Open,
5 | OpenSpaces,
6 | Image,
7 | Command,
8 | }
9 |
--------------------------------------------------------------------------------
/src/shared/types/caches.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export type PathCache = {
5 | [key: string]: any;
6 | metadata: Record;
7 | ctime: number;
8 | label: PathLabel;
9 | contentTypes: string[];
10 | tags: string[];
11 | type: string;
12 | subtype: string;
13 | parent: string;
14 | readOnly: boolean;
15 | };
16 |
17 |
18 | export type PathLabel = {
19 | name: string;
20 | sticker: string;
21 | color: string;
22 | cover?: string;
23 | thumbnail?: string;
24 | preview?: string;
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/src/shared/types/commands.ts:
--------------------------------------------------------------------------------
1 | import { SpaceProperty } from "./mdb";
2 |
3 | export type Library = {
4 | name: string;
5 | commands: Command[];
6 | }
7 |
8 | export type CommandSchema = {
9 | id: string;
10 | name: string;
11 | type: string;
12 | //used for type definition
13 | //used for view options including filter, order and group
14 | predicate?: string;
15 | primary?: string;
16 | def?: {
17 | icon?: string
18 | type?: string
19 | description?: string
20 | }
21 | }
22 |
23 | export type CommandWithPath = {
24 | scheme: string;
25 | path: string;
26 | } & Command;
27 |
28 | export type Command = {
29 | schema: CommandSchema;
30 |
31 | fields: SpaceProperty[];
32 | code?: any;
33 | codeType?: string;
34 | }
35 |
36 | export type CommandResult = {
37 | result: any;
38 | error: any;
39 | }
--------------------------------------------------------------------------------
/src/shared/types/context.ts:
--------------------------------------------------------------------------------
1 | export const PathPropertyName = "File"
2 | export const PathPropertyCreated = "Created"
3 |
4 | export type ContextDefType = 'tag'
5 | export type ContextDef = {
6 | type: ContextDefType,
7 | value: string
8 | }
9 |
10 | export type ContextLookup = {
11 | field: string;
12 | property: string;
13 | }
--------------------------------------------------------------------------------
/src/shared/types/emojis.ts:
--------------------------------------------------------------------------------
1 | export type EmojiData = Record<
2 | string,
3 | {
4 | n: [string, string];
5 | u: string;
6 | v?: string[];
7 | }[]
8 | >;
9 |
10 | export type Emoji = {
11 | label: string;
12 | desc: string;
13 | unicode: string;
14 | variants?: string[];
15 | };
16 |
--------------------------------------------------------------------------------
/src/shared/types/focus.ts:
--------------------------------------------------------------------------------
1 | export type Focus = {
2 | name: string;
3 | paths: string[];
4 | sticker: string;
5 | }
--------------------------------------------------------------------------------
/src/shared/types/kits.ts:
--------------------------------------------------------------------------------
1 | import { SpaceDefinition } from "shared/types/spaceDef";
2 | import { SpaceTables } from "./mdb";
3 | import { MDBFrame } from "./mframe";
4 |
5 | export type Kit = {
6 | id: string,
7 | name: string,
8 | colors: {[key: string]: string},
9 | frames: MDBFrame[],
10 | }
11 |
12 | export type Note = {
13 | name: string;
14 | properties: Record;
15 | content: string;
16 | }
17 | export type Assets = {
18 | name: string;
19 | path: string;
20 | payload?: string;
21 | type?: string
22 | }
23 |
24 | export type SpaceKit = {
25 | name: string;
26 | path: string;
27 | definition: SpaceDefinition;
28 | properties: Record;
29 | context: SpaceTables;
30 | frames: SpaceTables;
31 | children: SpaceKit[];
32 | notes: Note[];
33 | assets: Assets[];
34 | templates: TemplateKit[];
35 | content: string;
36 | }
37 |
38 | export type TemplateKit = {
39 | name: string;
40 | type: string;
41 | content: string | SpaceKit;
42 | }
--------------------------------------------------------------------------------
/src/shared/types/makemd.ts:
--------------------------------------------------------------------------------
1 | import { App, WorkspaceLeaf } from "obsidian";
2 | import { ISuperstate } from "shared/types/superstate";
3 |
4 | export interface IMakeMDPlugin {
5 | app: App;
6 | superstate: ISuperstate;
7 | openPath: (
8 | leaf: WorkspaceLeaf,
9 | path: string,
10 | flow?: boolean
11 | ) => Promise;
12 | }
--------------------------------------------------------------------------------
/src/shared/types/mdb.ts:
--------------------------------------------------------------------------------
1 |
2 | export type DBRow = Record;
3 | export type DBRows = DBRow[];
4 | export type DBTable = {
5 | uniques: string[];
6 | cols: string[];
7 | rows: DBRows;
8 | };
9 |
10 | export enum ContextSchemaType {
11 | SpaceType = 0,
12 | ContextType = 1,
13 | FrameType = 2,
14 | TableType = 3,
15 | CommandType = 4,
16 | }
17 |
18 | export type SpaceTableColumn = SpaceProperty & { table?: string };
19 |
20 | export type DBTables = Record;
21 |
22 | export type MDB = {
23 | schemas: SpaceTableSchema[];
24 | fields: SpaceProperty[];
25 | tables: {[key: string]: DBTable };
26 | };
27 |
28 | export type SpaceTable = {
29 | schema: SpaceTableSchema;
30 | cols: SpaceProperty[];
31 | rows: DBRows;
32 | };
33 | export type SpaceTables = Record;
34 | export type SpaceTableSchema = {
35 | id: string;
36 | name: string;
37 | type: string;
38 | //used for type definition
39 | def?: string;
40 | //used for view options including filter, order and group
41 | predicate?: string;
42 | primary?: string;
43 | };
44 |
45 | export type SpaceProperty = {
46 | name: string;
47 | //schema that the fields in
48 | schemaId?: string;
49 | type: string;
50 | //metadata for field
51 | value?: string;
52 | hidden?: string;
53 | //styling for field
54 | attrs?: string;
55 | //constraints at the db level
56 | unique?: string;
57 | primary?: string;
58 | };
59 |
--------------------------------------------------------------------------------
/src/shared/types/mframe.ts:
--------------------------------------------------------------------------------
1 | import { SpaceProperty, SpaceTableSchema } from "./mdb";
2 |
3 | export type MFrame = {
4 | id: string;
5 | schemaId: string;
6 | name: string;
7 | type: string;
8 | parentId?: string;
9 | props?: string;
10 | actions?: string;
11 | ref?: string;
12 | rank: string;
13 | styles?: string;
14 | contexts?: string;
15 | }
16 |
17 | export type FrameTreeProp = Record;
18 | export type FrameSchema = {
19 | id: string;
20 | name: string;
21 | type: string;
22 | //used for type definition
23 | //used for view options including filter, order and group
24 | predicate?: string;
25 | primary?: string;
26 | def?: {
27 | icon?: string
28 | context?: string
29 | db?: string
30 | type?: string
31 | id?: string
32 | }
33 | }
34 | export type FrameRoot = {
35 | id?: string;
36 | def: {[key: string]: string};
37 | children?: FrameRoot[];
38 | node: FrameNode;
39 | }
40 |
41 | export type FrameNode = {
42 | styles?: FrameTreeProp;
43 | name: string;
44 | //key value for a context and a path
45 | contexts?: FrameTreeProp;
46 | props?: FrameTreeProp;
47 | types?: FrameTreeProp;
48 | propsValue?: FrameTreeProp;
49 | propsAttrs?: FrameTreeProp;
50 | actions?: FrameTreeProp;
51 |
52 | parentId?: string;
53 | ref?: string;
54 | type: string;
55 | rank: number;
56 | schemaId: string;
57 | icon?: string;
58 | id: string;
59 | };
60 |
61 | export type MDBFrame = {
62 | schema: SpaceTableSchema;
63 | cols: SpaceProperty[];
64 | rows: MFrame[];
65 | };
66 |
67 | export type MDBFrames = { [key: string] : MDBFrame}
68 |
69 |
--------------------------------------------------------------------------------
/src/shared/types/path.ts:
--------------------------------------------------------------------------------
1 | import { SpaceFragmentType } from "./spaceFragment";
2 |
3 | export type PathRefTypes = SpaceFragmentType | 'block' | 'heading' | "unknown";
4 |
5 | export type URI = {
6 | basePath: string;
7 | scheme: string;
8 | path: string;
9 | authority: string;
10 |
11 | fullPath: string;
12 | alias?: string;
13 | ref?: string;
14 | refStr?: string;
15 | refType?: PathRefTypes;
16 | query?: {[key: string]: string}
17 | isRemote?: boolean;
18 | trailSlash: boolean;
19 | };export type TargetLocation = "split" | "overview" | "window" | "tab" | "left" | "right" | 'system' | 'hover' | boolean;
20 |
21 |
--------------------------------------------------------------------------------
/src/shared/types/persister.ts:
--------------------------------------------------------------------------------
1 | import { DBRow } from "shared/types/mdb";
2 |
3 |
4 | export abstract class LocalCachePersister {
5 | public abstract initialize(): Promise;
6 | public abstract isInitialized(): boolean;
7 | public abstract unload(): void;
8 | public abstract store(path: string, cache: string, type: string): Promise;
9 | public abstract reset(): void;
10 | public abstract remove(path: string, type: string): Promise;
11 | public abstract cleanType(type: string): void;
12 | public abstract loadAll(type: string): Promise;
13 | }
14 |
--------------------------------------------------------------------------------
/src/shared/types/predicate.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export type Filter = {
4 | field: string;
5 | fn: string;
6 | value: string;
7 | fType: string;
8 | };
9 |
10 | export type Predicate = {
11 | view: string;
12 |
13 | listView: string;
14 | listItem: string;
15 | listGroup: string;
16 | listViewProps: Record;
17 | listItemProps: Record;
18 | listGroupProps: Record;
19 | filters: Filter[];
20 | sort: Sort[];
21 | groupBy: string[];
22 |
23 | colsOrder: string[];
24 | colsHidden: string[];
25 | colsSize: Record;
26 | colsCalc: Record;
27 | };
28 |
29 | export type Sort = {
30 | field: string;
31 | fn: string;
32 | };
--------------------------------------------------------------------------------
/src/shared/types/spaceDef.ts:
--------------------------------------------------------------------------------
1 | import { Filter } from "./predicate";
2 |
3 | export type SpaceSort = {
4 | field: string;
5 | asc: boolean;
6 | group: boolean;
7 | recursive: boolean;
8 | };
9 |
10 | export type FilterDef = {
11 | type: string;
12 | fType: string;
13 | } & Filter;
14 | export type FilterGroupDef = {
15 | type: 'any' | 'all';
16 | trueFalse: boolean;
17 | filters: FilterDef[];
18 | };
19 | export type JoinDefGroup = {
20 | recursive: boolean;
21 | path: string;
22 | type: 'any' | 'all';
23 | groups: FilterGroupDef[];
24 | }
25 | export type SpaceType = 'folder' | 'tag' | 'vault' | 'default' | 'unknown';
26 |
27 |
28 | export type SpaceDefinition = {
29 | contexts?: string[];
30 | sort?: SpaceSort;
31 | joins?: JoinDefGroup[];
32 | links?: string[];
33 | tags?: string[];
34 | template?: string;
35 | templateName?: string;
36 | defaultSticker?: string;
37 | defaultColor?: string;
38 | readMode?: boolean;
39 | };
40 |
--------------------------------------------------------------------------------
/src/shared/types/spaceFragment.ts:
--------------------------------------------------------------------------------
1 |
2 | export type SpaceFragmentType = "context" | 'frame' | 'action';
3 |
4 | export type SpaceFragmentSchema = {
5 | id: string;
6 | name: string;
7 | sticker?: string;
8 | frameType?: string;
9 | type: SpaceFragmentType;
10 | path: string;
11 | };
12 |
13 |
--------------------------------------------------------------------------------
/src/shared/types/spaceInfo.ts:
--------------------------------------------------------------------------------
1 |
2 | export type FilesystemSpaceInfo = SpaceInfo & {
3 | folderPath: string;
4 | dbPath: string;
5 | framePath: string;
6 | commandsPath: string;
7 | };
8 |
9 | export type SpaceInfo = {
10 | name: string;
11 | path: string;
12 | isRemote: boolean;
13 | readOnly: boolean;
14 | defPath: string;
15 | notePath: string;
16 | };
17 |
--------------------------------------------------------------------------------
/src/shared/types/ui.ts:
--------------------------------------------------------------------------------
1 | export enum ScreenType { Phone, Desktop, Tablet }
2 | export enum InteractionType { Touch, Mouse, Controller, Voice }
3 | export type Sticker = {
4 | type: string;
5 | name: string;
6 | value: string;
7 | html: string;
8 | keywords: string;
9 | };
10 |
--------------------------------------------------------------------------------
/src/shared/utils/dom.ts:
--------------------------------------------------------------------------------
1 |
2 | export function selectElementContents(el: Element) {
3 | if (!el) return;
4 | const range = document.createRange();
5 | range.selectNodeContents(el);
6 | const sel = window.getSelection();
7 | sel.removeAllRanges();
8 | sel.addRange(range);
9 | }
10 |
11 | export const windowFromDocument = (doc: Document): Window => {
12 | return doc.defaultView || window;
13 | }
--------------------------------------------------------------------------------
/src/shared/utils/inputManager.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class InputManager {
4 | private events: { [key: string]: Function[] } = {};
5 | constructor() {
6 | this.addListeners();
7 | }
8 |
9 | on(eventName: string, listener: Function) {
10 | if (!this.events[eventName]) {
11 | this.events[eventName] = [];
12 | }
13 |
14 | this.events[eventName].push(listener);
15 | }
16 |
17 | off(eventName: string, listener: Function) {
18 | const listeners = this.events[eventName];
19 | if (listeners) {
20 | this.events[eventName] = listeners.filter(l => l !== listener);
21 | }
22 | }
23 |
24 | emit(eventName: string, data: any) {
25 | const listeners = this.events[eventName];
26 | if (listeners) {
27 | let propagate = false;
28 | listeners.slice().reverse().forEach(listener => {
29 | if (propagate) return;
30 | propagate = listener(data)
31 | });
32 | }
33 | }
34 |
35 | addListeners() {
36 | window.addEventListener('mousedown', this.handleMouseEvent, true);
37 | window.addEventListener('click', this.handleMouseEvent, true);
38 | window.addEventListener('contextmenu', this.handleMouseEvent, true);
39 | window.addEventListener('keydown', this.handleKeyEvent);
40 | window.addEventListener('keyup', this.handleKeyEvent);
41 | }
42 |
43 | removeListeners() {
44 | window.removeEventListener('mousedown', this.handleMouseEvent);
45 | window.removeEventListener('click', this.handleMouseEvent);
46 | window.removeEventListener('contextmenu', this.handleMouseEvent);
47 | window.removeEventListener('keydown', this.handleKeyEvent);
48 | window.removeEventListener('keyup', this.handleKeyEvent);
49 | }
50 | handleMouseEvent = (event: MouseEvent) => {
51 | this.emit(event.type, event);
52 | }
53 |
54 | handleKeyEvent = (event: KeyboardEvent) => {
55 | this.emit(event.type, event);
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/src/shared/utils/json.ts:
--------------------------------------------------------------------------------
1 |
2 | export const safelyParseJSON = (json: string) => {
3 | // This function cannot be optimised, it's best to
4 | // keep it small!
5 | let parsed;
6 | try {
7 | parsed = JSON.parse(json);
8 | } catch (e) {
9 | //
10 | // Oh well, but whatever...
11 | }
12 |
13 | return parsed; // Could be undefined!
14 | };
15 |
--------------------------------------------------------------------------------
/src/shared/utils/makemd/embed.ts:
--------------------------------------------------------------------------------
1 |
2 | import { SpaceState } from "shared/types/PathState";
3 |
4 | export const framePathForSpace = (space: SpaceState, schema: string) => {
5 | if (space.type == 'folder') {
6 | return `${space.path}/#*${schema}`
7 | }
8 | if (space.type == 'vault') {
9 | return `/#*${schema}`
10 | }
11 | return `${space.path}/#*${schema}`
12 | }
13 |
14 | export const actionPathForSpace = (space: SpaceState, schema: string) => {
15 | if (space.type == 'folder') {
16 | return `${space.path}/#;${schema}`
17 | }
18 | if (space.type == 'vault') {
19 | return `/#;${schema}`
20 | }
21 | return `${space.path}/#;${schema}`
22 | }
23 |
24 | export const contextPathForSpace = (space: SpaceState, schema: string) => {
25 | if (space.type == 'folder') {
26 | return `${space.path}/#^${schema}`
27 | }
28 | if (space.type == 'vault') {
29 | return `/#^${schema}`
30 | }
31 | return `${space.path}/#^${schema}`
32 | }
33 |
34 | export const contextViewEmbedStringFromContext = (space: SpaceState, schema: string) => `![![${framePathForSpace(space, schema)}]]`
35 |
36 | export const contextEmbedStringFromContext = (space: SpaceState, schema: string) => `![![${contextPathForSpace(space, schema)}]]`
37 |
38 |
--------------------------------------------------------------------------------
/src/shared/utils/makemd/fragment.ts:
--------------------------------------------------------------------------------
1 | import { mdbSchemaToFrameSchema } from "shared/utils/makemd/schema";
2 |
3 |
4 | import { SpaceFragmentSchema } from "shared/types/spaceFragment";
5 | import { ISuperstate } from "shared/types/superstate";
6 |
7 | export const uriToSpaceFragmentSchema = async (
8 | superstate: ISuperstate,
9 | path: string
10 | ): Promise => {
11 | const uri = superstate.spaceManager.uriByString(path);
12 | if (uri.refType == "context") {
13 | const schema = superstate.contextsIndex
14 | .get(uri.basePath)
15 | ?.schemas.find((s) => s.id == uri.ref);
16 | if (schema) {
17 | return {
18 | id: schema.id,
19 | name: schema.name,
20 | type: "context",
21 | path: uri.basePath,
22 | };
23 | }
24 | }
25 | if (uri.refType == "frame") {
26 | return superstate.spaceManager.readFrame(uri.basePath, uri.ref).then((s) => {
27 |
28 | const schema = s?.schema;
29 | if (schema) {
30 | const frameSchema = mdbSchemaToFrameSchema(schema);
31 | return {
32 | id: schema.id,
33 | name: frameSchema.name,
34 | sticker: frameSchema.def?.icon,
35 | type: "frame",
36 | frameType: frameSchema.type,
37 | path: uri.basePath,
38 | };
39 | }
40 | return null;
41 | });
42 | }
43 | if (uri.refType == "action") {
44 | const schema = superstate.actionsIndex
45 | .get(uri.path)
46 | ?.find((s) => s.schema.id == uri.ref)?.schema;
47 | if (schema) {
48 | return {
49 | id: schema.id,
50 | name: schema.name,
51 | sticker: schema.def?.icon,
52 | type: "action",
53 | path: uri.basePath,
54 | };
55 | }
56 | }
57 | return null;
58 | };
59 |
--------------------------------------------------------------------------------
/src/shared/utils/makemd/schema.ts:
--------------------------------------------------------------------------------
1 | import { SpaceTableSchema } from "shared/types/mdb";
2 | import { FrameSchema } from "shared/types/mframe";
3 | import { safelyParseJSON } from "shared/utils/json";
4 |
5 |
6 | export const frameSchemaToTableSchema = (frameSchema: FrameSchema) => {
7 | return {
8 | ...frameSchema,
9 | def: JSON.stringify(frameSchema.def)
10 | };
11 | };
12 | export const mdbSchemaToFrameSchema = (schema: SpaceTableSchema): FrameSchema => {
13 | if (!schema) return null;
14 | return {
15 | ...schema,
16 | def: safelyParseJSON(schema.def)
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/src/shared/utils/obsidian.ts:
--------------------------------------------------------------------------------
1 | import { App } from "obsidian";
2 |
3 |
4 |
5 |
6 | export const getLineRangeFromRef = (
7 | path: string,
8 | ref: string | undefined,
9 | app: App
10 | ): [number | undefined, number | undefined] => {
11 | if (!ref) {
12 | return [undefined, undefined];
13 | }
14 | const cache = app.metadataCache.getCache(path);
15 | if (!cache) return [undefined, undefined];
16 | const headings = cache.headings;
17 | const blocks = cache.blocks;
18 | const sections = cache.sections;
19 | if (blocks && ref.charAt(0) == "^" && blocks[ref.substring(1)]) {
20 | return [
21 | blocks[ref.substring(1)].position.start.line + 1,
22 | blocks[ref.substring(1)].position.end.line + 1,
23 | ];
24 | }
25 | const heading = headings?.find((f) => f.heading.replace("#", " ") == ref);
26 |
27 | if (heading) {
28 | const index = headings.findIndex((f) => f.heading == heading.heading);
29 | const level = headings[index]?.level;
30 | const nextIndex = headings.findIndex(
31 | (f, i) => i > index && f.level <= level
32 | );
33 |
34 | const start = heading.position.start.line + 2;
35 | if (index < headings.length - 1 && nextIndex != -1) {
36 | return [start, headings[nextIndex].position.end.line];
37 | }
38 | return [start, sections[sections.length - 1].position.end.line + 1];
39 | }
40 | return [undefined, undefined];
41 | };
42 |
--------------------------------------------------------------------------------
/src/shared/utils/openPathInElement.ts:
--------------------------------------------------------------------------------
1 | import { App, WorkspaceLeaf } from "obsidian";
2 | import { FlowEditor, FlowEditorParent } from "../FlowEditor";
3 |
4 |
5 |
6 |
7 | export const openPathInElement = (
8 | app: App,
9 | parentLeaf: WorkspaceLeaf,
10 | initiatingEl?: HTMLElement,
11 | fileName?: string,
12 | onShowCallback?: (leaf: FlowEditor) => Promise
13 | ) => {
14 | const parent = (parentLeaf ?? app.workspace.getLeaf()) as unknown as FlowEditorParent;
15 | if (!initiatingEl) initiatingEl = parent.containerEl;
16 | const hoverPopover = new FlowEditor(
17 | parent,
18 | initiatingEl!,
19 | app,
20 | undefined,
21 | onShowCallback
22 | );
23 |
24 | // plugin.attachPortal(hoverPopover);
25 | if (fileName)
26 | hoverPopover.titleEl.textContent = fileName.substring(
27 | 0,
28 | fileName.lastIndexOf(".")
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/shared/utils/paths.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | export const removeTrailingSlashFromFolder = (path: string) => path == "/"
6 | ? path
7 | : path.slice(-1) == "/"
8 | ? path.substring(0, path.length - 1)
9 | : path;
10 |
--------------------------------------------------------------------------------
/src/shared/utils/sanitizers.ts:
--------------------------------------------------------------------------------
1 |
2 | export const sanitizeSQLStatement = (name: string) => {
3 | try {
4 | return name?.replace(/'/g, `''`)
5 | } catch(e) {
6 | console.log(e, name);
7 | return ''
8 | }
9 | };export const sanitizeColumnName = (name: string): string => {
10 | if (name?.charAt(0) == "_" || name?.charAt(0) == "$") {
11 | return sanitizeColumnName(name.substring(1));
12 | }
13 | return name?.replace(/"/g, ``);
14 | };
15 | export const sanitizeTableName = (name: string) => {
16 | return name?.replace(/[^a-z0-9+]+/gi, "");
17 | };
18 | const folderReservedRe = /^[+\$#^]+/;
19 | const illegalRe = /[\/\?<>\\:\*\|":]/g;
20 | const controlRe = /[\x00-\x1f\x80-\x9f]/g;
21 | const reservedRe = /^\.+$/;
22 | const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
23 |
24 | export const sanitizeFolderName = (name: string) => {
25 | const replacement = "";
26 | return name
27 | .replace(folderReservedRe, replacement)
28 | .replace(illegalRe, replacement)
29 | .replace(controlRe, replacement)
30 | .replace(reservedRe, replacement)
31 | .replace(windowsReservedRe, replacement);
32 | };
33 | export const sanitizeFileName = (name: string) => {
34 | const replacement = "";
35 | return name
36 | .replace(illegalRe, replacement)
37 | .replace(controlRe, replacement)
38 | .replace(reservedRe, replacement)
39 | .replace(windowsReservedRe, replacement);
40 | };
41 |
42 |
--------------------------------------------------------------------------------
/src/shared/utils/sticker.ts:
--------------------------------------------------------------------------------
1 | import { ISuperstate as Superstate } from "shared/types/superstate";
2 |
3 | export const savePathSticker = async (
4 | superstate: Superstate,
5 | path: string,
6 | sticker: string
7 | ) => {
8 | superstate.spaceManager.saveLabel(path, superstate.settings.fmKeySticker, sticker);
9 | };export const removeIconsForPaths = (superstate: Superstate, paths: string[]) => {
10 | paths.forEach((path) => {
11 | savePathSticker(superstate, path, "");
12 | });
13 | };
14 |
15 |
--------------------------------------------------------------------------------
/src/shared/utils/stickers.ts:
--------------------------------------------------------------------------------
1 |
2 | export const emojiFromString = (emoji: string) => {
3 | let html;
4 | try {
5 | html = unifiedToNative(emoji);
6 | }
7 | catch {
8 | html = emoji;
9 | }
10 | return html;
11 | };
12 |
13 | export function parseStickerString(input: string): [string, string] {
14 | if (!input) {
15 | return ["", ""];
16 | }
17 | const match = input.match(/^(.*?)\s*\/\/\s*(.*)$/);
18 | if (match) {
19 | return [match[1], match[2]];
20 | } else {
21 | return ["", input];
22 | }
23 | }
24 | export const unifiedToNative = (unified: string) => {
25 | const unicodes = unified.split("-");
26 | const codePoints = unicodes.map((u) => `0x${u}`);
27 | // @ts-ignore
28 | return String.fromCodePoint(...codePoints);
29 | };
30 | export const nativeToUnified = (native: string) => native.codePointAt(0).toString(16);
31 |
32 |
--------------------------------------------------------------------------------
/src/shared/utils/uuid.js:
--------------------------------------------------------------------------------
1 | export function genId() {
2 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
3 | (
4 | c ^
5 | (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
6 | ).toString(16)
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/hide.ts:
--------------------------------------------------------------------------------
1 | import { MakeMDSettings } from "shared/types/settings";
2 |
3 | export const excludePathPredicate =
4 | (settings: MakeMDSettings, path: string) =>
5 | (
6 | settings.hiddenExtensions.some((e) => path.endsWith(e))
7 | || path.endsWith('/'+settings.spaceSubFolder) || path == settings.spaceSubFolder || path.split('/').pop() == settings.spaceSubFolder
8 | ) || path.startsWith(settings.spacesFolder+'/#') ||
9 | settings.hiddenFiles.some((e) => e == path)
10 |
11 | export const excludeSpacesPredicate =
12 | (settings: MakeMDSettings, path: string) =>
13 | (
14 | settings.skipFolderNames.some((e) => path.endsWith(e))
15 | || path.endsWith('/'+settings.spaceSubFolder) || path == settings.spaceSubFolder || path.split('/').pop() == settings.spaceSubFolder
16 | ) || path.startsWith(settings.spacesFolder+'/#') || path.startsWith(settings.spacesFolder+'/$') ||
17 | settings.skipFolders.some((e) => e == path)
18 |
--------------------------------------------------------------------------------
/src/utils/regex.ts:
--------------------------------------------------------------------------------
1 | export const relativeURLRegex = /^[a-zA-Z0-9][^\\\\:|<\>"*?]*$/g
2 | export const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
3 | export const emojiRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
--------------------------------------------------------------------------------
/src/utils/serializers.ts:
--------------------------------------------------------------------------------
1 | export const serializeMultiDisplayString = (value: string[]) => value.map(f => f.replace(',', '\\,')).join(', ');
2 | export const serializeMultiString = (value: string[]) => JSON.stringify(value)
3 | export const serializeSQLValues = (value: string[]) => value.join(', ');
4 | export const serializeSQLStatements = (value: string[]) => value.join('; ');
5 | export const serializeSQLFieldNames = (value: string[]) => value.join(',');
6 |
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | "esModuleInterop": true,
5 | "baseUrl": "src",
6 | "inlineSourceMap": true,
7 | "inlineSources": true,
8 | "isolatedModules": true,
9 | "module": "ESNext",
10 | "target": "es6",
11 | "allowJs": true,
12 | "alwaysStrict": true,
13 | "noImplicitAny": true,
14 | "moduleResolution": "node",
15 | "importHelpers": true,
16 | "removeComments": true,
17 | "allowSyntheticDefaultImports": true,
18 | "lib": ["dom", "es5", "scripthost", "es2020", "es2022"],
19 | "skipLibCheck": true,
20 | // "paths": {
21 | // "react": ["node_modules/preact/compat/"],
22 | // "react-dom": ["node_modules/preact/compat/"]
23 | // }
24 | },
25 | "include": [
26 | "**/*.ts",
27 | "**/*.tsx",
28 | "src/adapters/obsidian/ui/navigator/NavigatorView.tsx",
29 | "src/core/utils/autosizer.js",
30 | "src/core/utils/libs/detectElementResize.js",
31 | "src/core/utils/sqljs.ts",
32 | "src/core/react/components/UI/Menus/menu/Suggestions.jsx",
33 | "src/core/utils/uuid.js"
34 | , "src/adapters/text/removemd.js", "src/core/utils/color/gradientParser.js", "src/core/react/components/SpaceEditor/ts.worker.js" ]
35 | }
36 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "0.5.3": "0.5.3",
3 | "0.5.2": "0.5.2",
4 | "0.5.1": "0.5.1",
5 | "0.5.0": "0.5.0",
6 | "0.4.9": "0.4.9",
7 | "0.4.1": "0.4.1",
8 | "0.4.0": "0.4.0",
9 | "0.3.8": "0.3.8",
10 | "0.3.7": "0.3.7"
11 | }
12 |
--------------------------------------------------------------------------------