diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json
index 05cd3abdd1..3d39cfecb4 100644
--- a/Composer/packages/client/package.json
+++ b/Composer/packages/client/package.json
@@ -14,7 +14,7 @@
"babel-plugin-named-asset-import": "^0.3.1",
"babel-preset-react-app": "^7.0.1",
"bfj": "6.1.1",
- "botbuilder-lg": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.6.tgz",
+ "botbuilder-lg": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.8.tgz",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"code-editor": "*",
"codemirror": "^5.45.0",
diff --git a/Composer/packages/client/src/ShellApi.ts b/Composer/packages/client/src/ShellApi.ts
index 96fe32302b..4aaea0eb43 100644
--- a/Composer/packages/client/src/ShellApi.ts
+++ b/Composer/packages/client/src/ShellApi.ts
@@ -74,7 +74,7 @@ export const ShellApi: React.FC = () => {
const { dialogs, schemas, lgFiles, luFiles, designPageLocation, focusPath, breadcrumb } = state;
const updateDialog = actions.updateDialog;
const updateLuFile = actions.updateLuFile; //if debounced, error can't pass to form
- const updateLgFile = useDebouncedFunc(actions.updateLgFile);
+ const updateLgFile = actions.updateLgFile;
const updateLgTemplate = useDebouncedFunc(actions.updateLgTemplate);
const createLuFile = actions.createLuFile;
const createLgFile = actions.createLgFile;
diff --git a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx
index 9a7ef7cfa2..1ae4faf3e1 100644
--- a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx
+++ b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx
@@ -72,6 +72,10 @@ const shellApi = {
return apiClient.apiCall('updateLuFile', luFile);
},
+ updateLgFile: (id: string, content: string) => {
+ return apiClient.apiCall('updateLgFile', { id, content });
+ },
+
getLgTemplates: (id: string) => {
return apiClient.apiCall('getLgTemplates', { id });
},
diff --git a/Composer/packages/client/src/pages/language-generation/code-editor.js b/Composer/packages/client/src/pages/language-generation/code-editor.js
index fbc95713a1..f2bc43c4a7 100644
--- a/Composer/packages/client/src/pages/language-generation/code-editor.js
+++ b/Composer/packages/client/src/pages/language-generation/code-editor.js
@@ -7,7 +7,7 @@ import { get, debounce, isEmpty } from 'lodash';
import * as lgUtil from '../../utils/lgUtil';
export default function CodeEditor(props) {
- const { file } = props;
+ const { file, codeRange } = props;
const onChange = debounce(props.onChange, 500);
const [diagnostics, setDiagnostics] = useState(get(file, 'diagnostics', []));
const [content, setContent] = useState(get(file, 'content', ''));
@@ -41,6 +41,7 @@ export default function CodeEditor(props) {
lineDecorationsWidth: undefined,
lineNumbersMinChars: false,
}}
+ codeRange={codeRange}
errorMsg={errorMsg}
value={content}
onChange={_onChange}
@@ -51,4 +52,5 @@ export default function CodeEditor(props) {
CodeEditor.propTypes = {
file: PropTypes.object,
onChange: PropTypes.func,
+ codeRange: PropTypes.object,
};
diff --git a/Composer/packages/client/src/pages/language-generation/index.js b/Composer/packages/client/src/pages/language-generation/index.js
index 603d461382..15aa13a8b4 100644
--- a/Composer/packages/client/src/pages/language-generation/index.js
+++ b/Composer/packages/client/src/pages/language-generation/index.js
@@ -2,6 +2,7 @@ import React, { useContext, Fragment, useEffect, useState, useMemo } from 'react
import formatMessage from 'format-message';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { Nav } from 'office-ui-fabric-react/lib/Nav';
+import get from 'lodash.get';
import { OpenAlertModal, DialogStyle } from '../../components/Modal';
import { StoreContext } from '../../store';
@@ -25,6 +26,7 @@ export const LGPage = props => {
const { state, actions } = useContext(StoreContext);
const { lgFiles, dialogs } = state;
const [editMode, setEditMode] = useState(false);
+ const [codeRange, setCodeRange] = useState(null);
const subPath = props['*'];
const isRoot = subPath === '';
@@ -96,6 +98,11 @@ export const LGPage = props => {
}
}
+ function onToggleEditMode() {
+ setEditMode(!editMode);
+ setCodeRange(null);
+ }
+
async function onChange(newContent) {
const payload = {
id: lgFile.id,
@@ -113,7 +120,11 @@ export const LGPage = props => {
// #TODO: get line number from lg parser, then deep link to code editor this
// Line
- function onTableViewClickEdit() {
+ function onTableViewClickEdit(template) {
+ setCodeRange({
+ startLineNumber: get(template, 'ParseTree._start._line', 0),
+ endLineNumber: get(template, 'ParseTree._stop._line', 0),
+ });
navigateTo(`/language-generation`);
setEditMode(true);
}
@@ -139,7 +150,7 @@ export const LGPage = props => {
offText={formatMessage('Edit mode')}
checked={editMode}
disabled={!isRoot && editMode === false}
- onChange={() => setEditMode(!editMode)}
+ onChange={onToggleEditMode}
/>
@@ -175,7 +186,7 @@ export const LGPage = props => {
{editMode ? (
-
+
) : (
)}
diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx
index ffadb62162..9bcfdff186 100644
--- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx
+++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx
@@ -1,6 +1,7 @@
import React from 'react';
-import { useState, useEffect } from 'react';
-import { RichEditor } from 'code-editor';
+import { useState } from 'react';
+import { LgEditor } from 'code-editor';
+import debounce from 'lodash.debounce';
import { FormContext } from '../types';
@@ -8,7 +9,7 @@ const LG_HELP =
'https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/language-generation/docs/lg-file-format.md';
const getInitialTemplate = (fieldName: string, formData?: string): string => {
- let newTemplate = formData || '';
+ let newTemplate = formData || '- ';
if (newTemplate.includes(`bfd${fieldName}-`)) {
return '';
@@ -29,57 +30,50 @@ interface LgEditorWidgetProps {
export const LgEditorWidget: React.FC
= props => {
const { formContext, name, value, height = 250 } = props;
- const [templateToRender, setTemplateToRender] = useState({ Name: '', Body: '' });
const lgId = `bfd${name}-${formContext.dialogId}`;
const [errorMsg, setErrorMsg] = useState('');
- const ensureTemplate = async (newBody?: string): Promise => {
- const templates = await formContext.shellApi.getLgTemplates('common');
- const template = templates.find(template => {
- return template.Name === lgId;
- });
- if (template === null || template === undefined) {
- const newTemplate = getInitialTemplate(name, newBody);
+ const lgFileId = formContext.currentDialog.lgFile || 'common';
+ const lgFile = formContext.lgFiles.find(file => file.id === lgFileId);
+ const template = lgFile
+ ? lgFile.templates.find(template => {
+ return template.Name === lgId;
+ })
+ : undefined;
- if (formContext.dialogId && newTemplate) {
- formContext.shellApi.updateLgTemplate('common', lgId, newTemplate);
- props.onChange(`[${lgId}]`);
+ // template body code range
+ const codeRange = template
+ ? {
+ startLineNumber: template.Range.startLineNumber + 1, // cut template name
+ endLineNumber: template.Range.endLineNumber,
}
- setTemplateToRender({ Name: `# ${lgId}`, Body: newTemplate });
- } else {
- if (templateToRender.Name === '') {
- setTemplateToRender({ Name: `# ${lgId}`, Body: template.Body });
- }
- }
- };
+ : -1;
+
+ let content = lgFile ? lgFile.content : '';
+ if (!template) {
+ const newTemplateBody = getInitialTemplate(name, value || '-');
+ content += ['\n', '# ' + lgId, newTemplateBody].join('\n');
+ }
- const onChange = (data): void => {
+ const onChange = debounce((data): void => {
// hit the lg api and replace it's Body with data
if (formContext.dialogId) {
- let dataToEmit = data.trim();
- if (dataToEmit.length > 0 && dataToEmit[0] !== '-') {
- dataToEmit = `-${dataToEmit}`;
- }
-
- if (dataToEmit.length > 0) {
- setTemplateToRender({ Name: templateToRender.Name, Body: data });
- formContext.shellApi
- .updateLgTemplate('common', lgId, dataToEmit)
- .then(() => setErrorMsg(''))
- .catch(error => setErrorMsg(error));
- props.onChange(`[${lgId}]`);
- } else {
- setTemplateToRender({ Name: templateToRender.Name, Body: '' });
- formContext.shellApi.removeLgTemplate('common', lgId);
- props.onChange(undefined);
- }
+ formContext.shellApi
+ .updateLgFile(lgFileId, data)
+ .then(() => setErrorMsg(''))
+ .catch(error => setErrorMsg(error));
+ props.onChange(`[${lgId}]`);
}
- };
-
- useEffect(() => {
- ensureTemplate(value);
- }, [formContext.dialogId]);
+ }, 200);
- const { Body } = templateToRender;
- return ;
+ return (
+
+ );
};
diff --git a/Composer/packages/extensions/obiformeditor/src/types.ts b/Composer/packages/extensions/obiformeditor/src/types.ts
index 9ae38c63a2..19d0875ebe 100644
--- a/Composer/packages/extensions/obiformeditor/src/types.ts
+++ b/Composer/packages/extensions/obiformeditor/src/types.ts
@@ -50,14 +50,22 @@ export interface LuFile {
};
}
+export interface CodeRange {
+ startLineNumber: number;
+ endLineNumber: number;
+}
+
export interface LgFile {
id: string;
relativePath: string;
content: string;
+ templates: [LgTemplate];
}
export interface LgTemplate {
Name: string;
Body: string;
+ Parameters: string;
+ Range: CodeRange;
}
export interface FormData {
@@ -74,6 +82,7 @@ export interface ShellApi {
onFocusEvent: (eventId: string) => Promise;
createLuFile: (id: string) => Promise;
updateLuFile: (id: string, content: string) => Promise;
+ updateLgFile: (id: string, content: string) => Promise;
getLgTemplates: (id: string) => Promise;
createLgTemplate: (id: string, template: LgTemplate, position: number) => Promise;
updateLgTemplate: (id: string, templateName: string, templateStr: string) => Promise;
diff --git a/Composer/packages/lib/code-editor/demo/src/App.tsx b/Composer/packages/lib/code-editor/demo/src/App.tsx
index e2024d1780..7a4a51391a 100644
--- a/Composer/packages/lib/code-editor/demo/src/App.tsx
+++ b/Composer/packages/lib/code-editor/demo/src/App.tsx
@@ -5,14 +5,24 @@ import { RichEditor } from '../../src';
const LU_HELP =
'https://github.com/Microsoft/botbuilder-tools/blob/master/packages/Ludown/docs/lu-file-format.md#lu-file-format';
+const content = `# Greeting
+-Good morning
+-Good afternoon
+-Good evening`;
+
export default function App() {
- const [value, setValue] = useState('');
+ const [value, setValue] = useState(content);
const [showError, setShowError] = useState(true);
const placeholder = `> To learn more about the LU file format, read the documentation at
> ${LU_HELP}`;
const errorMsg = showError ? 'example error' : undefined;
+ const codeRange = {
+ startLineNumber: 2,
+ endLineNumber: 3,
+ };
+
return (
@@ -21,6 +31,7 @@ export default function App() {
setValue(newVal)}
value={value}
+ codeRange={codeRange}
placeholder={placeholder}
errorMsg={errorMsg}
helpURL="https://dev.botframework.com"
diff --git a/Composer/packages/lib/code-editor/package.json b/Composer/packages/lib/code-editor/package.json
index 7a14aaf27a..113a0e9100 100644
--- a/Composer/packages/lib/code-editor/package.json
+++ b/Composer/packages/lib/code-editor/package.json
@@ -55,7 +55,7 @@
},
"dependencies": {
"@bfcomposer/monaco-editor-webpack-plugin": "^1.7.2",
- "@bfcomposer/react-monaco-editor": "^0.30.4",
+ "@bfcomposer/react-monaco-editor": "^0.30.5",
"lodash.throttle": "^4.1.1"
}
}
diff --git a/Composer/packages/lib/code-editor/src/BaseEditor.tsx b/Composer/packages/lib/code-editor/src/BaseEditor.tsx
index ac3fa29fd8..96bf58b3a5 100644
--- a/Composer/packages/lib/code-editor/src/BaseEditor.tsx
+++ b/Composer/packages/lib/code-editor/src/BaseEditor.tsx
@@ -20,17 +20,27 @@ const defaultOptions: monacoEditor.editor.IEditorConstructionOptions = {
renderLineHighlight: 'none',
};
+interface ICodeRange {
+ startLineNumber: number;
+ endLineNumber: number;
+}
+
export interface BaseEditorProps extends Omit {
onChange: (newValue: string) => void;
placeholder?: string;
value?: string;
+ codeRange?: ICodeRange | -1;
}
export function BaseEditor(props: BaseEditorProps) {
- const { onChange, placeholder, value } = props;
+ const { onChange, placeholder, value, codeRange } = props;
const options = Object.assign({}, defaultOptions, props.options);
-
+ if (options.folding && codeRange) {
+ options.folding = false;
+ }
const containerRef = useRef(null);
+ // editor.setHiddenAreas is an internal api, not included in , so here mark it
+ const [editor, setEditor] = useState(null);
const [rect, setRect] = useState({ height: 0, width: 0 });
const updateRect = throttle(() => {
@@ -60,6 +70,57 @@ export function BaseEditor(props: BaseEditorProps) {
updateRect();
}, []);
+ const updateEditorCodeRangeUI = (editor?: monacoEditor.editor.IStandaloneCodeEditor | any) => {
+ if (codeRange && editor) {
+ // subtraction a hiddenAreaRange from CodeRange
+ // Tips, monaco lineNumber start from 1
+ const model = editor.getModel();
+ const lineCount = model && model.getLineCount();
+
+ // -1 is end line of file
+ if (codeRange === -1) {
+ editor.setHiddenAreas([
+ {
+ startLineNumber: 1,
+ endLineNumber: lineCount - 1,
+ },
+ ]);
+ return;
+ }
+ const hiddenRanges = [
+ {
+ startLineNumber: 1,
+ endLineNumber: codeRange.startLineNumber - 1,
+ },
+ {
+ startLineNumber: codeRange.endLineNumber + 1,
+ endLineNumber: lineCount,
+ },
+ ];
+
+ // code range start from first line, update hiddenRanges
+ if (codeRange.startLineNumber === 1) {
+ hiddenRanges.shift();
+ }
+ // code range end at last line, update hiddenRanges
+ if (codeRange.endLineNumber === lineCount) {
+ hiddenRanges.pop();
+ }
+ editor.setHiddenAreas(hiddenRanges);
+ }
+ };
+ useEffect(() => {
+ updateEditorCodeRangeUI(editor);
+ }, [codeRange]);
+
+ const editorDidMount = (editor: monacoEditor.editor.IStandaloneCodeEditor, monaco: typeof monacoEditor) => {
+ if (typeof props.editorDidMount === 'function') {
+ props.editorDidMount(editor, monaco);
+ }
+ setEditor(editor);
+ updateEditorCodeRangeUI(editor);
+ };
+
return (
-
+
);
}
diff --git a/Composer/packages/lib/code-editor/src/RichEditor.tsx b/Composer/packages/lib/code-editor/src/RichEditor.tsx
index 0e2abc81ba..df12d76735 100644
--- a/Composer/packages/lib/code-editor/src/RichEditor.tsx
+++ b/Composer/packages/lib/code-editor/src/RichEditor.tsx
@@ -1,4 +1,4 @@
-import React, { Fragment } from 'react';
+import React, { Fragment, useMemo } from 'react';
import { SharedColors, NeutralColors } from '@uifabric/fluent-theme';
import formatMessage from 'format-message';
@@ -28,6 +28,11 @@ export function RichEditor(props: RichEditorProps) {
}
);
+ const baseEditor = ;
+ // CodeRange editing require an non-controled/refresh component, so here make it memoed
+ const memoEditor = useMemo(() => {
+ return baseEditor;
+ }, []);
const getHeight = () => {
if (height === null || height === undefined) {
return '100%';
@@ -50,7 +55,7 @@ export function RichEditor(props: RichEditorProps) {
transition: `border-color 0.1s ${isInvalid ? 'ease-out' : 'ease-in'}`,
}}
>
-
+ {props.codeRange ? memoEditor : baseEditor}
{isInvalid && (
diff --git a/Composer/packages/server/package.json b/Composer/packages/server/package.json
index ea03cd1b7f..9e1d6227ce 100644
--- a/Composer/packages/server/package.json
+++ b/Composer/packages/server/package.json
@@ -72,7 +72,7 @@
"axios": "^0.18.0",
"azure-storage": "^2.10.3",
"body-parser": "^1.18.3",
- "botbuilder-lg": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.6.tgz",
+ "botbuilder-lg": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.8.tgz",
"cookie-parser": "^1.4.4",
"debug": "^4.1.1",
"dotenv": "^8.1.0",
diff --git a/Composer/packages/server/src/models/bot/indexers/lgIndexer.ts b/Composer/packages/server/src/models/bot/indexers/lgIndexer.ts
index 5739a1145a..59f1e0d700 100644
--- a/Composer/packages/server/src/models/bot/indexers/lgIndexer.ts
+++ b/Composer/packages/server/src/models/bot/indexers/lgIndexer.ts
@@ -1,4 +1,5 @@
import { LGParser, StaticChecker, DiagnosticSeverity, Diagnostic } from 'botbuilder-lg';
+import get from 'lodash.get';
import { Path } from '../../../utility/path';
import { FileInfo, LGFile, LGTemplate } from '../interface';
@@ -48,7 +49,15 @@ export class LGIndexer {
public parse(content: string, id: string): LGTemplate[] {
const resource = LGParser.parse(content, id);
const templates = resource.Templates.map(t => {
- return { Name: t.Name, Body: t.Body, Parameters: t.Parameters };
+ return {
+ Name: t.Name,
+ Body: t.Body,
+ Parameters: t.Parameters,
+ Range: {
+ startLineNumber: get(t, 'ParseTree._start._line', 0),
+ endLineNumber: get(t, 'ParseTree._stop._line', 0),
+ },
+ };
});
return templates;
}
diff --git a/Composer/packages/server/src/models/bot/interface.ts b/Composer/packages/server/src/models/bot/interface.ts
index a56196b169..7a03348805 100644
--- a/Composer/packages/server/src/models/bot/interface.ts
+++ b/Composer/packages/server/src/models/bot/interface.ts
@@ -26,10 +26,16 @@ export interface Dialog {
relativePath: string;
}
+export interface CodeRange {
+ startLineNumber: number;
+ endLineNumber: number;
+}
+
export interface LGTemplate {
Name: string;
Body: string;
Parameters: string[];
+ Range: CodeRange;
}
export interface LGFile {
diff --git a/Composer/yarn.lock b/Composer/yarn.lock
index dffabd8633..86823793d3 100644
--- a/Composer/yarn.lock
+++ b/Composer/yarn.lock
@@ -1432,10 +1432,10 @@
dependencies:
"@types/webpack" "^4.4.19"
-"@bfcomposer/monaco-editor@^0.17.4":
- version "0.17.4"
- resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/monaco-editor/-/@bfcomposer/monaco-editor-0.17.4.tgz#bc2d75c48fcbee1121ee4c168c508de81bcd2ee9"
- integrity sha1-vC11xI/L7hEh7kwWjFCN6BvNLuk=
+"@bfcomposer/monaco-editor@^0.17.5":
+ version "0.17.5"
+ resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/monaco-editor/-/@bfcomposer/monaco-editor-0.17.5.tgz#ceefd34199784d3f2fb745191cafab13bc88a268"
+ integrity sha1-zu/TQZl4TT8vt0UZHK+rE7yIomg=
"@bfcomposer/react-jsonschema-form@1.6.5":
version "1.6.5"
@@ -1448,12 +1448,12 @@
lodash.topath "^4.5.2"
prop-types "^15.5.8"
-"@bfcomposer/react-monaco-editor@^0.30.4":
- version "0.30.4"
- resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/react-monaco-editor/-/@bfcomposer/react-monaco-editor-0.30.4.tgz#3b7a6c60011218d3bc51f7eca5673341376db728"
- integrity sha1-O3psYAESGNO8UffspWczQTdttyg=
+"@bfcomposer/react-monaco-editor@^0.30.5":
+ version "0.30.5"
+ resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/react-monaco-editor/-/@bfcomposer/react-monaco-editor-0.30.5.tgz#f0936110d43b7cbc72dd4bdf2107cb9895ec84c3"
+ integrity sha1-8JNhENQ7fLxy3UvfIQfLmJXshMM=
dependencies:
- "@bfcomposer/monaco-editor" "^0.17.4"
+ "@bfcomposer/monaco-editor" "^0.17.5"
"@types/react" "^16.9.2"
prop-types "^15.7.2"
@@ -5045,9 +5045,9 @@ botbuilder-expression-parser@^4.5.9:
xmldom "^0.1.27"
xpath "0.0.27"
-"botbuilder-lg@https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.6.tgz":
- version "4.6.6"
- resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.6.tgz#2c699f475c05492239d83ab0863c8c85d369a284"
+"botbuilder-lg@https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.8.tgz":
+ version "4.6.8"
+ resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.8.tgz#24367ee35e33efdae7a31e390ba7d19a7d9166da"
dependencies:
antlr4ts "0.5.0-alpha.1"
botbuilder-expression "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-expression/-/4.5.9.tgz"
diff --git a/SampleBots/ToDoBot/ComposerDialogs/Main/Main.dialog b/SampleBots/ToDoBot/ComposerDialogs/Main/Main.dialog
index 6fb5ed85b5..b2140617d8 100644
--- a/SampleBots/ToDoBot/ComposerDialogs/Main/Main.dialog
+++ b/SampleBots/ToDoBot/ComposerDialogs/Main/Main.dialog
@@ -160,6 +160,9 @@
"actions": [
{
"$type": "Microsoft.SendActivity",
+ "$designer": {
+ "id": "677440"
+ },
"activity": "ok."
},
{
@@ -178,10 +181,13 @@
"actions": [
{
"$type": "Microsoft.SendActivity",
+ "$designer": {
+ "id": "677448"
+ },
"activity": "Hi! I'm a ToDo bot. Say \"add a todo named first\" to get started."
}
]
}
],
"$schema": "../../app.schema"
-}
+}
\ No newline at end of file