Skip to content
Closed
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
9 changes: 9 additions & 0 deletions config/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ module.exports = (env, argv) => {
},
},
compress: emulateProdServer,
//TODO: Remove this when a more sensible paradigm for code-running is established
proxy: [
{
context: ["/api/v4"],
target: "https://7f67beaa.compilers.sphere-engine.com",
secure: false,
changeOrigin: true
}
]
},
plugins: [
// create an html page for every item in ./site/views
Expand Down
22 changes: 22 additions & 0 deletions src/rich-text/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { inTable } from "./tables";

export * from "./tables";
export * from "./list";
export * from "./run-code"

// indent code with four [SPACE] characters (hope you aren't a "tabs" person)
const CODE_INDENT_STR = " ";
Expand Down Expand Up @@ -505,6 +506,27 @@ export function nodeTypeActive(
};
}

export function nodeTypeNotIn(
nodeTypes: NodeType[]
){
return function (state: EditorState) {
const { from, to } = state.selection;
let isExcluded = false;
const nodeTypeNames = nodeTypes.map(nt => nt.name);

// check all nodes in the selection for the right type
state.doc.nodesBetween(from, to, (node) => {
isExcluded = nodeTypeNames.includes(node.type.name);
// stop recursing if the current node is in the exclusion list
if(isExcluded){
return false;
}
});

return !isExcluded;
};
}

/**
* Creates an `active` method that returns true of the current selection has the passed mark
* @param mark The mark to check for
Expand Down
83 changes: 83 additions & 0 deletions src/rich-text/commands/run-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {EditorState, Transaction} from "prosemirror-state";
import {EditorView} from "prosemirror-view";
import {schema} from "prosemirror-markdown";
import {Node} from "prosemirror-model";
import {log} from "../../shared/logger";
import {MenuCommand} from "../../shared/menu";

export const runCodeBlockCommand: MenuCommand = (
state: EditorState,
dispatch: (tr: Transaction) => void,
view?: EditorView
): boolean => {
const { from, to } = state.selection;
let isCodeblock = false;
let codeBlockNode: Node;

state.doc.nodesBetween(from, to, (node) => {
isCodeblock = node.type.name === schema.nodes.code_block.name;
codeBlockNode = node;
return !isCodeblock;
});

if(!isCodeblock){
return false;
}

//Time to run some code boys
if(dispatch) {
const sourceCode = codeBlockNode.textContent;//.replace(/"/g, '\\"');
log("runCodeBlockCommand - codeblock source", sourceCode)

//TODO: Hey, probs should move this out to a Stack owned server first.
//TODO - in fact... CORS will fuck you immediately doing this.
fetch("/api/v4/submissions?access_token=<TOKEN>",{
method: "POST",
headers: {
"content-type": "application/json;charset=UTF-8",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
"compilerId": 86,
"compilerVersionId": 7,
"source": sourceCode
})
})
.then((res) => res.json())
.then((data) => {
log("runCodeBlockCommand submission result!", data)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return data.id as number;
})
.then((subId) => {
//wait 3 secs
return new Promise(resolve => setTimeout(() => resolve(subId), 8000))
})
.then((subId: number) => fetch(`/api/v4/submissions/${subId}?access_token=<TOKEN>`, {
headers: {
"Access-Control-Allow-Origin": "*"
}
}))
.then((res) => res.json())
.then((data) => {
log("runCodeBlockCommand execution result!", data)
//Essentially, Is this done, is it finished, and do we have a place to grab the result?
if(!data.executing && data.result.status.code == 15 && data.result.streams.output.uri){
const fetchUri: string = data.result.streams.output.uri.slice(44);
log(`runCodeBlockCommand result uri ${fetchUri}`);
return fetch(fetchUri, {
headers: {
"Access-Control-Allow-Origin": "*"
}
});
}
return new Response(JSON.stringify({error: "no results"}));
})
.then((res) => res.text())
.then((data) => {
log(`runCodeBlockCommand result`, data)
})
.catch(err => log("runCodeBlockCommand err", err))
}
return true;
}
8 changes: 8 additions & 0 deletions src/rich-text/node-views/code-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { IExternalPluginProvider } from "../../shared/editor-plugin";
import { getBlockLanguage } from "../../shared/highlighting/highlight-plugin";
import { _t } from "../../shared/localization";
import { escapeHTML, generateRandomId } from "../../shared/utils";
import { log } from "../../shared/logger";

type getPosParam = boolean | (() => number);

Expand All @@ -22,6 +23,7 @@ export class CodeBlockView implements NodeView {
getPos: getPosParam,
private additionalProcessors: IExternalPluginProvider["codeblockProcessors"]
) {
log("CodeView", "Constructor entered")
this.dom = document.createElement("div");
this.dom.classList.add("ps-relative", "p0", "ws-normal", "ow-normal");
this.render(view, getPos);
Expand All @@ -37,6 +39,8 @@ export class CodeBlockView implements NodeView {

const rawLanguage = this.getLanguageFromBlock(node);

log("CodeView", `Detected language : ${rawLanguage}`);

const processorApplies = this.getValidProcessorResult(
rawLanguage,
node
Expand Down Expand Up @@ -109,12 +113,16 @@ export class CodeBlockView implements NodeView {
let autodetectedLanguage = node.attrs
.detectedHighlightLanguage as string;

log("Codeview", `Autodetected: ${autodetectedLanguage}`)

if (autodetectedLanguage) {
autodetectedLanguage = _t("nodes.codeblock_lang_auto", {
lang: autodetectedLanguage,
});
}

log("Codeview", `Autodetected pp: ${autodetectedLanguage}`)

return autodetectedLanguage || getBlockLanguage(node);
}

Expand Down
37 changes: 37 additions & 0 deletions src/shared/code-execution/code-execution-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//TODO: Type the source language to ones we support execution for
type SourceLanguage = string;

/** Execution context for a snippet - a language and block of source code */
interface SnippetExecutionContext {
/** Language the source code will be compiled in */
compilerLanguage: SourceLanguage;
source: string;
}

/** A single attachment that will be loaded in a multi-file context */
interface SourceAttachment {
/** Name of the file within execution context */
file: string
/** Folder structure where the file is found in execution context */
filepath: string;
/** Contents of the file */
source: string;
}

/** Execution context for multiple files - a language and a file/folder structure */
interface MultifileExecutionContext {
compilerLanguage: SourceLanguage;
files: SourceAttachment[]
}

type ExecutionContext = SnippetExecutionContext | MultifileExecutionContext;

export interface CodeExecutionProvider {
/** Responsible for submitting the code for execution, returning a URL to get the results */
submissionHandler: (context: ExecutionContext) => Promise<string>;
}

export function isSnippetContext(context: ExecutionContext): context is SnippetExecutionContext {
const snippet = context as SnippetExecutionContext;
return snippet.source !== undefined && typeof snippet.source == "string";
}
4 changes: 4 additions & 0 deletions src/shared/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const defaultStrings = {
title: shortcut("Code block"),
description: "Multiline block of code with syntax highlighting",
},
run_code_block: {
title: shortcut("Run"),
description: "Run the current code block"
},
emphasis: shortcut("Italic"),
heading: {
dropdown: shortcut("Heading"),
Expand Down
39 changes: 34 additions & 5 deletions src/shared/menu/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
insertRichTextHorizontalRuleCommand,
insertRichTextTableCommand,
toggleList,
nodeTypeNotIn,
runCodeBlockCommand
} from "../../rich-text/commands";
import { _t } from "../localization";
import { makeMenuButton, makeMenuDropdown } from "./helpers";
Expand Down Expand Up @@ -112,7 +114,7 @@ const headingDropdown = (schema: Schema) =>
"Header",
_t("commands.heading.dropdown", { shortcut: getShortcut("Mod-H") }),
"heading-dropdown",
() => true,
nodeTypeNotIn([schema.nodes.code_block]),
nodeTypeActive(schema.nodes.heading),
makeDropdownItem(
_t("commands.heading.entry", { level: 1 }),
Expand Down Expand Up @@ -363,6 +365,25 @@ export const createMenuEntries = (
"code-block-btn"
),
},
//TODO: Make addIf, should probably lean on plugins more? (how does Image Upload do this, for example?)
{
key: "runCodeblock",
richText: {
command: runCodeBlockCommand,
visible: nodeTypeActive(schema.nodes.code_block)
},
commonmark: null,
display: makeMenuButton(
"Play",
{
title: _t("commands.run_code_block.title", {
shortcut: getShortcut("Mod-9")
}),
description: _t("commands.run_code_block.description")
},
"code-block-run-btn"
)
}
],
},
{
Expand Down Expand Up @@ -397,7 +418,10 @@ export const createMenuEntries = (
addIf(
{
key: "insertImage",
richText: insertRichTextImageCommand,
richText: {
command: insertRichTextImageCommand,
visible: nodeTypeNotIn([schema.nodes.code_block])
},
commonmark: insertCommonmarkImageCommand,
display: makeMenuButton(
"Image",
Expand All @@ -414,8 +438,10 @@ export const createMenuEntries = (
key: "insertTable",
richText: {
command: insertRichTextTableCommand,
visible: (state: EditorState) =>
!inTable(state.schema, state.selection),
visible: (state: EditorState) => {
return nodeTypeNotIn([schema.nodes.code_block])(state)
&& !inTable(state.schema, state.selection)
},
},
commonmark: insertCommonmarkTableCommand,
display: makeMenuButton(
Expand Down Expand Up @@ -476,7 +502,10 @@ export const createMenuEntries = (
},
{
key: "insertRule",
richText: insertRichTextHorizontalRuleCommand,
richText: {
command: insertRichTextHorizontalRuleCommand,
visible: nodeTypeNotIn([schema.nodes.code_block])
},
commonmark: insertCommonmarkHorizontalRuleCommand,
display: makeMenuButton(
"HorizontalRule",
Expand Down
6 changes: 5 additions & 1 deletion src/shared/menu/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
EditorState,
Plugin,
Plugin, PluginKey,
PluginView,
Transaction,
} from "prosemirror-state";
Expand All @@ -16,6 +16,7 @@ import {
makeMenuButton,
} from "./helpers";
import { hidePopover } from "@stackoverflow/stacks";
import {log} from "../logger";

/** NoOp to use in place of missing commands */
const commandNoOp = () => false;
Expand Down Expand Up @@ -399,6 +400,8 @@ export class MenuView implements PluginView {
}
}

export const MenuKey = new PluginKey("MenuPlugin");

/**
* Creates a menu plugin with the passed in entries
* @param blocks The entries to use on the generated menu
Expand All @@ -410,6 +413,7 @@ export function createMenuPlugin(
containerFn: (view: EditorView) => Node,
editorType: EditorType
): Plugin {
log("createMenuPlugin blocks", blocks);
return new Plugin({
view(editorView) {
const menuView = new MenuView(blocks, editorView, editorType);
Expand Down
2 changes: 2 additions & 0 deletions src/shared/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { EditorView } from "prosemirror-view";
import { EditorPlugin } from "./editor-plugin";
import type { ImageUploadOptions } from "./prosemirror-plugins/image-upload";
import { setAttributesOnElement, stackOverflowValidateLink } from "./utils";
import {CodeExecutionProvider} from "./code-execution/code-execution-options";

/** Describes each distinct editor type the StacksEditor handles */
export enum EditorType {
Expand Down Expand Up @@ -46,6 +47,7 @@ export interface CommonViewOptions {
imageUpload?: ImageUploadOptions;
/** Externally written plugins to add to the editor */
editorPlugins?: EditorPlugin[];
codeExecutionProvider?: CodeExecutionProvider;
}

/** Configuration options for parsing and rendering [tag:*] and [meta-tag:*] syntax */
Expand Down
2 changes: 2 additions & 0 deletions src/stacks-editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ export class StacksEditor implements View {
enabled: false,
renderer: null,
},
codeExecutionProvider: null
},
richTextOptions: {
classList: commonClasses,
codeExecutionProvider: null
},
};
}
Expand Down
Loading