Skip to content

Commit

Permalink
Merge pull request #449 from live-codes/import-from-playgrounds
Browse files Browse the repository at this point in the history
Import from playgrounds
  • Loading branch information
hatemhosny authored Oct 11, 2023
2 parents 474f4a3 + 14132a4 commit 6acaa10
Show file tree
Hide file tree
Showing 27 changed files with 289 additions and 124 deletions.
7 changes: 7 additions & 0 deletions docs/docs/features/import.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ For [embedded playgrounds](./embeds.md), use the [SDK](../sdk/index.md) property

[Open in LiveCodes](https://livecodes.io/?x=https://jsbin.com/iwovaj/73/embed?html,js,output)

- Vue Playground:

URL: [https://play.vuejs.org/#eNp9kUFKAzEUhq/yyKYKtUW6K9OCli4UUVFxlU2Z...](https://play.vuejs.org/#eNp9kUFKAzEUhq/yyKYKtUW6K9OCli4UUVFxlU2Zvk5TM0lIXsbCMGdw7QG8g+fxAl7Bl5RWF9Jd3v//7+cLrxUXzg2aiGIsilB65QgCUnRTaVTtrCdoweMKOlh5W0OPoz1ppCmtCQR1qGCS/JPejWpwZpcY4Ov94/vzDZ45eSpNMdzVciEPhLXTC0KeAIr1+bRtc0nXFUOesqqMiwTNWc1teiIF+1KwVQwP26IvKDDCSlWDTbCG6du0K0Vpa6c0+jtHihGlGEN2krfQ2r5eZ418xP5eL9dYvvyjb8I2aVLcewzoG5Ti4NHCV0g7e/54i1t+H0wmj5rTR8wHDFbHxLiLXUazZOw/uUx7lW+gTPUU5ltCE/afSqAp2eW8FHyX2ZGv/+KOBqO8J00nuh/8Wasi)

[Open in LiveCodes](https://livecodes.io/?x=https%3A%2F%2Fplay.vuejs.org%2F%23eNp9kUFKAzEUhq%2FyyKYKtUW6K9OCli4UUVFxlU2Zvk5TM0lIXsbCMGdw7QG8g%2BfxAl7Bl5RWF9Jd3v%2F%2F7%2BcLrxUXzg2aiGIsilB65QgCUnRTaVTtrCdoweMKOlh5W0OPoz1ppCmtCQR1qGCS%2FJPejWpwZpcY4Ov94%2FvzDZ45eSpNMdzVciEPhLXTC0KeAIr1%2BbRtc0nXFUOesqqMiwTNWc1teiIF%2B1KwVQwP26IvKDDCSlWDTbCG6du0K0Vpa6c0%2BjtHihGlGEN2krfQ2r5eZ418xP5eL9dYvvyjb8I2aVLcewzoG5Ti4NHCV0g7e%2F54i1t%2BH0wmj5rTR8wHDFbHxLiLXUazZOw%2FuUx7lW%2BgTPUU5ltCE%2FafSqAp2eW8FHyX2ZGv%2F%2BKOBqO8J00nuh%2F8Wasi)

## Sources

Import is supported from any of the following:
Expand All @@ -53,6 +59,7 @@ Import is supported from any of the following:
- Raw code
- Code in web page DOM
- Code in zip file
- Projects shared in official playgrounds of [TypeScript](https://www.typescriptlang.org/play), [Vue](https://play.vuejs.org/) and [Svelte](https://svelte.dev/repl)
- [Exported project JSON](./export.md) (single project and bulk import)

Import sources are identified by URL patterns (e.g. origin, pathname and extension).
Expand Down
1 change: 1 addition & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const esmBuild = () =>
'editor/codejar/codejar.ts',
'editor/blockly/blockly.ts',
'editor/quill/quill.ts',
'import/import-src.ts',
'services/firebase.ts',
'services/google-fonts.ts',
'languages/language-info.ts',
Expand Down
5 changes: 3 additions & 2 deletions src/livecodes/UI/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const createImportContainer = (eventsManager: ReturnType<typeof createEventsMana
};

export const createImportUI = ({
baseUrl,
modal,
notifications,
eventsManager,
Expand Down Expand Up @@ -81,7 +82,7 @@ export const createImportUI = ({
const importInput = getUrlImportInput(importContainer);
const url = importInput.value;

const imported = await importCode(url, {}, defaultConfig, await getUser?.());
const imported = await importCode(url, {}, defaultConfig, await getUser?.(), baseUrl);
if (imported && Object.keys(imported).length > 0) {
await loadConfig(
{
Expand Down Expand Up @@ -133,7 +134,7 @@ export const createImportUI = ({
}
});

const loadZipFile = (input: HTMLInputElement) => importFromZip(input.files![0]);
const loadZipFile = (input: HTMLInputElement) => importFromZip(input.files![0], populateConfig);

const codeImportInput = getCodeImportInput(importContainer);
eventsManager.addEventListener(codeImportInput, 'change', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/livecodes/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ import {
setConfig,
upgradeAndValidate,
} from './config';
import { isGithub } from './import/github';
import { isGithub } from './import/check-src';
import {
copyToClipboard,
debounce,
Expand Down Expand Up @@ -3911,7 +3911,7 @@ const importExternalContent = async (options: {
}

const importModule: typeof import('./UI/import') = await import(baseUrl + '{{hash:import.js}}');
urlConfig = await importModule.importCode(validUrl, getParams(), getConfig(), user);
urlConfig = await importModule.importCode(validUrl, getParams(), getConfig(), user, baseUrl);

if (Object.keys(urlConfig).length === 0) {
notifications.error('Invalid import URL');
Expand Down
1 change: 1 addition & 0 deletions src/livecodes/html/import.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<li>Raw code</li>
<li>Code in web page DOM</li>
<li>Code in zip file</li>
<li>Official playgrounds<br />(TypeScript, Vue and Svelte)</li>
</ul>
Please visit the
<a href="{{DOCS_BASE_URL}}features/import" target="_blank" rel="noopener"
Expand Down
16 changes: 9 additions & 7 deletions src/livecodes/import/__tests__/hosts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { isGithubUrl } from '../github';
import { isGithubDir } from '../github-dir';
import { isGithubGist } from '../github-gist';
import { isGitlabUrl } from '../gitlab';
import { isGitlabDir } from '../gitlab-dir';
import { isGitlabSnippet } from '../gitlab-snippet';
import { isJsbin } from '../jsbin';
import {
isGithubUrl,
isGithubDir,
isGithubGist,
isGitlabUrl,
isGitlabDir,
isGitlabSnippet,
isJsbin,
} from '../check-src';

describe('match hosts', () => {
test('isGithubUrl', () => {
Expand Down
103 changes: 103 additions & 0 deletions src/livecodes/import/check-src.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
export const getValidUrl = /* @__PURE__ */ (url: string) => {
try {
return url.startsWith('https://') ? new URL(url) : new URL('https://' + url);
} catch (error) {
return;
}
};

export const hostPatterns = {
github: /^(?:(?:http|https):\/\/)?github\.com\/(?:.*)/g,
githubGist: /^(?:(?:http|https):\/\/)?gist\.github\.com(?:\/\S*)?\/(\w+)/g,
gitlab: /^(?:(?:http|https):\/\/)?gitlab\.com\/(?:.*)/g,
codepen: /^(?:(?:http|https):\/\/)?codepen\.io\/(\w+)\/pen\/(\w+)/g,
jsbin: /^(?:(?:(?:http|https):\/\/)?(?:\w+.)?)?jsbin\.com\/((\w)+(\/\d+)?)(?:.*)/g,
typescriptPlayground: /^(?:(?:http|https):\/\/)?(?:www\.)?typescriptlang\.org\/play(?:.*)/g,
vuePlayground: /^(?:(?:http|https):\/\/)?play\.vuejs\.org(?:.*)/g,
sveltePlayground: /^(?:(?:http|https):\/\/)?svelte\.dev\/repl\/(?:.*)/g,
};

export const isCompressedCode = (url: string) => url.startsWith('code/');

export const isCodepen = (url: string, pattern = new RegExp(hostPatterns.codepen)) =>
pattern.test(url);

export const isDom = (url: string) => url.startsWith('dom/');

export const isGithubUrl = (url: string, pattern = new RegExp(hostPatterns.github)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[3] === 'blob';
} catch (error) {
return;
}
};

export const isGithub = (url: string) => isGithubDir(url) || isGithubUrl(url);

export const isGithubDir = (url: string, pattern = new RegExp(hostPatterns.github)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[3] === 'tree' || pathSplit.length === 3;
} catch (error) {
return;
}
};

export const isGithubGist = (url: string, pattern = new RegExp(hostPatterns.githubGist)) =>
pattern.test(url);

export const isGitlabUrl = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[4] === 'blob';
} catch (error) {
return;
}
};

export const isGitlabDir = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[4] === 'tree' || pathSplit.length === 3;
} catch (error) {
return;
}
};

export const isGitlabSnippet = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[pathSplit.length - 2] === 'snippets';
};

export const isJsbin = (url: string, pattern = new RegExp(hostPatterns.jsbin)) => pattern.test(url);

export const isProjectId = (url: string) => url.startsWith('id/');

export const isTypescriptPlayground = (
url: string,
pattern = new RegExp(hostPatterns.typescriptPlayground),
) => pattern.test(url);

export const isVuePlayground = (url: string, pattern = new RegExp(hostPatterns.vuePlayground)) =>
pattern.test(url);

export const isSveltePlayground = (
url: string,
pattern = new RegExp(hostPatterns.sveltePlayground),
) => pattern.test(url);
2 changes: 0 additions & 2 deletions src/livecodes/import/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import type { Config } from '../models';
// eslint-disable-next-line import/no-internal-modules
import { decompress } from '../utils/compression';

export const isCompressedCode = (url: string) => url.startsWith('code/');

export const importCompressedCode = (url: string) => {
const code = url.slice(5);
let config: Partial<Config>;
Expand Down
5 changes: 1 addition & 4 deletions src/livecodes/import/codepen.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { detectLanguage, getLanguageByAlias } from '../languages';
import type { Config, EditorId, Language } from '../models';
import { hostPatterns } from './utils';

export const isCodepen = (url: string, pattern = new RegExp(hostPatterns.codepen)) =>
pattern.test(url);
import { hostPatterns } from './check-src';

const languages: { [key in EditorId]: Language[] } = {
markup: ['html', 'markdown', 'haml'],
Expand Down
2 changes: 0 additions & 2 deletions src/livecodes/import/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { getLanguageByAlias, getLanguageEditorId, languages } from '../languages
import type { EditorId, Language, Config } from '../models';
import { decodeHTML } from '../utils';

export const isDom = (url: string) => url.startsWith('dom/');

type Selectors = {
[key in EditorId]: {
language: Language;
Expand Down
15 changes: 1 addition & 14 deletions src/livecodes/import/github-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@ import { decode } from 'js-base64';
import type { User } from '../models';
// eslint-disable-next-line import/no-internal-modules
import { getGithubHeaders } from '../services/github';
import { hostPatterns, populateConfig } from './utils';

export const isGithubDir = (url: string, pattern = new RegExp(hostPatterns.github)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
const pathSplit = urlObj.pathname.split('/');
return pathSplit[3] === 'tree' || pathSplit.length === 3;
} catch (error) {
return;
}
};
const getValidUrl = (url: string) =>
url.startsWith('https://') ? new URL(url) : new URL('https://' + url);
import { populateConfig } from './utils';

export const importFromGithubDir = async (
url: string,
Expand Down
6 changes: 2 additions & 4 deletions src/livecodes/import/github-gist.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { getLanguageByAlias } from '../languages';
import { getValidUrl, hostPatterns, populateConfig } from './utils';

export const isGithubGist = (url: string, pattern = new RegExp(hostPatterns.githubGist)) =>
pattern.test(url);
import { getValidUrl } from './check-src';
import { populateConfig } from './utils';

export const importFromGithubGist = async (url: string, params: { [key: string]: string }) => {
try {
Expand Down
15 changes: 0 additions & 15 deletions src/livecodes/import/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,6 @@ import { decode } from 'js-base64';
import type { Language, Config, User } from '../models';
import { getLanguageByAlias, getLanguageEditorId } from '../languages/utils';
import { getGithubHeaders } from '../services/github';
import { isGithubDir } from './github-dir';
import { hostPatterns } from './utils';

export const isGithubUrl = (url: string, pattern = new RegExp(hostPatterns.github)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
const pathSplit = urlObj.pathname.split('/');
return pathSplit[3] === 'blob';
} catch (error) {
return;
}
};

export const isGithub = (url: string) => isGithubDir(url) || isGithubUrl(url);

const getValidUrl = (url: string) =>
url.startsWith('https://') ? new URL(url) : new URL('https://' + url);
Expand Down
15 changes: 2 additions & 13 deletions src/livecodes/import/gitlab-dir.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import { getValidUrl, hostPatterns, populateConfig } from './utils';

export const isGitlabDir = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[4] === 'tree' || pathSplit.length === 3;
} catch (error) {
return;
}
};
import { getValidUrl } from './check-src';
import { populateConfig } from './utils';

export const importFromGitlabDir = async (url: string, params: { [key: string]: string }) => {
try {
Expand Down
11 changes: 2 additions & 9 deletions src/livecodes/import/gitlab-snippet.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { getValidUrl, hostPatterns, populateConfig } from './utils';

export const isGitlabSnippet = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[pathSplit.length - 2] === 'snippets';
};
import { getValidUrl } from './check-src';
import { populateConfig } from './utils';

export const importFromGitlabSnippet = async (url: string, params: { [key: string]: string }) => {
try {
Expand Down
17 changes: 3 additions & 14 deletions src/livecodes/import/gitlab.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import { getLanguageByAlias, getLanguageEditorId } from '../languages';
import type { Language, Config } from '../models';
import { getLanguageByAlias, getLanguageEditorId } from '../languages';
import { pipe } from '../utils';
import { getValidUrl, hostPatterns, type FileData } from './utils';

export const isGitlabUrl = (url: string, pattern = new RegExp(hostPatterns.gitlab)) => {
if (!pattern.test(url)) return;
try {
const urlObj = getValidUrl(url);
if (!urlObj) return;
const pathSplit = urlObj.pathname.split('/');
return pathSplit[4] === 'blob';
} catch (error) {
return;
}
};
import type { FileData } from './utils';
import { getValidUrl } from './check-src';

// based on https://github.com/yusanshi/embed-like-gist
const getfileData = async (urlObj: URL): Promise<FileData> => {
Expand Down
13 changes: 13 additions & 0 deletions src/livecodes/import/import-src.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export { importFromCodepen } from './codepen';
export { importFromDom } from './dom';
export { importFromGithub } from './github';
export { importFromGithubDir } from './github-dir';
export { importFromGithubGist } from './github-gist';
export { importFromGitlab } from './gitlab';
export { importFromGitlabDir } from './gitlab-dir';
export { importFromGitlabSnippet } from './gitlab-snippet';
export { importFromJsbin } from './jsbin';
export { importSveltePlayground } from './svelte-playground';
export { importTypescriptPlayground } from './typescript-playground';
export { importVuePlayground } from './vue-playground';
export { importFromUrl } from './url';
Loading

0 comments on commit 6acaa10

Please sign in to comment.