Skip to content

Commit

Permalink
feat: disable left area when inspecting, parse table from paste event (
Browse files Browse the repository at this point in the history
  • Loading branch information
imballinst committed Nov 19, 2022
1 parent 7235645 commit e70c62f
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 32 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dependencies": {
"@nanostores/solid": "0.2.0",
"classnames": "2.3.1",
"csv-parse": "5.3.2",
"marked": "4.1.0",
"nanostores": "0.6"
},
Expand Down
4 changes: 3 additions & 1 deletion src/components/Button/Button.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@

/* Disabled state. */
.button[disabled=''],
.button[disabled='']:hover {
.button[disabled='']:hover,
.button:disabled,
.button:disabled:hover {
background: theme('colors.slate.400');
border-color: theme('colors.slate.400');
color: theme('colors.slate.200');
Expand Down
1 change: 1 addition & 0 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function Button({ children, class: className, variant, size, isDisabled,
data-size={size}
disabled={isDisabled ? isDisabled() : false}
{...props}
aria-label={props.title}
>
{children}
</button>
Expand Down
43 changes: 36 additions & 7 deletions src/components/Editor/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
inspectContentStore,
InspectStatus,
setInspectContent,
setInspectStatus
setInspectStatus,
inspectStatusStore
} from '../../store/inspect';
import './MarkdownEditor.css';
import { useStore } from '@nanostores/solid';
Expand All @@ -20,17 +21,23 @@ import { modifyTextSelection, Toolbar } from './Toolbar';
import { ToolbarAction } from '../../utils/operators/toolbar';
import { Button } from '../Button';
import { getToolbarHoverText } from './Toolbar/common';
import { parseTableFromTabbedText } from '../../utils/parsers/table';
import {
parseTableFromCommaSeparatedText,
parseTableFromTabbedText
} from '../../utils/parsers/table';

export const MarkdownEditor = () => {
const markdown = useStore(markdownStore);
const editor = useStore(inspectContentStore);
const inspectStatus = useStore(inspectStatusStore);

const [selected, setSelected] = createSignal<[number, number] | undefined>(undefined);
const [prevSelected, setPrevSelected] = createSignal<[number, number] | undefined>(undefined);
const [textAreaElement, setTextAreaElement] = createSignal<HTMLTextAreaElement | undefined>(
undefined
);
const editor = useStore(inspectContentStore);

const [isRawPaste, setIsRawPaste] = createSignal(false);

createEffect<string | undefined>((previous) => {
const rawContent = editor()?.rawContent;
Expand Down Expand Up @@ -81,6 +88,11 @@ export const MarkdownEditor = () => {
};

const onKeyDown: JSX.TextareaHTMLAttributes<HTMLTextAreaElement>['onKeyDown'] = (e) => {
if (e.key.toLowerCase() === 'v' && e.shiftKey && isCtrlOrCmdKey(e)) {
setIsRawPaste(true);
return;
}

const { selectionStart, selectionEnd } = e.currentTarget;

if (e.key === 'Tab' && selectionStart === selectionEnd) {
Expand Down Expand Up @@ -152,7 +164,10 @@ export const MarkdownEditor = () => {
}

return (
<div class="flex flex-col mt-4">
<fieldset
class="flex flex-col mt-4"
disabled={inspectStatus() === InspectStatus.InspectingSnippet}
>
<div class="flex justify-between">
<Toolbar setSelected={setSelected} textAreaElement={textAreaElement} />

Expand Down Expand Up @@ -194,20 +209,34 @@ export const MarkdownEditor = () => {
setMarkdown(nextValue);
}}
onPaste={(e) => {
if (isRawPaste()) {
// Reset the thing that we set on `onKeyDown`.
// Since we don't do prevent default here, it'll passthrough to `onInput`.
setIsRawPaste(false);
return;
}

const pasted = e.clipboardData?.getData('text/plain');
const parseResult = parseTableFromTabbedText(pasted);
// First, try parse from tabbed text.
let parseResult = parseTableFromTabbedText(pasted);
if (!parseResult) {
// If the parse fails, check with comma-separated.
parseResult = parseTableFromCommaSeparatedText(pasted);
console.debug(parseResult);
}

if (parseResult) {
e.preventDefault();
const selectionStart = e.currentTarget.selectionStart;
setMarkdown((prev) =>
prev.slice(0, selectionStart).concat(parseResult).concat(prev.slice(selectionStart))
prev.slice(0, selectionStart).concat(parseResult!).concat(prev.slice(selectionStart))
);

const nextSelectionRange = selectionStart + parseResult.length;
e.currentTarget.setSelectionRange(nextSelectionRange, nextSelectionRange);
}
}}
/>
</div>
</fieldset>
);
};
8 changes: 4 additions & 4 deletions src/components/Editor/SegmentHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ interface SegmentHeadingProps {

export function SegmentHeading(props: SegmentHeadingProps) {
return (
<div>
<div class="segment-heading">{props.title}</div>
<div class="segment-subtext">{props.children}</div>
</div>
<>
<h2 class="segment-heading">{props.title}</h2>
<p class="segment-subtext">{props.children}</p>
</>
);
}
37 changes: 23 additions & 14 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface Props {
}
const { title } = Astro.props as Props;
const origin = (Astro.site?.href || '') + trimSlashes(import.meta.env.BASE_PATH || '')
const origin = (Astro.site?.href || '') + trimSlashes(import.meta.env.BASE_PATH || '');
const date = new Date();
const currentYear = date.getUTCFullYear().toString().padStart(2, '0');
Expand All @@ -21,7 +21,6 @@ let version = 'dev';
if (import.meta.env.VERSION && import.meta.env.GIT_HASH) {
version = `${import.meta.env.VERSION}.${import.meta.env.GIT_HASH}`;
}
---

<!DOCTYPE html>
Expand All @@ -32,22 +31,25 @@ if (import.meta.env.VERSION && import.meta.env.GIT_HASH) {
<link rel="icon" type="image/x-icon" href="/favicon.ico" />

<!-- Primary Meta Tags -->
<meta name="title" content={title}>
<meta name="description" content="Update Markdown tables easier with Markdown Clap.">
<meta name="title" content={title} />
<meta name="description" content="Update Markdown tables easier with Markdown Clap." />

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content={origin}>
<meta property="og:title" content={title}>
<meta property="og:description" content="Update Markdown tables easier with Markdown Clap.">
<meta property="og:image" content={origin + "/markdownclap-big.png"}>
<meta property="og:type" content="website" />
<meta property="og:url" content={origin} />
<meta property="og:title" content={title} />
<meta property="og:description" content="Update Markdown tables easier with Markdown Clap." />
<meta property="og:image" content={origin + '/markdownclap-big.png'} />

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content={origin}>
<meta property="twitter:title" content={title}>
<meta property="twitter:description" content="Update Markdown tables easier with Markdown Clap.">
<meta property="twitter:image" content={origin + "/markdownclap-big.png"}>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={origin} />
<meta property="twitter:title" content={title} />
<meta
property="twitter:description"
content="Update Markdown tables easier with Markdown Clap."
/>
<meta property="twitter:image" content={origin + '/markdownclap-big.png'} />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
Expand Down Expand Up @@ -111,6 +113,13 @@ if (import.meta.env.VERSION && import.meta.env.GIT_HASH) {
border-radius: theme('borderRadius.md');
}

:global(input:disabled),
:global(button:disabled),
:global(textarea:disabled),
:global(select:disabled) {
color: theme('colors.gray.500');
}

:global(input),
:global(select),
:global(textarea) {
Expand Down
8 changes: 4 additions & 4 deletions src/layouts/Main.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import { SegmentHeading } from '../components/Editor/SegmentHeading';

<div class="p-4">
<div class="editor">
<div class="editor-segment">
<section class="editor-segment">
<SegmentHeading title="Raw content">
Fill input below with Markdown. Currently supported formats are
<Link href="https://github.github.com/gfm/">GFM</Link> and
<Link href="https://commonmark.org">CommonMark</Link>.
</SegmentHeading>

<MarkdownEditor client:visible />
</div>
<div class="editor-segment">
</section>
<section class="editor-segment">
<RightContent client:visible />
</div>
</section>
</div>
</div>

Expand Down
85 changes: 84 additions & 1 deletion src/utils/parsers/table.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "vitest";
import { parseTableFromTabbedText } from "./table";
import { parseTableFromCommaSeparatedText, parseTableFromTabbedText } from "./table";

describe('parseTableFromTabbedText', () => {
test('invalid case: undefined', () => {
Expand Down Expand Up @@ -58,4 +58,87 @@ this is a | valid table\they!
|this is a \\| valid table|hey!|
`.trim())
})
})

describe('parseTableFromCommaSeparatedText', () => {
test('invalid case: undefined', () => {
expect(parseTableFromCommaSeparatedText(undefined)).toBe(undefined)
})

test('invalid case: empty string', () => {
expect(parseTableFromCommaSeparatedText('')).toBe(undefined)
})

test('invalid case: no columns', () => {
expect(parseTableFromCommaSeparatedText('test')).toBe(undefined)
})

test('invalid case: different number of columns', () => {
const str = `
hello,world
this is an invalid table
`

expect(parseTableFromCommaSeparatedText(str)).toBe(undefined)
})

test('valid case', () => {
const str = `
hello,world
this is a valid table,hey!
`

expect(parseTableFromCommaSeparatedText(str)).toBe(`
|hello|world|
|this is a valid table|hey!|
`.trim())
})

test('valid case: with trailing newline', () => {
const str = `
hello,world
this is a valid table,hey!
`

expect(parseTableFromCommaSeparatedText(str)).toBe(`
|hello|world|
|this is a valid table|hey!|
`.trim())
})

test('valid case, with pipe characters', () => {
const str = `
hello,world
this is a | valid table,hey!
`

expect(parseTableFromCommaSeparatedText(str)).toBe(`
|hello|world|
|this is a \\| valid table|hey!|
`.trim())
})

test('valid case, with quotes', () => {
const str = `
hello,world
"this is a valid, table",hey!
`

expect(parseTableFromCommaSeparatedText(str)).toBe(`
|hello|world|
|this is a valid, table|hey!|
`.trim())
})

test('valid case, with uneven quotes', () => {
const str = `
hello,world
“this is a valid, table”,hey!
`

expect(parseTableFromCommaSeparatedText(str)).toBe(`
|hello|world|
|this is a valid, table|hey!|
`.trim())
})
})
22 changes: 21 additions & 1 deletion src/utils/parsers/table.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { parse } from 'csv-parse/browser/esm/sync'

export function parseTableFromTabbedText(text: string | undefined): string | undefined {
if (!text) return undefined

Expand All @@ -6,5 +8,23 @@ export function parseTableFromTabbedText(text: string | undefined): string | und
const allLinesHaveEqualTabCount = tabCountPerLine.every(tabCount => tabCount === tabCountPerLine[0] && tabCount > 0)

if (!allLinesHaveEqualTabCount) return undefined
return lines.map(line => `|${line.replace(/\|/g, '\\|').replace(/\t+/g, '|')}|`).join('\n')
return lines.map(line => `|${escapePipes(line).replace(/\t+/g, '|')}|`).join('\n')
}

export function parseTableFromCommaSeparatedText(text: string | undefined): string | undefined {
if (!text) return undefined

try {
const parsed: string[][] = parse(text.trim().replace(/[“”]/g, '"'), { skipEmptyLines: true })
if (parsed.length === 1 && parsed[0].length === 1) return undefined

return parsed.map(line => `|${line.map(column => escapePipes(column)).join('|')}|`).join('\n')
} catch (err) {
return undefined
}
}

// Helper functions.
function escapePipes(line: string) {
return line.replace(/\|/g, '\\|')
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,11 @@ csstype@^3.1.0:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==

csv-parse@5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.2.tgz#a8ce2f8dec1b9c1013c9e73c6102fe0d2d436dbb"
integrity sha512-3jQ/JMs+voKxr4vwpmElS1d37J0o6rQdQyEKoPyA9HG8fYczpLaBJnmp5ykvkXL8ZeEGVP0qwLU645BZVykXKw==

data-uri-to-buffer@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
Expand Down

0 comments on commit e70c62f

Please sign in to comment.