diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml
deleted file mode 100644
index 008661e2d..000000000
--- a/.github/workflows/codesee-arch-diagram.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-# This workflow was added by CodeSee. Learn more at https://codesee.io/
-# This is v2.0 of this workflow file
-on:
- push:
- branches:
- - develop
- pull_request_target:
- types: [opened, synchronize, reopened]
-
-name: CodeSee
-
-permissions: read-all
-
-jobs:
- codesee:
- runs-on: ubuntu-latest
- continue-on-error: true
- name: Analyze the repo with CodeSee
- steps:
- - uses: Codesee-io/codesee-action@v2
- with:
- codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
- codesee-url: https://app.codesee.io
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1244155c1..0443809df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file. See [standa
---
+## [v23](https://github.com/live-codes/livecodes/compare/v22...v23) (2024-02-15)
+
+This release allows using the AI code assistant without browser extension, with no account or API token required, totally for free, by just flipping a switch! (Powered by [codeium](https://codeium.com))
+
+The AI code assistant can be enabled from [editor settings](https://livecodes.io?screen=editor-settings).
+Also the new config property [`enableAI`](https://livecodes.io/docs/configuration/configuration-object#enableai) allows enabling it using the SDK.
+
+In addition, new monochrome (light and dark) editor themes and the font [Astigmata](https://medium.com/codex/astigmata-my-monospace-programming-font-b28ccfa9b025) have been added.
+
+### Bug Fixes
+
+- **UI:** fix theme switch status ([ce0fe2c](https://github.com/live-codes/livecodes/commit/ce0fe2c8a6c45571c8175f92760ac0069c3257e3))
+
+### Features
+
+- **App:** AI code assistant with no accounts or browser extensions ([39916cf](https://github.com/live-codes/livecodes/commit/39916cf4fcf65374bc8cf70536d64a69e7dec4c2))
+- **UI:** allow enabling/disabling AI from UI (editor settings screen) ([c422ded](https://github.com/live-codes/livecodes/commit/c422dedc05347040df68f3fc6edecaf144dc5a56))
+- **Editor:** add Astigmata font ([39cd99b](https://github.com/live-codes/livecodes/commit/39cd99b28bad8c35e4367df18551295e8bf1fc78))
+- **Editor:** add monochrome prism themes ([0a507d1](https://github.com/live-codes/livecodes/commit/0a507d1013b6c9f1c25bc831f7885c1abbd95ade))
+- **Editor:** add monochrome themes for codemirror ([3ad8b37](https://github.com/live-codes/livecodes/commit/3ad8b3719c2a62696c59af45881f0f5c2ff3ba8e))
+- **Editor:** add monochrome themes for monaco ([876294a](https://github.com/live-codes/livecodes/commit/876294ad6d9852fd52903474471b72eeed492545))
+- **Editor:** use AI context from content of multiple editors ([e2066c5](https://github.com/live-codes/livecodes/commit/e2066c51517b25e2312c2e4800e289e8b9a43678))
+- **Result:** load stylesheets in importmap ([b943274](https://github.com/live-codes/livecodes/commit/b943274ec0e0990d856d45d5c29886f7e76ef311))
+
+---
+
## [v22](https://github.com/live-codes/livecodes/compare/v21...v22) (2024-02-03)
Add more docs, including for the [Preview in LiveCodes](https://github.com/live-codes/preview-in-livecodes) GitHub action.
diff --git a/README.md b/README.md
index 2ec40f276..0b87158a2 100644
--- a/README.md
+++ b/README.md
@@ -149,11 +149,6 @@ LiveCodes uses services that are generously provided by:
diff --git a/docs/docs/features/ai.md b/docs/docs/features/ai.md
index 0c9a31eb0..4b6f624bf 100644
--- a/docs/docs/features/ai.md
+++ b/docs/docs/features/ai.md
@@ -1,11 +1,17 @@
# AI Code Assistant 🪄
-LiveCodes supports AI-powered code completion, totally for **free**, using [Codeium](https://codeium.com/), the ultrafast Copilot alternative.
+LiveCodes supports AI-powered code completion, totally for **free** with **no account or API token required**, using [Codeium](https://codeium.com/), the ultrafast Copilot alternative. This can be easily enabled from the UI (as easy as [flipping a switch](#ui)!)
-The large generative machine learning model is capable of understanding the context of your code and comments in order to generate suggestions on what you might want to type next.
+The large generative machine learning model is capable of understanding the context of your code and comments (across the [3 code editors](./projects#markup-editor)) in order to generate suggestions on what you might want to type next.
It has a wide range of language support, and it works everywhere (in the [standalone app](../getting-started.md#standalone-app), [embedded playgrounds](./embeds.md) and [self-hosted](./self-hosting.md) apps).
+Currently, only [Monaco editor](./editor-settings.md#code-editor) is supported. Wider editor support is planned.
+
+Powered by:
+
+.
+
## Examples:
JavaScript:
@@ -24,23 +30,25 @@ Python:
## Instructions
-1. Install [Codeium chrome extension](https://codeium.com/chrome_tutorial).
-2. Login to Codeium.
-3. Enjoy the magic!
+The AI code assistant can be enabled from:
-Currently, only [Monaco editor](./editor-settings.md#code-editor) on desktop Chrome browser is supported. Wider editor and browser support is planned.
+### UI
-:::caution Note
+The [editor settings](./editor-settings.md) screen (App menu → Editor Settings → Enable AI Code Assistant).
-Please note that codeium extension sends your code to their servers for code completion. However, your code is not used for training their model. Check codeium [FAQ](https://codeium.com/faq#Will-Codeium-regurgitate-private-code%3F) and [privacy policy](https://codeium.com/privacy-policy) for more details.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
-:::
+
+
+![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings-1.png)
+
+**Note**
-## Disabling AI Code Assistant
+When set from the UI, this configuration is saved locally to [user settings](./user-settings.md) and is remembered across sessions.
-Obviously, if you want to disable the AI code completion on your device, just disable the browser extension.
+### Configuration
-However, if you want to prevent users from using AI code completion in playgrounds that you embed in your webpages (e.g. for code challenges or exercises), you may achieve that by adding the [query parameter](../configuration/query-params.md) `disableAI` (e.g. https://livecodes.io/?disableAI).
+Alternatively, this can be enabled (_only for the current session_) using the [`enableAI`](../configuration/configuration-object.md#enableai) property in the [configuration object](../configuration/configuration-object.md). This can be used to enable the AI code assistant in [embedded playgrounds](./embeds.md).
Example:
@@ -48,10 +56,17 @@ Example:
import { createPlayground } from 'livecodes';
createPlayground('#container', {
- params: {
- html: '
Hello LiveCodes!
',
+ config: {
// highlight-next-line
- disableAI: true,
+ enableAI: true,
},
});
```
+
+Also this can be enabled using [query params](../configuration/query-params.md) (e.g. https://livecodes.io/?enableAI).
+
+:::caution Note
+
+Please note that when using Codeium AI assistant, your code is sent to their servers for code completion. However, your code is not used for training their model. Check Codeium [FAQ](https://codeium.com/faq#Will-Codeium-regurgitate-private-code%3F) and [privacy policy](https://codeium.com/privacy-policy) for more details.
+
+:::
diff --git a/docs/docs/features/assets.md b/docs/docs/features/assets.md
index f90fa18fd..e369c2371 100644
--- a/docs/docs/features/assets.md
+++ b/docs/docs/features/assets.md
@@ -8,6 +8,10 @@ In addition, assets are supported in [sync](./sync.md), [backup](./backup-restor
The `Assets` screen can be accessed from app menu → Assets
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
Assets are either:
- Encoded as [data URLs](./data-urls.md).
diff --git a/docs/docs/features/backup-restore.md b/docs/docs/features/backup-restore.md
index 4d373cbae..ef41791e5 100644
--- a/docs/docs/features/backup-restore.md
+++ b/docs/docs/features/backup-restore.md
@@ -4,6 +4,10 @@ LiveCodes data can be backed-up, so that it can be later restored on the same or
The Backup/Restore screen can be accessed from the app menu → Backup / Restore.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
## Backup
![LiveCode Backup](../../static/img/screenshots/backup.jpg)
diff --git a/docs/docs/features/broadcast.md b/docs/docs/features/broadcast.md
index 6518de741..0d3607d2e 100644
--- a/docs/docs/features/broadcast.md
+++ b/docs/docs/features/broadcast.md
@@ -10,7 +10,11 @@ Broadcast can only be performed from the full app, and not from embedded playgro
:::
-The `Broadcast` screen can be accessed from the app menu → Broadcast, or from the Broadcast icon in the [tools pane](./tools-pane.md) (below the result page).
+The `Broadcast` screen can be accessed from the Broadcast icon in the [tools pane](./tools-pane.md) (below the result page), or from the app menu → Broadcast.
+
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
![Broadcast UI](./../../static/img/screenshots/broadcast.jpg)
diff --git a/docs/docs/features/deploy.md b/docs/docs/features/deploy.md
index 8662e6c1a..aebecd887 100644
--- a/docs/docs/features/deploy.md
+++ b/docs/docs/features/deploy.md
@@ -4,6 +4,10 @@ The result page (of any number of projects) can be deployed and hosted at [GitHu
The `Deploy` screen can be accessed from the app menu → Deploy.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
![LiveCodes Deploy](../../static/img/screenshots/deploy.jpg)
The result page (and optionally the source code) is pushed to `gh-pages` branch of a **public** GitHub repo (new or existing). The page, shortly, becomes available on `https://{user}.github.io/{repo}/`.
diff --git a/docs/docs/features/editor-settings.md b/docs/docs/features/editor-settings.md
index f33037f8f..6f026aabf 100644
--- a/docs/docs/features/editor-settings.md
+++ b/docs/docs/features/editor-settings.md
@@ -4,14 +4,24 @@ LiveCodes allows a lot of flexibility for configuring which code editor to use a
`Editor Settings` screen can be accessed from app menu → Editor Settings.
-![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings.jpg)
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
-![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings2.jpg)
+
+
+![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings-1.png)
+
+![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings-2.png)
+
+![LiveCodes Editor Settings](../../static/img/screenshots/editor-settings-3.png)
A preview code editor is displayed to preview the settings in real time.
The settings selected in the `Editor Settings` screen are saved locally to [user settings](./user-settings.md) and are used subsequently. These include:
+### Enable AI Code Assistant
+
+Enables the [AI code assistant](./ai.md). (Free and no account required)
+
### Code Editor
The following code editors are supported:
diff --git a/docs/docs/features/embeds.md b/docs/docs/features/embeds.md
index 130658cc5..8fcb10f19 100644
--- a/docs/docs/features/embeds.md
+++ b/docs/docs/features/embeds.md
@@ -18,6 +18,10 @@ The embedding web page can communicate with the playground using a powerful [SDK
In the [standalone app](../getting-started.md#standalone-app), the Embed Screen can be accessed from app menu → Embed.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
It shows a preview of the embedded playground, allows customizations of [embed options](../sdk/js-ts.md#embed-options) and provides generated code that can be added to the web page that will embed the playground.
![LiveCodes embed](../../static/img/screenshots/embed1.png)
diff --git a/docs/docs/features/external-resources.md b/docs/docs/features/external-resources.md
index 3411a0ec0..8f32211d9 100644
--- a/docs/docs/features/external-resources.md
+++ b/docs/docs/features/external-resources.md
@@ -4,6 +4,10 @@
URLs to external CSS stylesheets and JS scripts can be added to the page from the UI using the app menu → External Resources. In addition, there is a button to the External Resources in the toolbar below the editors.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
URLs to stylesheets/scripts should be added each in a separate line.
Stylesheets and scripts are loaded in the [result page](./result.md) before editor codes. Thus, CSS properties defined in external stylesheets can be overriden in the style editor. Global javascript variables defined in external scripts are available to code in the script editor.
diff --git a/docs/docs/features/import.md b/docs/docs/features/import.md
index 3d650bf71..3d852704c 100644
--- a/docs/docs/features/import.md
+++ b/docs/docs/features/import.md
@@ -6,6 +6,10 @@ LiveCodes supports importing code from a wide variety of sources.
The Import screen can be accessed from the app menu → Import.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
![LiveCodes Import](../../static/img/screenshots/import.jpg)
Alternatively, a URL of any of the sources can be imported on-load by adding it as a value to [query param](../configuration/query-params.md) key: `x`. This is easier using the [bookmarklet](../bookmarklet.md).
diff --git a/docs/docs/features/share.md b/docs/docs/features/share.md
index 1fc035b9a..b45ce0557 100644
--- a/docs/docs/features/share.md
+++ b/docs/docs/features/share.md
@@ -4,7 +4,7 @@ It is easy to share LiveCodes projects!
A URL is generated to load the shared project. This URL can be copied or shared to different social media.
-The share screen can be accessed from app menu → Share.
+The share screen can be accessed from the share icon at the top right or from the app menu → Share.
![LiveCodes Share](../../static/img/screenshots/share.jpg)
diff --git a/docs/docs/features/snippets.md b/docs/docs/features/snippets.md
index a077c5477..caedabe02 100644
--- a/docs/docs/features/snippets.md
+++ b/docs/docs/features/snippets.md
@@ -6,6 +6,10 @@ Code snippets are saved locally on user's device. However, they are supported in
Code snippets screen can be accessed from app menu → Code Snippets.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
Each snippet has a title, description, language and code.
![Code Snippets](../../static/img/screenshots/add-snippet.png)
diff --git a/docs/docs/features/sync.md b/docs/docs/features/sync.md
index 33d18c28b..96e484e45 100644
--- a/docs/docs/features/sync.md
+++ b/docs/docs/features/sync.md
@@ -6,6 +6,10 @@ A GitHub account is required. The user must give access to [(Private Repos) whil
The Sync screen can be accessed from the app menu → Sync.
+import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx';
+
+
+
![LiveCodes Sync](../../static/img/screenshots/sync.png)
Data can be synchronized to a new (**private**) or existing repo.
diff --git a/docs/static/img/credits/codeium.svg b/docs/static/img/credits/codeium.svg
new file mode 100644
index 000000000..4d4dc9f51
--- /dev/null
+++ b/docs/static/img/credits/codeium.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/docs/static/img/screenshots/editor-settings-1.png b/docs/static/img/screenshots/editor-settings-1.png
new file mode 100644
index 000000000..2e5071411
Binary files /dev/null and b/docs/static/img/screenshots/editor-settings-1.png differ
diff --git a/docs/static/img/screenshots/editor-settings-2.png b/docs/static/img/screenshots/editor-settings-2.png
new file mode 100644
index 000000000..67d8eac8f
Binary files /dev/null and b/docs/static/img/screenshots/editor-settings-2.png differ
diff --git a/docs/static/img/screenshots/editor-settings-3.png b/docs/static/img/screenshots/editor-settings-3.png
new file mode 100644
index 000000000..74e99ef14
Binary files /dev/null and b/docs/static/img/screenshots/editor-settings-3.png differ
diff --git a/package.json b/package.json
index 1d4e4aa44..739a9cf90 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "livecodes",
"version": "0.0.0",
- "appVersion": "22",
+ "appVersion": "23",
"description": "Code Playground That Just Works!",
"author": "Hatem Hosny",
"license": "MIT",
diff --git a/src/livecodes/UI/editor-settings.ts b/src/livecodes/UI/editor-settings.ts
index b185b3bea..642793a81 100644
--- a/src/livecodes/UI/editor-settings.ts
+++ b/src/livecodes/UI/editor-settings.ts
@@ -50,8 +50,16 @@ export const createEditorSettingsUI = async ({
name: keyof UserConfig | `editorTheme-${Config['editor']}-${Config['theme']}`;
options: Array<{ label?: string; value: string; checked?: boolean }>;
help?: string;
+ note?: string;
}
const formFields: FormField[] = [
+ {
+ title: 'Enable AI Code Assistant',
+ name: 'enableAI',
+ options: [{ value: 'true' }],
+ help: `${process.env.DOCS_BASE_URL}features/ai`,
+ note: `Powered by `,
+ },
{
title: 'Editor',
name: 'editor',
@@ -210,11 +218,11 @@ export const createEditorSettingsUI = async ({
baseUrl,
container: previewContainer,
editorId: 'editorSettings',
- getLanguageExtension: () => 'tsx',
+ getLanguageExtension: () => 'jsx',
isEmbed: false,
isHeadless: false,
- language: 'tsx',
- mapLanguage: () => 'typescript',
+ language: 'jsx',
+ mapLanguage: () => 'javascript',
readonly: false,
value: editorContent,
...getEditorConfig(userConfig),
@@ -342,6 +350,7 @@ export const createEditorSettingsUI = async ({
input.value = option.value;
input.checked =
field.name === 'theme' ? optionValue === 'dark' : optionValue === option.value;
+
optionContainer.appendChild(input);
if (isCheckBox) {
@@ -354,6 +363,13 @@ export const createEditorSettingsUI = async ({
optionContainer.appendChild(label);
}
});
+
+ if (field.note) {
+ const note = document.createElement('div');
+ note.classList.add('input-container', 'field-note');
+ note.innerHTML = field.note;
+ form.appendChild(note);
+ }
});
let editor = await initializeEditor(editorOptions);
@@ -454,7 +470,7 @@ const editorContent = `
import React, { useState } from 'react';
import { createRoot } from "react-dom/client";
-function App(props: { name: string }) {
+function App(props) {
const [count, setCount] = useState(0);
// increment on click!
const onClick = () => setCount(count + 1);
@@ -472,6 +488,6 @@ function App(props: { name: string }) {
);
}
-const root = createRoot(document.querySelector("#app"));
+const root = createRoot(document.querySelector("#root"));
root.render();
`.trimStart();
diff --git a/src/livecodes/config/config.ts b/src/livecodes/config/config.ts
index 63adb0029..82f589f5f 100644
--- a/src/livecodes/config/config.ts
+++ b/src/livecodes/config/config.ts
@@ -75,6 +75,7 @@ export const getEditorConfig = (config: Config | UserConfig): EditorConfig => ({
wordWrap: config.wordWrap,
closeBrackets: config.closeBrackets,
emmet: config.emmet,
+ enableAI: config.enableAI,
editorMode: config.editorMode,
});
diff --git a/src/livecodes/config/default-config.ts b/src/livecodes/config/default-config.ts
index ffd3af3c6..30629c452 100644
--- a/src/livecodes/config/default-config.ts
+++ b/src/livecodes/config/default-config.ts
@@ -62,6 +62,7 @@ export const defaultConfig: Config = {
singleQuote: false,
trailingComma: true,
emmet: true,
+ enableAI: false,
editorMode: undefined,
version: process.env.VERSION as string,
};
diff --git a/src/livecodes/config/validate-config.ts b/src/livecodes/config/validate-config.ts
index 7dad6dabb..c0666a589 100644
--- a/src/livecodes/config/validate-config.ts
+++ b/src/livecodes/config/validate-config.ts
@@ -152,6 +152,7 @@ export const validateConfig = (config: Partial): Partial => {
...(is(config.singleQuote, 'boolean') ? { singleQuote: config.singleQuote } : {}),
...(is(config.trailingComma, 'boolean') ? { trailingComma: config.trailingComma } : {}),
...(is(config.emmet, 'boolean') ? { emmet: config.emmet } : {}),
+ ...(is(config.enableAI, 'boolean') ? { enableAI: config.enableAI } : {}),
...(includes(editorModes, config.editorMode) ? { editorMode: config.editorMode } : {}),
...(is(config.imports, 'object') ? { imports: config.imports } : {}),
...(is(config.types, 'object') ? { types: config.types } : {}),
diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts
index 8b4cbecb8..6d11763e4 100644
--- a/src/livecodes/core.ts
+++ b/src/livecodes/core.ts
@@ -1650,6 +1650,7 @@ const setTheme = (theme: Theme, editorTheme: Config['editorTheme']) => {
const root = document.querySelector(':root');
root?.classList.remove(...themes);
root?.classList.add(theme);
+ UI.getThemeToggle().checked = theme === 'dark';
getAllEditors().forEach((editor) => {
editor?.setTheme(theme, editorTheme);
customEditors[editor?.getLanguage()]?.setTheme(theme);
diff --git a/src/livecodes/editor/codejar/prism-themes.ts b/src/livecodes/editor/codejar/prism-themes.ts
index f2674a6a4..57bd7e77d 100644
--- a/src/livecodes/editor/codejar/prism-themes.ts
+++ b/src/livecodes/editor/codejar/prism-themes.ts
@@ -102,6 +102,18 @@ export const prismThemes: Array<{
title: 'Material Oceanic',
url: prismThemesBaseUrl + 'prism-material-oceanic.css',
},
+ {
+ name: 'monochrome',
+ title: 'Monochrome',
+ // code[class*="language-"],pre[class*="language-"]{color:#24292e;background-color:#fffffe;}
+ url: 'data:text/css;charset=UTF-8;base64,Y29kZVtjbGFzcyo9Imxhbmd1YWdlLSJdLHByZVtjbGFzcyo9Imxhbmd1YWdlLSJde2NvbG9yOiMyNDI5MmU7YmFja2dyb3VuZC1jb2xvcjojZmZmZmZlO30=',
+ },
+ {
+ name: 'monochrome-dark',
+ title: 'Monochrome Dark',
+ // code[class*="language-"],pre[class*="language-"]{color:#e2e2e3;background-color:#24292e;}
+ url: 'data:text/css;charset=UTF-8;base64,Y29kZVtjbGFzcyo9Imxhbmd1YWdlLSJdLHByZVtjbGFzcyo9Imxhbmd1YWdlLSJde2NvbG9yOiNlMmUyZTM7YmFja2dyb3VuZC1jb2xvcjojMjQyOTJlO30=',
+ },
{ name: 'night-owl', title: 'Night Owl', url: prismThemesBaseUrl + 'prism-night-owl.css' },
{ name: 'nord', title: 'Nord', url: prismThemesBaseUrl + 'prism-nord.css' },
{ name: 'okaidia', title: 'Okaidia', url: prismOfficialThemesBaseUrl + 'prism-okaidia.css' },
diff --git a/src/livecodes/editor/codemirror/codemirror-themes.ts b/src/livecodes/editor/codemirror/codemirror-themes.ts
index 6b668c989..4c22ad0b0 100644
--- a/src/livecodes/editor/codemirror/codemirror-themes.ts
+++ b/src/livecodes/editor/codemirror/codemirror-themes.ts
@@ -1,3 +1,6 @@
+import { EditorView } from 'codemirror';
+import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
+
import type { CodemirrorTheme } from '../../models';
import {
cm6ThemeBasicDarkUrl,
@@ -139,6 +142,8 @@ export const codemirrorThemes: Array<{
url: ddietrCmThemesBaseUrl + 'material-light.js',
exportName: 'materialLight',
},
+ { name: 'monochrome', title: 'Monochrome' },
+ { name: 'monochrome-dark', title: 'Monochrome Dark' },
{
name: 'noctis-lilac',
title: 'Noctis Lilac',
@@ -201,3 +206,79 @@ export const codemirrorThemes: Array<{
exportName: 'tomorrow',
},
];
+
+// from https://github.com/vadimdemedes/thememirror/blob/main/source/create-theme.ts
+const createTheme = ({
+ variant,
+ settings,
+ styles,
+}: {
+ variant: 'light' | 'dark';
+ settings: any;
+ styles: any[];
+}) => {
+ const theme = EditorView.theme(
+ {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ '&': {
+ backgroundColor: settings.background,
+ color: settings.foreground,
+ },
+ '.cm-content': {
+ caretColor: settings.caret,
+ },
+ '.cm-cursor, .cm-dropCursor': {
+ borderLeftColor: settings.caret,
+ },
+ '&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-selectionMatch, .cm-content ::selection':
+ {
+ backgroundColor: settings.selection,
+ },
+ '.cm-activeLine': {
+ backgroundColor: settings.lineHighlight,
+ },
+ '.cm-gutters': {
+ backgroundColor: settings.gutterBackground,
+ color: settings.gutterForeground,
+ },
+ '.cm-activeLineGutter': {
+ backgroundColor: settings.lineHighlight,
+ },
+ },
+ {
+ dark: variant === 'dark',
+ },
+ );
+ const highlightStyle = HighlightStyle.define(styles);
+ const extension = [theme, syntaxHighlighting(highlightStyle)];
+ return extension;
+};
+
+export const customThemes = {
+ monochrome: createTheme({
+ variant: 'light',
+ settings: {
+ background: '#fffffe',
+ foreground: '#24292e',
+ caret: '#24292e',
+ selection: '#c8c8fa',
+ gutterBackground: '#fffffe',
+ gutterForeground: '#24292e',
+ lineHighlight: '#f1faff',
+ },
+ styles: [],
+ }),
+ 'monochrome-dark': createTheme({
+ variant: 'dark',
+ settings: {
+ background: '#24292e',
+ foreground: '#e2e2e3',
+ caret: '#e2e2e3',
+ selection: '#444d56',
+ gutterBackground: '#24292e',
+ gutterForeground: '#e2e2e3',
+ lineHighlight: '#444d56',
+ },
+ styles: [],
+ }),
+};
diff --git a/src/livecodes/editor/codemirror/codemirror.ts b/src/livecodes/editor/codemirror/codemirror.ts
index c8b0304f4..230cd646a 100644
--- a/src/livecodes/editor/codemirror/codemirror.ts
+++ b/src/livecodes/editor/codemirror/codemirror.ts
@@ -33,7 +33,7 @@ import { getEditorTheme } from '../themes';
import { basicSetup, lineNumbers, closeBrackets } from './basic-setup';
import { editorLanguages } from './editor-languages';
import { colorPicker, indentationMarkers, vscodeKeymap } from './extras';
-import { codemirrorThemes } from './codemirror-themes';
+import { codemirrorThemes, customThemes } from './codemirror-themes';
export const createEditor = async (options: EditorOptions): Promise => {
const { container, readonly, isEmbed, editorId, getFormatterConfig, getFontFamily } = options;
@@ -56,6 +56,7 @@ export const createEditor = async (options: EditorOptions): Promise
'&': { backgroundColor: '#ffffff' },
}),
],
+ ...customThemes,
};
const defaultThemes: Record = { dark: 'one-dark', light: 'cm-light' };
const getActiveTheme = () => themes[theme] || themes[defaultThemes[options.theme]] || [];
diff --git a/src/livecodes/editor/fonts.ts b/src/livecodes/editor/fonts.ts
index b65e95ead..e49708218 100644
--- a/src/livecodes/editor/fonts.ts
+++ b/src/livecodes/editor/fonts.ts
@@ -1,5 +1,6 @@
import {
fontAnonymousProUrl,
+ fontAstigmataUrl,
fontCascadiaCodeUrl,
fontCodeNewRomanUrl,
fontComicMonoUrl,
@@ -44,6 +45,11 @@ export const fonts: Font[] = [
name: 'Anonymous Pro',
url: fontAnonymousProUrl,
},
+ {
+ id: 'astigmata',
+ name: 'Astigmata',
+ url: fontAstigmataUrl,
+ },
{
id: 'cascadia-code',
name: 'Cascadia Code',
diff --git a/src/livecodes/editor/monaco/monaco-themes.ts b/src/livecodes/editor/monaco/monaco-themes.ts
index 55c81045b..f5f5fd1e5 100644
--- a/src/livecodes/editor/monaco/monaco-themes.ts
+++ b/src/livecodes/editor/monaco/monaco-themes.ts
@@ -1,3 +1,4 @@
+import type * as Monaco from 'monaco-editor';
import type { MonacoTheme } from '../../models';
import { monacoThemesBaseUrl } from '../../vendors';
@@ -160,6 +161,14 @@ export const monacoThemes: Array<{ name: MonacoTheme; title: string; url?: strin
title: 'Merbivore',
url: monacoThemesBaseUrl + 'Merbivore.json',
},
+ {
+ name: 'monochrome',
+ title: 'Monochrome',
+ },
+ {
+ name: 'monochrome-dark',
+ title: 'Monochrome Dark',
+ },
{
name: 'monoindustrial',
title: 'monoindustrial',
@@ -284,3 +293,58 @@ export const monacoThemes: Array<{ name: MonacoTheme; title: string; url?: strin
url: monacoThemesBaseUrl + 'Zenburnesque.json',
},
];
+
+export const customThemes: Array<{ name: MonacoTheme; theme: Monaco.editor.IStandaloneThemeData }> =
+ [
+ {
+ name: 'custom-vs-light',
+ theme: {
+ base: 'vs',
+ inherit: true,
+ rules: [{ token: 'comment', fontStyle: 'italic' }],
+ colors: {},
+ },
+ },
+ {
+ name: 'custom-vs-dark',
+ theme: {
+ base: 'vs-dark',
+ inherit: true,
+ rules: [{ token: 'comment', fontStyle: 'italic' }],
+ colors: {},
+ },
+ },
+ {
+ name: 'monochrome',
+ theme: {
+ base: 'vs',
+ inherit: false,
+ rules: [],
+ colors: {
+ 'editor.foreground': '#24292e',
+ 'editorBracketHighlight.foreground1': '#24292e',
+ 'editorBracketHighlight.foreground2': '#24292e',
+ 'editorBracketHighlight.foreground3': '#24292e',
+ 'editorBracketHighlight.foreground4': '#24292e',
+ 'editorBracketHighlight.unexpectedBracket.foreground': '#24292e',
+ },
+ },
+ },
+ {
+ name: 'monochrome-dark',
+ theme: {
+ base: 'vs-dark',
+ inherit: false,
+ rules: [],
+ colors: {
+ 'editor.foreground': '#e2e2e3',
+ 'editor.background': '#24292e',
+ 'editorBracketHighlight.foreground1': '#e2e2e3',
+ 'editorBracketHighlight.foreground2': '#e2e2e3',
+ 'editorBracketHighlight.foreground3': '#e2e2e3',
+ 'editorBracketHighlight.foreground4': '#e2e2e3',
+ 'editorBracketHighlight.unexpectedBracket.foreground': '#e2e2e3',
+ },
+ },
+ },
+ ];
diff --git a/src/livecodes/editor/monaco/monaco.ts b/src/livecodes/editor/monaco/monaco.ts
index 0c00d184b..f892131c0 100644
--- a/src/livecodes/editor/monaco/monaco.ts
+++ b/src/livecodes/editor/monaco/monaco.ts
@@ -1,6 +1,6 @@
/* eslint-disable import/no-internal-modules */
// eslint-disable-next-line import/no-unresolved
-import type * as Monaco from 'monaco-editor'; // only for typescript types
+import type * as Monaco from 'monaco-editor';
import type {
EditorLibrary,
@@ -17,12 +17,12 @@ import type {
Config,
} from '../../models';
import { cloneObject, getRandomString, loadScript } from '../../utils/utils';
-import { emmetMonacoUrl, monacoEmacsUrl, monacoVimUrl } from '../../vendors';
+import { codeiumProviderUrl, emmetMonacoUrl, monacoEmacsUrl, monacoVimUrl } from '../../vendors';
import { getImports } from '../../compiler/import-map';
import { getEditorModeNode } from '../../UI/selectors';
import { pkgInfoService } from '../../services/pkgInfo';
import { getEditorTheme } from '../themes';
-import { monacoThemes } from './monaco-themes';
+import { customThemes, monacoThemes } from './monaco-themes';
type Options = Monaco.editor.IStandaloneEditorConstructionOptions;
@@ -30,6 +30,9 @@ let monacoGloballyLoaded = false;
const disposeEmmet: { html?: any; css?: any; jsx?: any; disabled?: boolean } = {};
let monaco: typeof Monaco;
const loadedThemes = new Set();
+let codeiumProvider: { dispose: () => void } | undefined;
+// track editors for providing context for AI
+let editors: Monaco.editor.IStandaloneCodeEditor[] = [];
export const createEditor = async (options: EditorOptions): Promise => {
const {
@@ -79,18 +82,7 @@ export const createEditor = async (options: EditorOptions): Promise
throw new Error('Failed to load monaco editor');
}
- monaco.editor.defineTheme('custom-vs-light', {
- base: 'vs',
- inherit: true,
- rules: [{ token: 'comment', fontStyle: 'italic' }],
- colors: {},
- });
- monaco.editor.defineTheme('custom-vs-dark', {
- base: 'vs-dark',
- inherit: true,
- rules: [{ token: 'comment', fontStyle: 'italic' }],
- colors: {},
- });
+ customThemes.forEach((t) => monaco.editor.defineTheme(t.name, t.theme));
const loadTheme = async (theme: Theme, editorTheme: Config['editorTheme']) => {
const selectedTheme = getEditorTheme({
@@ -293,6 +285,11 @@ export const createEditor = async (options: EditorOptions): Promise
});
setModel(editor, options.value, language);
+ const contentEditors: Array = ['markup', 'style', 'script', 'tests'];
+ if (contentEditors.includes(editorId)) {
+ editors.push(editor);
+ }
+
if (editorOptions.theme === 'vs-light') container.style.backgroundColor = '#fff';
if (editorOptions.theme?.startsWith('http') || editorOptions.theme?.startsWith('./')) {
fetch(editorOptions.theme)
@@ -542,6 +539,7 @@ export const createEditor = async (options: EditorOptions): Promise
configureEmmet(settings.emmet);
configureEditorMode(settings.editorMode);
editor.updateOptions(editorOptions);
+ configureCodeium(settings.enableAI);
};
const undo = () => {
@@ -569,8 +567,30 @@ export const createEditor = async (options: EditorOptions): Promise
setTimeout(() => editor.revealPositionInCenter(newPosition, 0), 50);
};
+ const configureCodeium = (enabled: boolean) => {
+ if (!enabled) {
+ codeiumProvider?.dispose();
+ codeiumProvider = undefined;
+ return;
+ }
+
+ // already loaded or loading
+ if (codeiumProvider) {
+ return;
+ }
+
+ // avoid race condition between different editors
+ codeiumProvider = { dispose: () => 'loading...' };
+
+ import(codeiumProviderUrl).then((codeiumModule) => {
+ codeiumProvider = codeiumModule.registerCodeiumProvider(monaco, {
+ getEditors: () => editors,
+ });
+ });
+ };
+
const destroy = () => {
- configureEmmet(false);
+ editors = editors.filter((e) => e !== editor);
editorMode?.dispose();
listeners.length = 0;
clearTypes(true);
diff --git a/src/livecodes/main.ts b/src/livecodes/main.ts
index ed4512f7a..7b07f31ae 100644
--- a/src/livecodes/main.ts
+++ b/src/livecodes/main.ts
@@ -25,7 +25,11 @@ export const loading: EmbedOptions['loading'] = !isEmbed
: loadingParam === 'lazy' || loadingParam === 'click' || loadingParam === 'eager'
? loadingParam
: 'lazy';
-export const disableAI = params.get('disableAI') != null && params.get('disableAI') !== 'false';
+
+// for backwards compatibility with using extension
+export const disableAI =
+ (params.get('disableAI') != null && params.get('disableAI') !== 'false') ||
+ params.get('enableAI') === 'false';
export const livecodes = (container: string, config: Partial = {}): Promise =>
new Promise(async (resolve) => {
diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts
index b154918ad..3da98b53e 100644
--- a/src/livecodes/result/result-page.ts
+++ b/src/livecodes/result/result-page.ts
@@ -109,16 +109,28 @@ export const createResultPage = async ({
dom.head.appendChild(stylesheet);
});
+ const configImports = {
+ ...config.imports,
+ ...config.customSettings.imports,
+ };
+
// stylesheets imported in script editor
const stylesheetImports = getImports(code.script.compiled).filter(
- (mod) => (mod.endsWith('.css') && !mod.startsWith('.')) || mod.startsWith('data:text/css'),
+ (mod) =>
+ mod.startsWith('data:text/css') ||
+ (mod.endsWith('.css') && (Object.keys(configImports).includes(mod) || !mod.startsWith('.'))),
);
stylesheetImports.forEach((mod) => {
- const url = modulesService.getUrl(mod);
+ const url = configImports[mod] || modulesService.getUrl(mod);
const stylesheet = dom.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = url;
dom.head.appendChild(stylesheet);
+
+ if (Object.keys(configImports).includes(mod)) {
+ // map stylesheets in import map to empty script to avoid loading css as js
+ configImports[mod] = 'data:text/javascript;charset=UTF-8;base64,';
+ }
});
// editor styles
@@ -287,8 +299,7 @@ export const createResultPage = async ({
...scriptImport,
...compilerImports,
...(runTests ? testImports : {}),
- ...config.imports,
- ...config.customSettings.imports,
+ ...configImports,
};
if (Object.keys(importMaps).length > 0) {
const esModuleShims = dom.createElement('script');
diff --git a/src/livecodes/styles/app.scss b/src/livecodes/styles/app.scss
index 531f66e75..ecb178210 100644
--- a/src/livecodes/styles/app.scss
+++ b/src/livecodes/styles/app.scss
@@ -2208,6 +2208,15 @@ i.arrow {
height: 250px !important;
max-height: 250px;
}
+
+ #editor-settings-form {
+ margin-top: 1.5em;
+ }
+
+ .field-note {
+ font-size: 0.8em;
+ margin-top: -1em;
+ }
}
#resources-container {
diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts
index 72b27deeb..1351688e3 100644
--- a/src/livecodes/vendors.ts
+++ b/src/livecodes/vendors.ts
@@ -80,6 +80,10 @@ export const cm6ThemeSolarizedDarkUrl = /* @__PURE__ */ getUrl(
'cm6-theme-solarized-dark@0.2.0/dist/index.js',
);
+export const codeiumProviderUrl = /* @__PURE__ */ getUrl(
+ '@live-codes/monaco-codeium-provider@0.2.2/dist/index.js',
+);
+
export const coffeeScriptUrl = /* @__PURE__ */ getUrl(
'coffeescript@2.7.0/lib/coffeescript-browser-compiler-legacy/coffeescript.js',
);
@@ -116,6 +120,10 @@ export const fontAnonymousProUrl = /* @__PURE__ */ getUrl(
'@fontsource/anonymous-pro@4.5.9/index.css',
);
+export const fontAstigmataUrl = /* @__PURE__ */ getUrl(
+ 'gh:hatemhosny/astigmata-font@6d0ee00a07fb1932902f0b81a504d075d47bd52f/index.css',
+);
+
export const fontCascadiaCodeUrl = /* @__PURE__ */ getUrl(
'@fontsource/cascadia-code@4.2.1/index.css',
);
diff --git a/src/sdk/models.ts b/src/sdk/models.ts
index 9db2a963f..c5f15afac 100644
--- a/src/sdk/models.ts
+++ b/src/sdk/models.ts
@@ -126,6 +126,7 @@ export interface EditorConfig {
closeBrackets: boolean;
emmet: boolean;
editorMode: 'vim' | 'emacs' | undefined;
+ enableAI: boolean;
}
export interface FormatterConfig {
@@ -759,6 +760,8 @@ export type MonacoTheme =
| 'clouds'
| 'cobalt'
| 'cobalt2'
+ | 'custom-vs-light' // hidden
+ | 'custom-vs-dark' // hidden
| 'dawn'
| 'dracula'
| 'dreamweaver'
@@ -779,6 +782,8 @@ export type MonacoTheme =
| 'magicwb-amiga'
| 'merbivore-soft'
| 'merbivore'
+ | 'monochrome'
+ | 'monochrome-dark'
| 'monokai'
| 'monokai-bright'
| 'monoindustrial'
@@ -827,6 +832,8 @@ export type CodemirrorTheme =
| 'gruvbox-light'
| 'material-dark'
| 'material-light'
+ | 'monochrome'
+ | 'monochrome-dark'
| 'noctis-lilac'
| 'nord'
| 'one-dark'
@@ -868,6 +875,8 @@ export type CodejarTheme =
| 'material-dark'
| 'material-light'
| 'material-oceanic'
+ | 'monochrome'
+ | 'monochrome-dark'
| 'night-owl'
| 'nord'
| 'okaidia'