parent
2f94183dc6
commit
5fad97c6d2
11 changed files with 214 additions and 114 deletions
5
.changeset/pretty-months-mix.md
Normal file
5
.changeset/pretty-months-mix.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-products-feed": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update the UI to the common theme
|
|
@ -1,22 +1,22 @@
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { LinearProgress, Paper } from "@material-ui/core";
|
import { LinearProgress, Paper } from "@material-ui/core";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { EditIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||||
import { AppConfigContainer } from "../app-config-container";
|
import { AppConfigContainer } from "../app-config-container";
|
||||||
import { UrlConfigurationForm } from "./url-configuration-form";
|
import { UrlConfigurationForm } from "./url-configuration-form";
|
||||||
import { ChannelsList } from "./channels-list";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { AppColumnsLayout } from "../../ui/app-columns-layout";
|
import { AppColumnsLayout } from "../../ui/app-columns-layout";
|
||||||
import { FeedPreviewCard } from "./feed-preview-card";
|
import { FeedPreviewCard } from "./feed-preview-card";
|
||||||
import { Instructions } from "./instructions";
|
import { Instructions } from "./instructions";
|
||||||
|
import SideMenu from "./side-menu";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => {
|
const useStyles = makeStyles((theme) => {
|
||||||
return {
|
return {
|
||||||
header: { marginBottom: 20 },
|
grid: {
|
||||||
grid: { display: "grid", gridTemplateColumns: "1fr 1fr", alignItems: "start", gap: 40 },
|
display: "grid",
|
||||||
formContainer: {
|
gridTemplateColumns: "1fr 1fr",
|
||||||
top: 0,
|
alignItems: "start",
|
||||||
position: "sticky",
|
gap: 40,
|
||||||
},
|
},
|
||||||
instructionsContainer: {
|
instructionsContainer: {
|
||||||
padding: 15,
|
padding: 15,
|
||||||
|
@ -78,16 +78,30 @@ export const ChannelsConfiguration = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppColumnsLayout>
|
<AppColumnsLayout>
|
||||||
<ChannelsList
|
<SideMenu
|
||||||
channels={channels.data}
|
title="Channels"
|
||||||
activeChannelSlug={activeChannel.slug}
|
selectedItemId={activeChannel?.slug}
|
||||||
onChannelClick={setActiveChannelSlug}
|
headerToolbar={
|
||||||
|
<IconButton
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Redirect({
|
||||||
|
to: `/channels/`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
onClick={(id) => setActiveChannelSlug(id)}
|
||||||
|
items={channels.data.map((c) => ({ label: c.name, id: c.slug })) || []}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activeChannel ? (
|
{activeChannel ? (
|
||||||
<div className={styles.configurationColumn}>
|
<div className={styles.configurationColumn}>
|
||||||
<FeedPreviewCard channelSlug={activeChannel.slug} />
|
<Paper elevation={0}>
|
||||||
<Paper elevation={0} className={styles.formContainer}>
|
|
||||||
<UrlConfigurationForm
|
<UrlConfigurationForm
|
||||||
channelID={activeChannel.id}
|
channelID={activeChannel.id}
|
||||||
key={activeChannelSlug}
|
key={activeChannelSlug}
|
||||||
|
@ -106,6 +120,7 @@ export const ChannelsConfiguration = () => {
|
||||||
/>
|
/>
|
||||||
{saveError && <span>{saveError.message}</span>}
|
{saveError && <span>{saveError.message}</span>}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<FeedPreviewCard channelSlug={activeChannel.slug} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Instructions />
|
<Instructions />
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import {
|
|
||||||
makeStyles,
|
|
||||||
OffsettedList,
|
|
||||||
OffsettedListBody,
|
|
||||||
OffsettedListHeader,
|
|
||||||
OffsettedListItem,
|
|
||||||
OffsettedListItemCell,
|
|
||||||
} from "@saleor/macaw-ui";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
import { ChannelFragment } from "../../../../generated/graphql";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => {
|
|
||||||
return {
|
|
||||||
listItem: {
|
|
||||||
cursor: "pointer",
|
|
||||||
height: "auto !important",
|
|
||||||
border: `1px solid transparent`,
|
|
||||||
},
|
|
||||||
listItemActive: {
|
|
||||||
border: `1px solid ${
|
|
||||||
theme.palette.type === "light" ? theme.palette.divider : theme.palette.grey.A200
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
cellSlug: {
|
|
||||||
fontFamily: "monospace",
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channels: ChannelFragment[];
|
|
||||||
activeChannelSlug: string;
|
|
||||||
onChannelClick(channelSlug: string): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChannelsList = ({ channels, activeChannelSlug, onChannelClick }: Props) => {
|
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OffsettedList gridTemplate={["1fr"]}>
|
|
||||||
<OffsettedListBody>
|
|
||||||
{channels.map((c) => {
|
|
||||||
return (
|
|
||||||
<OffsettedListItem
|
|
||||||
className={clsx(styles.listItem, {
|
|
||||||
[styles.listItemActive]: c.slug === activeChannelSlug,
|
|
||||||
})}
|
|
||||||
key={c.slug}
|
|
||||||
onClick={() => {
|
|
||||||
onChannelClick(c.slug);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<OffsettedListItemCell>
|
|
||||||
{c.name}
|
|
||||||
<Typography variant="caption">
|
|
||||||
<code>{c.slug}</code>
|
|
||||||
</Typography>
|
|
||||||
</OffsettedListItemCell>
|
|
||||||
</OffsettedListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</OffsettedListBody>
|
|
||||||
</OffsettedList>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -38,8 +38,8 @@ export const FeedPreviewCard = ({ channelSlug }: FeedPreviewCardProps) => {
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Paper elevation={0} className={styles.instructionsContainer}>
|
<Paper elevation={0} className={styles.instructionsContainer}>
|
||||||
<Typography paragraph variant="h4">
|
<Typography paragraph variant="h3">
|
||||||
Your Google Merchant Feed
|
Your Google Merchant Feed preview
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
label="Google feed URL"
|
label="Google feed URL"
|
||||||
|
@ -47,6 +47,7 @@ export const FeedPreviewCard = ({ channelSlug }: FeedPreviewCardProps) => {
|
||||||
value={googleFeedUrl}
|
value={googleFeedUrl}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
className={styles.field}
|
className={styles.field}
|
||||||
|
helperText="Dedicated URL for your Google Merchant Feed"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { Card, CardContent, CardHeader, Divider } from "@material-ui/core";
|
||||||
|
("@material-ui/icons");
|
||||||
|
import { DeleteIcon, IconButton, List, ListItem, ListItemCell } from "@saleor/macaw-ui";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import { Skeleton } from "@material-ui/lab";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles((theme) => ({
|
||||||
|
menu: {
|
||||||
|
height: "fit-content",
|
||||||
|
},
|
||||||
|
clickable: {
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
"&&&&::before": {
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
width: "4px",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: theme.palette.saleor.active[1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spaceBetween: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
minHeight: "48px",
|
||||||
|
"&::after": {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
greyText: {
|
||||||
|
color: theme.palette.text.hint,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
all: "inherit",
|
||||||
|
display: "contents",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface SideMenuProps {
|
||||||
|
title: string;
|
||||||
|
noItemsText?: string;
|
||||||
|
items: { id: string; label: string }[];
|
||||||
|
selectedItemId?: string;
|
||||||
|
headerToolbar?: React.ReactNode;
|
||||||
|
onDelete?: (itemId: string) => void;
|
||||||
|
onClick: (itemId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SideMenu: React.FC<SideMenuProps> = ({
|
||||||
|
title,
|
||||||
|
items,
|
||||||
|
headerToolbar,
|
||||||
|
selectedItemId,
|
||||||
|
noItemsText,
|
||||||
|
onDelete,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const isNoItems = !items || !items.length;
|
||||||
|
return (
|
||||||
|
<Card className={classes.menu}>
|
||||||
|
<CardHeader title={title} action={headerToolbar} />
|
||||||
|
{isNoItems ? (
|
||||||
|
!!noItemsText && <CardContent className={classes.greyText}>{noItemsText}</CardContent>
|
||||||
|
) : (
|
||||||
|
<List gridTemplate={["1fr"]}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<React.Fragment key={item.id}>
|
||||||
|
<Divider />
|
||||||
|
<ListItem
|
||||||
|
className={clsx(classes.clickable, classes.tableRow, {
|
||||||
|
[classes.selected]: item.id === selectedItemId,
|
||||||
|
})}
|
||||||
|
onClick={() => onClick(item.id)}
|
||||||
|
>
|
||||||
|
<ListItemCell>
|
||||||
|
<div className={classes.spaceBetween}>
|
||||||
|
{item.label}
|
||||||
|
{!!onDelete && (
|
||||||
|
<IconButton
|
||||||
|
variant="secondary"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
onDelete(item.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ListItemCell>
|
||||||
|
</ListItem>
|
||||||
|
</React.Fragment>
|
||||||
|
)) ?? <Skeleton />}
|
||||||
|
<Divider />
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SideMenu;
|
|
@ -53,12 +53,12 @@ export const UrlConfigurationForm = (props: Props) => {
|
||||||
})}
|
})}
|
||||||
className={styles.form}
|
className={styles.form}
|
||||||
>
|
>
|
||||||
<Typography variant="body1" paragraph>
|
<Typography variant="h3" paragraph>
|
||||||
Configure
|
Configure
|
||||||
<strong onClick={handleChannelNameClick} className={styles.channelName}>
|
<strong onClick={handleChannelNameClick} className={styles.channelName}>
|
||||||
{` ${props.channelName} `}
|
{` ${props.channelName} `}
|
||||||
</strong>
|
</strong>
|
||||||
channel:
|
channel
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField label="Storefront home URL" {...CommonFieldProps} {...register("storefrontUrl")} />
|
<TextField label="Storefront home URL" {...CommonFieldProps} {...register("storefrontUrl")} />
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "280px auto 400px",
|
gridTemplateColumns: "280px auto 400px",
|
||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
gap: 32,
|
gap: theme.spacing(3),
|
||||||
maxWidth: 1180,
|
marginTop: theme.spacing(3),
|
||||||
margin: "0 auto",
|
marginLeft: theme.spacing(3),
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
type Props = PropsWithChildren<{}>;
|
type AppColumnsLayoutProps = PropsWithChildren<{}>;
|
||||||
|
|
||||||
export const AppColumnsLayout = ({ children }: Props) => {
|
export const AppColumnsLayout = ({ children }: AppColumnsLayoutProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
return <div className={styles.root}>{children}</div>;
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
import { Theme } from "@material-ui/core/styles";
|
import { createGenerateClassName, Theme } from "@material-ui/core/styles";
|
||||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||||
import { ThemeProvider as MacawUIThemeProvider } from "@saleor/macaw-ui";
|
import {
|
||||||
|
dark,
|
||||||
|
light,
|
||||||
|
SaleorThemeColors,
|
||||||
|
ThemeProvider as MacawUIThemeProvider,
|
||||||
|
} from "@saleor/macaw-ui";
|
||||||
import React, { PropsWithChildren, useEffect } from "react";
|
import React, { PropsWithChildren, useEffect } from "react";
|
||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
|
|
||||||
|
@ -11,12 +16,49 @@ import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
||||||
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
||||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||||
|
|
||||||
const themeOverrides: Partial<Theme> = {
|
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
|
||||||
/**
|
|
||||||
* You can override MacawUI theme here
|
/**
|
||||||
*/
|
* Temporary override of colors, to match new dashboard palette.
|
||||||
|
* Long term this will be replaced with Macaw UI 2.x with up to date design tokens
|
||||||
|
*/
|
||||||
|
const palettes: PalettesOverride = {
|
||||||
|
light: {
|
||||||
|
...light,
|
||||||
|
background: {
|
||||||
|
default: "#fff",
|
||||||
|
paper: "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
...dark,
|
||||||
|
background: {
|
||||||
|
default: "hsla(211, 42%, 14%, 1)",
|
||||||
|
paper: "hsla(211, 42%, 14%, 1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const themeOverrides: Partial<Theme> = {
|
||||||
|
overrides: {
|
||||||
|
MuiTableCell: {
|
||||||
|
body: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
height: 56,
|
||||||
|
paddingBottom: 4,
|
||||||
|
paddingTop: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateClassName = createGenerateClassName({
|
||||||
|
productionPrefix: "c",
|
||||||
|
disableGlobal: true,
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* Ensure instance is a singleton.
|
* Ensure instance is a singleton.
|
||||||
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
||||||
|
@ -27,7 +69,7 @@ export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge()
|
||||||
* That's a hack required by Macaw-UI incompatibility with React@18
|
* That's a hack required by Macaw-UI incompatibility with React@18
|
||||||
*/
|
*/
|
||||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
||||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean }>
|
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean; palettes: PalettesOverride }>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function NextApp({ Component, pageProps }: AppProps) {
|
function NextApp({ Component, pageProps }: AppProps) {
|
||||||
|
@ -44,7 +86,7 @@ function NextApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<NoSSRWrapper>
|
<NoSSRWrapper>
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||||
<ThemeProvider overrides={themeOverrides} ssr={false}>
|
<ThemeProvider palettes={palettes} overrides={themeOverrides} ssr={false}>
|
||||||
<ThemeSynchronizer />
|
<ThemeSynchronizer />
|
||||||
<RoutePropagator />
|
<RoutePropagator />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
|
|
@ -3,23 +3,11 @@ import React, { useEffect } from "react";
|
||||||
import { ChannelsConfiguration } from "../modules/app-configuration/ui/channels-configuration";
|
import { ChannelsConfiguration } from "../modules/app-configuration/ui/channels-configuration";
|
||||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
|
|
||||||
const ConfigurationPage: NextPage = () => {
|
const ConfigurationPage: NextPage = () => {
|
||||||
const channels = trpcClient.channels.fetch.useQuery();
|
const channels = trpcClient.channels.fetch.useQuery();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { appBridge } = useAppBridge();
|
|
||||||
|
|
||||||
const openInNewTab = (url: string) => {
|
|
||||||
appBridge?.dispatch(
|
|
||||||
actions.Redirect({
|
|
||||||
to: url,
|
|
||||||
newContext: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (channels.isSuccess && channels.data.length === 0) {
|
if (channels.isSuccess && channels.data.length === 0) {
|
||||||
router.push("/not-ready");
|
router.push("/not-ready");
|
||||||
|
|
|
@ -23,7 +23,7 @@ const IndexPage: NextPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Saleor Merchant Feed</h1>
|
<h1>Saleor Product Feed</h1>
|
||||||
<p>This is Saleor App that allows product feed generation</p>
|
<p>This is Saleor App that allows product feed generation</p>
|
||||||
<p>Install app in your Saleor instance and open in with Dashboard</p>
|
<p>Install app in your Saleor instance and open in with Dashboard</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,10 +2,11 @@ body {
|
||||||
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||||
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||||
color: #111;
|
color: #111;
|
||||||
padding: 1rem 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
background: #f6f8fa;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
@ -13,3 +14,8 @@ code {
|
||||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code::before {
|
||||||
|
content: "$ ";
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue