├── .editorconfig ├── .gitignore ├── .tool-versions ├── CHANGELOG.md ├── README.md ├── manifest.json ├── package.json ├── rollup.config.js ├── src ├── gist_processor.ts ├── main.ts └── settings.ts ├── styles.css ├── test └── Test Gist Valut │ ├── .obsidian │ ├── app.json │ ├── appearance.json │ ├── community-plugins.json │ ├── core-plugins-migration.json │ ├── core-plugins.json │ ├── hotkeys.json │ ├── plugins │ │ └── obsidian-gist │ └── workspace.json │ └── Note with gist.md ├── tsconfig.json └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.ts] 4 | indent_style = space 5 | indent_size = 2 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | main.js 11 | *.js.map 12 | 13 | # obsidian 14 | data.json 15 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 19.8.1 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## master 4 | 5 | ## 0.10.0 6 | 7 | - Fixes the gist style will be blocked by CSP. 8 | - Fixed CORS issues (#9). 9 | 10 | ## 0.9.1 11 | 12 | - Fixes gist won't render when the username contains character `-`. (Fixes #7). 13 | 14 | ## 0.9.0 15 | 16 | ⚠️ 17 | 18 | - Set the minimum required Obsidian version to `0.15.0`. 19 | 20 | ## 0.8.1 21 | 22 | - Fixes the settings will be loaded incorrectly. 23 | 24 | ## 0.8.0 25 | 26 | - Allow to override the Gist's default stylesheet. (Fixes #2) 27 | 28 | ## 0.7.0 29 | 30 | - Fixes the link in the gist view will be opened in the iFrame. 31 | - Use the `postMessage` mechanism to set the iFrame's height, which should be much secure. 32 | 33 | ## 0.6.0 34 | 35 | - Move container style to `styles.css`. 36 | 37 | ## 0.5.0 38 | 39 | - Fixes the async chain. 40 | - Embed every gist into a iFrame so it will prevent DOM injection. 41 | 42 | ## 0.4.0 43 | 44 | - Allow to render a gist with full URL. 45 | 46 | ## 0.3.0 47 | 48 | - Allow to render multiple gists in the fenced code. 49 | 50 | ## 0.2.0 51 | 52 | - Remove unused boilerplate from the template. 53 | 54 | ## 0.1.0 55 | 56 | Initial release. 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian GitHub Gist Plugin 2 | 3 | ![GitHub release badge](https://badgen.net/github/release/linjunpop/obsidian-gist) 4 | 5 | This is a plugin to render the [GitHub Gist](https://gist.github.com) in [Obsidian](https://obsidian.md). 6 | 7 | ## Example 8 | 9 | The `gist` fenced code blocks will be rendered as a embed Gist view. 10 | 11 | 1. With only the Gist ID: 12 | 13 | ```` 14 | ```gist 15 | 30efbfd874fb1a16176d3f638a1e712a 16 | ``` 17 | ```` 18 | 19 | 2. With the username and Gist ID: 20 | 21 | ```` 22 | ```gist 23 | linjunpop/30efbfd874fb1a16176d3f638a1e712a 24 | ``` 25 | ```` 26 | 27 | 3. Specify to only show a single file in the Gist: 28 | 29 | ```` 30 | ```gist 31 | linjunpop/30efbfd874fb1a16176d3f638a1e712a#math.ex 32 | 30efbfd874fb1a16176d3f638a1e712a#concat.ex 33 | ``` 34 | ```` 35 | 36 | ![Example Image](https://user-images.githubusercontent.com/214616/120093926-f90eb580-c14f-11eb-94e3-a414c7528aef.png) 37 | 38 | ## Installation 39 | 40 | ### Installing from the Obsidian App 41 | 42 | Search "Gist" in __Settings__ -> __Community plugins__, you can find and install this plugin. 43 | 44 | You can check https://help.obsidian.md/Advanced+topics/Third-party+plugins#Discover+and+install+community+plugins for an official guide. 45 | 46 | ### Manually installing the plugin 47 | 48 | Find the latest release: https://github.com/linjunpop/obsidian-gist/releases, then copy over `main.js`, `manifest.json`, `styles.css` to your vault's `.obsidian/plugins/obsidian-gist` directory (ex. `VaultFolder/.obsidian/plugins/obsidian-gist/`). 49 | 50 | ## Development 51 | 52 | ### Develop the plugin locally 53 | 54 | - Clone this repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/obsidian-gist` folder. 55 | - Install NodeJS, then run `npm i` in the command line under your repo folder. 56 | - Run `npm run dev` to compile your plugin from `main.ts` to `main.js`. 57 | - Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`. 58 | - Reload Obsidian to load the new version of your plugin. 59 | - Enable plugin in settings window. 60 | 61 | ### Releasing new releases 62 | 63 | - Update the `manifest.json` with a new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. 64 | - Update the `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. 65 | - Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. 66 | - Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. 67 | - Publish the release. 68 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-gist", 3 | "name": "Gist", 4 | "version": "0.10.0", 5 | "minAppVersion": "0.15.0", 6 | "description": "This is a plugin to display the GitHub Gist.", 7 | "author": "Jun Lin", 8 | "authorUrl": "https://github.com/linjunpop/obsidian-gist", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-gist", 3 | "version": "0.10.0", 4 | "description": "This is a plugin to display the GitHub Gist (https://gist.github.com) in Obsidian (https://obsidian.md).", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js --environment BUILD:production" 9 | }, 10 | "keywords": [ 11 | "obsidian" 12 | ], 13 | "author": "Jun Lin", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@rollup/plugin-commonjs": "^18.0.0", 17 | "@rollup/plugin-node-resolve": "^11.2.1", 18 | "@rollup/plugin-typescript": "^8.2.1", 19 | "@types/node": "^14.14.37", 20 | "obsidian": "^0.15.0", 21 | "rollup": "^2.32.1", 22 | "tslib": "^2.2.0", 23 | "typescript": "^4.2.4" 24 | }, 25 | "dependencies": { 26 | "nanoid": "^4.0" 27 | } 28 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | 5 | const isProd = (process.env.BUILD === 'production'); 6 | 7 | const banner = 8 | `/* 9 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 10 | if you want to view the source visit the plugins github repository 11 | */ 12 | `; 13 | 14 | export default { 15 | input: 'src/main.ts', 16 | output: { 17 | dir: '.', 18 | sourcemap: 'inline', 19 | sourcemapExcludeSources: isProd, 20 | format: 'cjs', 21 | exports: 'default', 22 | banner, 23 | }, 24 | external: ['obsidian'], 25 | plugins: [ 26 | typescript(), 27 | nodeResolve({ browser: true }), 28 | commonjs(), 29 | ] 30 | }; -------------------------------------------------------------------------------- /src/gist_processor.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | 3 | import GistPluginSettings from './settings'; 4 | import { request, RequestUrlParam } from "obsidian"; 5 | 6 | type GistJSON = { 7 | description: string, 8 | public: Boolean, 9 | created_at: string, 10 | files: [string], 11 | owner: string, 12 | div: string, 13 | stylesheet: string 14 | } 15 | 16 | const pluginName = "obsidian-gist" 17 | const obsidianAppOrigin = 'app://obsidian.md' 18 | 19 | class GistProcessor { 20 | settings: GistPluginSettings 21 | 22 | constructor(settings: GistPluginSettings) { 23 | this.settings = settings 24 | } 25 | 26 | messageEventHandler = (messageEvent: MessageEvent) => { 27 | if (messageEvent.origin !== 'null') { 28 | // a message received from the iFrame with `srcdoc` attribute, the `origin` will be `null`. 29 | return; 30 | } 31 | 32 | const sender = messageEvent.data.sender 33 | 34 | // only process message coming from this plugin 35 | if (sender === pluginName) { 36 | const gistUUID = messageEvent.data.gistUUID 37 | const contentHeight = messageEvent.data.contentHeight 38 | 39 | const gistContainer: HTMLElement = document.querySelector('iframe#' + gistUUID) 40 | gistContainer.setAttribute('height', contentHeight) 41 | } 42 | } 43 | 44 | processor = async (sourceString: string, el: HTMLElement) => { 45 | const gists = sourceString.trim().split("\n") 46 | 47 | return Promise.all( 48 | gists.map(async (gist) => { 49 | return this._processGist(el, gist) 50 | }) 51 | ) 52 | }; 53 | 54 | // private 55 | 56 | private async _processGist(el: HTMLElement, gistString: string) { 57 | const pattern = /(?https?:\/\/)?(?gist\.github\.com\/)?((?[\w-]+)\/)?(?\w+)(\#(?.+))?/ 58 | 59 | const matchResult = gistString.match(pattern).groups 60 | 61 | const gistID = matchResult.gistID 62 | 63 | if (gistID === undefined) { 64 | return this._showError(el, gistString, `Could not found a valid Gist ID, please make sure your content and format is correct.`) 65 | } 66 | 67 | let gistURL = `https://gist.github.com/${gistID}.json` 68 | 69 | if (matchResult.filename !== undefined) { 70 | gistURL = `${gistURL}?file=${matchResult.filename}` 71 | } 72 | 73 | const urlParam: RequestUrlParam = { 74 | url: gistURL, 75 | method: "GET", 76 | headers: { 77 | "Accept": "application/json" 78 | } 79 | } 80 | try { 81 | const res = await request(urlParam) 82 | const gistJSON = JSON.parse(res) as GistJSON 83 | return this._insertGistElement(el, gistID, gistJSON) 84 | } catch (error) { 85 | return this._showError(el, gistString, `Could not fetch the Gist from GitHub server. (Error: ${error})`) 86 | } 87 | } 88 | 89 | private async _insertGistElement(el: HTMLElement, gistID: string, gistJSON: GistJSON) { 90 | // generate an uuid for each gist element 91 | const gistUUID = `${pluginName}-${gistID}-${nanoid()}` 92 | 93 | // container 94 | const container = document.createElement('iframe'); 95 | container.id = gistUUID 96 | container.classList.add(`${pluginName}-container`) 97 | container.setAttribute('sandbox', 'allow-scripts allow-top-navigation-by-user-activation') 98 | container.setAttribute('loading', 'lazy') 99 | // container.setAttribute('csp', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://github.githubassets.com;") 100 | 101 | // reset the default things on HTML 102 | const resetStylesheet = ` 103 | 110 | ` 111 | 112 | // height adjustment script 113 | const heightAdjustmentScript = ` 114 | 129 | ` 130 | 131 | // hack to make links open in the parent 132 | const parentLinkHack = document.createElement('base') 133 | parentLinkHack.target = "_parent" 134 | 135 | // load stylesheet as text 136 | const stylesheetText = await this._loadStylesheet(el, gistID, gistJSON.stylesheet); 137 | 138 | // custom stylesheet 139 | let customStylesheet = "" 140 | if (this.settings.styleSheet && this.settings.styleSheet.length > 0) { 141 | customStylesheet = this.settings.styleSheet 142 | } 143 | 144 | // Inject content into the iframe 145 | container.srcdoc = ` 146 | 147 | 148 | 149 | ${resetStylesheet} 150 | ${parentLinkHack.outerHTML} 151 | ${heightAdjustmentScript} 152 | 153 | 154 | 157 | 158 | 159 | 162 | 163 | 164 | 165 | ${gistJSON.div} 166 | 167 | 168 | ` 169 | 170 | // insert container into the DOM 171 | el.appendChild(container) 172 | } 173 | 174 | private async _showError(el: HTMLElement, gistIDAndFilename: String, errorMessage: String = '') { 175 | const errorText = ` 176 | Failed to load the Gist (${gistIDAndFilename}). 177 | 178 | Error: 179 | 180 | ${errorMessage} 181 | `.trim() 182 | 183 | el.createEl('pre', { text: errorText }) 184 | } 185 | 186 | private async _loadStylesheet(el: HTMLElement, gistString: String, url: string) { 187 | const urlParam: RequestUrlParam = { 188 | url: url, 189 | method: "GET", 190 | headers: { 191 | "Accept": "text/css" 192 | } 193 | } 194 | 195 | try { 196 | const res = await request(urlParam) 197 | return res; 198 | } catch (error) { 199 | return this._showError(el, gistString, `Could not fetch the Gist Style from GitHub server. (Error: ${error})`) 200 | } 201 | } 202 | } 203 | 204 | export { GistProcessor } 205 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { App, Plugin, PluginSettingTab, Setting } from 'obsidian'; 2 | 3 | import GistPluginSettings from './settings'; 4 | import { GistProcessor } from './gist_processor'; 5 | 6 | const DEFAULT_SETTINGS: GistPluginSettings = { 7 | styleSheet: null 8 | } 9 | 10 | export default class GistPlugin extends Plugin { 11 | settings: GistPluginSettings 12 | 13 | async onload() { 14 | // Settings 15 | await this.loadSettings() 16 | this.addSettingTab(new GistSettingTab(this.app, this)); 17 | 18 | // Load the Gist processor 19 | const gistProcessor = new GistProcessor(this.settings) 20 | 21 | // Register the processor to Obsidian 22 | this.registerDomEvent(window, "message", gistProcessor.messageEventHandler) 23 | this.registerMarkdownCodeBlockProcessor("gist", gistProcessor.processor) 24 | } 25 | 26 | async loadSettings() { 27 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 28 | } 29 | 30 | async saveSettings() { 31 | await this.saveData(this.settings); 32 | } 33 | } 34 | 35 | class GistSettingTab extends PluginSettingTab { 36 | plugin: GistPlugin; 37 | 38 | constructor(app: App, plugin: GistPlugin) { 39 | super(app, plugin); 40 | this.plugin = plugin; 41 | } 42 | 43 | display(): void { 44 | let { containerEl } = this; 45 | 46 | containerEl.empty(); 47 | 48 | containerEl.createEl('h2', { text: 'Settings for Gist Plugin.' }); 49 | 50 | new Setting(containerEl) 51 | .setName('Custom Stylesheet') 52 | .setDesc('Override the default stylesheet') 53 | .addTextArea(text => text 54 | .setPlaceholder('Paste your custom stylesheet here') 55 | .setValue(this.plugin.settings.styleSheet) 56 | .onChange(async (value) => { 57 | this.plugin.settings.styleSheet = value; 58 | await this.plugin.saveSettings(); 59 | })); 60 | } 61 | } -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | export default interface GistPluginSettings { 2 | styleSheet: string | null; 3 | } -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .block-language-gist > iframe { 2 | width: 100%; 3 | border: 0; 4 | } 5 | -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/app.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/appearance.json: -------------------------------------------------------------------------------- 1 | { 2 | "accentColor": "" 3 | } -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/community-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "obsidian-gist" 3 | ] -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/core-plugins-migration.json: -------------------------------------------------------------------------------- 1 | { 2 | "file-explorer": true, 3 | "global-search": true, 4 | "switcher": true, 5 | "graph": true, 6 | "backlink": true, 7 | "canvas": true, 8 | "outgoing-link": true, 9 | "tag-pane": true, 10 | "properties": true, 11 | "page-preview": true, 12 | "daily-notes": true, 13 | "templates": true, 14 | "note-composer": true, 15 | "command-palette": true, 16 | "slash-command": false, 17 | "editor-status": true, 18 | "bookmarks": true, 19 | "markdown-importer": false, 20 | "zk-prefixer": false, 21 | "random-note": false, 22 | "outline": true, 23 | "word-count": true, 24 | "slides": false, 25 | "audio-recorder": false, 26 | "workspaces": false, 27 | "file-recovery": true, 28 | "publish": false, 29 | "sync": false 30 | } -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/core-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "file-explorer", 3 | "global-search", 4 | "switcher", 5 | "graph", 6 | "backlink", 7 | "canvas", 8 | "outgoing-link", 9 | "tag-pane", 10 | "properties", 11 | "page-preview", 12 | "daily-notes", 13 | "templates", 14 | "note-composer", 15 | "command-palette", 16 | "editor-status", 17 | "bookmarks", 18 | "outline", 19 | "word-count", 20 | "file-recovery" 21 | ] -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/hotkeys.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/plugins/obsidian-gist: -------------------------------------------------------------------------------- 1 | ../../../../ -------------------------------------------------------------------------------- /test/Test Gist Valut/.obsidian/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "id": "106c194db774a1c3", 4 | "type": "split", 5 | "children": [ 6 | { 7 | "id": "c3e19c42689ff0d9", 8 | "type": "tabs", 9 | "children": [ 10 | { 11 | "id": "77aba9d0517742d2", 12 | "type": "leaf", 13 | "state": { 14 | "type": "empty", 15 | "state": {} 16 | } 17 | }, 18 | { 19 | "id": "8f04235ad6a5474b", 20 | "type": "leaf", 21 | "state": { 22 | "type": "markdown", 23 | "state": { 24 | "file": "Note with gist.md", 25 | "mode": "source", 26 | "source": false 27 | } 28 | } 29 | }, 30 | { 31 | "id": "09f6f0d57e13b3bc", 32 | "type": "leaf", 33 | "state": { 34 | "type": "markdown", 35 | "state": { 36 | "file": "Note with gist.md", 37 | "mode": "source", 38 | "source": false 39 | } 40 | } 41 | } 42 | ], 43 | "currentTab": 2 44 | } 45 | ], 46 | "direction": "vertical" 47 | }, 48 | "left": { 49 | "id": "d0a975af9a17034d", 50 | "type": "split", 51 | "children": [ 52 | { 53 | "id": "d3551c5ca1838e6e", 54 | "type": "tabs", 55 | "children": [ 56 | { 57 | "id": "50640ad7f3c164bd", 58 | "type": "leaf", 59 | "state": { 60 | "type": "file-explorer", 61 | "state": { 62 | "sortOrder": "alphabetical" 63 | } 64 | } 65 | }, 66 | { 67 | "id": "7fe33c2a7c1ee72b", 68 | "type": "leaf", 69 | "state": { 70 | "type": "search", 71 | "state": { 72 | "query": "", 73 | "matchingCase": false, 74 | "explainSearch": false, 75 | "collapseAll": false, 76 | "extraContext": false, 77 | "sortOrder": "alphabetical" 78 | } 79 | } 80 | }, 81 | { 82 | "id": "deefb181135d6d99", 83 | "type": "leaf", 84 | "state": { 85 | "type": "bookmarks", 86 | "state": {} 87 | } 88 | } 89 | ] 90 | } 91 | ], 92 | "direction": "horizontal", 93 | "width": 300 94 | }, 95 | "right": { 96 | "id": "a6c1622d4a5761a2", 97 | "type": "split", 98 | "children": [ 99 | { 100 | "id": "2e96ce5718769127", 101 | "type": "tabs", 102 | "children": [ 103 | { 104 | "id": "65c7c73820d4dc91", 105 | "type": "leaf", 106 | "state": { 107 | "type": "backlink", 108 | "state": { 109 | "file": "Note with gist.md", 110 | "collapseAll": false, 111 | "extraContext": false, 112 | "sortOrder": "alphabetical", 113 | "showSearch": false, 114 | "searchQuery": "", 115 | "backlinkCollapsed": false, 116 | "unlinkedCollapsed": true 117 | } 118 | } 119 | }, 120 | { 121 | "id": "c561e6806cd2e1f1", 122 | "type": "leaf", 123 | "state": { 124 | "type": "outgoing-link", 125 | "state": { 126 | "file": "Note with gist.md", 127 | "linksCollapsed": false, 128 | "unlinkedCollapsed": true 129 | } 130 | } 131 | }, 132 | { 133 | "id": "4dcd7177d210ed2c", 134 | "type": "leaf", 135 | "state": { 136 | "type": "tag", 137 | "state": { 138 | "sortOrder": "frequency", 139 | "useHierarchy": true 140 | } 141 | } 142 | }, 143 | { 144 | "id": "ab594eeef5e70402", 145 | "type": "leaf", 146 | "state": { 147 | "type": "outline", 148 | "state": { 149 | "file": "Note with gist.md" 150 | } 151 | } 152 | }, 153 | { 154 | "id": "380e479fa0ffdeec", 155 | "type": "leaf", 156 | "state": { 157 | "type": "all-properties", 158 | "state": { 159 | "sortOrder": "frequency", 160 | "showSearch": false, 161 | "searchQuery": "" 162 | } 163 | } 164 | } 165 | ] 166 | } 167 | ], 168 | "direction": "horizontal", 169 | "width": 300, 170 | "collapsed": true 171 | }, 172 | "left-ribbon": { 173 | "hiddenItems": { 174 | "switcher:Open quick switcher": false, 175 | "graph:Open graph view": false, 176 | "canvas:Create new canvas": false, 177 | "daily-notes:Open today's daily note": false, 178 | "templates:Insert template": false, 179 | "command-palette:Open command palette": false 180 | } 181 | }, 182 | "active": "09f6f0d57e13b3bc", 183 | "lastOpenFiles": [ 184 | "Untitled.md", 185 | "Note with gist.md", 186 | "Test.md" 187 | ] 188 | } -------------------------------------------------------------------------------- /test/Test Gist Valut/Note with gist.md: -------------------------------------------------------------------------------- 1 | Gist Note 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | ```gist 9 | 30efbfd874fb1a16176d3f638a1e712a 10 | ``` 11 | 12 | 2 13 | 14 | ```gist 15 | linjunpop/30efbfd874fb1a16176d3f638a1e712a 16 | ``` 17 | 18 | 3 19 | 20 | ```gist 21 | linjunpop/30efbfd874fb1a16176d3f638a1e712a#math.ex 22 | 30efbfd874fb1a16176d3f638a1e712a#concat.ex 23 | ``` 24 | 25 | 4 26 | 27 | ```gist 28 | https://gist.github.com/linjunpop/30efbfd874fb1a16176d3f638a1e712a 29 | ``` 30 | 31 | 5 32 | 33 | ```gist 34 | gist.github.com/linjunpop/30efbfd874fb1a16176d3f638a1e712a 35 | ``` 36 | 37 | 5 38 | 39 | ```gist 40 | gist.github.com/linjunpop/30efbfd874fb1a16176d3f638a1e712a 41 | ``` 42 | 43 | 5 44 | 45 | ```gist 46 | gist.github.com/linjunpop/30efbfd874fb1a16176d3f638a1e712a 47 | ``` 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.10.0": "0.15.0", 3 | "0.9.1": "0.15.0", 4 | "0.9.0": "0.15.0", 5 | "0.8.1": "0.9.12", 6 | "0.8.0": "0.9.12", 7 | "0.7.0": "0.9.12", 8 | "0.6.0": "0.9.12", 9 | "0.5.0": "0.9.12", 10 | "0.4.0": "0.9.12", 11 | "0.3.0": "0.9.12", 12 | "0.2.0": "0.9.12", 13 | "0.1.0": "0.9.12" 14 | } --------------------------------------------------------------------------------