{"id":23594027,"url":"https://github.com/magicink/boundless","last_synced_at":"2025-08-30T08:31:36.403Z","repository":{"id":58660126,"uuid":"530671173","full_name":"magicink/boundless","owner":"magicink","description":"Boundless is a React-based story format for Twine","archived":false,"fork":false,"pushed_at":"2024-08-24T03:09:31.000Z","size":1123,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2024-08-24T04:23:38.134Z","etag":null,"topics":["react","rehype","remarkjs","story-format","twine","twine-format","twine2"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/magicink.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-30T13:32:19.000Z","updated_at":"2024-08-24T04:23:51.346Z","dependencies_parsed_at":"2024-08-24T04:23:43.086Z","dependency_job_id":"b1afcc60-c5bf-40bb-bad7-a9148a108937","html_url":"https://github.com/magicink/boundless","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicink%2Fboundless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicink%2Fboundless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicink%2Fboundless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magicink%2Fboundless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/magicink","download_url":"https://codeload.github.com/magicink/boundless/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231461742,"owners_count":18380303,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","rehype","remarkjs","story-format","twine","twine-format","twine2"],"created_at":"2024-12-27T09:14:47.662Z","updated_at":"2024-12-27T09:14:48.300Z","avatar_url":"https://github.com/magicink.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Boundless\r\n\r\nBoundless is a React-based Story Format for [Twine](https://www.twinery.org)\r\nIts built upon the [Unified](https://unifiedjs.com) ecosystem.\r\n\r\n## Compatibility\r\n\r\nBoundless is compatible with Twine 2.3.0 and above.\r\n\r\n## Installation\r\n\r\nGo to the Boundless [releases page](https://github.com/magicink/boundless/releases)\r\nand expand the assets for the latest release. Copy the link of the `format.js`.\r\n\r\n![Copy the `format.js` link](https://gdurl.com/T8ned)\r\n\r\nOpen Twine and click on the `Twine` menu\r\n\r\n![Twine Menu](https://gdurl.com/TwkI)\r\n\r\nThen click on `Story Formats`\r\n\r\n![Story Formats](https://gdurl.com/rq6Z)\r\n\r\nThen click on `+ Add` menu item and paste in the URL of the `format.js` file.\r\n\r\n![Add Story Format](https://gdurl.com/od8V)\r\n\r\nIf done correctly, the dialog should say `Boundless \u003cversion\u003e will be added`.\r\nNext, click the `+ Add` button beneath the text field.\r\n\r\n### Setting the Story Format\r\n\r\nAfter you have installed Boundless you will need to specify the story format.\r\nOpen the story you wish to use Boundless with and click on the `Story` menu.\r\n\r\n![Story Menu](https://gdurl.com/bNMx)\r\n\r\nThen click the `Details` tab. This will open a dialog in the bottom right corner\r\nof the screen.\r\n\r\n![Click the `Details` tab](https://gdurl.com/2NsO)\r\n\r\nIn the dialog box use the `Story Format` dropdown to select the version of Boundless\r\nyou want to use.\r\n\r\n## Syntax\r\n\r\nBoundless supports [GitHub Flavored Markdown](https://github.github.com/gfm/)\r\nand a few additional features.\r\n\r\n### Passage Links\r\n\r\nPassage links are defined using the `[[` and `]]` syntax. Boundless supports the\r\nfollowing link formats:\r\n\r\n| Syntax                       | Example                      |\r\n|------------------------------|------------------------------|\r\n| `[[link target]]`            | `[[store]]`                  |\r\n| `[[link text-\u003elink target]]` | `[[go to the store-\u003estore]]` |\r\n| `[[link target\u003c-link text]]` | `[[store\u003c-go to the store]]` |\r\n| `[[link text\\|link target]]` | `[[go to the store\\|store]]` |\r\n\r\n### Application State (`window.App`)\r\n\r\nBoundless exposes a global `zustand` store called `App`. It is attached\r\nto the `window` object and can be accessed anywhere in the application.\r\n\r\n#### API\r\n\r\n##### `App.get([key])`\r\n\r\nReturns the value of the specified key in the application state. If no key is\r\nspecified, the entire application state is returned.\r\n\r\n```js\r\nApp.get('key') // =\u003e value of `key`\r\nApp.get() // =\u003e { key: value }\r\n```\r\n\r\n##### `App.set(key, [value])`\r\n\r\nSets the value of the specified key in the application state. If no value is\r\nspecified and the key is an object, the new state is merged with the existing\r\nstate.\r\n\r\n```js\r\nApp.set('key', 'value') // =\u003e { key: 'value' }\r\nApp.set({ key: 'value2', key2: 'value2' }) // =\u003e { key: 'value2', key2: 'value2' }\r\n```\r\n\r\n### Directives\r\n\r\nBoundless supports Remark's implementation of Markdown directives\r\n(via [`remark-directive`](https://www.npmjs.com/package/remark-directive)),\r\nwhich are used to add HTML elements not normally supported by Markdown.\r\n\r\nDirectives are only meant to supplement Markdown, not supersede it.\r\nWhen choosing between Markdown or Directives, it is recommended to use\r\nthe Markdown syntax whenever possible. For instance, if you want to create\r\na link, use `[link text](https://www.test.com)`\r\ninstead of `:::a{href=https://www.test.com}`.\r\n\r\nDirectives come in three flavors: container directives, leaf directives,\r\nand text directives.\r\n\r\n#### Container Directives\r\n\r\nContainer directives start with `:::tag` (where `tag` is\r\nan HTML tag) and end with `:::`, both of which must appear\r\non their own separate line. For example:\r\n\r\n```\r\n:::section\r\nThis is a section.\r\n:::\r\n```\r\n\r\nWill render as:\r\n\r\n```html\r\n\u003csection\u003e\r\n  \u003cp\u003eThis is a section.\u003c/p\u003e\r\n\u003c/section\u003e\r\n```\r\n\r\n**Note:** nesting leaf and text directives should work fine, but nesting container directives, while possible,\r\nis not predictable.\r\n\r\n#### Leaf Directives\r\n\r\nLeaf directives are similar to container directives, but they do not\r\nhave a closing tag. For example:\r\n\r\n```\r\ntest\r\n:::br\r\ntest\r\n```\r\n\r\nWill render as:\r\n\r\n```html\r\n\u003cp\u003etest\u003cbr\u003etest\u003c/p\u003e\r\n```\r\n\r\nIf a leaf directive contains text, you can place the content between two\r\nsquare brackets. For example, the text of an `option` would be written as:\r\n\r\n```\r\n:::select{name=selectExample required}\r\n::option[Red]{value=red}\r\n::option[White]{value=white}\r\n::option[Blue]{value=blue}\r\n:::\r\n```\r\n\r\n#### Text Directives\r\n\r\nText directives are inline and can be used anywhere in a paragraph.\r\nThe content affected by a text directive is indicated by the square\r\nbrackets (`[]`). For example:\r\n\r\nFor example:\r\n\r\n```\r\nThis is a paragraph with a :em[emphasized content].\r\n```\r\n\r\nWill render as:\r\n\r\n```html\r\n\u003cp\u003eThis is a paragraph with a \u003cem\u003eemphasized content\u003c/em\u003e.\u003c/p\u003e\r\n```\r\n\r\n#### Attributes\r\n\r\nYou can also add attributes to the container directive by adding\r\n`{key=\"value\"}` after the tag name.\r\n\r\n```\r\n:::section{style=\"color: red\"}\r\nHere is some content\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003csection style=\"color: red\"\u003e\r\n  \u003cp\u003eHere is some content\u003c/p\u003e\r\n\u003c/section\u003e\r\n```\r\n\r\nNote that the double quotes are optional if the value does not contain\r\nany spaces.\r\n\r\n```\r\n:::section{id=test}\r\nHere is some content\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003csection id=\"test\"\u003e\r\n  Here is some content\r\n\u003c/section\u003e\r\n```\r\n\r\nAny attributes that starts with a `.` will be added to the\r\ncontainer's class list.\r\n\r\n```\r\n:::section{.red .bold}\r\nHere is some content\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003csection class=\"red bold\"\u003e\r\n  Here is some content\r\n\u003c/section\u003e\r\n```\r\n\r\n#### `:::if[condition]`\r\n\r\nThe `:::if[condition]` container directive is used to conditionally render content. The\r\ncontent inside the directive will only be rendered if the condition is `true`.\r\n\r\n```\r\n:::if[2\u003e1]\r\nThis will be rendered\r\n:::\r\n\r\n:::if[2\u003c1]\r\nThis will not be rendered\r\n:::\r\n```\r\n\r\nUsing a single word as the condition is a shorthand way of checking the `App`\r\nstate. Therefore, the following two examples are equivalent:\r\n\r\n```\r\n:::if[key]\r\nThis will be rendered assuming the `key` exists in the application state and is truthy.\r\n:::\r\n```\r\n\r\nIs equivalent to:\r\n\r\n```\r\n:::if[App.get('key')]\r\nThis will be rendered assuming the `key` exists in the application state and is truthy.\r\n:::\r\n```\r\n\r\nYou can also negate the condition by prefixing it with `!`.\r\n\r\n```\r\n:::if[!key]\r\nThis will be rendered assuming the `key` does not exist in the application state or is\r\nfalsy.\r\n:::\r\n```\r\n\r\nIs equivalent to:\r\n\r\n```\r\n:::if[!App.get('key')]\r\nThis will be rendered assuming the `key` does not exist in the application state or is\r\nfalsy.\r\n:::\r\n```\r\n\r\n#### `:::script`\r\n\r\nThe `:::script` container directive is used to add JavaScript to the passage.\r\nThe code is executed as soon as the passage loads (see \"Application State\" below for\r\ninformation on how to access application variables).\r\n\r\n```\r\n:::script\r\nconsole.log(\"Hello, world!\")\r\n:::\r\n```\r\n\r\n#### `:::ejs`\r\n\r\nThe `:::ejs` container directive is used to render complex content using the EJS templating\r\nlanguage.\r\n\r\n```ejs\r\n:::script\r\nApp.set('name', 'Bob')\r\nApp.set('numbers', [1, 2, 3])\r\n:::\r\n\r\n:::ejs\r\n  \u003cp\u003eHello, \u003c%= App.get('name') %\u003e!\u003c/p\u003e\r\n  \u003cul\u003e\r\n    \u003c% if (App.get().numbers.length \u003e 0) { %\u003e\r\n      \u003c% App.get().numbers.forEach(function (number) { %\u003e\r\n        \u003cli\u003e\u003c%= number %\u003e\u003c/li\u003e\r\n      \u003c% }) %\u003e\r\n    \u003c% } %\u003e\r\n  \u003c/ul\u003e\r\n:::\r\n```\r\n\r\nWill render as:\r\n\r\n```html\r\n\u003cp\u003eHello, Bob!\u003c/p\u003e\r\n\u003cul\u003e\r\n  \u003cli\u003e1\u003c/li\u003e\r\n  \u003cli\u003e2\u003c/li\u003e\r\n  \u003cli\u003e3\u003c/li\u003e\r\n\u003c/ul\u003e\r\n```\r\n\r\n#### `::include[passageName]`\r\n\r\n`::include[passageName]` is a leaf directive used to include other passages in the\r\ncurrent passage. For example, let's say you have two passages: `welcome` and `store`.\r\n\r\nHere is the contents of `welcome`:\r\n\r\n```\r\nWelcome to the store!\r\n```\r\n\r\nHere is the contents of `store`:\r\n\r\n```\r\n::include[welcome]\r\n\r\nThese are the items in the store:\r\n- content\r\n- content\r\n...\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\nWelcome to the store!\r\n\r\nThese are the items in the store:\r\n- content\r\n- content\r\n...\r\n```\r\n\r\n#### `:state[key]`\r\n\r\nThe `:state[key]` leaf directive is used to access the value of the specified key\r\nin the application state. It can be used anywhere in a passage.\r\n\r\n```\r\n:::script\r\nApp.set({test: \"Hello World!\"})\r\n:::\r\n\r\nThis is the value of the `test` variable: :state[test]\r\n```\r\n\r\nIt can also be used as an attribute value:\r\n\r\n```\r\n:::script\r\nApp.set({test: \"Hello World!\"})\r\n:::\r\n\r\n:::section{data-state=\":state[test]\"}\r\nHere is some content\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003csection data-state=\"Hello World!\"\u003e\r\n  Here is some content\r\n\u003c/section\u003e\r\n```\r\n\r\nYou can also use state values as class names:\r\n\r\n```\r\n:::script\r\nApp.set({test: \"red\"})\r\n:::\r\n\r\n:::section{.bold .:state[test]}\r\nHere is some content\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003csection class=\"bold red\"\u003e\r\n  Here is some content\r\n\u003c/section\u003e\r\n```\r\n\r\nThe `:state[key]` directive also works within `:::ejs` directives. However, using\r\n`App.get([key])` is recommended instead because directives cannot be used in\r\nlogic.\r\n\r\n```\r\n:::script\r\nApp.set({test: \"Hello World!\"})\r\n:::\r\n\r\n:::ejs\r\n\u003cdiv\u003e\r\n  This is some \u003cstrong\u003eHTML\u003c/strong\u003e content.\r\n  \u003cp\u003eHere is a state variable: :state[test]\u003c/p\u003e\r\n\u003c/div\u003e\r\n:::\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003cdiv\u003e\r\n  This is some \u003cstrong\u003eHTML\u003c/strong\u003e content.\r\n  \u003cp\u003eHere is a state variable: Hello World!\u003c/p\u003e\r\n\u003c/div\u003e\r\n```\r\n\r\n#### `:eval[statement]`\r\n\r\n`:eval[statement]` is a text directive used to evaluate JavaScript statements.\r\n\r\n```\r\nThe current time is :eval[new Date().toLocaleTimeString()]\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003cp\u003eThe current time is 12:00:00 PM\u003c/p\u003e\r\n```\r\n\r\nAnother use is to display computations based on the application state:\r\n\r\n```\r\n:::script\r\nApp.set({hitPoints: 13, damage: 2})\r\n:::\r\n\r\nYou have :eval[App.get('hitPoints') - App.get('damage')] hit points left\r\n```\r\n\r\nWill produce:\r\n\r\n```html\r\n\u003cp\u003eYou have 11 hit points left\u003c/p\u003e\r\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagicink%2Fboundless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmagicink%2Fboundless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagicink%2Fboundless/lists"}