Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(embed): handle snippet not found #53

Merged
merged 6 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-code-embed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
EMBED_STYLE_URL: ${{ secrets.EMBED_STYLE_URL }}
EMBED_JS_URL: ${{ secrets.EMBED_JS_URL }}
WEB_APP_URL: ${{ secrets.WEB_APP_URL }}
WEB_APP_SNIPPET_VIEW_URL: ${{ secrets.WEB_APP_SNIPPET_VIEW_URL }}
ENV: ${{ secrets.ENV }}
SENTRY_DSN: ${{ secrets.CODE_EMBED_SENTRY_DSN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand Down
6 changes: 1 addition & 5 deletions .github/workflows/publish-embed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ on:
paths:
- 'packages/embed/src/scripts/**'
- 'packages/embed/src/styles/**'
pull_request:
branches: [ main ]
paths:
- 'packages/embed/src/scripts/**'
- 'packages/embed/src/styles/**'

jobs:
build:
runs-on: ubuntu-latest
Expand Down
3 changes: 2 additions & 1 deletion apps/functions/code-embed/.env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
DATABASE_URL="mysql:https://root:@127.0.0.1:3311/sharingan"
EMBED_STYLE_URL=https://cdn.jsdelivr.net/npm/sharingan-embed@latest/style.min.css
EMBED_JS_URL=https://cdn.jsdelivr.net/npm/sharingan-embed@latest/script.min.js
WEB_APP_URL=http:https://localhost:7500
ENV=development
SENTRY_DSN=
WEB_APP_URL=http:https://localhost:7500
WEB_APP_SNIPPET_VIEW_URL=http:https://localhost:7500/snippets
1 change: 1 addition & 0 deletions apps/functions/code-embed/serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const serverlessConfiguration: AWS = {
ENV: '${env:ENV}',
NODE_OPTIONS: '--stack-trace-limit=1000',
SENTRY_DSN: '${env:SENTRY_DSN}',
WEB_APP_SNIPPET_VIEW_URL: '${env:WEB_APP_SNIPPET_VIEW_URL}',
WEB_APP_URL: '${env:WEB_APP_URL}',
},
name: 'aws',
Expand Down
2 changes: 2 additions & 0 deletions apps/functions/code-embed/src/functions/renderer/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const main: Handler<APIGatewayProxyEvent, APIGatewayProxyResult> = async
options: {
scriptUrl: process.env.EMBED_JS_URL,
styleUrl: process.env.EMBED_STYLE_URL,
webAppUrl: process.env.WEB_APP_URL,
webAppViewUrl: process.env.WEB_APP_SNIPPET_VIEW_URL,
},
shiki,
snippet,
Expand Down
1 change: 1 addition & 0 deletions packages/embed/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ DATABASE_URL="mysql:https://root:@127.0.0.1:3311/sharingan"
EMBED_STYLE_URL=http:https://localhost:7502/sharingan/style.css
EMBED_JS_URL=http:https://localhost:7502/sharingan/script.js
WEB_APP_URL=http:https://localhost:7500
WEB_APP_SNIPPET_VIEW_URL=http:https://localhost:7500/snippets
2 changes: 1 addition & 1 deletion packages/embed/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"ignorePatterns": ["dist", "build", "tsup.config.ts", "env.d.ts", "src/server/public"],
"ignorePatterns": ["dist", "build", "tsup.config.ts", "env.d.ts", "src/server/public", "jest.config.ts"],
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ save. The URL follows this pattern: `http:https://localhost:7502/snippets/:id`.

Run the command below:
```shell
yarn preview
yarn iframe:preview
```
Navigate to the URL `http:https://localhost:7503` to see the result.

Expand Down
1 change: 1 addition & 0 deletions packages/embed/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type EnvironmentVariables = {
EMBED_JS_URL: string;
EMBED_STYLE_URL: string;
WEB_APP_URL: string;
WEB_APP_SNIPPET_VIEW_URL: string;
};

declare global {
Expand Down
15 changes: 15 additions & 0 deletions packages/embed/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Config } from '@jest/types';

const config: Config.InitialOptions = {
roots: ['.'],
preset: 'ts-jest',
testMatch: ['**/?(*.)+(spec|test).[jt]s'],
testEnvironment: 'node',
clearMocks: true,
maxWorkers: 1,
snapshotFormat: {
printBasicPrototype: false,
},
};

export default config;
10 changes: 7 additions & 3 deletions packages/embed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,26 @@
"scripts": {
"build:cdn": "tsup",
"build:cdn:watch": "tsup --watch",
"build:lib": "tsc",
"build:lib": "tsc --project tsconfig.prod.json",
"build": "yarn build:lib && yarn build:cdn",
"clean": "rm -rf .turbo dist build",
"dev": "nodemon --watch \"*.ts\" --exec \"ts-node\" ./src/server/index.ts",
"lint": "eslint src",
"preview": "serve ./src/server/static -l 7503",
"push": "cp package.publish.json build/package.json && cd build && npm publish --access=public"
"iframe:preview": "serve ./src/server/static -l 7503",
"push": "cp package.publish.json build/package.json && cd build && npm publish --access=public",
"test": "jest"
},
"devDependencies": {
"@sharingan/database": "*",
"@types/express": "^4.17.13",
"@types/jest": "^29.1.2",
"@types/node": "^18.7.11",
"express": "^4.18.1",
"jest": "^29.1.2",
"nodemon": "^2.0.19",
"serve": "^14.0.1",
"shiki": "^0.11.1",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"tsup": "^6.2.2",
"typescript": "^4.8.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/embed/package.publish.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sharingan-embed",
"version": "1.0.2",
"version": "1.1.0",
"repository": "https://github.com/tericcabrel/sharingan.git",
"author": "Eric Cabrel TIOGO <[email protected]>",
"license": "MIT"
Expand Down
34 changes: 34 additions & 0 deletions packages/embed/src/oembed/tests/generate-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { generateOembedMetadata } from '../index';

describe('Test Generate Oembed metadata', () => {
it('should generate Oembed metadata for a code snippet', () => {
// GIVEN
const snippet = { id: 'snippet_id', name: 'snippet-name.java' };
const SNIPPET_RENDERER_URL = 'https://embed.sharingan.dev';
const WEB_APP_URL = 'https://sharingan.dev';

// WHEN
const result = generateOembedMetadata({
snippet,
snippetRendererURL: SNIPPET_RENDERER_URL,
webAppURL: WEB_APP_URL,
});

// THEN
expect(result).toMatchInlineSnapshot(`
{
"height": 500,
"html": "<iframe width="750" height="500" src="https://embed.sharingan.dev/snippets/snippet_id" style="width:750px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>",
"provider_name": "Sharingan",
"provider_url": "https://sharingan.dev",
"thumbnail_height": "720",
"thumbnail_url": "https://sharingan.dev/assets/og.png",
"thumbnail_width": "1280",
"title": "snippet-name.java",
"type": "rich",
"version": "1.0",
"width": 750,
}
`);
});
});
34 changes: 34 additions & 0 deletions packages/embed/src/renderer/content/html-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Snippet } from '@sharingan/database';
import { Lang } from 'shiki';

import { Shiki } from '../types';
import { generateLineHighlightOptions, parseHTMLSnippetCode } from './utils';

export const generateNoSnippetHtmlContent = (webAppUrl: string) => {
return `<div class="no-content">
<h3>Oops! Snippet not found!</h3>
<div>Go to <a href="${webAppUrl}" target="_blank">Sharingan</a> to ensure it exists and is accessible</div>
</div>`;
};

export const generateSnippetHtmlContent = async ({ shiki, snippet }: { shiki: Shiki; snippet: Snippet }) => {
const highlighter = await shiki.getHighlighter({
langs: [snippet.language] as Lang[],
theme: snippet.theme,
themes: [snippet.theme],
});

const snippetCodeHtml = highlighter.codeToHtml(snippet.content.trim(), {
lang: snippet.language,
lineOptions: generateLineHighlightOptions(snippet.lineHighlight),
});

const backgroundColor = highlighter.getBackgroundColor();

const html = parseHTMLSnippetCode(snippetCodeHtml);

return {
backgroundColor,
html,
};
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { generateRandomString } from './utils';

type Args = {
export type Args = {
code: string;
color?: string;
rawCode: string;
scriptUrl?: string;
styleUrl: string;
title: string;
webAppUrl: string;
};

const DEFAULT_COLOR = '#22272e';

export const generateHtmlPreview = ({ code, color = DEFAULT_COLOR, rawCode, scriptUrl, styleUrl, title }: Args) => {
export const generateHTMLPreview = ({
code,
color = DEFAULT_COLOR,
rawCode,
scriptUrl,
styleUrl,
title,
webAppUrl,
}: Args) => {
const id = generateRandomString(6);
const isEmpty = !rawCode;

return `
<!DOCTYPE html>
Expand All @@ -28,22 +38,22 @@ export const generateHtmlPreview = ({ code, color = DEFAULT_COLOR, rawCode, scri
<div class="ctner">
<div class="ctner-header">
<div>${title}</div>
<div>view on <a href="">Sharingan</a></div>
<div>view on <a href="${webAppUrl}" target="_blank">Sharingan</a></div>
</div>
<textarea id="raw-code-${id}" class="hidden" rows="1" cols="1">${rawCode}</textarea>
<div class="code-editor-container" id="code-${id}" style="border: solid 1px ${color}; background-color: ${color}">
<button id="btn-copy-${id}" class="btn-copy hidden">
<button id="btn-copy-${id}" class="btn-copy hidden" style="${isEmpty ? 'display: none' : ''}">
<svg class="ic show" id="ic-copy-${id}" fill="none" stroke="#fff" viewBox="0 0 24 24" xmlns="http:https://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
<svg class="ic hidden" id="ic-copied-${id}" fill="none" stroke="#10B981" viewBox="0 0 24 24" xmlns="http:https://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</button>
<pre>${code}</pre>
${isEmpty ? `${code}` : `<pre>${code}</pre>`}
</div>
</div>
${scriptUrl ? `<script type="text/javascript" src="${scriptUrl}"></script>` : ''}
${scriptUrl && !isEmpty ? `<script type="text/javascript" src="${scriptUrl}"></script>` : ''}
</body>
</html>
`;
Expand Down
19 changes: 19 additions & 0 deletions packages/embed/src/renderer/content/tests/html-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { generateNoSnippetHtmlContent } from '../html-generator';

describe('Test HTML generator functions', () => {
it.only('should generates html content for a non existing code snippet', () => {
// GIVEN
const WEB_APP_URL = 'https://sharingan.dev';

// WHEN
const result = generateNoSnippetHtmlContent(WEB_APP_URL);

// THEN
expect(result).toMatchInlineSnapshot(`
"<div class="no-content">
<h3>Oops! Snippet not found!</h3>
<div>Go to <a href="https://sharingan.dev" target="_blank">Sharingan</a> to ensure it exists and is accessible</div>
</div>"
`);
});
});
80 changes: 80 additions & 0 deletions packages/embed/src/renderer/content/tests/preview-template.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Args, generateHTMLPreview } from '../preview-template';

jest.mock('../utils', () => {
return {
generateRandomString: () => 'random-id',
};
});

describe('Test generateHTMLPreview()', () => {
it('should generates the html preview for a code snippet', () => {
// GIVEN
const args: Args = {
code:
'<span class="line">export const hashPassword = (password: string): string => {</span>\n' +
'<span class="line"> const SALT_ROUNDS = 10;</span>\n' +
'<span class="line"></span>\n' +
'<span class="line"> return bcrypt.hashSync(password, SALT_ROUNDS);</span>\n' +
'<span class="line">};</span>',
color: '#f5f5f5',
rawCode:
'export const hashPassword = (password: string): string => {\n' +
' const SALT_ROUNDS = 10;\n' +
'\n' +
' return bcrypt.hashSync(password, SALT_ROUNDS);\n' +
'};',
scriptUrl: 'https://cdn.com/sharigan/script.js',
styleUrl: 'https://cdn.com/sharigan/style.css',
title: 'helpers.ts',
webAppUrl: 'https://sharingan.dev',
};

// WHEN
const result = generateHTMLPreview(args);

// THEN
expect(result).toMatchInlineSnapshot(`
"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,">
<title>Sharingan - helpers.ts</title>
<link rel="stylesheet" type="text/css" href="https://cdn.com/sharigan/style.css" />
</head>
<body data-id="random-id">
<div class="ctner">
<div class="ctner-header">
<div>helpers.ts</div>
<div>view on <a href="https://sharingan.dev" target="_blank">Sharingan</a></div>
</div>
<textarea id="raw-code-random-id" class="hidden" rows="1" cols="1">export const hashPassword = (password: string): string => {
const SALT_ROUNDS = 10;

return bcrypt.hashSync(password, SALT_ROUNDS);
};</textarea>
<div class="code-editor-container" id="code-random-id" style="border: solid 1px #f5f5f5; background-color: #f5f5f5">
<button id="btn-copy-random-id" class="btn-copy hidden" style="">
<svg class="ic show" id="ic-copy-random-id" fill="none" stroke="#fff" viewBox="0 0 24 24" xmlns="http:https://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
<svg class="ic hidden" id="ic-copied-random-id" fill="none" stroke="#10B981" viewBox="0 0 24 24" xmlns="http:https://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</button>
<pre><span class="line">export const hashPassword = (password: string): string => {</span>
<span class="line"> const SALT_ROUNDS = 10;</span>
<span class="line"></span>
<span class="line"> return bcrypt.hashSync(password, SALT_ROUNDS);</span>
<span class="line">};</span></pre>
</div>
</div>
<script type="text/javascript" src="https://cdn.com/sharigan/script.js"></script>
</body>
</html>
"
`);
});
});
Loading