• Extending Gingko

    If you’re willing to play around with the API, you can do some pretty amazing things with Gingko.

  • API

    Gingko’s API can change on occasion, so if you’d like to be notified of any changes, get in touch with me.

  • Client-side

    Open up your browser’s console, and play with this. To prevent headaches, make a copy of your tree before messing with this.

  • Examples

    Send emails to a given card, automatically create a PDF from Gingko’s LaTeX output. If you use Gingko’s API for something, no matter how small or specific your use case, please share it with me, so I can add it here.

  • GET

    Action Method
    Get user information GET /api/index
    Get all of user’s cards GET /api/cards
    Get all of user’s trees GET /api/trees
    Get list of tree’s collabs GET /api/trees/:treeId/collaborators
    Get all of tree’s cards GET /api/trees/:treeId/cards
    Get tree export GET /api/export/:treeId.:format
  • PUT/POST/DELETE Cards

    Action Method
    Create new card POST /api/trees/:treeId/cards
    Modify card by id PUT /api/trees/:treeId/cards/:cardId
    Delete card by id DELETE /api/trees/:treeId/cards/:cardId
    Import from JSON or Markdown POST /api/trees/:treeId/cards/seed
  • PUT/POST/DELETE Trees

    Action Method
    Create new tree POST /api/trees
    Copy your private tree by id POST /api/trees/:treeId/clone
    Copy a public tree by id POST /api/trees/:publicTreeId/clonePublic
    Modify tree PUT /api/trees/:treeId
    Delete tree DELETE /api/trees/:treeId
  • app object

    The Global app object is the main way to interact with your current tree. You can get a list of all cards, edit content, clear your trash, or do several other actions that don’t yet have a UI attached to them.

  • Events

    You can trigger almost any event manually. Here are some examples:

    Backbone.trigger("key:up");
    Backbone.trigger("key:edit");
    Backbone.trigger("key:save");
    Backbone.trigger("key:bold");
    Backbone.trigger("key:addDown");
    Backbone.trigger("modal:show:new-tree");

    To react to events, you can use Backbone.on:

    Backbone.on("card:save", function(id){console.log(id, 'do something')})

    Events:

    • card:edit
    • card:save
    • card:cancel
    • … (see Debug Mode for more events)
  • Get user information

    GET /api/index

    curl -u you@email.com:password https://gingkoapp.com/api/index
  • List all trees

    GET /api/trees

    curl -u you@email.com:password https://gingkoapp.com/api/trees | python -mjson.tool

    (python tool is optional, to format JSON response nicely).

  • Get exports

    You can use the API to get any of our supported export formats (txt, html, json, docx, impress). You can also use flags like column=3 or cardId=someCardId.

    Simplest option is to export the view you want from within the app, and copy the URL & parameters.

  • Create new card

    POST JSON data to /api/trees/:treeId/cards

    Schema:

    var CardSchema = new Schema({
      content: { type: String, default: '' },
      position: { type: Number, required: true },
      treeId: { type: ObjectId, required: true },
      parentId: { type: ObjectId, default: null, validate: notId }
      // plus private fields
    });

    I’m aware that requiring position index is tricky (for instance, adding a card to the end of a list requires that you know the index of the last card). I’m working on a fix for this.

  • Modify card by id

    PUT JSON data to /api/trees/:treeId/cards/:cardId

    Can update content, position, and parentId.

  • Import from JSON or Markdown

    POST JSON data to /api/trees/:treeId/cards/seed

    Import Markdown:

    { "parentId": "someParentId" || null,
      "seed": "# Readme\nThis is some markdown document"
    }

    You can also import a JSON string in seed, using the format that Gingko exports JSON in.

    Note that neither of these “import” functions will replace any part of your tree. The card someParentId will simply have a new subtree with this content (if null then the subtree will start in first column).

  • Sample methods of app object

    Get currently active card’s id

    app.get('cardId')

    Get current tree model

    app.get('currentTree')

    Get children ids of current card

    app.graph.children(app.get('cardId'))
  • Array of all cards (models & views)

    To get an array of all cards:

    app.get('cards')

    This is an array of the card views. Use:

    app.get('cards').models

    to get the Backbone.js models for the cards.

  • Custom Shortcuts

    By binding to the Moustrap.js global object, and triggering Backbone events, you can create shortcuts for your own actions (or override the defaults).

    Mousetrap.bind('mod+enter', function(e) {
      if (app.get('keyscope') === 'editCard') {
        Backbone.trigger("key:save", e);
      } else if (app.get('keyscope') === 'tree') {
        Backbone.trigger("key:edit", e);
      }
      return false;
    });
  • Debug mode

    In order to see all the events that are being triggered, you can paste the following into the console:

    trigger = Backbone.trigger;
    events = [];
    
    Backbone.trigger = function() {
      var args, name, now, time;
      args = _.toArray(arguments);
      name = _.first(args);
      events.push(name);
      console.log(args);
      trigger.apply(Backbone, args);
      events.pop();
    }
  • Response

    {
     "_id":"50abcdef12347c360b000001",
     "email":"you@email.com",
     "limit":-1,
     "hasStripeId":false,
     "createdAt":"2012-11-18T15:45:45.197Z",
     "treesCount":150,
     "cardsCount":12970,
     "favoriteTrees": ["42a08d2a519abcdefgh12345",
     "42a08d2a519abcdefgh12346","42a08d2a519abcdefgh12347"],
     "randomIndex":82,
     "userHash":"abcde12345",
     "offers":{},
     "csrf":""
    }
  • Response

    [
        {
            "__v": 0,
            "_id": "6e9bncfcvttpjealtkb2tfdw",
            "createdAt": "2012-11-19T02:22:41.530Z",
            "latex": true,
            "name": "Ideas",
            "owner": "50abcdef12347c360b000001",
            "updatedAt": "2014-08-05T21:46:15.333Z",
            "users": [
                "50abcdef12347c360b000001"
            ]
        },
        {
            "__v": 0,
            "_id": "yij7q888czecbermdwwn59gy",
            "createdAt": "2012-12-14T21:03:26.000Z",
            "name": "Archive Tree",
            "owner": "50abcdef12347c360b000001",
            "updatedAt": "2014-08-13T11:30:42.322Z",
            "users": [
                "50abcdef12347c360b000001"
            ]
        },
        {
            "__v": 0,
            "_id": "kilgsbxzg6e1pwyajcjd7dl7",
            "createdAt": "2012-11-28T21:11:19.029Z",
            "name": "Screenplay: Alien (1979)",
            "owner": "50abcdef12347c360b000001",
            "publicUrl": "alien-1979",
            "updatedAt": "2014-04-18T12:47:29.279Z",
            "users": [
                "50abcdef12347c360b000001",
                "50abcdef12347c360b000002"
            ]
        }
    ]
  • Example: Find & Replace

    For example, to do a “find and replace”, paste this code in your browser’s console:

    function findAndReplace(find,replace){
      app.get('cards').models.map(function(c) {
        if(c.get('content').match(find)) {
          c.saveContent(c.get('content').replace(find, replace));
      }});
    }

    For the rest of the browser session, you can type:
    findAndReplace("some text", "replacement text")
    and press enter.

{"cards":[{"_id":"4d5f6e863a37f8d35a0000b2","treeId":"428bb66e8f89e8e2dc00018f","seq":2873436,"position":0.25,"parentId":null,"content":"# Extending Gingko\nIf you're willing to play around with the API, you can do some pretty amazing things with Gingko."},{"_id":"4d56c17ebeda9ea7e20000a1","treeId":"428bb66e8f89e8e2dc00018f","seq":1273083,"position":1,"parentId":"4d5f6e863a37f8d35a0000b2","content":"## API\nGingko's API can change on occasion, so if you'd like to be notified of any changes, get in touch with me."},{"_id":"428bb6b88f89e8e2dc000192","treeId":"428bb66e8f89e8e2dc00018f","seq":1272813,"position":1,"parentId":"4d56c17ebeda9ea7e20000a1","content":"### GET\n| Action | Method | |\n|----------------------------------|:------:|----------------------------------|\n| Get user information | GET | /api/index |\n| Get all of user's cards | GET | /api/cards |\n| Get all of user's trees | GET | /api/trees |\n| Get list of tree's collabs | GET | /api/trees/:treeId/collaborators |\n| Get all of tree's cards | GET | /api/trees/:treeId/cards |\n| Get tree export | GET | /api/export/:treeId.:format |"},{"_id":"428bc2f28f89e8e2dc000193","treeId":"428bb66e8f89e8e2dc00018f","seq":1272870,"position":1,"parentId":"428bb6b88f89e8e2dc000192","content":"### Get user information\nGET /api/index\n\n\n```bash\ncurl -u you@email.com:password https://gingkoapp.com/api/index\n```"},{"_id":"4d5ed9493a37f8d35a0000a5","treeId":"428bb66e8f89e8e2dc00018f","seq":1272744,"position":1,"parentId":"428bc2f28f89e8e2dc000193","content":"### Response\n```json\n{\n \"_id\":\"50abcdef12347c360b000001\",\n \"email\":\"you@email.com\",\n \"limit\":-1,\n \"hasStripeId\":false,\n \"createdAt\":\"2012-11-18T15:45:45.197Z\",\n \"treesCount\":150,\n \"cardsCount\":12970,\n \"favoriteTrees\": [\"42a08d2a519abcdefgh12345\",\n \"42a08d2a519abcdefgh12346\",\"42a08d2a519abcdefgh12347\"],\n \"randomIndex\":82,\n \"userHash\":\"abcde12345\",\n \"offers\":{},\n \"csrf\":\"\"\n}\n```"},{"_id":"428bd90f8f89e8e2dc0001d9","treeId":"428bb66e8f89e8e2dc00018f","seq":1272869,"position":2,"parentId":"428bb6b88f89e8e2dc000192","content":"### List all trees\nGET /api/trees\n\n```bash\ncurl -u you@email.com:password https://gingkoapp.com/api/trees | python -mjson.tool\n```\n(python tool is optional, to format JSON response nicely)."},{"_id":"4d5ed9c03a37f8d35a0000a6","treeId":"428bb66e8f89e8e2dc00018f","seq":1272748,"position":1,"parentId":"428bd90f8f89e8e2dc0001d9","content":"### Response\n```json\n[\n {\n \"__v\": 0,\n \"_id\": \"6e9bncfcvttpjealtkb2tfdw\",\n \"createdAt\": \"2012-11-19T02:22:41.530Z\",\n \"latex\": true,\n \"name\": \"Ideas\",\n \"owner\": \"50abcdef12347c360b000001\",\n \"updatedAt\": \"2014-08-05T21:46:15.333Z\",\n \"users\": [\n \"50abcdef12347c360b000001\"\n ]\n },\n {\n \"__v\": 0,\n \"_id\": \"yij7q888czecbermdwwn59gy\",\n \"createdAt\": \"2012-12-14T21:03:26.000Z\",\n \"name\": \"Archive Tree\",\n \"owner\": \"50abcdef12347c360b000001\",\n \"updatedAt\": \"2014-08-13T11:30:42.322Z\",\n \"users\": [\n \"50abcdef12347c360b000001\"\n ]\n },\n {\n \"__v\": 0,\n \"_id\": \"kilgsbxzg6e1pwyajcjd7dl7\",\n \"createdAt\": \"2012-11-28T21:11:19.029Z\",\n \"name\": \"Screenplay: Alien (1979)\",\n \"owner\": \"50abcdef12347c360b000001\",\n \"publicUrl\": \"alien-1979\",\n \"updatedAt\": \"2014-04-18T12:47:29.279Z\",\n \"users\": [\n \"50abcdef12347c360b000001\",\n \"50abcdef12347c360b000002\"\n ]\n }\n]\n```"},{"_id":"4d5fa7adfcf97ebe3b0000b0","treeId":"428bb66e8f89e8e2dc00018f","seq":1273352,"position":3,"parentId":"428bb6b88f89e8e2dc000192","content":"### Get exports\nYou can use the API to get any of our supported export formats (`txt`, `html`, `json`, `docx`, `impress`). You can also use flags like `column=3` **or** `cardId=someCardId`.\n\nSimplest option is to export the view you want from within the app, and copy the URL & parameters."},{"_id":"4d5f08133a37f8d35a0000aa","treeId":"428bb66e8f89e8e2dc00018f","seq":1272977,"position":1.75,"parentId":"4d56c17ebeda9ea7e20000a1","content":"### PUT/POST/DELETE **Cards**\n| Action | Method | |\n|--------------------------------------|:------:|----------------------------------|\n| Create new card | POST | /api/trees/:treeId/cards |\n| Modify card by id | PUT | /api/trees/:treeId/cards/:cardId |\n| Delete card by id | DELETE | /api/trees/:treeId/cards/:cardId |\n| Import from JSON or Markdown | POST | /api/trees/:treeId/cards/seed |"},{"_id":"4d5f190e3a37f8d35a0000ac","treeId":"428bb66e8f89e8e2dc00018f","seq":1272932,"position":1,"parentId":"4d5f08133a37f8d35a0000aa","content":"### Create new card\nPOST JSON data to /api/trees/:treeId/cards\n\nSchema:\n```js\nvar CardSchema = new Schema({\n content: { type: String, default: '' },\n position: { type: Number, required: true },\n treeId: { type: ObjectId, required: true },\n parentId: { type: ObjectId, default: null, validate: notId }\n // plus private fields\n});\n```\n\nI'm aware that requiring position index is tricky (for instance, adding a card to the end of a list requires that you know the index of the last card). I'm working on a fix for this."},{"_id":"4d5f43c13a37f8d35a0000ae","treeId":"428bb66e8f89e8e2dc00018f","seq":1272980,"position":1.5,"parentId":"4d5f08133a37f8d35a0000aa","content":"### Modify card by id\nPUT JSON data to /api/trees/:treeId/cards/:cardId\n\nCan update `content`, `position`, and `parentId`."},{"_id":"4d5f3e8d3a37f8d35a0000ad","treeId":"428bb66e8f89e8e2dc00018f","seq":1272974,"position":2,"parentId":"4d5f08133a37f8d35a0000aa","content":"### Import from JSON or Markdown\nPOST JSON data to /api/trees/:treeId/cards/seed\n\nImport Markdown:\n```json\n{ \"parentId\": \"someParentId\" || null,\n \"seed\": \"# Readme\\nThis is some markdown document\"\n}\n```\n\nYou can also import a JSON string in `seed`, using the format that Gingko exports JSON in.\n\nNote that neither of these \"import\" functions will replace any part of your tree. The card `someParentId` will simply have a new subtree with this content (if `null` then the subtree will start in first column)."},{"_id":"4d5ead176ae552a4f50003e4","treeId":"428bb66e8f89e8e2dc00018f","seq":1272825,"position":2,"parentId":"4d56c17ebeda9ea7e20000a1","content":"### PUT/POST/DELETE **Trees**\n| Action | Method | |\n|------------------------------|:------:|--------------------------------------|\n| Create new tree | POST | /api/trees |\n| Copy your private tree by id | POST | /api/trees/:treeId/clone |\n| Copy a public tree by id | POST | /api/trees/:publicTreeId/clonePublic |\n| Modify tree | PUT | /api/trees/:treeId |\n| Delete tree | DELETE | /api/trees/:treeId |"},{"_id":"4d56c1b4beda9ea7e20000a2","treeId":"428bb66e8f89e8e2dc00018f","seq":1273084,"position":2,"parentId":"4d5f6e863a37f8d35a0000b2","content":"## Client-side\nOpen up your browser's console, and play with this. To prevent headaches, **make a copy** of your tree before messing with this."},{"_id":"4d56c201beda9ea7e20000a3","treeId":"428bb66e8f89e8e2dc00018f","seq":1267301,"position":1,"parentId":"4d56c1b4beda9ea7e20000a2","content":"### `app` object\nThe Global app object is the main way to interact with your current tree. You can get a list of all cards, edit content, clear your trash, or do several other actions that don't yet have a UI attached to them."},{"_id":"4d5f67783a37f8d35a0000b0","treeId":"428bb66e8f89e8e2dc00018f","seq":3198237,"position":0.5,"parentId":"4d56c201beda9ea7e20000a3","content":"## Sample methods of `app` object\nGet currently active card's id\n```\napp.get('cardId')\n```\n\nGet current tree model\n```\napp.get('currentTree')\n```\n\nGet children ids of current card\n```\napp.graph.children(app.get('cardId'))\n```"},{"_id":"4d56c718beda9ea7e20000a5","treeId":"428bb66e8f89e8e2dc00018f","seq":1267526,"position":1,"parentId":"4d56c201beda9ea7e20000a3","content":"## Array of all cards (models & views)\n\nTo get an array of all cards:\n```js\napp.get('cards')\n```\n\nThis is an array of the card *views*. Use:\n```js\napp.get('cards').models\n```\nto get the [Backbone.js](http://backbonejs.com) models for the cards."},{"_id":"4d56e181beda9ea7e20000a7","treeId":"428bb66e8f89e8e2dc00018f","seq":1839302,"position":1,"parentId":"4d56c718beda9ea7e20000a5","content":"### Example: Find & Replace\nFor example, to do a \"find and replace\", paste this code in your browser's console:\n```js\nfunction findAndReplace(find,replace){\n app.get('cards').models.map(function(c) {\n if(c.get('content').match(find)) {\n c.saveContent(c.get('content').replace(find, replace));\n }});\n}\n```\n\nFor the rest of the browser session, you can type:\n`findAndReplace(\"some text\", \"replacement text\")`\nand press enter."},{"_id":"4d5f59873a37f8d35a0000af","treeId":"428bb66e8f89e8e2dc00018f","seq":11057801,"position":2,"parentId":"4d56c1b4beda9ea7e20000a2","content":"### Events\nYou can trigger almost any event manually. Here are some examples:\n```js\nBackbone.trigger(\"key:up\");\nBackbone.trigger(\"key:edit\");\nBackbone.trigger(\"key:save\");\nBackbone.trigger(\"key:bold\");\nBackbone.trigger(\"key:addDown\");\nBackbone.trigger(\"modal:show:new-tree\");\n```\nTo react to events, you can use `Backbone.on`:\n```\nBackbone.on(\"card:save\", function(id){console.log(id, 'do something')})\n```\n\nEvents:\n* `card:edit`\n* `card:save`\n* `card:cancel`\n* ... (see Debug Mode for more events)"},{"_id":"5dbf9084c1277bb1040000bf","treeId":"428bb66e8f89e8e2dc00018f","seq":5156118,"position":1,"parentId":"4d5f59873a37f8d35a0000af","content":"## Custom Shortcuts\nBy binding to the [Moustrap.js](https://craig.is/killing/mice) global object, and triggering Backbone events, you can create shortcuts for your own actions (or override the defaults).\n\n```\nMousetrap.bind('mod+enter', function(e) {\n if (app.get('keyscope') === 'editCard') {\n Backbone.trigger(\"key:save\", e);\n } else if (app.get('keyscope') === 'tree') {\n Backbone.trigger(\"key:edit\", e);\n }\n return false;\n});\n```"},{"_id":"7f38178ddb3d4831e10000e1","treeId":"428bb66e8f89e8e2dc00018f","seq":11057797,"position":2,"parentId":"4d5f59873a37f8d35a0000af","content":"## Debug mode\nIn order to see all the events that are being triggered, you can paste the following into the console:\n\n```\ntrigger = Backbone.trigger;\nevents = [];\n\nBackbone.trigger = function() {\n var args, name, now, time;\n args = _.toArray(arguments);\n name = _.first(args);\n events.push(name);\n console.log(args);\n trigger.apply(Backbone, args);\n events.pop();\n}\n```"},{"_id":"4f22687b1d3ae5650d0003b1","treeId":"428bb66e8f89e8e2dc00018f","seq":3198242,"position":2.5,"parentId":"4d5f6e863a37f8d35a0000b2","content":"## Examples\nSend emails to a given card, automatically create a PDF from Gingko's LaTeX output. If you use Gingko's API for something, no matter how small or specific your use case, please <a href=\"mailto:adriano@gingkoapp.com\">share it with me</a>, so I can add it here."},{"_id":"4f2269621d3ae5650d0003b2","treeId":"428bb66e8f89e8e2dc00018f","seq":1516644,"position":0.5,"parentId":"4f22687b1d3ae5650d0003b1","content":"## Add to Gingko via Alfred\nIf you use [Alfred](http://www.alfredapp.com), you can quickly jot down a note in a designated Gingko tree:\nhttps://github.com/fabianmu/alfred-add-to-gingko\n\nThanks to [@fabianmu](https://twitter.com/fabianmu) for this one.\n"},{"_id":"53316698cf998130b70001af","treeId":"428bb66e8f89e8e2dc00018f","seq":2081957,"position":0.75,"parentId":"4f22687b1d3ae5650d0003b1","content":"## Node - Gingko TeX to PDF\n\"And the other I fixed with a little post-production. Actually, I wrote a node server that mashes a header and footer around the LaTeX output from Gingko and spits out a nice PDF in about 3-5 seconds.\" - A user\n\nhttps://gist.github.com/AdrianoFerrari/653da1d745dcf8dd31b6"},{"_id":"4e9cc51f23cc13eac30000a8","treeId":"428bb66e8f89e8e2dc00018f","seq":1516624,"position":1,"parentId":"4f22687b1d3ae5650d0003b1","content":"## Further Ideas\nAs an exercise, try using http://zapier.com to:\n* Update an existing card with, say, your last tweet.\n* (harder) Copy the body of any Gmail message you label 'gko', to a new card.\n\nOr how about getting raw Markdown from Github README's, and displaying that as a gingko tree?\n* https://github.com/jeromegn/DocumentUp/blob/master/README.md\n* https://raw.githubusercontent.com/jeromegn/DocumentUp/master/README.md"}],"tree":{"_id":"428bb66e8f89e8e2dc00018f","name":"Gingko API","publicUrl":"api-docs"}}