Full Code of coddx-hq/coddx-alpha for AI

master fb0ac6595a5a cached
46 files
96.3 KB
26.3k tokens
65 symbols
1 requests
Download .txt
Repository: coddx-hq/coddx-alpha
Branch: master
Commit: fb0ac6595a5a
Files: 46
Total size: 96.3 KB

Directory structure:
gitextract_gosdofly/

├── .coddx-template
├── .editorconfig
├── .gitignore
├── .prettierrc.yml
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── docs/
│   ├── documentation.md
│   ├── generate-files.md
│   └── task-board.md
├── package.json
├── src/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .prettierrc.yml
│   ├── .vscodeignore
│   ├── extension.ts
│   ├── test/
│   │   ├── extension.test.ts
│   │   └── index.ts
│   ├── view/
│   │   ├── TaskBoardLoader.ts
│   │   ├── ViewLoader.ts
│   │   └── app/
│   │       ├── Utils.ts
│   │       ├── components/
│   │       │   ├── CodeGen/
│   │       │   │   ├── CodeEditor.css
│   │       │   │   ├── CodeEditor.tsx
│   │       │   │   ├── EditTemplatePanel.tsx
│   │       │   │   ├── MainView.css
│   │       │   │   └── MainView.tsx
│   │       │   └── TaskBoard/
│   │       │       ├── ButtonBar.tsx
│   │       │       ├── Helpers.tsx
│   │       │       ├── SearchInput.tsx
│   │       │       ├── Task.tsx
│   │       │       ├── TaskBoard.css
│   │       │       ├── TaskBoard.tsx
│   │       │       ├── TaskColumn.tsx
│   │       │       └── TaskMenu.tsx
│   │       ├── config.tsx
│   │       ├── index.css
│   │       ├── index.tsx
│   │       ├── model.ts
│   │       └── tsconfig.json
│   └── webpack.config.js
├── tsconfig.json
├── tslint.json
├── vsc-extension-quickstart.md
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .coddx-template
================================================
// -------------->> {{fileName}}.tsx
// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}
import * as React from 'react';
import './{{fileName}}.css';

export default class {{fileName}} extends React.Component {
  render() {
    return (
      <div>
        {/* ...code goes here... */}
      </div>
    )
  }
}

// -------------->> {{fileName}}.css
.main {}

// -------------->> __test__/{{fileName}}.test.tsx
import * as React from 'react';

it('{{fileName}} - render', () => {
  // render(<{{fileName}} />);
  // expect(...);
});


================================================
FILE: .editorconfig
================================================
# EditorConfig: http://EditorConfig.org

root = true

[*]
indent_style = space
indent_size = 2

end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .gitignore
================================================
out
node_modules
.vscode-test/
*.vsix

configViewer/configViewer.js

================================================
FILE: .prettierrc.yml
================================================
---
printWidth: 120
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
trailingComma: none
bracketSpacing: true
jsxBracketSameLine: false
arrowParens: avoid


================================================
FILE: .vscodeignore
================================================
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts

================================================
FILE: CHANGELOG.md
================================================
# Change Log

## [0.3.0] - 2021-05-24

- Task title supports markdown now for styling, hyperlinks, simple html or even img tags.
- New Task Action: move a task to the column on the right.

## [0.2.27] - 2020-03-28

- Task Board - support multiple task lists defined in user's settings.json.
- Task Board - refresh button to reload file content.
- Task Board - checkbox is now optional (if task title doesn't have it).
- Task Board - support sub-task (task title starts with 2-space indentation).
- Task Board - task menu for: toggling sub-task; inserting emojis;
- Task Board - respect theme colors.

## [0.2.12] - 2020-03-21

- Task Board - search box
- Task Board - autofocus when creating a new task.
- Task Board - checkmark to mark a task as complete.
- Task Board Doc

## [0.2.3] - 2020-03-15

- Task Board - manage tasks and save them as TODO.md - a simple plain text file. The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)

## [0.1.4] - 2020-03-10

- Output can be edited before generating files.

## [0.1.3]

- Initial release
- Follow this format - Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Duc Nguyen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# Todo Kanban Board

Todo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file.

## Features

- The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)
- TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories.
- Support custom file name, multiple task lists.
- Checkboxes are optional (if your task titles don't have them).
- Task title can also have markdown for styling, hyperlinks, simple html or even img tags.
- Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.).
- See also: <a href="https://bit.ly/2SfcKaH">Documentation / Guides</a>

## Usage:

- Open Coddx Task Board:
  - Bring up the Command Palette (F1), type and select: Coddx: Task Board.
- When interacting with the Task Board, TODO.md will be created or updated automatically.
- Vice versa, TODO.md can be edited manually, Task Board will load it every time (click the Refresh icon).

<img src="docs/media/task-board-demo.gif" />

<img src="docs/media/task-board.png" />

<hr />

## Support

- For Feedbacks, Bug Reports: https://github.com/coddx-hq/coddx-alpha/issues
- Documentation: <a href="https://bit.ly/2SfcKaH">Documentation / Guides</a>
- <a href="https://bit.ly/2y4fgqh">CHANGELOG</a>

## Next milestone:

- Feedbacks, suggestions or ideas are welcome! Thanks.
- Check out [TODO.md](TODO.md)


================================================
FILE: TODO.md
================================================
# TODO.md Kanban Board

TODO.md Kanban Board

<em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>

### Todo

- [ ] Publish on open-vsx.org
- [ ] Get Todo\*.md files for the dropdown
- [ ] Rename Git Repos
  - [ ] Update Readme, Docs
  - [ ] Update all URLs
- [ ] Animate Search Input
- [ ] Support Task description
- [ ] Markdown links for File or URL
- [ ] Press _Enter_ to add the next task

### In Progress

- [ ] Task menu: add hyperlinks ?
- [ ] Smart positioning for Task Menu
- [ ] Change icon on Marketplace
- [ ] 🐞 Fix sub-task toggling for a new task
- [ ] 🐞 Bug: it flashes the default data, then loads the real data

### Done ✓

- [x] Markdown text formats 05/21/2021
- [x] New task should be at the top 05/20/2021
- [x] Dropdown option to help about adding more todo files 05/19/2021
- [x] Respect theme colors
- [x] Task menu: Insert Emojis 😊
- [x] Task menu: Toggle sub-task
- [x] Rename to Todo.md Kanban Board
- [x] Support sub-task
- [x] Optional checkbox in task title
- [x] Default to the 1st Task List
- [x] Add Help link
- [x] Multiple task lists
- [x] Add Search Box
- [x] Auto save for Task Re-ordering
- [x] Add Open File link
- [x] Split to different Doc Topics
- [x] Auto focus on New Task 2020-03-18
- [x] Check mark to mark as Done 2020-03-18
- [x] Setup coddx mailbox


================================================
FILE: docs/documentation.md
================================================
# Documentation

### Task Board - TODO.md

[Task Board Doc](task-board.md)

### Generate Files

[Generate Files Doc](generate-files.md)


================================================
FILE: docs/generate-files.md
================================================
# Generate Files

### Features

- Work with any programming languages.
- Templating syntax.
- Store templates as a single file (can be committed to git, shared with others).
- Auto-create sub-directories.
- Built-in params (fileName, date, time, etc.).
- Custom params.
- Context menu to generate files/directories from anywhere.

#### Usage:

- Open Coddx panel:
  - Bring up the Command Palette (F1), type and select: Coddx: Generate Files.
  - Or: right-click on a directory, select: Generate files.
- Verify Template to suit your needs. (See <a href="https://bit.ly/2WHprLW">Docs</a> for syntax)
- Verify the output path (relative to Project root), a new directory will be created if not existed.
- Enter the new file name to generate file(s).

<img src="docs/media/coddx-demo.gif" />
  
<img src="docs/media/panel.png" height="280" />

### How does it work?

Coddx uses Mozilla's Templating Engine called <a href="https://github.com/mozilla/nunjucks">nunjucks</a>. Nunjucks is a powerful library, supports more advanced use cases like parameters, custom filters, etc.

### Templating Syntax

Example:

```
// -------------->> {{fileName}}.tsx
// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}
// file1 content...

// -------------->> __test__/{{fileName}}.test.tsx
// file2 content... (sub-directory will be created)
```

Built-in Template Variables:

- {{fileName}} - will be replace with the value of "File Name" field.
- {{YYYY}}, {{MM}}, {{DD}}, {{HH}}, {{mm}} - year, month, day, hours (24), minutes.

Read more: <a href="https://mozilla.github.io/nunjucks/templating.html">Nunjucks Templating Syntax</a>

### Custom Variables

You can declare custom variables (Params field) in JSON format, for example:

- { "myVar": "value" } - declare `myVar`, then use it in your template like `{{myVar}}`

### Custom filters

(Coming soon)


================================================
FILE: docs/task-board.md
================================================
# Todo Kanban Board

Todo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file.

## Features

- The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)
- TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories.
- Support custom file name, multiple task lists.
- Checkboxes are optional (if your task titles don't have them).
- Task title can also have markdown for styling, hyperlinks, simple html or even img tags.
- Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.).
- See also: <a href="https://bit.ly/2SfcKaH">Documentation / Guides</a>

## TODO.md

- Tasks are synced to the markdown file using [TODO.md format](https://bit.ly/2JdEuET).
- Task Board is a bit strict about the format.

  - Please follow the typical structure like in the example so it can work properly. If it fails to open, please revert or re-generate your TODO.md file to make it work again.

- For now, after making changes to TODO.md file, please click the Refresh icon (next to the board title) to reload.

- [Example of a TODO.md file](https://github.com/todomd/todo.md/blob/master/TODO.md)

## Settings

- Multiple TODO files:
  - In your workspace settings.json file, add this: `"coddx.taskBoard.fileList": "TODO.md, folder/TODO-name.md"` (comma separated, use your file names)

## Tips & Tricks

- In the task title:
  - You can type `#bug` or `#feat` to classify your task.
  - You can type a name like `@john`, `@jane`.
  - Date format can be yyyy-mm-dd
- Use the Search Box to filter for types, names, etc.


================================================
FILE: package.json
================================================
{
  "name": "coddx-alpha",
  "displayName": "TODO.md Kanban Board",
  "description": "Coddx - a collection of tools that help developers program efficiently. Some useful features like: Kanban Board to manage project tasks in TODO.md, generating multiple files from templates quickly.",
  "version": "0.2.55",
  "publisher": "coddx",
  "license": "MIT",
  "homepage": "https://github.com/coddx-hq",
  "repository": {
    "type": "git",
    "url": "https://github.com/coddx-hq/coddx-alpha.git"
  },
  "keywords": [
    "todo",
    "todolist",
    "kanban",
    "task",
    "project",
    "management",
    "generate",
    "generator",
    "template",
    "webdev",
    "snippet",
    "file"
  ],
  "icon": "docs/media/logo.png",
  "engines": {
    "vscode": "^1.29.0"
  },
  "categories": [
    "Extension Packs",
    "Programming Languages",
    "Snippets",
    "Other"
  ],
  "activationEvents": [
    "onCommand:extension.viewconfig",
    "onCommand:extension.taskboard"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "languages": [],
    "commands": [
      {
        "command": "extension.viewconfig",
        "title": "Generate files",
        "category": "Coddx"
      },
      {
        "command": "extension.taskboard",
        "title": "TODO.md Kanban Task Board",
        "category": "Coddx"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "extension.viewconfig"
        }
      ]
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "npm-run-all compile:*",
    "watch": "npm-run-all -p watch:*",
    "compile:extension": "rm -rf out && tsc --jsx react -p ./",
    "compile:views": "webpack --mode development",
    "watch:extension": "tsc --jsx react -watch -p ./",
    "watch:views": "webpack --watch --mode development",
    "postinstall": "node ./node_modules/vscode/bin/install",
    "test": "npm run compile && node ./node_modules/vscode/bin/test"
  },
  "devDependencies": {
    "@types/marked": "^2.0.2",
    "@types/mocha": "^2.2.42",
    "@types/node": "^10.12.21",
    "@types/react": "^16.9.0",
    "@types/react-dom": "^16.9.0",
    "css-loader": "^3.0.0",
    "npm-run-all": "^4.1.5",
    "style-loader": "^0.23.1",
    "ts-loader": "^6.0.4",
    "tslint": "^5.12.1",
    "typescript": "^3.3.1",
    "vscode": "^1.1.37",
    "webpack": "^4.35.2",
    "webpack-cli": "^3.3.5"
  },
  "dependencies": {
    "marked": "^2.0.4",
    "nunjucks": "^3.2.0",
    "prismjs": "^1.19.0",
    "react": "^16.13.0",
    "react-autosize-textarea": "^7.0.0",
    "react-beautiful-dnd": "^13.0.0",
    "react-dom": "^16.13.0",
    "react-select": "^3.1.0",
    "react-simple-code-editor": "^0.11.0",
    "styled-components": "^5.0.1"
  }
}


================================================
FILE: src/.editorconfig
================================================
# EditorConfig: http://EditorConfig.org

root = true

[*]
indent_style = space
indent_size = 2

end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: src/.gitignore
================================================
out
node_modules
.vscode-test/
*.vsix

configViewer/configViewer.js

================================================
FILE: src/.prettierrc.yml
================================================
---
printWidth: 120
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
trailingComma: none
bracketSpacing: true
jsxBracketSameLine: false
arrowParens: avoid


================================================
FILE: src/.vscodeignore
================================================
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts

================================================
FILE: src/extension.ts
================================================
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// import { telemetry } from './view/app/Utils';

import ViewLoader from './view/ViewLoader';
import TaskBoardLoader from './view/TaskBoardLoader';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
  // Use the console to output diagnostic information (console.log) and errors (console.error)
  // This line of code will only be executed once when your extension is activated
  // console.log('Congratulations, your extension "vscode-react" is now active!');
  // context.subscriptions.push(telemetry);

  // The command has been defined in the package.json file
  // Now provide the implementation of the command with registerCommand
  // The commandId parameter must match the command field in package.json
  let disposable = vscode.commands.registerCommand('extension.viewconfig', (uri: vscode.Uri) => {
    // let openDialogOptions: vscode.OpenDialogOptions = {
    //   canSelectFiles: true,
    //   canSelectFolders: false,
    //   canSelectMany: false,
    //   filters: {
    //     Json: ["json"]
    //   }
    // };

    // vscode.window
    //   .showOpenDialog(openDialogOptions)
    //   .then(async (uri: vscode.Uri[] | undefined) => {
    //     if (uri && uri.length > 0) {
    //       const view = new ViewLoader(uri[0], context.extensionPath);
    //     } else {
    //       vscode.window.showErrorMessage("No valid file selected!");
    //       return;
    //     }
    //   });
    const view = new ViewLoader(context.extensionPath, uri);
    // telemetry.sendTelemetryEvent('init-file-generator');
    return view;
  });
  context.subscriptions.push(disposable);

  let taskBoardCmd = vscode.commands.registerCommand('extension.taskboard', (uri: vscode.Uri) => {
    const view = new TaskBoardLoader(context.extensionPath, uri);
    // telemetry.sendTelemetryEvent('init-task-board');
    return view;
  });
  context.subscriptions.push(taskBoardCmd);
}

// this method is called when your extension is deactivated
export function deactivate() {
  // telemetry.dispose();
}


================================================
FILE: src/test/extension.test.ts
================================================
//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//

// The module 'assert' provides assertion methods from node
import * as assert from 'assert';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
// import * as vscode from 'vscode';
// import * as myExtension from '../extension';

// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", function () {

    // Defines a Mocha unit test
    test("Something 1", function() {
        assert.equal(-1, [1, 2, 3].indexOf(5));
        assert.equal(-1, [1, 2, 3].indexOf(0));
    });
});

================================================
FILE: src/test/index.ts
================================================
//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testsRoot: string, clb: (error: Error, failures?: number) => void): void
// that the extension host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.

import * as testRunner from 'vscode/lib/testrunner';

// You can directly control Mocha options by configuring the test runner below
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options
// for more info
testRunner.configure({
    ui: 'tdd', 		// the TDD UI is being used in extension.test.ts (suite, test, etc.)
    useColors: true // colored output from test results
});

module.exports = testRunner;

================================================
FILE: src/view/TaskBoardLoader.ts
================================================
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';

import { IConfig, ICommand, CommandAction } from './app/model';
import { deepFind, VER } from './app/Utils';

let __panel = null;
let selectedFile = '';

export default class ViewLoader {
  private readonly _panel: vscode.WebviewPanel | undefined;
  private readonly _extensionPath: string = '';
  private _disposables: vscode.Disposable[] = [];

  constructor(extensionPath: string, uri: vscode.Uri) {
    // load "coddx.taskBoard.fileList" from config (settings):
    const configuration = vscode.workspace.getConfiguration();
    const fileList: string = configuration.get('coddx.taskBoard.fileList') || 'TODO.md';
    const filesArr = fileList.split(',').map(str => str.trim());
    selectedFile = filesArr[0];

    this._extensionPath = extensionPath;
    const column = vscode.window.activeTextEditor
      ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn  vscode.ViewColumn.Two
      : undefined;

    // let config = this.getFileContent();
    // if (config) {
    this._panel = vscode.window.createWebviewPanel('configView', 'Task Board', column || vscode.ViewColumn.Two, {
      enableScripts: true,
      localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))]
    });

    // get base path (from the user's workspace path):
    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';
    // const templateFilePath = rootPath + '/TODO.md';
    let basePath = ''; // relative

    if (uri && uri.fsPath) {
      // path from Context Menu
      basePath = uri.fsPath.replace(rootPath, ''); // get relative path
    }

    // const fileUri = vscode.Uri.file(templateFilePath);
    const todoStr = ''; // this.getFileContent(fileUri);

    this._panel.webview.html = this.getWebviewContent({
      basePath,
      templateString: todoStr || '',
      fileList,
      selectedFile,
      rootPath
    });
    __panel = this._panel;

    this._panel.webview.onDidReceiveMessage(
      (command: ICommand) => {
        switch (command.action) {
          case CommandAction.ShowMessage:
            vscode.window.showInformationMessage(command.content.description);
            return;
          case CommandAction.OpenFile:
            const rootPath2 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';
            const filePath2 = rootPath2 + (selectedFile || 'TODO.md');
            vscode.window.showTextDocument(vscode.Uri.file(filePath2));
            return;
          case CommandAction.Save:
            this.saveFileContent(command.content);
            return;
          case CommandAction.Load:
            selectedFile = command.content.description || 'TODO.md';
            const rootPath3 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';
            const filePath3 = rootPath3 + selectedFile;
            const fileUri = vscode.Uri.file(filePath3);
            const todoStr = this.getFileContent(fileUri);
            this._panel.webview.html = this.getWebviewContent({
              basePath,
              templateString: todoStr || '',
              fileList,
              selectedFile,
              rootPath
            });
            // __panel.webview.postMessage({ command: 'load', content }); // Doesn't work ???
            return;
          // case CommandAction.GetListFiles:
          // load config (settings)
          // vscode.window.showInformationMessage(`👍 Config:`, JSON.stringify(filesArr));

          // WORKS!
          // const workspace = vscode.workspace.workspaceFolders[0];
          // if (workspace) {
          //   vscode.workspace
          //     .findFiles(new vscode.RelativePattern(workspace, '**/*/TODO.md'), '**/node_modules/**')
          //     .then(results => {
          //       console.log('results: ', results);
          //     });
          // }
          // return;
        }
      },
      undefined,
      this._disposables
    );
    // }
  }

  private getWebviewContent({ basePath, templateString, fileList, selectedFile, rootPath }): string {
    // Local path to main script run in the webview
    const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js'));
    const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' });

    // const configJson = JSON.stringify(config);
    const fullPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + `/${selectedFile}`;
    // rootPathBase64 = toBase64(rootPathBase64);

    return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Task Board</title>

        <meta http-equiv="Content-Security-Policy"
                    content="default-src 'none';
                             img-src https:;
                             script-src 'unsafe-eval' 'unsafe-inline' vscode-resource:;
                             style-src vscode-resource: 'unsafe-inline';">
        <script>
          window.acquireVsCodeApi = acquireVsCodeApi;
          window.initialData = { name: 'TaskBoard', path: \`${basePath}\`, dataString: \`${templateString}\`, fileList: \`${fileList}\`, selectedFile: \`${selectedFile}\` };
        </script>
    </head>
    <body>
        <div id="root"></div>

        <script src="${reactAppUri}"></script>
    </body>
    </html>`;
  }

  private getFileContent(fileUri: vscode.Uri) {
    //: IConfig | undefined {
    if (fs.existsSync(fileUri.fsPath)) {
      let content = fs.readFileSync(fileUri.fsPath, 'utf8');
      // let config: IConfig = JSON.parse(content);

      // return config;
      return content;
    }
    return undefined;
  }

  private saveFileContent(config: IConfig) {
    const content = config.description;
    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';
    const filePath = rootPath + (selectedFile || 'TODO.md');

    const uri = vscode.Uri.file(filePath);
    fs.writeFileSync(uri.fsPath, content);

    // vscode.window.showInformationMessage(`👍 TODO.md saved!`);
  }
}


================================================
FILE: src/view/ViewLoader.ts
================================================
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';

import { IConfig, ICommand, CommandAction } from './app/model';
import { deepFind, VER } from './app/Utils';

export default class ViewLoader {
  private readonly _panel: vscode.WebviewPanel | undefined;
  private readonly _extensionPath: string = '';
  private _disposables: vscode.Disposable[] = [];

  constructor(extensionPath: string, uri: vscode.Uri) {
    this._extensionPath = extensionPath;
    const column = vscode.window.activeTextEditor
      ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn
      : undefined;

    // let config = this.getFileContent();
    // if (config) {
    this._panel = vscode.window.createWebviewPanel(
      'configView',
      'Coddx - Generate Files',
      column || vscode.ViewColumn.Two,
      {
        enableScripts: true,
        localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))]
      }
    );

    // get base path (from the user's workspace path):
    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';
    const templateFilePath = rootPath + '/.coddx-template';
    let basePath = ''; // relative

    if (uri && uri.fsPath) {
      // path from Context Menu
      basePath = uri.fsPath.replace(rootPath, ''); // get relative path
    }
    // if (uri.fsPath) {
    //   basePath = uri.fsPath;
    // } else if (vscode.workspace.workspaceFolders) {
    //   basePath = vscode.workspace.workspaceFolders[0].uri.fsPath;
    // }

    // load template file:
    // const filePath = '/Users/duc/Documents/downloads/coddx-ext/.coddx-template';
    const fileUri = vscode.Uri.file(templateFilePath);
    const templateString = this.getFileContent(fileUri);

    this._panel.webview.html = this.getWebviewContent(basePath, templateString || '');

    this._panel.webview.onDidReceiveMessage(
      (command: ICommand) => {
        switch (command.action) {
          case CommandAction.Save:
            const fileUri = vscode.Uri.file(templateFilePath);

            this.saveFileContent(fileUri, command.content);
            return;
          case CommandAction.GenerateFiles:
            this.generateFiles(command.content);
            return;
        }
      },
      undefined,
      this._disposables
    );
    // }
  }

  // constructor(fileUri: vscode.Uri, extensionPath: string) {
  //   this._extensionPath = extensionPath;

  //   const column = vscode.window.activeTextEditor
  // 		? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn
  // 		: undefined;

  //   let config = this.getFileContent(fileUri);
  //   if (config) {
  //     this._panel = vscode.window.createWebviewPanel(
  //       "configView",
  //       "Config View",
  //       column || vscode.ViewColumn.Two,
  //       {
  //         enableScripts: true,

  //         localResourceRoots: [
  //           vscode.Uri.file(path.join(extensionPath, "configViewer"))
  //         ]
  //       }
  //     );

  //     this._panel.webview.html = this.getWebviewContent(config);

  //     this._panel.webview.onDidReceiveMessage(
  //       (command: ICommand) => {
  //         switch (command.action) {
  //           case CommandAction.Save:
  //             this.saveFileContent(fileUri, command.content);
  //             return;
  //         }
  //       },
  //       undefined,
  //       this._disposables
  //     );
  //   }
  // }

  private getWebviewContent(basePath: string, templateString: string): string {
    // Local path to main script run in the webview
    const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js'));
    const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' });

    // const configJson = JSON.stringify(config);

    return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Coddx - Generate Files</title>

        <meta http-equiv="Content-Security-Policy"
                    content="default-src 'none';
                             img-src https:;
                             script-src 'unsafe-eval' 'unsafe-inline' vscode-resource:;
                             style-src vscode-resource: 'unsafe-inline';">

        <script>
          window.acquireVsCodeApi = acquireVsCodeApi;
          window.initialData = { path: \`${basePath}\`, templateString: \`${templateString}\` };
        </script>
    </head>
    <body>
        <div id="root"></div>

        <script src="${reactAppUri}"></script>
    </body>
    </html>`;
  }

  private getFileContent(fileUri: vscode.Uri) {
    //: IConfig | undefined {
    if (fs.existsSync(fileUri.fsPath)) {
      let content = fs.readFileSync(fileUri.fsPath, 'utf8');
      // let config: IConfig = JSON.parse(content);

      // return config;
      return content;
    }
    return undefined;
  }

  private saveFileContent(fileUri: vscode.Uri, config: IConfig) {
    // if (fs.existsSync(fileUri.fsPath)) {}
    fs.writeFileSync(fileUri.fsPath, config.description || '');

    vscode.window.showInformationMessage(`👍 Template saved to .coddx-template`);
  }

  private mkDirByPathSync(targetDir: string, { baseDir = '', isRelativeToScript = false } = {}) {
    const sep = path.sep;
    const initDir = path.isAbsolute(targetDir) ? sep : '';
    // const baseDir = isRelativeToScript ? __dirname : '.';

    return targetDir.split(sep).reduce((parentDir, childDir) => {
      const curDir = path.resolve(baseDir, parentDir, childDir);
      try {
        fs.mkdirSync(curDir);
      } catch (err) {
        if (err.code === 'EEXIST') {
          // curDir already exists!
          return curDir;
        }

        // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
        if (err.code === 'ENOENT') {
          // Throw the original parentDir error on curDir `ENOENT` failure.
          throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
        }

        const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
        if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) {
          throw err; // Throw if it's just the last created dir.
        }
      }

      return curDir;
    }, initDir);
  }

  private generateFiles(config: IConfig) {
    const { path, files } = JSON.parse(config.description || '');
    let counter = 0;
    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';

    if (!fs.existsSync(rootPath + path)) {
      this.mkDirByPathSync(path, { baseDir: rootPath });
    }

    Object.keys(files).forEach(itemKey => {
      const item = files[itemKey];

      if (item.checked === true) {
        const fileName = item.fileName; // itemKey.trim()
        const filePath = rootPath + (path + '/' + fileName).trim();

        if (fileName.indexOf('/') > 0) {
          // create directories, e.g. __test__/mocks
          const dirPath = fileName.substr(0, fileName.lastIndexOf('/'));
          this.mkDirByPathSync(dirPath, { baseDir: rootPath + path });
        }

        const fileUri = vscode.Uri.file(filePath);
        fs.writeFileSync(fileUri.fsPath, item.fileContent);
        counter++;
      }
    });

    vscode.window.showInformationMessage(`👍 ${counter} files generated.`);
  }
}


================================================
FILE: src/view/app/Utils.ts
================================================
import nunjucks from 'nunjucks';
import { ICommand, CommandAction } from './model';

export const VER = '0.2.55'; // TODO: get this from package.json.

// const TelemetryReporter = require('vscode-extension-telemetry');
// const extensionId = 'coddx-alpha';
// const extensionVersion = '0.2.11';
// const key = '';
// export const telemetry = new TelemetryReporter(extensionId, extensionVersion, key); // TODO: this crashed!

export interface FilesInterface {
  checked?: boolean;
  fileMarker?: string;
  fileeName?: string;
  fileContent?: string;
}

export const FILE_SEPARATOR = '--->>';

export const DefaultTemplateString = `// -------------->> {{fileName}}.tsx
// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}
import * as React from 'react';
import './{{fileName}}.css';

export default class {{fileName}} extends React.Component {
  render() {
    return (
      <div>
        {/* ...code goes here... */}
      </div>
    )
  }
}

// -------------->> {{fileName}}.css
.main {}

// -------------->> __test__/{{fileName}}.test.tsx
import * as React from 'react';

it('{{fileName}} - render', () => {
  // render(<{{fileName}} />);
  // expect(...);
});
`;

const getBuiltInParams = () => {
  const fd = formatDate(new Date());
  return {
    YYYY: fd.year,
    MM: fd.month,
    DD: fd.day,
    HH: fd.hours24,
    mm: fd.minutes,
    ss: fd.seconds
  };
};

// format to: yyy-mm-dd
export function formatDate(d: Date) {
  let month = '' + (d.getMonth() + 1);
  let day = '' + d.getDate();
  const year = d.getFullYear();

  if (month.length < 2) month = '0' + month;
  if (day.length < 2) day = '0' + day;
  return {
    year,
    month,
    day,
    hours24: d.getHours(),
    minutes: d.getMinutes(),
    seconds: d.getSeconds(),
    yyyymmdd: [year, month, day].join('-')
  };
}

export function parseJsonString(jsonString: string) {
  if (!jsonString || !jsonString.trim()) {
    return {};
  }
  let useParams = {};
  try {
    useParams = JSON.parse(jsonString.trim());
  } catch {}
  return useParams;
}

export function jsonClone(obj: any) {
  if (!obj) {
    return obj; // null or undefined
  }
  return JSON.parse(JSON.stringify(obj));
}

export function deepFind(obj: any, path: string, defaultValue: any) {
  const travel = regexp =>
    String.prototype.split
      .call(path, regexp)
      .filter(Boolean)
      .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
  const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
  return result === undefined || result === obj ? defaultValue : result;
}

export function nunjucksRender(str: string, data: any): string {
  const tmpl = nunjucks.compile(str);
  return tmpl.render({ ...getBuiltInParams(), ...data });
}

// from content string => parse to get file contents & put in "files" object {}
export function getTemplateItems(content: string, params: any = {}, existingFiles: any = {}): FilesInterface {
  const lines = content.split('\n');
  const output: FilesInterface = {};
  let fileContent = '';
  let lastKey = '';

  lines.forEach(line => {
    if (line.indexOf(FILE_SEPARATOR) >= 0) {
      // when seeing a new file separator => set the accumulated "fileContent" string for the lastKey:
      if (lastKey) {
        output[lastKey].fileContent = nunjucksRender(fileContent, params).trim();
        fileContent = ''; // reset fileContent, prepare for the next file.
      }

      const arr = line.split(FILE_SEPARATOR);
      const itemKey = arr[arr.length - 1].trim();
      const existingItem = existingFiles[itemKey] || {};

      const fileName = nunjucksRender(itemKey, params).trim();
      output[itemKey] = {
        checked: existingItem.checked !== undefined ? existingItem.checked : true,
        fileMarker: line.replace(itemKey, fileName),
        fileName,
        fileContent
      };
      lastKey = itemKey;
    } else {
      if (output[lastKey] && output[lastKey].checked === true) {
        fileContent += line + '\n';
      }
    }
  });

  if (lastKey) {
    output[lastKey].fileContent = nunjucksRender(fileContent, params).trim();
  }
  return output;
}

export const sendCommand = (vscode: any, action: CommandAction, dataStr: string) => {
  let command: ICommand = {
    action: action,
    content: {
      name: '',
      description: dataStr
    }
  };
  if (vscode.postMessage) {
    vscode.postMessage(command);
  }
};

export function getVscodeHelper(vscode: any) {
  return {
    showMessage: (msg: string) =>
      vscode.postMessage({
        action: CommandAction.ShowMessage,
        content: {
          name: '',
          description: msg
        }
      }),
    saveList: (dataStr: string) => {
      let command: ICommand = {
        action: CommandAction.Save,
        content: {
          name: '',
          description: dataStr
        }
      };
      if (vscode.postMessage) {
        vscode.postMessage(command);
      }
    }
  };
}

// In VSCode, "btoa" function is not available (?) => use this:
// A helper that returns Base64 characters and their indices.
// const chars = {
//   ascii: function() {
//     return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
//   },
//   indices: function() {
//     if (!this.cache) {
//       this.cache = {};
//       var ascii = chars.ascii();

//       for (var c = 0; c < ascii.length; c++) {
//         var chr = ascii[c];
//         this.cache[chr] = c;
//       }
//     }
//     return this.cache;
//   }
// };
// const toBase64 = function(data) {
//   var ascii = chars.ascii(),
//     len = data.length - 1,
//     i = -1,
//     b64 = '';
//   while (i < len) {
//     var code = (data.charCodeAt(++i) << 16) | (data.charCodeAt(++i) << 8) | data.charCodeAt(++i);
//     b64 += ascii[(code >>> 18) & 63] + ascii[(code >>> 12) & 63] + ascii[(code >>> 6) & 63] + ascii[code & 63];
//   }
//   var pads = data.length % 3;
//   if (pads > 0) {
//     b64 = b64.slice(0, pads - 3);

//     while (b64.length % 4 !== 0) {
//       b64 += '=';
//     }
//   }
//   return b64;
// };


================================================
FILE: src/view/app/components/CodeGen/CodeEditor.css
================================================
.container__content {
    width: 440px;
    max-width: 100%;
    padding: 10px;
    text-align: center;
  }
  
  .container_editor_area {
    tab-size: 4ch;
    max-height: 400px;
    overflow: auto;
    margin: 1.67em 0;
  }
  
  .container__editor {
    font-size: 12px;
    font-variant-ligatures: common-ligatures;
    background-color: #fafafa;
    border-radius: 3px;
  }
  
  .container__editor textarea {
    outline: 0;
  }
  
  .button {
    display: inline-block;
    padding: 0 6px;
    text-decoration: none;
    background: #000;
    color: #fff;
  }
  
  .button:hover {
    background: linear-gradient(45deg, #E42B66, #E2433F);
  }
  
  /* Syntax highlighting */
  .token.comment,
  .token.prolog,
  .token.doctype,
  .token.cdata {
    color: #5a9b4b;
  }
  .token.punctuation {
    color: #9e9e9e;
  }
  .namespace {
    opacity: 0.7;
  }
  .token.property,
  .token.tag,
  .token.boolean,
  .token.number,
  .token.constant,
  .token.symbol,
  .token.deleted {
    color: #e91e63;
  }
  .token.selector,
  .token.attr-name,
  .token.string,
  .token.char,
  .token.builtin,
  .token.inserted {
    color: #4caf50;
  }
  .token.operator,
  .token.entity,
  .token.url,
  .language-css .token.string,
  .style .token.string {
    color: #795548;
  }
  .token.atrule,
  .token.attr-value,
  .token.keyword {
    color: #3f51b5;
  }
  .token.function {
    color: #f44336;
  }
  .token.regex,
  .token.important,
  .token.variable {
    color: #ff9800;
  }
  .token.important,
  .token.bold {
    font-weight: bold;
  }
  .token.italic {
    font-style: italic;
  }
  .token.entity {
    cursor: help;
  }

================================================
FILE: src/view/app/components/CodeGen/CodeEditor.tsx
================================================
import * as React from 'react';

import Editor from 'react-simple-code-editor';
import { highlight, languages } from 'prismjs/components/prism-core';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';

import './CodeEditor.css';

const styles = {
  editor: {
    fontFamily: '"Fira code", "Fira Mono", monospace',
    fontSize: 12
  }
}

interface Props {
  code: string,
  onValueChange?: (code: string) => void
}

export default class CodeEditor extends React.Component<Props> {
  state = { code: this.props.code };

  componentWillReceiveProps(newProps: any) {
    this.setState({ code: newProps.code });
  }

  render() {
    return (
      <>
        <Editor
          value={this.state.code}
          onValueChange={code => {
            this.setState({ code });
            if (this.props.onValueChange) {
              this.props.onValueChange(code);
            }
          }}
          highlight={code => highlight(code || '', languages.js)}
          padding={10}
          style={styles.editor}
        />
      </>
    );
  }
}


================================================
FILE: src/view/app/components/CodeGen/EditTemplatePanel.tsx
================================================
import * as React from 'react';
import CodeEditor from './CodeEditor';

export default class EditTemplatePanel extends React.Component<any> {
  state = {
    code: this.props.code
  };

  componentWillReceiveProps(newProps: any) {
    this.setState({ code: newProps.code });
  }

  render() {
    return (
      <div>
        <div>
          <button onClick={() => this.props.onClose(this.state.code)}>Save</button>
          &nbsp;
          {/* <button onClick={() => {}}>Cancel</button> */}
        </div>
        <hr />
        <CodeEditor code={this.state.code} onValueChange={code => this.setState({ code })} />
      </div>
    );
  }
}


================================================
FILE: src/view/app/components/CodeGen/MainView.css
================================================
body,
html {
  margin: 0;
  padding: 0;
}
button {
  background-color: #0063a0;
  color: #fff;
  padding: 5px 20px;
  border: none;
  cursor: pointer;
}
button:hover {
  background-color: #0073bb;
}
button:disabled {
  color: #333;
  cursor: not-allowed;
}

.mainView {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 10px;
}

.mainView .body {
  /* min-height: 92vh; */
  padding: 10px 0;
}
.mainView a {
  text-decoration: none;
}
.mainView textarea {
  width: 97%;
  height: 50vh;
  padding: 5px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 1.2em;
}

.mainView .fileList {
  margin: 10px 10px;
  padding: 0;
}
.mainView .fileList li {
  list-style: none;
}
.mainView .fileList input {
  margin-right: 10px;
}

.mainView footer {
  position: fixed;
  bottom: 0px;
  width: 100%;
  background-color: #333;
  display: flex;
  margin: 0;
  padding: 0;
}

.mainView footer input {
  flex: 1;
  height: 25px;
  background-color: #333;
  color: #eee;
  border-color: #555;
  padding: 2px 5px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 1.2em;
}

.mainView .infoBox div {
  display: flex;
  margin-bottom: 10px;
}
.mainView .infoBox span {
  display: inline-block;
  width: 90px;
  align-self: center;
}
.mainView .infoBox input {
  flex: 1;
  font-family: Verdana, Geneva, Tahoma, sans-serif; /* 'Courier New', Courier, monospace */
  background-color: #373837;
  color: #fff;
  padding: 2px 4px;
  border: 1px solid #333;
}


================================================
FILE: src/view/app/components/CodeGen/MainView.tsx
================================================
import * as React from 'react';
import { IConfig, ICommand, CommandAction } from '../../model';
import { FilesInterface, nunjucksRender, getTemplateItems, DefaultTemplateString, parseJsonString } from '../../Utils';

import EditTemplatePanel from './EditTemplatePanel';
import CodeEditor from './CodeEditor';
import './MainView.css';

const saveTemplate = (vscode: any, templateString: string) => {
  let command: ICommand = {
    action: CommandAction.Save,
    content: {
      name: '',
      description: templateString
    }
  };
  if (vscode.postMessage) {
    vscode.postMessage(command);
  }
};

const generateFiles = (vscode: any, path: string, files: FilesInterface) => {
  let command: ICommand = {
    action: CommandAction.GenerateFiles,
    content: {
      name: '',
      description: JSON.stringify({
        path,
        files
      })
    }
  };
  if (vscode.postMessage) {
    vscode.postMessage(command);
  }
};

interface IConfigProps {
  vscode: any;
  initialData?: IConfig;
}

interface IConfigState {
  path: string;
  templateString: string;
  editMode: boolean;
  config: IConfig;
  output: string;
  name: string;
  action: string;
  files: any;
  params: string;
}

let templateStr = window && window['initialData'] ? window['initialData']['templateString'] : '';
if (!templateStr) {
  templateStr = DefaultTemplateString;
}

const initialPath = window && window['initialData'] ? window['initialData']['path'] : '';
const DefaultName = 'NewComponent';

export default class MainView extends React.Component<IConfigProps, IConfigState> {
  state = {
    path: '',
    templateString: templateStr,
    editMode: false,
    config: null,
    output: '',
    name: DefaultName,
    action: 'Preview Output',
    files: getTemplateItems(window && window['initialData'] ? window['initialData']['templateString'] : ''),
    params: ''
  };

  componentDidMount() {
    this.setState({ path: initialPath + '/' + DefaultName });
  }

  render() {
    // const templateString = window['initialData']['templateString'];
    const { path, templateString, output, name, params } = this.state;

    // const [editMode, setEditMode] = React.useState(false); // Hooks don't work in VSCode Ext ??
    // console.log(
    //   '--- ',
    //   getTemplateItems(
    //     templateString,
    //     {},
    //     {
    //       '{{fileName}}.css': { checked: false }
    //     }
    //   )
    // );
    return (
      <main className="mainView">
        <section className="body">
          {this.state.editMode ? (
            <EditTemplatePanel
              code={templateString}
              onClose={(newContent: string) => {
                this.setState({
                  editMode: false,
                  templateString: newContent,
                  output: ''
                });
                saveTemplate(this.props.vscode, newContent);
              }}
            />
          ) : (
            <div>
              <div>
                <a href="javascript:;" onClick={() => this.setState({ editMode: true })}>
                  Edit Templates
                </a>
              </div>
              <p className="infoBox">
                <div>
                  <span>File Name*</span>
                  <input
                    placeholder="to replace {{fileName}}"
                    value={name}
                    onChange={e => {
                      const name = e.target.value;
                      this.setState({
                        name,
                        output: '',
                        path: initialPath + '/' + name
                      });
                    }}
                  />
                </div>
                <div>
                  <span>Path*</span>
                  <input
                    placeholder="relative to project"
                    value={path}
                    onChange={e => this.setState({ path: e.target.value, output: '' })}
                  />
                </div>
                <div>
                  <span>Params</span>
                  <input
                    placeholder="(json, optional)"
                    value={this.state.params}
                    onChange={e => this.setState({ params: e.target.value, output: '' })}
                  />
                </div>
              </p>
              <p>Choose files to generate:</p>
              <ul className="fileList">
                {Object.keys(this.state.files).map(itemKey => {
                  const item = this.state.files[itemKey];

                  const outputName = nunjucksRender(itemKey, {
                    fileName: name,
                    ...parseJsonString(params)
                  });
                  return (
                    <li>
                      <label>
                        <input
                          type="checkbox"
                          checked={item.checked}
                          onChange={() => {
                            const files = this.state.files;
                            files[itemKey].checked = !files[itemKey].checked;
                            this.setState({ files });
                          }}
                        />
                        {outputName}
                      </label>
                    </li>
                  );
                })}
              </ul>
              <p>
                <button
                  disabled={!name}
                  onClick={() => {
                    const files = getTemplateItems(
                      templateString,
                      { fileName: name, ...parseJsonString(params) },
                      this.state.files
                    );
                    let newOutput = '';
                    Object.keys(files).forEach(itemKey => {
                      if (files[itemKey].checked === true) {
                        newOutput += '\n\n' + files[itemKey].fileMarker + '\n';
                        newOutput += files[itemKey].fileContent;
                      }
                    });

                    this.setState({ output: newOutput });
                    // this.props.vscode.window.showInformationMessage()
                  }}
                >
                  Preview Outputs
                </button>
                &nbsp;&nbsp;
                {name.length > 0 && output && (
                  <button
                    onClick={() => {
                      // const output = nunjucksRender(templateString, {
                      //   fileName: name
                      // });
                      // this.setState({ output });

                      const files = getTemplateItems(
                        this.state.output, // templateString,
                        { fileName: name, ...parseJsonString(params) },
                        this.state.files
                      );
                      generateFiles(this.props.vscode, path, files);
                    }}
                  >
                    Generate Files
                  </button>
                )}
              </p>
              {output && <hr />}
              <CodeEditor code={output} onValueChange={(code) => this.setState({ output: code })} />
            </div>
          )}
        </section>
        <footer>
          <input />
        </footer>
      </main>
    );
  }
}


================================================
FILE: src/view/app/components/TaskBoard/ButtonBar.tsx
================================================
import * as React from 'react';
import styled from 'styled-components';
import Select from 'react-select';

import { getMarkdown, SearchIcon } from './Helpers';
import SearchInput from './SearchInput';

const Container = styled.div`
  display: flex;
  justify-content: space-between;
  text-align: right;
  margin: 5px 10px;
  span[data-name='project-name'] {
    font-size: 1.2em;
    align-self: center;
    margin-top: 5px;
    margin-left: 0px;
    svg {
      visibility: hidden;
    }
    &:hover {
      svg {
        visibility: visible;
      }
    }
  }
  > button {
    margin-left: 10px;
  }
`;
// const newData = parseMarkdown(defaultDataString);

export function OpenFileIcon(props: any) {
  return (
    <svg
      {...props}
      title="Open file"
      style={{
        width: '0.7em',
        height: '0.7em',
        cursor: 'pointer',
        marginLeft: 5,
        marginTop: 5
      }}
      width="16"
      height="16"
      viewBox="0 0 16 16"
      version="1.1"
    >
      <path d="M14 16v-11l-1 1v9h-12v-12h9l1-1h-11v14z" fill="currentColor" />
      <path d="M16 0h-5l1.8 1.8-6.8 6.8 1.4 1.4 6.8-6.8 1.8 1.8z" fill="currentColor" />
    </svg>
  );
}

export function RefreshIcon(props: any) {
  return (
    <svg
      {...props}
      title="Refresh"
      style={{ verticalAlign: 'middle', cursor: 'pointer', marginLeft: 5 }}
      width="16"
      height="16"
      viewBox="0 0 100.25 100.25"
    >
      <polyline points="70,57.75 82.5,45.75 95,57.75 " />
      <g>
        <path
          fill="currentColor"
          d="M83.854,45.613c-0.076-0.825-0.807-1.438-1.632-1.356c-0.824,0.076-1.432,0.806-1.356,1.631 c0.09,0.97,0.135,1.956,0.135,2.929c0,17.333-14.043,31.434-31.304,31.434c-8.36,0-16.222-3.269-22.134-9.205 c-0.585-0.587-1.534-0.589-2.121-0.004s-0.589,1.535-0.004,2.122c6.479,6.505,15.095,10.087,24.259,10.087 c18.915,0,34.304-15.447,34.304-34.434C84,47.752,83.95,46.674,83.854,45.613z"
        />
        <path
          fill="currentColor"
          d="M31.082,44.712c-0.575-0.599-1.524-0.618-2.121-0.043L18.513,54.7c-0.336-1.852-0.512-3.737-0.512-5.632 c0-17.268,14.051-31.317,31.322-31.317c8.351,0,16.206,3.247,22.118,9.143c0.588,0.585,1.538,0.583,2.122-0.003 c0.585-0.586,0.583-1.536-0.003-2.121c-6.479-6.46-15.087-10.019-24.237-10.019c-18.926,0-34.322,15.395-34.322,34.317 c0,1.505,0.102,3.004,0.296,4.488L6.04,44.669c-0.597-0.573-1.545-0.555-2.121,0.043c-0.574,0.597-0.555,1.547,0.043,2.121l12.5,12 c0.29,0.279,0.665,0.418,1.039,0.418s0.749-0.139,1.039-0.418l12.5-12C31.637,46.259,31.656,45.309,31.082,44.712z"
        />
      </g>
    </svg>
  );
}

export default ({
  vscodeHelper,
  fileArray,
  selectedFile,
  data,
  onLoadData,
  onSave,
  onRefresh,
  onOpenFile,
  onSearch,
  onSelectFile
}) => {
  const fileOptions = fileArray.map(path => {
    return { value: path, label: path };
  });
  fileOptions.push({ value: 'ADD', label: '+ Add file...' });

  const selectedOpt = fileOptions.find(opt => opt.value === selectedFile);
  const [selectValue, setSelectValue] = React.useState(selectedOpt);
  const [searchActive, setSearchActive] = React.useState(false);
  return (
    <div>
      <div style={{ display: 'flex' }}>
        <span style={{ width: '30%', marginLeft: 10 }}>
          <Select
            className="__reactSelect"
            classNamePrefix="__select"
            options={fileOptions}
            value={selectValue}
            styles={{}}
            onChange={sel => {
              if (sel.value === 'ADD') {
                vscodeHelper.showMessage(
                  'In your workspace "settings.json" file, add this: "coddx.taskBoard.fileList": "TODO.md, folder/TODO-name.md" (comma separated, use your file paths)'
                );
                return;
              }
              setSelectValue(sel);
              if (onSelectFile) {
                onSelectFile(sel);
              }
            }}
          />
        </span>
        <a
          style={{ position: 'absolute', right: 15, top: 15 }}
          href="https://nnote.cc/s/k0o93/todomd-kanban-board-documentation"
        >
          Help | Doc
        </a>
      </div>
      <Container>
        <span data-name="project-name">
          {data.projectName} <RefreshIcon onClick={() => onRefresh()} />{' '}
          <OpenFileIcon
            onClick={() => {
              onOpenFile();
            }}
          />
        </span>
        <div>
          {searchActive ? (
            <SearchInput style={{ marginRight: 10 }} onChange={value => onSearch(value)} />
          ) : (
            <SearchIcon
              style={{ cursor: 'pointer', marginRight: 10, verticalAlign: '-4px' }}
              onClick={() => setSearchActive(true)}
            />
          )}
          <button
            onClick={() => {
              const id = `newtask_${Math.random()}`;
              // const newData = parseMarkdown(defaultDataString);
              const newData = { ...data };

              let hasCheckbox = true;
              const firstCol = Object.keys(newData.columns)[0];
              const firstColTaskIds = newData.columns[firstCol].taskIds;
              if (firstColTaskIds && firstColTaskIds.length > 0) {
                const lastColTask = data.tasks[firstColTaskIds[firstColTaskIds.length - 1]];
                if (lastColTask && lastColTask.hasCheckbox === false) {
                  hasCheckbox = false;
                }
              }
              newData.tasks[id] = { id, content: '', done: false, hasCheckbox };

              newData.columns[firstCol].taskIds.unshift(id); // add to the top.
              onLoadData(newData);

              const str = getMarkdown(newData);
              onSave(str);
              window['isCreatingTask'] = true; // global flag to be caught in Task.tsx
            }}
          >
            + Task
          </button>
        </div>
        {/* <button
        onClick={() => {
          refresh(newData);
        }}
      >
        Refresh
      </button> */}
        {/* <button onClick={() => {
        const str = getMarkdown(data);
        onSave(str);
      }}>Save to TODO.md</button> */}
      </Container>
    </div>
  );
};


================================================
FILE: src/view/app/components/TaskBoard/Helpers.tsx
================================================
import * as React from 'react';
import { TaskInterface } from './Task';

export const defaultDataString = `# Project

Project Description

<em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>

### Todo

### In Progress

### Done ✓

`;
// export const defaultDataString = `# Project Name

// Project Description

// <em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>

// ### Todo

// - [ ] Build Launch Pad
// - [ ] Launch time 🎉
//   - [ ] Prepare for launching
//   - [ ] Detail description

// ### In Progress

// - [ ] Build the rocket engine

// ### Done ✓

// - [x] Designed my rocket
// `;

const isDoneColumn = (columnName: string) => {
  if (!columnName) {
    return false;
  }
  const lowerColName = columnName.toLowerCase();
  if (lowerColName.indexOf('[x]') > 0 || lowerColName.indexOf('✓') > 0) {
    return true;
  }
  return false;
};

export function getMarkdown(data) {
  let md = '';
  // loop through "columns", then column.taskIds => write to lines
  for (const colKey in data.columns) {
    const col = data.columns[colKey];
    md += '### ' + col.title + '\n\n';

    let checkboxStr = '[ ] ';
    if (isDoneColumn(col.title)) {
      checkboxStr = '[x] ';
    }

    // for (let i = 0; i < col.taskIds.length; i += 1) {
    //   const task = col.taskIds[i];
    //   md += '[ ] ' + task.content + '\n';
    // }
    col.taskIds.forEach((taskId: string) => {
      const task: TaskInterface = data.tasks[taskId];
      if (!task) {
        return;
      }
      const indent = task.level === 1 ? '  - ' : '- ';
      md += indent + (task.hasCheckbox === false ? '' : checkboxStr) + task.content.trim() + '  \n';
    });
    md += '\n';
  }
  md = data.precontent + md; // prepend "data.precontent"
  return md;
}

// parse TODO.md content (markdown), return object { tasks: {}, ... } - see "output":
export function parseMarkdown(md: string) {
  const output = {
    projectName: '',
    precontent: '', // description content before the Lists.
    tasks: {},
    columns: {},
    columnOrder: []
  };
  let lastColName = '';
  const lines = md.split('\n');
  let taskNum = 0;

  let listFound = false; // found after '### '

  lines.forEach(line => {
    if (listFound === false) {
      if (line.indexOf('# ') === 0) {
        output.projectName = line.replace('# ', '').trim();
      }
      output.precontent += line.indexOf('### ') === 0 ? '' : line + '\n'; // append to precontent
    }

    if (line.indexOf('### ') === 0) {
      listFound = true;
      lastColName = line.replace('### ', '').trim();
      output.columns[lastColName] = {
        id: lastColName.trim(),
        title: lastColName.trim(),
        taskIds: []
      };
      output.columnOrder.push(lastColName);
      return;
    }
    if (!listFound) {
      return;
    }
    if (line.trim().length === 0) {
      return;
    }

    // after a List is found => Tasks come next:
    taskNum++;
    const id = `task${taskNum}`;
    const hasCheckbox = line.indexOf('[ ]') >= 0 || line.indexOf('[x]') >= 0;
    const level = line.indexOf('  - ') === 0 ? 1 : 0;
    let title = line
      .replace('  - [ ] ', '')
      .replace('  - [x] ', '')
      .replace('  - ', '')
      .replace('- [ ] ', '')
      .replace('- [x] ', '')
      .trim();
    if (title.indexOf('- ') === 0) {
      title = title.slice(2); // remove '- '
    }
    const task: TaskInterface = {
      id,
      content: title,
      hasCheckbox,
      done: isDoneColumn(lastColName),
      level
    };
    output.tasks[id] = task;
    output.columns[lastColName].taskIds.push(id);
  });
  // console.log('---- output', output);
  return output;
}

export function DragIcon(props: any) {
  return (
    <svg {...props} style={{ width: '1em', height: '1em' }} viewBox="0 0 1024 1024" version="1.1">
      <path
        d="M384 128h85.333333v85.333333H384V128m170.666667 0h85.333333v85.333333h-85.333333V128M384 298.666667h85.333333v85.333333H384V298.666667m170.666667 0h85.333333v85.333333h-85.333333V298.666667m-170.666667 170.666666h85.333333v85.333334H384v-85.333334m170.666667 0h85.333333v85.333334h-85.333333v-85.333334m-170.666667 170.666667h85.333333v85.333333H384v-85.333333m170.666667 0h85.333333v85.333333h-85.333333v-85.333333m-170.666667 170.666667h85.333333v85.333333H384v-85.333333m170.666667 0h85.333333v85.333333h-85.333333v-85.333333z"
        fill="currentColor"
      />
    </svg>
  );
}

export function SearchIcon(props: any) {
  return (
    <svg {...props} width="16" height="16" x="0px" y="0px" viewBox="0 0 511.999 511.999">
      <g>
        <g>
          <path
            d="M508.874,478.708L360.142,329.976c28.21-34.827,45.191-79.103,45.191-127.309c0-111.75-90.917-202.667-202.667-202.667 S0,90.917,0,202.667s90.917,202.667,202.667,202.667c48.206,0,92.482-16.982,127.309-45.191l148.732,148.732 c4.167,4.165,10.919,4.165,15.086,0l15.081-15.082C513.04,489.627,513.04,482.873,508.874,478.708z M202.667,362.667 c-88.229,0-160-71.771-160-160s71.771-160,160-160s160,71.771,160,160S290.896,362.667,202.667,362.667z"
            fill="currentColor"
          />
        </g>
      </g>
    </svg>
  );
}


================================================
FILE: src/view/app/components/TaskBoard/SearchInput.tsx
================================================
import * as React from 'react';

interface Props {
  style?: object;
  placeholder?: string;
  delayTime?: number;
  onChange?: (value: string) => void;
}

export default function ({ style, placeholder = 'Search', delayTime = 200, onChange }: Props) {
  const [timeoutId, setTimeoutId] = React.useState(null);
  const [value, setValue] = React.useState('');
  const inputRef: React.RefObject<HTMLInputElement> = React.useRef(null);
  const allStyle = {
    padding: '4px 3px',
    backgroundColor: 'var(--vscode-tab-background)',
    color: 'var(--button-text-color)',
    border: '1px solid var(--vscode-tab-inactiveBackground)',
    ...style
  };
  const resetTimer = () =>
    setTimeout(() => {
      clearTimeout(timeoutId);
      setTimeoutId(null);
      if (onChange) {
        onChange(inputRef.current.value);
      }
    }, delayTime);

  return (
    <input
      autoFocus
      ref={inputRef}
      placeholder={placeholder}
      defaultValue={value}
      onChange={ev => {
        setValue(ev.target.value);
        if (timeoutId) {
          clearTimeout(timeoutId);
          setTimeoutId(null);
        }
        const id = resetTimer();
        setTimeoutId(id);
      }}
      style={{ ...allStyle }}
    />
  );
}


================================================
FILE: src/view/app/components/TaskBoard/Task.tsx
================================================
import * as React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
import TextareaAutosize from 'react-autosize-textarea';
import { DragIcon } from './Helpers';
import TaskMenu from './TaskMenu';
import { parseInline } from 'marked';

const { memo } = React;

export interface TaskInterface {
  id: string;
  content: string;
  done: boolean;
  hasCheckbox?: boolean;
  matched?: boolean;
  level?: number;
}

const TaskContainer = styled.div<{ isDragging: boolean }>`
  position: relative;
  border-radius: 4px;
  margin-left: 5px;
  margin-bottom: 8px;
  background-color: ${props => (props.isDragging ? '#eef' : '#333')};
  transition: background 0.1s;
`;

const Handle = styled.span`
  display: flex;
  margin-right: 5px;
`;

const TaskDisplay = styled.div`
  box-sizing: border-box;
  width: 100%;
  padding: 3px 0 3px 0;
  margin-bottom: 2px;
  background-color: inherit;
  color: var(--vscode-tab-foreground);
  border: 1px solid transparent;
  font-family: inherit;
`;

const StyledTextarea = styled(TextareaAutosize)`
  resize: none;
  box-sizing: border-box;
  width: 100%;
  background-color: inherit;
  color: var(--vscode-tab-foreground);
  border: 1px solid transparent;
  margin-top: 1px;
  margin-bottom: 3px;
  font-family: inherit;
`;

// use "svg, [data-type='action-icon']" to hide the drag icon svg also.
const TaskWrapper = styled.div`
  display: flex;
  align-items: center;
  background-color: var(--vscode-tab-inactiveBackground);
  padding-right: 4px;
  [draggable] {
    margin: 0;
  }
  [data-type='action-icon'] {
    visibility: hidden;
  }
  &:hover {
    [data-type='action-icon'] {
      visibility: visible;
    }
  }
`;

const ActionWrapper = styled.div`
  position: absolute;
  right: 0;
  padding: 2px;
`;

const ActionIcon = styled.span`
  font-size: 1em;
  padding: 1px 5px;
  margin: 0 2px;
  cursor: pointer;
  background-color: #555;
  color: #eee;
  border-radius: 4px;
  &:hover {
    background-color: #777;
  }
`;

const TickMark = styled.span`
  font-size: 1em;
  color: #ddd;
  margin-right: 3px;
`;

interface TaskProps {
  column: any;
  columnIndex: number;
  task: TaskInterface;
  index: number;
  onChangeTitle: (title: string) => void;
  onDelete: (task: TaskInterface) => void;
  onInProgress: (task: TaskInterface) => void;
  onComplete: (task: TaskInterface) => void;
  onChangeTask: (id: string, task: TaskInterface) => void;
}

export default memo(
  ({
    column,
    columnIndex,
    task,
    index,
    onChangeTitle,
    onDelete,
    onInProgress,
    onComplete,
    onChangeTask
  }: TaskProps) => {
    // mainKey is used to force re-render StyledTextarea as it doesn't auto re-render as expected.
    const [mainKey, setMainKey] = React.useState('key_' + Math.random());
    const [isEditing, setIsEditing] = React.useState(false);
    const [menuActive, setMenuActive] = React.useState('');
    const inputRef: React.RefObject<HTMLTextAreaElement> = React.createRef();

    React.useEffect(() => {
      // on did mount
      if (window['isCreatingTask'] === true) {
        // after clicking on "+ New Task" button => auto focus when creating task
        window['isCreatingTask'] = false;
        setIsEditing(true);
      }
    }, []);

    React.useEffect(() => {
      if (isEditing === true) {
        // when editing, auto set the cursor at the end:
        inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
      }
    }, [isEditing]);

    const isHidden = task.matched === false; // filtered by SearchInput's value
    if (isHidden) {
      return null;
    }

    // console.log('column.title', column.title);
    return (
      <Draggable draggableId={task.id} index={index}>
        {(provided, snapshot) => (
          <TaskContainer
            {...provided.draggableProps}
            // {...provided.dragHandleProps}
            ref={provided.innerRef}
            isDragging={snapshot.isDragging}
          >
            <TaskWrapper>
              <Handle {...provided.dragHandleProps}>
                <DragIcon />
              </Handle>
              <TickMark>{column.title.indexOf('✓') >= 0 ? '✓ ' : ''}</TickMark>

              {isEditing ? (
                <StyledTextarea
                  placeholder="New Task"
                  autoFocus={true}
                  key={mainKey}
                  ref={inputRef}
                  onKeyDown={ev => {
                    if (ev.keyCode === 13) {
                      ev.preventDefault(); // in this Textarea, ignore Enter Key. Otherwise, it will be a linebreak.
                      // inputRef.current.blur();
                      setIsEditing(false);
                    }
                    // TODO: Ctrl or Cmd Enter to add the next task?
                  }}
                  style={{ paddingLeft: task.level > 0 ? 10 : 0 }}
                  onChange={(ev: any) => onChangeTitle(ev.target.value)}
                  onFocus={() => {
                    setIsEditing(true);
                    setMenuActive('');
                  }}
                  onClick={() => {
                    setMenuActive('');
                  }}
                  onBlur={() => {
                    if (menuActive === '' || menuActive === 'MENU') {
                      // e.g. if user is focusing in EMOJI menu, don't exit out of editing:
                      setIsEditing(false);
                    }
                  }}
                >
                  {task.content}
                </StyledTextarea>
              ) : (
                <TaskDisplay
                  dangerouslySetInnerHTML={{ __html: parseInline(task.content || '&nbsp;') }}
                  onClick={ev => {
                    if (ev.target.tagName.toUpperCase() === 'A') {
                      // user clicked on a hyperlink <a> tag, let it behaves normally.
                    } else {
                      setIsEditing(true);
                    }
                  }}
                />
              )}

              {isEditing ? (
                <ActionWrapper>
                  <ActionIcon
                    data-type="action-icon"
                    onMouseOver={() => setMenuActive('MENU')}
                    onClick={() => {
                      setMenuActive('');
                      setIsEditing(false);
                    }}
                  >
                    ☰
                  </ActionIcon>
                  {menuActive && (
                    <TaskMenu
                      task={task}
                      menuActive={menuActive}
                      setMenuActive={setMenuActive}
                      onChangeTask={onChangeTask}
                      setMainKey={setMainKey}
                      setIsEditing={setIsEditing}
                    />
                  )}
                </ActionWrapper>
              ) : (
                <ActionWrapper>
                  {(!task.done || !column.isLast) && (
                    <ActionIcon data-type="action-icon" onClick={() => onInProgress(task)}>
                      🡢
                    </ActionIcon>
                  )}
                  {/* TODO: don't show Tick icon on the first column => need column index? */}
                  {!task.done && (
                    <ActionIcon data-type="action-icon" onClick={() => onComplete(task)}>
                      ✓
                    </ActionIcon>
                  )}
                  <ActionIcon data-type="action-icon" onClick={() => onDelete(task)}>
                    ✕
                  </ActionIcon>
                </ActionWrapper>
              )}
            </TaskWrapper>
          </TaskContainer>
        )}
      </Draggable>
    );
  }
);


================================================
FILE: src/view/app/components/TaskBoard/TaskBoard.css
================================================


================================================
FILE: src/view/app/components/TaskBoard/TaskBoard.tsx
================================================
import * as React from 'react';
import styled from 'styled-components';
import { CommandAction } from '../../model';
import { sendCommand, getVscodeHelper } from '../../Utils';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { parseMarkdown, defaultDataString, getMarkdown } from './Helpers';

import { TaskInterface } from './Task';
import TaskColumn, { ColumnInterface } from './TaskColumn';
import ButtonBar from './ButtonBar';

// import '@atlaskit/css-reset';
import '../../index.css';
import './TaskBoard.css';
const { useState } = React;

const Columns = styled.div`
  display: flex;
`;

const selectedFile = (window && window['initialData'] ? window['initialData']['selectedFile'] : '') || 'TODO.md';
const fileArray = (window && window['initialData'] ? window['initialData']['fileList'] : 'TODO.md')
  .split(',')
  .map(str => str.trim());
const dataString = (window && window['initialData'] ? window['initialData']['dataString'] : '') || defaultDataString;
let data = parseMarkdown(dataString);

export default function TaskBoard({ vscode, initialData }) {
  const [state, setState] = useState(data);
  const vscodeHelper = getVscodeHelper(vscode);

  const reloadFile = () => sendCommand(vscode, CommandAction.Load, selectedFile);

  React.useEffect(() => {
    reloadFile();
  }, []);

  const updateStateAndSave = newState => {
    setState(newState);
    vscodeHelper.saveList(getMarkdown(newState));
  };

  // const [msg, setMsg] = useState('');
  // window.addEventListener('message', event => {
  //   setMsg(JSON.stringify(event));
  //   // const message = event.data; // The JSON data our extension sent
  //   // switch (message.command) {
  //   //     case 'load':
  //   //       break;
  //   // }
  // });
  return (
    <div>
      <ButtonBar
        vscodeHelper={vscodeHelper}
        fileArray={fileArray}
        selectedFile={selectedFile}
        data={state}
        onLoadData={newData => {
          data = newData;
          setState(newData);
        }}
        onSave={dataStr => {
          vscodeHelper.saveList(dataStr);
        }}
        onRefresh={() => reloadFile()}
        onOpenFile={() => sendCommand(vscode, CommandAction.OpenFile, '')}
        onSearch={searchTerm => {
          const searchTermStr = searchTerm.toLowerCase();
          // console.log('search: ', searchTerm);
          const newState = { ...state };
          Object.keys(newState.tasks).forEach(taskId => {
            const t = newState.tasks[taskId];
            newState.tasks[taskId].matched = t.content.toLowerCase().indexOf(searchTermStr) >= 0;
          });
          updateStateAndSave(newState);
        }}
        onSelectFile={selectedOpt => {
          sendCommand(vscode, CommandAction.Load, selectedOpt.value);
        }}
      />
      <DragDropContext
        onDragEnd={({ destination, source, draggableId, type }) => {
          if (!destination) {
            return;
          }
          if (destination.droppableId === source.droppableId && destination.index === source.index) {
            return;
          }

          if (type === 'column') {
            const newColOrd = Array.from(state.columnOrder);
            newColOrd.splice(source.index, 1);
            newColOrd.splice(destination.index, 0, draggableId);

            const newState = {
              ...state,
              columnOrder: newColOrd
            };
            updateStateAndSave(newState);
            return;
          }

          const startcol = state.columns[source.droppableId];
          const endcol = state.columns[destination.droppableId];

          // console.log("startcol", startcol);
          // if (!startcol) {
          //   return;
          // }

          if (startcol === endcol) {
            const tasks = Array.from(startcol.taskIds);
            tasks.splice(source.index, 1);
            tasks.splice(destination.index, 0, draggableId);

            const newCol = {
              ...startcol,
              taskIds: tasks
            };

            const newState = {
              ...state,
              columns: {
                ...state.columns,
                [newCol.id]: newCol
              }
            };

            // setState(newState);
            updateStateAndSave(newState);
            return;
          }
          const startTaskIds = Array.from(startcol.taskIds);
          startTaskIds.splice(source.index, 1);
          const newStart = {
            ...startcol,
            taskIds: startTaskIds
          };
          const endTaskIds = Array.from(endcol.taskIds);
          endTaskIds.splice(destination.index, 0, draggableId);
          const newEnd = {
            ...endcol,
            taskIds: endTaskIds
          };
          const newState = {
            ...state,
            columns: {
              ...state.columns,
              [newStart.id]: newStart,
              [newEnd.id]: newEnd
            },
            tasks: data.tasks
          };
          // setState(newState);
          updateStateAndSave(newState);
          // loadData(vscode, getMarkdown(newState)) // reload data to make sure it's reliable.
        }}
      >
        <Droppable droppableId="columns" direction="horizontal" type="column">
          {provided => (
            <Columns {...provided.droppableProps} ref={provided.innerRef}>
              {state.columnOrder.map((id, idx) => {
                const col = state.columns[id];
                if (idx === Object.keys(state.columns).length - 1) {
                  col.isLast = true;
                } else {
                  col.isLast = false;
                }
                const tasks = col.taskIds.map(taskid => state.tasks[taskid]);
                return (
                  <TaskColumn
                    key={id}
                    column={col}
                    columnIndex={idx}
                    tasks={tasks}
                    onChangeTask={(id: string, newTask: TaskInterface) => {
                      tasks[id] = newTask;
                      const newState = {
                        ...state,
                        tasks: data.tasks
                      };
                      updateStateAndSave(newState);
                    }}
                    onDeleteTask={(task: TaskInterface, column: ColumnInterface) => {
                      const newState = { ...state };
                      delete newState.tasks[task.id];
                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(
                        (taskId: string) => taskId !== task.id
                      );
                      updateStateAndSave(newState);
                    }}
                    onInProgressTask={(task: TaskInterface, column: ColumnInterface) => {
                      const newState = { ...state };
                      const columnKeys = Object.keys(newState.columns);
                      const currentColumnIdx = Object.keys(newState.columns).findIndex(
                        (id: string) => id === column.id
                      );
                      const doneColumnKey = columnKeys[columnKeys.length - 1];
                      const nextColumnKey = columnKeys[currentColumnIdx + 1];
                      if (nextColumnKey === doneColumnKey) {
                        task.done = true; // user moved this task to the right column and reached Done Column.
                      }
                      // remove task from current column:
                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(
                        (taskId: string) => taskId !== task.id
                      );
                      // append task to the next column:
                      newState.columns[nextColumnKey].taskIds.unshift(task.id);
                      updateStateAndSave(newState);
                    }}
                    onCompleteTask={(task: TaskInterface, column: ColumnInterface) => {
                      task.done = true;
                      const newState = { ...state };
                      const columnKeys = Object.keys(newState.columns);
                      const doneColumnKey = columnKeys[columnKeys.length - 1];
                      // remove task from current column:
                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(
                        (taskId: string) => taskId !== task.id
                      );
                      // append task to the top of Done column:
                      newState.columns[doneColumnKey].taskIds.unshift(task.id);
                      updateStateAndSave(newState);
                    }}
                  />
                );
              })}
              {provided.placeholder}
            </Columns>
          )}
        </Droppable>
      </DragDropContext>
      {/* <pre>{msg}</pre> */}
    </div>
  );
}


================================================
FILE: src/view/app/components/TaskBoard/TaskColumn.tsx
================================================
import * as React from 'react';
import styled from 'styled-components';
import { Droppable, Draggable } from 'react-beautiful-dnd';
import Task, { TaskInterface } from './Task';

const { memo } = React;

const Container = styled.div<{ isDragging: boolean }>`
  min-height: 150px;
  margin: 0px;
  border-radius: 2px;
  width: 33.3vw;
  display: flex;
  flex-direction: column;
  background-color: ${props => (props.isDragging ? 'lightgreen' : 'inherit')};
`;
const Title = styled.div`
  padding: 5px;
  margin: 5px;
  > span {
    padding: 5px;
    background-color: var(--vscode-editor-selectionBackground);
    border-radius: 2px;
    color: var(--vscode-editor-selectionForeground);
  }
`; // or use: vscode-tab-activeBackground & vscode-tab-foreground
const List = styled.div<{ isDraggingOver: boolean }>`
  padding: 5px;
  transition: background 0.1s;
  background-color: ${props => (props.isDraggingOver ? 'var(--vscode-tab-border)' : 'inherit ')};
  flex-grow: 1;
`;

export interface ColumnInterface {
  id: string;
  title: string;
}

interface ColumnProps {
  tasks: TaskInterface[];
  columnIndex: number;
  column: ColumnInterface;
  onChangeTask: (idx: string, task: any) => void;
  onDeleteTask: (task: TaskInterface, column: ColumnInterface) => void;
  onInProgressTask: (task: TaskInterface, column: ColumnInterface) => void;
  onCompleteTask: (task: TaskInterface, column: ColumnInterface) => void;
}

export default memo(
  ({ column, tasks, columnIndex, onChangeTask, onDeleteTask, onInProgressTask, onCompleteTask }: ColumnProps) => (
    <Draggable draggableId={column.id} index={columnIndex}>
      {(provided, snapshot) => (
        <Container
          // {...provided.draggableProps}
          // {...provided.dragHandleProps}
          isDragging={snapshot.isDragging}
          ref={provided.innerRef}
        >
          <Title {...provided.dragHandleProps}>
            <span>{column.title}</span>
          </Title>
          <Droppable droppableId={column.id} type="task">
            {(provided, snapshot) => (
              <List ref={provided.innerRef} {...provided.droppableProps} isDraggingOver={snapshot.isDraggingOver}>
                {tasks.map((t, i) => {
                  if (!t || !t.id) {
                    return null;
                  }
                  return (
                    <Task
                      key={t.id}
                      column={column}
                      columnIndex={columnIndex}
                      task={t}
                      index={i}
                      onChangeTitle={(newTitle: string) => {
                        t.content = newTitle;
                        onChangeTask(t.id, t);
                      }}
                      onDelete={(task: TaskInterface) => onDeleteTask(task, column)}
                      onInProgress={(task: TaskInterface) => onInProgressTask(task, column)}
                      onComplete={(task: TaskInterface) => onCompleteTask(task, column)}
                      onChangeTask={onChangeTask}
                    />
                  );
                })}
                {provided.placeholder}
              </List>
            )}
          </Droppable>
        </Container>
      )}
    </Draggable>
  )
);


================================================
FILE: src/view/app/components/TaskBoard/TaskMenu.tsx
================================================
import * as React from 'react';
import styled from 'styled-components';

const emojis = [
  { text: '(bug)', char: '🐞', names: 'bug, ladybug, defect, issue, problem, error' },
  // JIRA emojis
  { text: ':)', char: '😊', names: 'smile, happy, glad' },
  { text: ':(', char: '😟', names: 'sad, unhappy, cry' },
  { text: ':P', char: '😛', names: 'tongue, fun' },
  { text: ':D', char: '😄', names: 'broad smile, big open smile, amusemed, excited, happy' },
  { text: ';)', char: '😉', names: 'wink' },
  { text: '(y)', char: '👍', names: 'yes, thumb up, okay, agree, correct, good' },
  { text: '(n)', char: '👎', names: 'no, thumb down, not okay, disagree, bad' },
  { text: '(on)', char: '☑️', names: 'on, ticked, checked, yes, done' },
  { text: '(off)', char: '🔲', names: 'off, unchecked, checkbox, empty box, square' },
  { text: '(!)', char: '⚠️', names: 'warning, attention, dangerous, exclamation' },
  { text: '(*)', char: '⭐', names: 'star, rate, favorite, favourite, award, reward' },
  { text: '(/)', char: '✅', names: 'ticked, checked, yes, done, correct' },
  { text: '(x)', char: '❌', names: 'crossed, deleted, blocked, wrong, incorrect, no' },
  { text: '(i)', char: 'ℹ️', names: 'info, information, attention' },
  { text: '(+)', char: '➕', names: 'plus, addition' },
  { text: '(-)', char: '➖', names: 'minus, substraction' },
  { text: '(?)', char: '❓', names: 'question mark, ask' },
  { text: '<3', char: '💗', names: 'heart, pink heart, love, happy' },
  { text: '</3', char: '💔', names: 'broken heart, unhappy, sad' },
  // More emojis
  { text: '(rocket)', char: '🚀', names: 'rocket, launch, delivered' },
  { text: '(party)', char: '🎉', names: 'party, celebration, firework, excited, awesome, funs, congratulations' },
  { text: '(sparkle)', char: '✨', names: 'sparkle' },
  { text: '(hot)', char: '🔥', names: 'hot, fire, flame, burn' },
  { text: '(fire)', char: '🔥', names: 'fire, hot, flame, burn' },
  { text: '(burger)', char: '🍔', names: 'burger, food' },
  { text: '(pizza)', char: '🍕', names: 'pizza, food' },
  { text: '(hotdog)', char: '🌭', names: 'hotdog, food' },
  { text: '(cake)', char: '🎂', names: 'cake, sweet' },
  { text: '(cupcake)', char: '🧁', names: 'cupcake, sweet' },
  { text: '(cookie)', char: '🍪', names: 'cookie, sweet' },
  { text: '(candy)', char: '🍬', names: 'candy, sweet' },
  { text: '(eat)', char: '🍽️', names: 'eat, dine, food, lunch, dinner' },
  { text: '(icecream)', char: '🍦', names: 'icecream, sweet' },
  { text: '(coffee)', char: '☕', names: 'coffee, cafe' },
  { text: '(wine)', char: '🍷', names: 'wine, drink' },
  { text: '(beer)', char: '🍺', names: 'beer, drink' },
  { text: '(balloon)', char: '🎈', names: 'balloon' },
  { text: '(rose)', char: '🌹', names: 'rose, flower' },
  { text: '(soccer)', char: '⚽', names: 'soccer, sport, ball' },
  { text: '(baseball)', char: '⚾', names: 'baseball, sport, ball' },
  { text: '(tennis)', char: '🎾', names: 'tennis, sport' },
  { text: '(football)', char: '🏈', names: 'football, sport' },
  { text: '(basketball)', char: '🏀', names: 'basketball, sport' },
  { text: '(car)', char: '🚗', names: 'car, travel, drive' },
  { text: '(plane)', char: '✈️', names: 'plane, travel, fly, flight' },
  { text: '(beach)', char: '🏖️', names: 'beach, relax, vacation' },
  { text: '(sun)', char: '☀️', names: 'sun, weather' },
  { text: '(moon)', char: '🌙', names: 'moon, night' },
  { text: '(rain)', char: '🌧️', names: 'rain, weather' },
  { text: '(snow)', char: '❄️', names: 'snow, weather' },
  { text: '(chart)', char: '📈', names: 'chart, presentation, stock' },
  { text: '(search)', char: '🔎', names: 'search, find, looking, research' }
];

const MenuList = styled.ul`
  list-style: none;
  margin: 0;
  padding: 0;
  li {
    margin: 0;
    padding: 10px;
    cursor: pointer;
    &:hover {
      background-color: #777;
    }
  }
`;

export default function TaskMenu({ task, menuActive, setMenuActive, onChangeTask, setMainKey, setIsEditing }) {
  const [searchText, setSearchText] = React.useState('');
  const closeMenu = () => {
    setMenuActive('');
    setIsEditing(false);
  };
  return (
    <div
      style={{
        position: 'absolute',
        top: 25,
        left: 5,
        borderRadius: 4,
        backgroundColor: 'var(--vscode-tab-border)',
        width: 170,
        minHeight: 100,
        maxHeight: 250,
        overflowX: 'hidden',
        overflowY: 'auto',
        fontFamily: 'Verdana',
        fontSize: '0.8em',
        zIndex: 1
      }}
    >
      {menuActive === 'MENU' ? (
        <MenuList>
          <li
            onMouseDown={ev => {
              // can't use onClick as it will close the menu without coming here.
              ev.preventDefault();
              task.level = (task.level + 1) % 2;
              onChangeTask(task.id, task);
              closeMenu();
            }}
          >
            Toggle Task / Sub-task
          </li>
          <li
            onMouseDown={ev => {
              // can't use onClick as it will close the menu without coming here.
              ev.preventDefault();
              setMenuActive('EMOJI');
            }}
          >
            Insert Emoji Icon
          </li>
        </MenuList>
      ) : (
        <MenuList>
          <input
            autoFocus
            placeholder="Search"
            value={searchText}
            onChange={ev => setSearchText(ev.target.value)}
            onKeyUp={ev => {
              if (ev.keyCode === 27) {
                closeMenu();
              }
            }}
            style={{ width: '87%', margin: 5, padding: 3 }}
          />
          {emojis.map(emoji => {
            const txt = searchText.trim().toLowerCase();
            if (txt && emoji.names.toLowerCase().indexOf(txt) < 0) {
              return null;
            }
            return (
              <li
                onMouseDown={ev => {
                  ev.preventDefault();
                  task.content += emoji.char;
                  onChangeTask(task.id, task);
                  closeMenu();
                  setMainKey('key_' + Math.random());
                }}
              >
                <span style={{ fontSize: '1.5em' }}>{emoji.char}</span> &nbsp;&nbsp;&nbsp; {emoji.text}
              </li>
            );
          })}
        </MenuList>
      )}
    </div>
  );
}


================================================
FILE: src/view/app/config.tsx
================================================
import * as React from "react";
import { IConfig, IUser, ICommand, CommandAction } from "./model";

interface IConfigProps {
  vscode: any;
  initialData: IConfig;
}

interface IConfigState {
  config: IConfig;
}

export default class Config extends React.Component<
  IConfigProps,
  IConfigState
> {
  constructor(props: any) {
    super(props);

    let initialData = this.props.initialData;

    let oldState = this.props.vscode.getState();
    if (oldState) {
      this.state = oldState;
    } else {
      this.state = { config: initialData };
    }
  }

  private defineState(newSate: IConfigState) {
    this.setState(newSate);
    this.props.vscode.setState(newSate);
  }

  onChangeUserActiveState(userIndex: number) {
    let newState = { ...this.state };
    newState.config.users[userIndex].active = !newState.config.users[userIndex]
      .active;

    this.defineState(newState);
  }

  onAddRole(event: React.KeyboardEvent<HTMLInputElement>, userIndex: number) {
    if (event.keyCode === 13 && event.currentTarget.value !== "") {
      let newState = { ...this.state };
      newState.config.users[userIndex].roles.push(event.currentTarget.value);
      this.defineState(newState);
      event.currentTarget.value = "";
    }
  }

  onAddUser(event: React.KeyboardEvent<HTMLInputElement>) {
    if (event.keyCode === 13 && event.currentTarget.value !== "") {
      let newState = { ...this.state };
      let newUser: IUser = {
        name: event.currentTarget.value,
        active: true,
        roles: []
      };
      newState.config.users.push(newUser);
      this.defineState(newState);
      event.currentTarget.value = "";
    }
  }

  renderUsers(users: IUser[]) {
    return (
      <React.Fragment>
        <h2>User List :</h2>
        <ul className="">
          {users && users.length > 0
            ? users.map((user, userIndex) => {
                let roles =
                  user.roles && user.roles.length > 0
                    ? user.roles.join(",")
                    : null;

                return (
                  <li key={userIndex}>
                    {user.name}
                    <br />
                    Is active :{" "}
                    <input
                      type="checkbox"
                      checked={user.active}
                      onChange={() => this.onChangeUserActiveState(userIndex)}
                    />
                    <br />
                    Roles : {roles}
                    <input
                      type="text"
                      placeholder="Add Role"
                      onKeyUp={event => this.onAddRole(event, userIndex)}
                    />
                  </li>
                );
              })
            : null}
        </ul>
        <input
          type="text"
          placeholder="Add User"
          onKeyUp={event => this.onAddUser(event)}
        />
      </React.Fragment>
    );
  }

  render() {
    return (
      <React.Fragment>
        <h1>Config name : {this.state.config.name}</h1>{" "}
        {this.state.config.description}
        {this.renderUsers(this.state.config.users)}
        <br />
        <input
          className="save"
          type="button"
          value="Save the configuration"
          onClick={() => this.saveConfig()}
        />
      </React.Fragment>
    );
  }

  saveConfig() {
    let command: ICommand = {
      action: CommandAction.Save,
      content: this.state.config
    };
    this.props.vscode.postMessage(command);
  }
}


================================================
FILE: src/view/app/index.css
================================================
:root {
  /* --text-color: #eee; */
  /* --bg-color: #1d1e1d; */

  --button-bg-color: rgb(3, 0, 150);
  --button-text-color: #777;
}
html,
body {
  background-color: var(--bg-color); /* Dracula+ Dark Theme */
}

a {
  text-decoration: none;
  color: darkslategray;
}

li {
  margin-bottom: 15px;
}

input {
  margin-left: 10px;
}

.save {
  margin-top: 10px;
}

.__reactSelect .__select__control {
  background-color: var(--vscode-tab-background);
  border: none;
  margin-top: 5px;
  min-height: 30px;
}
.__reactSelect .__select__input {
  color: var(--button-text-color);
}
.__reactSelect .__select__single-value {
  color: var(--button-text-color);
  padding: 0;
}
.__reactSelect .__select__option {
  color: var(--button-text-color);
}
.__reactSelect .__select__indicator-separator {
  background-color: var(--vscode-tab-inactiveBackground);
}
.__reactSelect .__select__indicator {
  color: var(--button-text-color);
}
.__reactSelect .__select__option--is-selected {
  background-color: var(--vscode-tab-inactiveBackground);
}


================================================
FILE: src/view/app/index.tsx
================================================
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import './index.css';
import { IConfig } from './model';
// import Config from "./config";

import MainView from './components/CodeGen/MainView';
import TaskBoard from './components/TaskBoard/TaskBoard';

declare global {
  interface Window {
    acquireVsCodeApi(): any;
    initialData: IConfig;
  }
}

const vscode = window.acquireVsCodeApi();

if (window.initialData.name === 'TaskBoard') {
  ReactDOM.render(
    <TaskBoard vscode={vscode} initialData={window.initialData} />,
    document.getElementById('root')
  );
} else {
  ReactDOM.render(
    <MainView vscode={vscode} initialData={window.initialData} />,
    document.getElementById('root')
  );
}


================================================
FILE: src/view/app/model.ts
================================================
export interface IConfig {
  name: string;
  description?: string;
  users?: IUser[];
}
export interface IUser {
  name: string;
  active: boolean;
  roles: string[];
}

export interface ICommand {
  action: CommandAction;
  content: IConfig;
}

export enum CommandAction {
  ShowMessage,
  Save,
  Load,
  OpenFile,
  GenerateFiles
}


================================================
FILE: src/view/app/tsconfig.json
================================================
{
    "compilerOptions": {
        "module": "esnext",
        "moduleResolution": "node",
        "target": "es6",
        "outDir": "configViewer",
        "lib": [
            "es6",
            "dom"
        ],  
        "jsx": "react",
        "sourceMap": true,
        "rootDir": "..",
        "noUnusedLocals": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "experimentalDecorators": true
    },
    "exclude": [
        "node_modules"
    ]
}

================================================
FILE: src/webpack.config.js
================================================
const path = require("path");

module.exports = {
  entry: {
    configViewer: "./src/view/app/index.tsx"
  },
  output: {
    path: path.resolve(__dirname, "configViewer"),
    filename: "[name].js"
  },
  devtool: "eval-source-map",
  resolve: {
    extensions: [".js", ".ts", ".tsx", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: "ts-loader",
        options: {}
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader"
          }
        ]
      }
    ]
  },
  performance: {
    hints: false
  }
};


================================================
FILE: tsconfig.json
================================================
{
	"compilerOptions": {
		"module": "commonjs",
		"target": "es6",
		"outDir": "out",
		"lib": [
			"es6", "dom"
		],
		"sourceMap": false,
		"rootDir": "src",
		// "strict": true   /* enable all strict type-checking options */
		/* Additional Checks */
		// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
		// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
		// "noUnusedParameters": true,  /* Report errors on unused parameters. */
	},
	"exclude": [
		"node_modules",
		".vscode-test",
		"**/view/app/index.css",
		"**/view/app/index.tsx",
		"**/view/app/tsconfig.json",
		"**/view/app/config.tsx"
	]
}


================================================
FILE: tslint.json
================================================
{
	"rules": {
		"no-string-throw": true,
		"no-unused-expression": true,
		"no-duplicate-variable": true,
		"curly": true,
		"class-name": true,
		"semicolon": [
			true,
			"always"
		],
		"triple-equals": true
	},
	"defaultSeverity": "warning"
}


================================================
FILE: vsc-extension-quickstart.md
================================================
# Welcome to your VS Code Extension

## What's in the folder

* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
  * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
  * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
  * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.

## Get up and running straight away

* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.

## Make changes

* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.

## Explore the API

* You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`.

## Run tests

* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
* Press `F5` to run the tests in a new window with your extension loaded.
* See the output of the test result in the debug console.
* Make changes to `test/extension.test.ts` or create new test files inside the `test` folder.
  * By convention, the test runner will only consider files matching the name pattern `**.test.ts`.
  * You can create folders inside the `test` folder to structure your tests any way you want.

## Go further

 * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/testing-extension).
 * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
 * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).


================================================
FILE: webpack.config.js
================================================
const path = require("path");

module.exports = {
  entry: {
    configViewer: "./src/view/app/index.tsx"
  },
  output: {
    path: path.resolve(__dirname, "configViewer"),
    filename: "[name].js"
  },
  devtool: "eval-source-map",
  resolve: {
    extensions: [".js", ".ts", ".tsx", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: "ts-loader",
        options: {}
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader"
          }
        ]
      }
    ]
  },
  performance: {
    hints: false
  }
};
Download .txt
gitextract_gosdofly/

├── .coddx-template
├── .editorconfig
├── .gitignore
├── .prettierrc.yml
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── docs/
│   ├── documentation.md
│   ├── generate-files.md
│   └── task-board.md
├── package.json
├── src/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .prettierrc.yml
│   ├── .vscodeignore
│   ├── extension.ts
│   ├── test/
│   │   ├── extension.test.ts
│   │   └── index.ts
│   ├── view/
│   │   ├── TaskBoardLoader.ts
│   │   ├── ViewLoader.ts
│   │   └── app/
│   │       ├── Utils.ts
│   │       ├── components/
│   │       │   ├── CodeGen/
│   │       │   │   ├── CodeEditor.css
│   │       │   │   ├── CodeEditor.tsx
│   │       │   │   ├── EditTemplatePanel.tsx
│   │       │   │   ├── MainView.css
│   │       │   │   └── MainView.tsx
│   │       │   └── TaskBoard/
│   │       │       ├── ButtonBar.tsx
│   │       │       ├── Helpers.tsx
│   │       │       ├── SearchInput.tsx
│   │       │       ├── Task.tsx
│   │       │       ├── TaskBoard.css
│   │       │       ├── TaskBoard.tsx
│   │       │       ├── TaskColumn.tsx
│   │       │       └── TaskMenu.tsx
│   │       ├── config.tsx
│   │       ├── index.css
│   │       ├── index.tsx
│   │       ├── model.ts
│   │       └── tsconfig.json
│   └── webpack.config.js
├── tsconfig.json
├── tslint.json
├── vsc-extension-quickstart.md
└── webpack.config.js
Download .txt
SYMBOL INDEX (65 symbols across 17 files)

FILE: src/extension.ts
  function activate (line 11) | function activate(context: vscode.ExtensionContext) {
  function deactivate (line 55) | function deactivate() {

FILE: src/view/TaskBoardLoader.ts
  class ViewLoader (line 11) | class ViewLoader {
    method constructor (line 16) | constructor(extensionPath: string, uri: vscode.Uri) {
    method getWebviewContent (line 108) | private getWebviewContent({ basePath, templateString, fileList, select...
    method getFileContent (line 142) | private getFileContent(fileUri: vscode.Uri) {
    method saveFileContent (line 154) | private saveFileContent(config: IConfig) {

FILE: src/view/ViewLoader.ts
  class ViewLoader (line 8) | class ViewLoader {
    method constructor (line 13) | constructor(extensionPath: string, uri: vscode.Uri) {
    method getWebviewContent (line 110) | private getWebviewContent(basePath: string, templateString: string): s...
    method getFileContent (line 143) | private getFileContent(fileUri: vscode.Uri) {
    method saveFileContent (line 155) | private saveFileContent(fileUri: vscode.Uri, config: IConfig) {
    method mkDirByPathSync (line 162) | private mkDirByPathSync(targetDir: string, { baseDir = '', isRelativeT...
    method generateFiles (line 193) | private generateFiles(config: IConfig) {

FILE: src/view/app/Utils.ts
  constant VER (line 4) | const VER = '0.2.55';
  type FilesInterface (line 12) | interface FilesInterface {
  constant FILE_SEPARATOR (line 19) | const FILE_SEPARATOR = '--->>';
  function formatDate (line 61) | function formatDate(d: Date) {
  function parseJsonString (line 79) | function parseJsonString(jsonString: string) {
  function jsonClone (line 90) | function jsonClone(obj: any) {
  function deepFind (line 97) | function deepFind(obj: any, path: string, defaultValue: any) {
  function nunjucksRender (line 107) | function nunjucksRender(str: string, data: any): string {
  function getTemplateItems (line 113) | function getTemplateItems(content: string, params: any = {}, existingFil...
  function getVscodeHelper (line 165) | function getVscodeHelper(vscode: any) {

FILE: src/view/app/components/CodeGen/CodeEditor.tsx
  type Props (line 17) | interface Props {
  class CodeEditor (line 22) | class CodeEditor extends React.Component<Props> {
    method componentWillReceiveProps (line 25) | componentWillReceiveProps(newProps: any) {
    method render (line 29) | render() {

FILE: src/view/app/components/CodeGen/EditTemplatePanel.tsx
  class EditTemplatePanel (line 4) | class EditTemplatePanel extends React.Component<any> {
    method componentWillReceiveProps (line 9) | componentWillReceiveProps(newProps: any) {
    method render (line 13) | render() {

FILE: src/view/app/components/CodeGen/MainView.tsx
  type IConfigProps (line 38) | interface IConfigProps {
  type IConfigState (line 43) | interface IConfigState {
  class MainView (line 63) | class MainView extends React.Component<IConfigProps, IConfigState> {
    method componentDidMount (line 76) | componentDidMount() {
    method render (line 80) | render() {

FILE: src/view/app/components/TaskBoard/ButtonBar.tsx
  function OpenFileIcon (line 33) | function OpenFileIcon(props: any) {
  function RefreshIcon (line 56) | function RefreshIcon(props: any) {

FILE: src/view/app/components/TaskBoard/Helpers.tsx
  function getMarkdown (line 50) | function getMarkdown(data) {
  function parseMarkdown (line 81) | function parseMarkdown(md: string) {
  function DragIcon (line 150) | function DragIcon(props: any) {
  function SearchIcon (line 161) | function SearchIcon(props: any) {

FILE: src/view/app/components/TaskBoard/SearchInput.tsx
  type Props (line 3) | interface Props {

FILE: src/view/app/components/TaskBoard/Task.tsx
  type TaskInterface (line 11) | interface TaskInterface {
  type TaskProps (line 101) | interface TaskProps {

FILE: src/view/app/components/TaskBoard/TaskBoard.tsx
  function TaskBoard (line 28) | function TaskBoard({ vscode, initialData }) {

FILE: src/view/app/components/TaskBoard/TaskColumn.tsx
  type ColumnInterface (line 34) | interface ColumnInterface {
  type ColumnProps (line 39) | interface ColumnProps {

FILE: src/view/app/components/TaskBoard/TaskMenu.tsx
  function TaskMenu (line 76) | function TaskMenu({ task, menuActive, setMenuActive, onChangeTask, setMa...

FILE: src/view/app/config.tsx
  type IConfigProps (line 4) | interface IConfigProps {
  type IConfigState (line 9) | interface IConfigState {
  class Config (line 13) | class Config extends React.Component<
    method constructor (line 17) | constructor(props: any) {
    method defineState (line 30) | private defineState(newSate: IConfigState) {
    method onChangeUserActiveState (line 35) | onChangeUserActiveState(userIndex: number) {
    method onAddRole (line 43) | onAddRole(event: React.KeyboardEvent<HTMLInputElement>, userIndex: num...
    method onAddUser (line 52) | onAddUser(event: React.KeyboardEvent<HTMLInputElement>) {
    method renderUsers (line 66) | renderUsers(users: IUser[]) {
    method render (line 109) | render() {
    method saveConfig (line 126) | saveConfig() {

FILE: src/view/app/index.tsx
  type Window (line 12) | interface Window {

FILE: src/view/app/model.ts
  type IConfig (line 1) | interface IConfig {
  type IUser (line 6) | interface IUser {
  type ICommand (line 12) | interface ICommand {
  type CommandAction (line 17) | enum CommandAction {
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (105K chars).
[
  {
    "path": ".coddx-template",
    "chars": 534,
    "preview": "// -------------->> {{fileName}}.tsx\n// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}\nimport * as React from 'react"
  },
  {
    "path": ".editorconfig",
    "chars": 230,
    "preview": "# EditorConfig: http://EditorConfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\n\nend_of_line = lf\ncharset"
  },
  {
    "path": ".gitignore",
    "chars": 67,
    "preview": "out\nnode_modules\n.vscode-test/\n*.vsix\n\nconfigViewer/configViewer.js"
  },
  {
    "path": ".prettierrc.yml",
    "chars": 162,
    "preview": "---\nprintWidth: 120\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: none\nbracketSpacing: true\njsx"
  },
  {
    "path": ".vscodeignore",
    "chars": 133,
    "preview": ".vscode/**\n.vscode-test/**\nout/test/**\nsrc/**\n.gitignore\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/tslint.json\n**/"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1185,
    "preview": "# Change Log\n\n## [0.3.0] - 2021-05-24\n\n- Task title supports markdown now for styling, hyperlinks, simple html or even i"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2021 Duc Nguyen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 1402,
    "preview": "# Todo Kanban Board\n\nTodo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain"
  },
  {
    "path": "TODO.md",
    "chars": 1309,
    "preview": "# TODO.md Kanban Board\n\nTODO.md Kanban Board\n\n<em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>\n\n### Todo\n\n"
  },
  {
    "path": "docs/documentation.md",
    "chars": 136,
    "preview": "# Documentation\n\n### Task Board - TODO.md\n\n[Task Board Doc](task-board.md)\n\n### Generate Files\n\n[Generate Files Doc](gen"
  },
  {
    "path": "docs/generate-files.md",
    "chars": 1843,
    "preview": "# Generate Files\n\n### Features\n\n- Work with any programming languages.\n- Templating syntax.\n- Store templates as a singl"
  },
  {
    "path": "docs/task-board.md",
    "chars": 1625,
    "preview": "# Todo Kanban Board\n\nTodo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain"
  },
  {
    "path": "package.json",
    "chars": 2732,
    "preview": "{\n  \"name\": \"coddx-alpha\",\n  \"displayName\": \"TODO.md Kanban Board\",\n  \"description\": \"Coddx - a collection of tools that"
  },
  {
    "path": "src/.editorconfig",
    "chars": 230,
    "preview": "# EditorConfig: http://EditorConfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\n\nend_of_line = lf\ncharset"
  },
  {
    "path": "src/.gitignore",
    "chars": 67,
    "preview": "out\nnode_modules\n.vscode-test/\n*.vsix\n\nconfigViewer/configViewer.js"
  },
  {
    "path": "src/.prettierrc.yml",
    "chars": 162,
    "preview": "---\nprintWidth: 120\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: none\nbracketSpacing: true\njsx"
  },
  {
    "path": "src/.vscodeignore",
    "chars": 133,
    "preview": ".vscode/**\n.vscode-test/**\nout/test/**\nsrc/**\n.gitignore\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/tslint.json\n**/"
  },
  {
    "path": "src/extension.ts",
    "chars": 2322,
    "preview": "// The module 'vscode' contains the VS Code extensibility API\n// Import the module and reference it with the alias vscod"
  },
  {
    "path": "src/test/extension.test.ts",
    "chars": 721,
    "preview": "//\n// Note: This example test is leveraging the Mocha test framework.\n// Please refer to their documentation on https://"
  },
  {
    "path": "src/test/index.ts",
    "chars": 1050,
    "preview": "//\n// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING\n//\n// This file is providing the test runner to u"
  },
  {
    "path": "src/view/TaskBoardLoader.ts",
    "chars": 6216,
    "preview": "import * as vscode from 'vscode';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { IConfig, ICommand, C"
  },
  {
    "path": "src/view/ViewLoader.ts",
    "chars": 7438,
    "preview": "import * as vscode from 'vscode';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { IConfig, ICommand, C"
  },
  {
    "path": "src/view/app/Utils.ts",
    "chars": 6021,
    "preview": "import nunjucks from 'nunjucks';\nimport { ICommand, CommandAction } from './model';\n\nexport const VER = '0.2.55'; // TOD"
  },
  {
    "path": "src/view/app/components/CodeGen/CodeEditor.css",
    "chars": 1620,
    "preview": ".container__content {\n    width: 440px;\n    max-width: 100%;\n    padding: 10px;\n    text-align: center;\n  }\n  \n  .contai"
  },
  {
    "path": "src/view/app/components/CodeGen/CodeEditor.tsx",
    "chars": 1078,
    "preview": "import * as React from 'react';\n\nimport Editor from 'react-simple-code-editor';\nimport { highlight, languages } from 'pr"
  },
  {
    "path": "src/view/app/components/CodeGen/EditTemplatePanel.tsx",
    "chars": 644,
    "preview": "import * as React from 'react';\nimport CodeEditor from './CodeEditor';\n\nexport default class EditTemplatePanel extends R"
  },
  {
    "path": "src/view/app/components/CodeGen/MainView.css",
    "chars": 1479,
    "preview": "body,\nhtml {\n  margin: 0;\n  padding: 0;\n}\nbutton {\n  background-color: #0063a0;\n  color: #fff;\n  padding: 5px 20px;\n  bo"
  },
  {
    "path": "src/view/app/components/CodeGen/MainView.tsx",
    "chars": 7338,
    "preview": "import * as React from 'react';\nimport { IConfig, ICommand, CommandAction } from '../../model';\nimport { FilesInterface,"
  },
  {
    "path": "src/view/app/components/TaskBoard/ButtonBar.tsx",
    "chars": 6175,
    "preview": "import * as React from 'react';\nimport styled from 'styled-components';\nimport Select from 'react-select';\n\nimport { get"
  },
  {
    "path": "src/view/app/components/TaskBoard/Helpers.tsx",
    "chars": 5115,
    "preview": "import * as React from 'react';\nimport { TaskInterface } from './Task';\n\nexport const defaultDataString = `# Project\n\nPr"
  },
  {
    "path": "src/view/app/components/TaskBoard/SearchInput.tsx",
    "chars": 1237,
    "preview": "import * as React from 'react';\n\ninterface Props {\n  style?: object;\n  placeholder?: string;\n  delayTime?: number;\n  onC"
  },
  {
    "path": "src/view/app/components/TaskBoard/Task.tsx",
    "chars": 7740,
    "preview": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { Draggable } from 'react-beautiful-dnd';"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskBoard.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskBoard.tsx",
    "chars": 8915,
    "preview": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { CommandAction } from '../../model';\nimp"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskColumn.tsx",
    "chars": 3235,
    "preview": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { Droppable, Draggable } from 'react-beau"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskMenu.tsx",
    "chars": 6310,
    "preview": "import * as React from 'react';\nimport styled from 'styled-components';\n\nconst emojis = [\n  { text: '(bug)', char: '🐞', "
  },
  {
    "path": "src/view/app/config.tsx",
    "chars": 3508,
    "preview": "import * as React from \"react\";\nimport { IConfig, IUser, ICommand, CommandAction } from \"./model\";\n\ninterface IConfigPro"
  },
  {
    "path": "src/view/app/index.css",
    "chars": 1032,
    "preview": ":root {\n  /* --text-color: #eee; */\n  /* --bg-color: #1d1e1d; */\n\n  --button-bg-color: rgb(3, 0, 150);\n  --button-text-c"
  },
  {
    "path": "src/view/app/index.tsx",
    "chars": 733,
    "preview": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\n\nimport './index.css';\nimport { IConfig } from '."
  },
  {
    "path": "src/view/app/model.ts",
    "chars": 335,
    "preview": "export interface IConfig {\n  name: string;\n  description?: string;\n  users?: IUser[];\n}\nexport interface IUser {\n  name:"
  },
  {
    "path": "src/view/app/tsconfig.json",
    "chars": 497,
    "preview": "{\n    \"compilerOptions\": {\n        \"module\": \"esnext\",\n        \"moduleResolution\": \"node\",\n        \"target\": \"es6\",\n    "
  },
  {
    "path": "src/webpack.config.js",
    "chars": 655,
    "preview": "const path = require(\"path\");\n\nmodule.exports = {\n  entry: {\n    configViewer: \"./src/view/app/index.tsx\"\n  },\n  output:"
  },
  {
    "path": "tsconfig.json",
    "chars": 708,
    "preview": "{\n\t\"compilerOptions\": {\n\t\t\"module\": \"commonjs\",\n\t\t\"target\": \"es6\",\n\t\t\"outDir\": \"out\",\n\t\t\"lib\": [\n\t\t\t\"es6\", \"dom\"\n\t\t],\n\t\t"
  },
  {
    "path": "tslint.json",
    "chars": 248,
    "preview": "{\n\t\"rules\": {\n\t\t\"no-string-throw\": true,\n\t\t\"no-unused-expression\": true,\n\t\t\"no-duplicate-variable\": true,\n\t\t\"curly\": tru"
  },
  {
    "path": "vsc-extension-quickstart.md",
    "chars": 2596,
    "preview": "# Welcome to your VS Code Extension\n\n## What's in the folder\n\n* This folder contains all of the files necessary for your"
  },
  {
    "path": "webpack.config.js",
    "chars": 655,
    "preview": "const path = require(\"path\");\n\nmodule.exports = {\n  entry: {\n    configViewer: \"./src/view/app/index.tsx\"\n  },\n  output:"
  }
]

About this extraction

This page contains the full source code of the coddx-hq/coddx-alpha GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (96.3 KB), approximately 26.3k tokens, and a symbol index with 65 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!