From 77e0fd5cbfd73d8bd02c8170b1d72fbc032554fe Mon Sep 17 00:00:00 2001 From: ChapelR Date: Sun, 29 Mar 2020 00:47:59 -0400 Subject: [PATCH 1/5] macro passages add third party macro 'macro passages' --- build.js | 10 +- docs/download/fetch.js | 10 +- docs/examples/macro-passages.md | 12 ++ docs/examples/main.md | 1 + examples/third-party/macro-passages.js | 123 ++++++++++++++++++ .../minified/macro-passages.min.js | 3 + 6 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 docs/examples/macro-passages.md create mode 100644 examples/third-party/macro-passages.js create mode 100644 examples/third-party/minified/macro-passages.min.js diff --git a/build.js b/build.js index fb94a30..0867102 100644 --- a/build.js +++ b/build.js @@ -13,6 +13,7 @@ const scriptName = 'Harlowe Macro Framework', src = 'src/', dist = 'dist/macro.min.js', examples = 'examples/', + thirdp = 'examples/third-party/', binary = 'harlowe-macro-api.zip'; const files = [ @@ -53,11 +54,14 @@ function build (list, path, output) { } -function minifyExamples (ex) { +function minifyExamples (ex, tp) { const jsFiles = jetpack.find(ex, { matching : '*.js', recursive : false - }); + }).concat(jetpack.find(tp, { + matching : '*.js', + recursive : false + })); jsFiles.forEach( function (file) { const source = jetpack.read(file); @@ -129,6 +133,6 @@ function zipUp (path, output) { } build(files, src, dist); -minifyExamples(examples); +minifyExamples(examples, thirdp); minifyCSS(examples); zipUp(dist, binary); \ No newline at end of file diff --git a/docs/download/fetch.js b/docs/download/fetch.js index 2d0b8d2..cc6beda 100644 --- a/docs/download/fetch.js +++ b/docs/download/fetch.js @@ -4,6 +4,7 @@ var mainFile = 'https://cdn.jsdelivr.net/gh/chapelr/harlowe-macro-api@latest/dist/macro.min.js'; var baseURL = 'https://cdn.jsdelivr.net/gh/chapelr/harlowe-macro-api@latest/examples/minified/'; + var tpURL = 'https://cdn.jsdelivr.net/gh/chapelr/harlowe-macro-api@latest/examples/third-party/minified/'; var extension = '.min.js'; var extensionCSS = '.min.css'; @@ -16,7 +17,8 @@ 'Hotkeys': 'hotkeys', 'Playtime': 'playtime', 'Speech Box': 'speechbox+css', - 'Textbox': 'textbox' + 'Textbox': 'textbox', + 'Macro Passages (by rachek64)': '~macro-passages' }; var macros = Object.keys(fileNameMap); @@ -35,7 +37,11 @@ fn = fn.split('+')[0]; files.push(baseURL + fn + extensionCSS); } - files.push(baseURL + fn + extension); + if (fn[0] === '~') { + files.push(tpURL + fn.substr(1) + extension); + } else { + files.push(baseURL + fn + extension); + } }); return [ mainFile ].concat(files); } diff --git a/docs/examples/macro-passages.md b/docs/examples/macro-passages.md new file mode 100644 index 0000000..a57ab1e --- /dev/null +++ b/docs/examples/macro-passages.md @@ -0,0 +1,12 @@ +## Macro Passages + +By [rachek64](https://github.com/rachek64). + +Description. + +> **Get the Code** +> +> - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/minified/macro-passages.min.js) +> - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/macro-passages.js) + +Coming soon. \ No newline at end of file diff --git a/docs/examples/main.md b/docs/examples/main.md index b37dd26..e42a6ca 100644 --- a/docs/examples/main.md +++ b/docs/examples/main.md @@ -15,6 +15,7 @@ The following are some example macros created by me using this framework. They'r - [Dialog](/examples/dialog.md) - [Clamp](/examples/clamp.md) - [Achievements](/examples/achievements.md) +- [Macro Passages](/examples/macro-passages.md) (by [rachek64](https://github.com/rachek64)) ## Installation diff --git a/examples/third-party/macro-passages.js b/examples/third-party/macro-passages.js new file mode 100644 index 0000000..3f0ae8b --- /dev/null +++ b/examples/third-party/macro-passages.js @@ -0,0 +1,123 @@ +(function () { + /** + ## Macro Passages + For use with https://github.com/ChapelR/harlowe-macro-api/ + BY: rachek64 (https://gist.github.com/rachek64), with alterations by Chapel. + + Adds the (macro:) macro that lets you run entire passages inline. + This gives you full access to twinescript without the need for javascript. + + Make a passage and tag it with `macro`. Add whatever commands you want to + calculate a value, set variables, or perform some sort of routine task. + + The only limitations are the entire passage should run at once (i.e. no (prompt:), + (live:), or similar "delayed" commands) and they cannot display any output. This is + suitable for code-only macros that perform complex calculations or automate repetative + tasks. If you need to print results, just return the results from the macro and print + it outside. + + Use (macro: "macro passage name", [...arguments]) to run the macro passage. + The passage name must exactly match, unless you make it into an auto-macro (see below). + + Arguments passed into the macro call can be accessed by the macro passage in _args + (yes, it's a temporary variable) as an array. No temporary variables from the caller + passage are available inside a macro passage, because there's no way to guarantee consistency. + Global variables are available as usual. + + If the macro calculates a value, it can be returned by assigning a value to _result. + This value will be passed through as the result of the (macro:) call, so you could + do something like `(set: _totalCost to (macro:"calculate total cost", _price, _quantity, _taxRate))` + and _totalCost will be set to whatever _result was set to in the `"calculate total cost"` passage. + + ### Auto Macros + For added utility, tagging a passage with both `macro` and `auto-macro` will make the + passage into a first-class macro on startup by Harlowe-ifying the passage name. The + example above would become `(calculate-total-cost: _price, _quantity, _taxRate)`. + Normal Harlowe name-matching rules apply, and creating different passages with the + same name or the name of built-in macros will throw errors about redefining things. + + ## Notes + * No text is displayed while running the macro passage, nor can it have any + direct effect on the caller passage. The contents are rendered into a + dummy screen that is never shown on the page. + ** This means you can nicely comment your macro with plain text. No need to use `{}` + or worry about line breaks or spacing. + ** This also means no user input is possible. If you render a link to click, + it will never be able to be clicked. The macro will complete assuming the link + wasn't clicked. + * User input in general is not possible in macro passages, nor can you run + asynchronous commands like (alert:) that pause the passage until you interact + with them. No errors will happen, but the value in `_result` may not be + picked up properly if it was changed after the delaying command was used. + * Saving and loading from inside a macro is, in general, a terrible idea. Just don't do it. + * Argument type checking is not possible at this time. If a macro takes arguments, + thoroughly inspect _args before attempting to use them. + * Unfortunately, errors in macro passages are handled a bit oddly. Error details + are provided as best as possible, but so far as Harlowe is concerned the error + occured in the caller passage. Check the dropdown in the error bubble for the + actual line that triggered the error. + */ + + "use strict"; + + function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } + function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + + var Passages = require('passages'); + var TwineError = require('internaltypes/twineerror'); + var Section = require('section'); + var VarScope = require('internaltypes/varscope'); + + function macrocall(name) { + if (!Passages.hasValid(name)) return TwineError.create('macro', '(macro:) The passage "' + name + '" does not exist.'); + var passage = Passages.get(name); + if (!passage.get('tags').includes('macro')) return TwineError.create('macro', '(macro:) The passage "' + name + '" is not marked with the "macro" tag.', "Custom macros are opt-in via the macro tag to reduce the likelihood of running a non-macro formatted passage on accident."); + var source = passage.get('source'); + var dummy = $(document.createElement('p')); + dummy.attr({ + tags: '' + }); + var dummySection = Section.create(dummy); + var tempVars = Object.create(VarScope); + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + tempVars.args = args; + dummySection.renderInto(source, dummy, undefined, tempVars); // If there was an error, it'll be rendered into the dummy section. + // Rethrow it, extracting the relevant details. + + var error = dummy.find('tw-error').first(); + + if (error.length != 0) { + var context = error.attr('title'); + var message = error.textNodes()[0].textContent; + var explanation = error.textNodes()[2].textContent; + return TwineError.create('macro', '(macro:"' + name + '") ' + message, context + ': ' + explanation); + } + + return tempVars.result; + } + + Harlowe.macro('macro', macrocall); + + var _iterator = _createForOfIteratorHelper(Passages.getTagged('macro').filter(function (x) { + return x.get('tags').includes('auto-macro'); + })), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var automacro = _step.value; + var name = automacro.get('name'); + name = name.toLowerCase().replace(/[^a-z]/g, ''); + Harlowe.macro(name, macrocall.bind(this, automacro.get('name'))); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } +})(); \ No newline at end of file diff --git a/examples/third-party/minified/macro-passages.min.js b/examples/third-party/minified/macro-passages.min.js new file mode 100644 index 0000000..a24c286 --- /dev/null +++ b/examples/third-party/minified/macro-passages.min.js @@ -0,0 +1,3 @@ +// macro-passages.min.js, for Harlow Macro API, by Chapel +;!function(){"use strict";function c(e,r){(null==r||r>e.length)&&(r=e.length);for(var t=0,n=new Array(r);t=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:t}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var n,a,o=!0,i=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return o=e.done,e},e:function(e){i=!0,a=e},f:function(){try{o||null==n.return||n.return()}finally{if(i)throw a}}}}(g.getTagged("macro").filter(function(e){return e.get("tags").includes("auto-macro")}));try{for(t.s();!(r=t.n()).done;){var n=r.value,a=n.get("name");a=a.toLowerCase().replace(/[^a-z]/g,""),Harlowe.macro(a,e.bind(this,n.get("name")))}}catch(e){t.e(e)}finally{t.f()}}(); +// end macro-passages.min.js \ No newline at end of file From 07a1c136c0c42ca857dc4cd5dbf99731202105b3 Mon Sep 17 00:00:00 2001 From: ChapelR Date: Mon, 20 Apr 2020 05:08:36 -0400 Subject: [PATCH 2/5] notify macro ported notify macro over from CMFSC2 --- examples/minified/notify.min.css | 3 + examples/minified/notify.min.js | 3 + examples/notify.css | 21 ++++++ examples/notify.js | 111 +++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 examples/minified/notify.min.css create mode 100644 examples/minified/notify.min.js create mode 100644 examples/notify.css create mode 100644 examples/notify.js diff --git a/examples/minified/notify.min.css b/examples/minified/notify.min.css new file mode 100644 index 0000000..3e2bf50 --- /dev/null +++ b/examples/minified/notify.min.css @@ -0,0 +1,3 @@ +/* notify.min.css, for Harlow Macro API, by Chapel */ +#notify{position:fixed;display:block;width:16em;right:-20em;top:2em;padding:.5em;background-color:#fff;color:#000;font-family:Georgia,serif;font-size:16px;-webkit-transition:right .3s;transition:right .3s;z-index:10000}#notify.open{right:0} +/* end notify.min.css*/ \ No newline at end of file diff --git a/examples/minified/notify.min.js b/examples/minified/notify.min.js new file mode 100644 index 0000000..1cda8de --- /dev/null +++ b/examples/minified/notify.min.js @@ -0,0 +1,3 @@ +// notify.min.js, for Harlow Macro API, by Chapel +;!function(){var a=/^([+-]?(?:\d*\.)?\d+)([Mm]?[Ss])$/,i=$(document.createElement("div")).attr("id","notify").appendTo(document.body);function r(e,n,t){return"string"==typeof e?e=function(e){var n=a.exec(String(e));if(null===n)throw new SyntaxError("invalid time value syntax: "+e);var t=Number(n[1]);if(1===n[2].length&&(t*=1e3),Number.isNaN(t)||!Number.isFinite(t))throw new RangeError("invalid time value: "+e);return t}(e):"number"!=typeof e&&(e=!1),$(document).trigger({type:":notify",message:t,delay:e,class:n||""}),i}$(document).on(":notify",function(e){e.class?"string"==typeof e.class?e.class="open macro-notify "+e.class:Array.isArray(e.class)?e.class="open macro-notify "+e.class.join(" "):e.class="open macro-notify":e.class="open macro-notify",e.delay?("number"!=typeof e.delay&&(e.delay=Number(e.delay)),Number.isNaN(e.delay)&&(e.delay=2e3)):e.delay=2e3,i.empty().append(e.message).addClass(e.class),setTimeout(function(){i.removeClass()},e.delay)}),Harlowe.macro("notify",function(){var e=this.typeCheck(["string|number|undefined","string|undefined","string|undefined"]);if(e)throw e},function(e,n,t){this.descriptor.target.addClass("in-notification"),n&&t&&(n=Harlowe.helpers.arrayify(arguments,1).join(" ")),r(e,n,this.descriptor.target)}),setup.notify=r}(); +// end notify.min.js \ No newline at end of file diff --git a/examples/notify.css b/examples/notify.css new file mode 100644 index 0000000..7e5d46a --- /dev/null +++ b/examples/notify.css @@ -0,0 +1,21 @@ +/* + notify.min.css + version 1.0.0 + */ +#notify { + position : fixed; + display : block; + width : 16em; + right : -20em; top : 2em; + padding : 0.5em; + background-color : #fff; + color : #000; + font-family: Georgia, serif; + font-size: 16px; + -webkit-transition : right 0.3s; + -moz-transition : right 0.3s; + -o-transition : right 0.3s; + transition : right 0.3s; + z-index: 10000; +} +#notify.open { right : 0; } \ No newline at end of file diff --git a/examples/notify.js b/examples/notify.js new file mode 100644 index 0000000..6b83781 --- /dev/null +++ b/examples/notify.js @@ -0,0 +1,111 @@ +(function () { + // version 1.1.1 + + var DEFAULT_TIME = 2000; // default notification time (in MS) + + var isCssTime = /^([+-]?(?:\d*\.)?\d+)([Mm]?[Ss])$/; + + var $notify = $(document.createElement('div')) + .attr('id', 'notify') + .appendTo(document.body); + + $(document).on(':notify', function (ev) { + // classes + if (ev.class) { + if (typeof ev.class === 'string') { + ev.class = 'open macro-notify ' + ev.class; + } else if (Array.isArray(ev.class)) { + ev.class = 'open macro-notify ' + ev.class.join(' '); + } else { + ev.class = 'open macro-notify'; + } + } else { + ev.class = 'open macro-notify'; + } + + // delay + if (ev.delay) { + if (typeof ev.delay !== 'number') { + ev.delay = Number(ev.delay); + } + if (Number.isNaN(ev.delay)) { + ev.delay = DEFAULT_TIME; + } + } else { + ev.delay = DEFAULT_TIME; + } + + $notify + .empty() + .append(ev.message) + .addClass(ev.class); + + setTimeout(function () { + $notify.removeClass(); + }, ev.delay); + }); + + function convertCssTime (cssTime) { + // taken from SugarCube: https://github.com/tmedwards/sugarcube-2/blob/master/src/lib/util.js#L339 + var match = isCssTime.exec(String(cssTime)); + + if (match === null) { + throw new SyntaxError("invalid time value syntax: " + cssTime); + } + + var msec = Number(match[1]); + + if (match[2].length === 1) { + msec *= 1000; + } + + if (Number.isNaN(msec) || !Number.isFinite(msec)) { + throw new RangeError("invalid time value: " + cssTime); + } + + return msec; + } + + function notify (time, classes, contentEl) { + + if (typeof time === 'string') { + time = convertCssTime(time); + } else if (typeof time !== 'number') { + time = false; + } + + $(document).trigger({ + type : ':notify', + message : contentEl, + delay : time, + class : classes || '' + }); + + return $notify; + } + + // (notify:) macro def + Harlowe.macro('notify', function () { + + var err = this.typeCheck([ + 'string|number|undefined', + 'string|undefined', + 'string|undefined' + ]); + if (err) throw err; + + }, function (time, classes, test) { + // `tw-hook.in-notification` -> styles for dialog content + this.descriptor.target.addClass('in-notification'); + + if (classes && test) { + classes = Harlowe.helpers.arrayify(arguments, 1).join(' '); + } + + notify(time, classes, this.descriptor.target); + + }); + + setup.notify = notify; + +}()); \ No newline at end of file From 84600cee3235f39df2838c8e1d63badd82ce1b38 Mon Sep 17 00:00:00 2001 From: ChapelR Date: Mon, 20 Apr 2020 05:14:56 -0400 Subject: [PATCH 3/5] docs start --- docs/changelog.md | 7 +++++++ docs/examples/main.md | 1 + docs/examples/notify.md | 13 +++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 docs/examples/notify.md diff --git a/docs/changelog.md b/docs/changelog.md index da8d9de..d992a4e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,12 @@ ## Release Versions +### v1.1.0 + +- **[Examples]** Added more examples: + - `(notify:)` A port of the CMFSC2 `<>` macro. + - `(macro:)` A widget-style macro for Harlowe by rachek64. +- **[Docs]** Documented example macros. + ### v1.0.0 - **[Update]** Changed `Harlowe.version` to `Harlowe.framework` to alleviate any confusion with `Harlowe.engine`. diff --git a/docs/examples/main.md b/docs/examples/main.md index e42a6ca..6034148 100644 --- a/docs/examples/main.md +++ b/docs/examples/main.md @@ -15,6 +15,7 @@ The following are some example macros created by me using this framework. They'r - [Dialog](/examples/dialog.md) - [Clamp](/examples/clamp.md) - [Achievements](/examples/achievements.md) +- [Notify](/examples/notify.md) - [Macro Passages](/examples/macro-passages.md) (by [rachek64](https://github.com/rachek64)) ## Installation diff --git a/docs/examples/notify.md b/docs/examples/notify.md new file mode 100644 index 0000000..a49bad3 --- /dev/null +++ b/docs/examples/notify.md @@ -0,0 +1,13 @@ +## Notify + +Description. + +> - **Get the Code** +> +> - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/minified/notify.min.js) +> - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/notify.js) +> - **Required CSS** +> - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/minified/notify.min.css) +> - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/notify.css) + +Coming soon. \ No newline at end of file From 1a3b37eb94de55ba91b3002a0ea68213489077eb Mon Sep 17 00:00:00 2001 From: ChapelR Date: Mon, 20 Apr 2020 11:38:22 -0400 Subject: [PATCH 4/5] call and reply + notify fix --- examples/call.js | 82 +++++++++++++++++++++++++++++++++ examples/minified/call.min.js | 3 ++ examples/minified/notify.min.js | 2 +- examples/notify.js | 4 +- 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 examples/call.js create mode 100644 examples/minified/call.min.js diff --git a/examples/call.js b/examples/call.js new file mode 100644 index 0000000..dfeb405 --- /dev/null +++ b/examples/call.js @@ -0,0 +1,82 @@ +(function () { + // call and reply macro set, v1.0.0 + 'use strict'; + + var eventNameSpace = '.userland'; + var eventPrefix = ':tw-call-macro-'; + + function isDef (is) { + return is != undefined; + } + + function evType (type) { + return eventPrefix + String(type) + eventNameSpace; + } + + function setCall (type, args, test) { + if (isDef(args) && isDef(test)) { + args = Harlowe.helpers.arrayify(arguments, 2); + } else if (isDef(args) && !(args instanceof Array)) { + args = [args]; + } + + $(document).trigger({ + type : evType(type), + args : isDef(args) ? args : undefined + }); + } + + function removeCall (type) { + $(document).off(evType(type)); + } + + function reply (type, cb, once, context) { + $(document)[ once ? 'one' : 'on' ](evType(type), function (ev) { + var shallow = Harlowe.variable('args'), deep; + if (shallow instanceof Map) { + deep = new Map(shallow.entries()); + } else if (shallow instanceof Set) { + deep = new Set(shallow.entries()); + } else if (typeof shallow === 'object') { + deep = JSON.parse(JSON.stringify(shallow)); + } else { + deep = shallow; + } + + Harlowe.variable('args', ev.args); + // callback + cb.call(context); + Harlowe.variable('args', deep); + }); + } + + Harlowe.macro('call', function (type, args) { + var err = this.typeCheck([ 'string' ]); + if (err) throw err; + + setCall.apply(null, arguments); + }); + + Harlowe.macro('reply', function (type, once) { + + var err = this.typeCheck([ 'string', 'boolean|undefined' ]); + if (err) throw err; + + }, function (type, once) { + + this.descriptor.enabled = false; + + reply(type, function () { + this.enabled = true; + }, !!once, this.descriptor); + + }); + + Harlowe.macro('silence', function (type) { + var err = this.typeCheck([ 'string' ]); + if (err) throw err; + + removeCall(type); + }); + +}()); \ No newline at end of file diff --git a/examples/minified/call.min.js b/examples/minified/call.min.js new file mode 100644 index 0000000..d49f196 --- /dev/null +++ b/examples/minified/call.min.js @@ -0,0 +1,3 @@ +// call.min.js, for Harlow Macro API, by Chapel +;!function(){"use strict";var r=".userland",n=":tw-call-macro-";function t(e){return null!=e}function o(e){return n+String(e)+r}Harlowe.macro("call",function(e,r){var n=this.typeCheck(["string"]);if(n)throw n;(function(e,r,n){t(r)&&t(n)?r=Harlowe.helpers.arrayify(arguments,2):!t(r)||r instanceof Array||(r=[r]),$(document).trigger({type:o(e),args:t(r)?r:void 0})}).apply(null,arguments)}),Harlowe.macro("reply",function(e,r){var n=this.typeCheck(["string","boolean|undefined"]);if(n)throw n},function(e,r){var n,t,a,i;this.descriptor.enabled=!1,n=e,t=function(){this.enabled=!0},a=!!r,i=this.descriptor,$(document)[a?"one":"on"](o(n),function(e){var r,n=Harlowe.variable("args");r=n instanceof Map?new Map(n.entries()):n instanceof Set?new Set(n.entries()):"object"==typeof n?JSON.parse(JSON.stringify(n)):n,Harlowe.variable("args",e.args),t.call(i),Harlowe.variable("args",r)})}),Harlowe.macro("silence",function(e){var r,n=this.typeCheck(["string"]);if(n)throw n;r=e,$(document).off(o(r))})}(); +// end call.min.js \ No newline at end of file diff --git a/examples/minified/notify.min.js b/examples/minified/notify.min.js index 1cda8de..36cfc67 100644 --- a/examples/minified/notify.min.js +++ b/examples/minified/notify.min.js @@ -1,3 +1,3 @@ // notify.min.js, for Harlow Macro API, by Chapel -;!function(){var a=/^([+-]?(?:\d*\.)?\d+)([Mm]?[Ss])$/,i=$(document.createElement("div")).attr("id","notify").appendTo(document.body);function r(e,n,t){return"string"==typeof e?e=function(e){var n=a.exec(String(e));if(null===n)throw new SyntaxError("invalid time value syntax: "+e);var t=Number(n[1]);if(1===n[2].length&&(t*=1e3),Number.isNaN(t)||!Number.isFinite(t))throw new RangeError("invalid time value: "+e);return t}(e):"number"!=typeof e&&(e=!1),$(document).trigger({type:":notify",message:t,delay:e,class:n||""}),i}$(document).on(":notify",function(e){e.class?"string"==typeof e.class?e.class="open macro-notify "+e.class:Array.isArray(e.class)?e.class="open macro-notify "+e.class.join(" "):e.class="open macro-notify":e.class="open macro-notify",e.delay?("number"!=typeof e.delay&&(e.delay=Number(e.delay)),Number.isNaN(e.delay)&&(e.delay=2e3)):e.delay=2e3,i.empty().append(e.message).addClass(e.class),setTimeout(function(){i.removeClass()},e.delay)}),Harlowe.macro("notify",function(){var e=this.typeCheck(["string|number|undefined","string|undefined","string|undefined"]);if(e)throw e},function(e,n,t){this.descriptor.target.addClass("in-notification"),n&&t&&(n=Harlowe.helpers.arrayify(arguments,1).join(" ")),r(e,n,this.descriptor.target)}),setup.notify=r}(); +;!function(){var i=/^([+-]?(?:\d*\.)?\d+)([Mm]?[Ss])$/,a=$(document.createElement("div")).attr("id","notify").appendTo(document.body);function r(e,n,t){return"string"==typeof e?e=function(e){var n=i.exec(String(e));if(null===n)throw new SyntaxError("invalid time value syntax: "+e);var t=Number(n[1]);if(1===n[2].length&&(t*=1e3),Number.isNaN(t)||!Number.isFinite(t))throw new RangeError("invalid time value: "+e);return t}(e):"number"!=typeof e&&(e=!1),$(document).trigger({type:":notify",message:t,delay:e,class:n||""}),a}$(document).on(":notify",function(e){e.class?"string"==typeof e.class?e.class="open macro-notify "+e.class:Array.isArray(e.class)?e.class="open macro-notify "+e.class.join(" "):e.class="open macro-notify":e.class="open macro-notify",e.delay?("number"!=typeof e.delay&&(e.delay=Number(e.delay)),Number.isNaN(e.delay)&&(e.delay=2e3)):e.delay=2e3,a.empty().append(e.message).addClass(e.class),setTimeout(function(){a.removeClass()},e.delay)}),Harlowe.macro("notify",function(){var e=this.typeCheck(["string|number|undefined","string|undefined","string|undefined"]);if(e)throw e},function(e,n,t){this.descriptor.target.addClass("in-notification"),n&&t&&(n=Harlowe.helpers.arrayify(arguments,1).join(" ")),r(e,n,this.descriptor.target)}),window.setup=window.setup||{},window.setup.notify=r}(); // end notify.min.js \ No newline at end of file diff --git a/examples/notify.js b/examples/notify.js index 6b83781..763bd4f 100644 --- a/examples/notify.js +++ b/examples/notify.js @@ -106,6 +106,8 @@ }); - setup.notify = notify; + window.setup = window.setup || {}; + + window.setup.notify = notify; }()); \ No newline at end of file From 16ecf93745ecf72ddbacce20a68cacc7616e96a0 Mon Sep 17 00:00:00 2001 From: ChapelR Date: Tue, 21 Apr 2020 06:55:44 -0400 Subject: [PATCH 5/5] v1.0.1 --- dist/macro.min.js | 4 +- docs/changelog.md | 4 +- docs/download/fetch.js | 1 + docs/examples/macro-passages.md | 67 ++++++++++++++++++++++++++- docs/examples/notify.md | 38 ++++++++++++++- examples/call.js | 82 --------------------------------- package.json | 2 +- 7 files changed, 107 insertions(+), 91 deletions(-) delete mode 100644 examples/call.js diff --git a/dist/macro.min.js b/dist/macro.min.js index 5d94d4b..eaf0db9 100644 --- a/dist/macro.min.js +++ b/dist/macro.min.js @@ -1,3 +1,3 @@ -// Harlowe Macro Framework, by Chapel; version 1.0.0 -;!function(){"use strict";var r={major:1,minor:0,patch:0},e=[r.major,r.minor,r.patch].join(".");r.semantic=e,r=Object.freeze(r);var t=$("tw-storydata"),a=Object.freeze({name:t.attr("name"),ifid:t.attr("ifid")}),n=t.attr("format-version"),o=n.split("."),i=Object.freeze({major:o[0],minor:o[1],patch:o[2],semantic:n});window.Harlowe=window.Harlowe||{},window.Harlowe=Object.assign(window.Harlowe,{framework:r,API_ACCESS:Object.freeze({MACROS:require("macros"),STATE:require("state"),CHANGER:require("datatypes/changercommand"),ENGINE:require("engine")}),engine:i,story:a})}(),function(){"use strict";function e(r){return"number"==typeof r||"boolean"==typeof r||"string"==typeof r||null===r||Array.isArray(r)&&r.every(e)||r instanceof Set&&Array.from(r).every(e)||r instanceof Map&&Array.from(r.values()).every(e)||_changer.isPrototypeOf(r)}window.Harlowe=Object.assign(window.Harlowe,{helpers:{isSerialisable:e,isSerializable:e,arrayify:function(r,e){if(r){var t=[].slice.call(r);return void 0!==e&&(t=t.slice(e)),t}},getPassageData:function(r){var e=$('tw-passagedata[name="'+r+'"]');if(e[0])return e}}})}(),function(){"use strict";var n=window.localStorage||!1,o=Harlowe.story.ifid+"-tw-storage";function r(){try{if(!n)throw new Error("storage is inaccessible");n.setItem(o,JSON.stringify({ifid:Harlowe.story.ifid}))}catch(r){console.warn(r)}}function i(r){try{var e;if(n)return e=JSON.parse(n.getItem(o)),r&&"string"==typeof r?e[r]:e;throw new Error("storage is inaccessible")}catch(r){console.warn(r)}}void 0===i()&&r(),Harlowe.storage={clear:r,save:function(r,e){try{if(!r||"string"!=typeof r)throw new TypeError("cannot store values without a valid storage key");if(void 0===e)throw new TypeError("cannot store undefined values");var t={};if(t[r]=e,!n)throw new Error("storage is inaccessible");var a=i();Object.assign(a,t),n.setItem(o,JSON.stringify(a))}catch(r){console.warn(r)}},load:i,remove:function(r){try{if(!r||"string"!=typeof r)throw new TypeError("cannot store values without a valid storage key");if(!n)throw new Error("storage is inaccessible");var e=i();e.hasOwnProperty(r)&&(delete e[r],n.setItem(o,JSON.stringify(e)))}catch(r){console.warn(r)}}}}(),function(){"use strict";function a(r,e,t){if(!(this instanceof a))return new a(r,e,t);this.name=r||"unknown",this.args=e||[],this.data=t||{},this.type=t&&t.type||"basic",this.fn=t&&t.fn||"handler","changer"===this.type&&("handler"===this.fn?this.instance=t&&t.instance||null:this.descriptor=t&&t.descriptor||null)}a.create=function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");return e&&e instanceof Array||(e=[]),t&&"object"==typeof t||(t={type:"basic",fn:"handler"}),new a(r,e,t)},Object.assign(a.prototype,{clone:function(){return a.create(this.name,this.args,this.data)},syntax:function(){return"("+this.name+":)"},error:function(r,e){var t="Error in the "+this.syntax()+" macro: "+r;return e&&alert(t),console.warn("HARLOWE CUSTOM MACRO ERROR -> ",t),new Error(r)},typeCheck:function(r){r&&r instanceof Array||(r=Harlowe.helpers.arrayify(arguments));var n=this,o=[];if(r.forEach(function(r,e){var t=e+1,a=[];"string"==typeof r&&("any"===(a=r.includes("|")?r.split("|").map(function(r){return r.trim().toLowerCase()}):[r.trim().toLowerCase()])[0]||a.some(function(r){return typeof n.args[e]===r})||o.push("argument "+t+" should be a(n) "+a.join(" or ")))}),o.length)return n.error(o.join("; "))}}),window.Harlowe=Object.assign(window.Harlowe,{MacroContext:a})}(),function(){"use strict";var c=Harlowe.API_ACCESS.MACROS,f=Harlowe.API_ACCESS.CHANGER;window.Harlowe=Object.assign(window.Harlowe||{},{macro:function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");if(!e||"function"!=typeof e)throw new TypeError("Invalid macro handler.");var a,n,o,i,s;t&&"function"==typeof t?(o=r,i=e,s=t,c.addChanger(o,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=f.create(o,r),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"handler",instance:e});return i.apply(t,r),e},function(){var r=Harlowe.helpers.arrayify(arguments),e=r.shift(),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"changer",descriptor:e});s.apply(t,r)},c.TypeSignature.zeroOrMore(c.TypeSignature.Any))):(a=r,n=e,c.add(a,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=Harlowe.MacroContext.create(a,r,{type:"basic",fn:"handler"}),t=n.apply(e,r);return null==t?"":t},c.TypeSignature.zeroOrMore(c.TypeSignature.Any)))}})}(),function(){"use strict";var t=Harlowe.API_ACCESS.STATE,e=Harlowe.API_ACCESS.ENGINE;function a(){return t.passage}window.Harlowe=Object.assign(window.Harlowe||{},{passage:a,tags:function(r){r=r||a();try{var e=Harlowe.helpers.getPassageData(r).attr("tags");return e?e.split(" "):[]}catch(r){return console.warn(r.message),[]}},goto:function(r){return e.goToPassage(r)},variable:function(r,e){if("$"!==r[0])throw new Error('cannot access variable "'+r+'"');if(r=r.substr(1),void 0!==e){if(!Harlowe.helpers.isSerialisable(e))throw new Error('The value passed to variable "'+r+'" cannot be serialized.');t.variables[r]=e}return t.variables[r]},visited:function(r){return t.passageNameVisited(r||a())},hasVisited:function(r){return 0 ",t),new Error(r)},typeCheck:function(r){r&&r instanceof Array||(r=Harlowe.helpers.arrayify(arguments));var n=this,o=[];if(r.forEach(function(r,e){var t=e+1,a=[];"string"==typeof r&&("any"===(a=r.includes("|")?r.split("|").map(function(r){return r.trim().toLowerCase()}):[r.trim().toLowerCase()])[0]||a.some(function(r){return typeof n.args[e]===r})||o.push("argument "+t+" should be a(n) "+a.join(" or ")))}),o.length)return n.error(o.join("; "))}}),window.Harlowe=Object.assign(window.Harlowe,{MacroContext:a})}(),function(){"use strict";var c=Harlowe.API_ACCESS.MACROS,f=Harlowe.API_ACCESS.CHANGER;window.Harlowe=Object.assign(window.Harlowe||{},{macro:function(r,e,t){if(!r||"string"!=typeof r||!r.trim())throw new TypeError("Invalid macro name.");if(!e||"function"!=typeof e)throw new TypeError("Invalid macro handler.");var a,n,o,i,s;t&&"function"==typeof t?(o=r,i=e,s=t,c.addChanger(o,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=f.create(o,r),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"handler",instance:e});return i.apply(t,r),e},function(){var r=Harlowe.helpers.arrayify(arguments),e=r.shift(),t=Harlowe.MacroContext.create(o,r,{type:"changer",fn:"changer",descriptor:e});s.apply(t,r)},c.TypeSignature.zeroOrMore(c.TypeSignature.Any))):(a=r,n=e,c.add(a,function(){var r=Harlowe.helpers.arrayify(arguments,1),e=Harlowe.MacroContext.create(a,r,{type:"basic",fn:"handler"}),t=n.apply(e,r);return null==t?"":t},c.TypeSignature.zeroOrMore(c.TypeSignature.Any)))}})}(),function(){"use strict";var t=Harlowe.API_ACCESS.STATE,e=Harlowe.API_ACCESS.ENGINE;function a(){return t.passage}window.Harlowe=Object.assign(window.Harlowe||{},{passage:a,tags:function(r){r=r||a();try{var e=Harlowe.helpers.getPassageData(r).attr("tags");return e?e.split(" "):[]}catch(r){return console.warn(r.message),[]}},goto:function(r){return e.goToPassage(r)},variable:function(r,e){if("$"!==r[0])throw new Error('cannot access variable "'+r+'"');if(r=r.substr(1),void 0!==e){if(!Harlowe.helpers.isSerialisable(e))throw new Error('The value passed to variable "'+r+'" cannot be serialized.');t.variables[r]=e}return t.variables[r]},visited:function(r){return t.passageNameVisited(r||a())},hasVisited:function(r){return 0>` macro. - - `(macro:)` A widget-style macro for Harlowe by rachek64. + - `(macro:)` A widget-style macro passage system for Harlowe by rachek64. - **[Docs]** Documented example macros. ### v1.0.0 diff --git a/docs/download/fetch.js b/docs/download/fetch.js index cc6beda..9086350 100644 --- a/docs/download/fetch.js +++ b/docs/download/fetch.js @@ -13,6 +13,7 @@ 'Articles' : 'articles', 'Clamp': 'clamp', 'Dialog' : 'dialog+css', + 'Notify' : 'notify+css', 'Dice': 'dice', 'Hotkeys': 'hotkeys', 'Playtime': 'playtime', diff --git a/docs/examples/macro-passages.md b/docs/examples/macro-passages.md index a57ab1e..0b824af 100644 --- a/docs/examples/macro-passages.md +++ b/docs/examples/macro-passages.md @@ -2,11 +2,74 @@ By [rachek64](https://github.com/rachek64). -Description. +Allows you to run passages inline, similar to `(display:)`, but with no output and with the option of passing arguments to the passage, similar to SugarCube's widgets. > **Get the Code** > > - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/minified/macro-passages.min.js) > - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/third-party/macro-passages.js) -Coming soon. \ No newline at end of file +### Macro: `(macro:)` + +Runs a macro passage, defined by adding the `macro` tag to a passage. If you add both the `macro` tag and the `auto-macro` tag, a first-class, named macro will be created. + +#### Syntax + +``` +(macro: passage [, argsList]) +``` + +#### Arguments + +- `passage` ( *`string`* ) The name of a passage with the tag `macro`. The passage's code will be run with the given arguments. +- `args` ( *`any`* ) ( optional ) Arguments to be passed on to the passage, which may be accessed as an array using the temporary variable `_args`. Just list arguments separated by commas after the passage name. + +#### Returns + +Optionally returns the value given to the `_result` temporary variable, or nothing. + +#### Examples + +``` +:: damage [macro] + +(set: $player's hp to it - _args's 1st) +(if: $player's hp <= 0)[ + (goto: 'game over') +] + +:: some passage +The enemy attacks! +(macro: 'damage', $enemy's attack) +You take (print: $enemy's attack) damage and have (print: $player's hp) health remaining. + +:: say-hi [macro auto-macro] + +(set: _result to 'Hi, ' + _args's 1st + '.') + +:: some other passage +(alert: (say-hi: 'Bob')) +``` + +### Additional Usage Notes + +* The entire macro-tagged passage should run at once (i.e., no `(prompt:)`, `(live:)`, or similar "delayed" commands) and they cannot display any output. This is suitable for code-only macros that perform complex calculations or automate repetitive tasks. If you need to print results, just return the results from the macro with `_result` and print it outside. +* No text is displayed while running the macro passage, nor can it have any +direct effect on the caller passage. The contents are rendered into a +dummy screen that is never shown on the page. +* This means you can nicely comment your macro with plain text. No need to use `{}` + or worry about line breaks or spacing. +* This also means no user input is possible. If you render a link to click, + it will never be able to be clicked. The macro will complete assuming the link + wasn't clicked. +* User input in general is not possible in macro-tagged passages, nor can you run +asynchronous commands like (alert:) that pause the passage until you interact +with them. No errors will happen, but the value in `_result` may not be +picked up properly if it was changed after the delaying command was used. +* Saving and loading from inside a macro is, in general, a terrible idea. Just don't do it. +* Argument type checking is not possible at this time. If a macro takes arguments, +thoroughly inspect `_args` before attempting to use them. +* Unfortunately, errors in macro passages are handled a bit oddly. Error details +are provided as best as possible, but so far as Harlowe is concerned the error +occurred in the caller passage. Check the dropdown in the error bubble for the +actual line that triggered the error. \ No newline at end of file diff --git a/docs/examples/notify.md b/docs/examples/notify.md index a49bad3..f66dcfa 100644 --- a/docs/examples/notify.md +++ b/docs/examples/notify.md @@ -1,6 +1,6 @@ ## Notify -Description. +Creates a quick user notification that rolls out from the top right of the screen, hangs for a minute, and rolls back in, useful for a quick note about status updates, achievements, or whatever else when a dialog box is a bit too much. Ported over from the similar CMFSC2 macro. > - **Get the Code** > @@ -10,4 +10,38 @@ Description. > - [Minified](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/minified/notify.min.css) > - [Pretty](https://github.com/ChapelR/harlowe-macro-api/blob/master/examples/notify.css) -Coming soon. \ No newline at end of file +### Macro: `(notify:)` + +Creates and displays a quick notification and renders the content of the associated hook into it. + +#### Syntax + +``` +(notify: [delay] [, classList])[ ... ] +``` + +#### Arguments + +- `delay` ( *`time`* ) ( optional ) A CSS-style time value (e.g., `5s`, `500ms`) for controlling how long the notification should remain. A short animation plays before and after this delay. Defaults to two seconds. +- `classList` ( *`string`* | *`string array`* ) ( optional ) You may pass a single string of space separated class names (e.g. `"my-class another-class a-third-class"`), an array of strings, or simply list several different strings as arguments to the macro to have them added to the `#notify` element for styling. You must include a delay time to add classes + +#### Returns + +Nothing. + +#### Examples + +``` + +(notify:)[Inventory updated.] + + +(notify: '10s', 'my-class')[Achievement unlocked.] + + +(notify: '5000ms')[Gold earned.] +``` + +#### Note + +Giving the player unlimited control over these notifications, or trying to show several at once or right after each other will cause them to trip over themselves as they try to animate, so try to keep them spaced out, and don't assign them to links or buttons you expect the player to press repeatedly. \ No newline at end of file diff --git a/examples/call.js b/examples/call.js deleted file mode 100644 index dfeb405..0000000 --- a/examples/call.js +++ /dev/null @@ -1,82 +0,0 @@ -(function () { - // call and reply macro set, v1.0.0 - 'use strict'; - - var eventNameSpace = '.userland'; - var eventPrefix = ':tw-call-macro-'; - - function isDef (is) { - return is != undefined; - } - - function evType (type) { - return eventPrefix + String(type) + eventNameSpace; - } - - function setCall (type, args, test) { - if (isDef(args) && isDef(test)) { - args = Harlowe.helpers.arrayify(arguments, 2); - } else if (isDef(args) && !(args instanceof Array)) { - args = [args]; - } - - $(document).trigger({ - type : evType(type), - args : isDef(args) ? args : undefined - }); - } - - function removeCall (type) { - $(document).off(evType(type)); - } - - function reply (type, cb, once, context) { - $(document)[ once ? 'one' : 'on' ](evType(type), function (ev) { - var shallow = Harlowe.variable('args'), deep; - if (shallow instanceof Map) { - deep = new Map(shallow.entries()); - } else if (shallow instanceof Set) { - deep = new Set(shallow.entries()); - } else if (typeof shallow === 'object') { - deep = JSON.parse(JSON.stringify(shallow)); - } else { - deep = shallow; - } - - Harlowe.variable('args', ev.args); - // callback - cb.call(context); - Harlowe.variable('args', deep); - }); - } - - Harlowe.macro('call', function (type, args) { - var err = this.typeCheck([ 'string' ]); - if (err) throw err; - - setCall.apply(null, arguments); - }); - - Harlowe.macro('reply', function (type, once) { - - var err = this.typeCheck([ 'string', 'boolean|undefined' ]); - if (err) throw err; - - }, function (type, once) { - - this.descriptor.enabled = false; - - reply(type, function () { - this.enabled = true; - }, !!once, this.descriptor); - - }); - - Harlowe.macro('silence', function (type) { - var err = this.typeCheck([ 'string' ]); - if (err) throw err; - - removeCall(type); - }); - -}()); \ No newline at end of file diff --git a/package.json b/package.json index 627d2ec..ebfbef7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harlowe-macro-api", - "version": "1.0.0", + "version": "1.0.1", "description": "Unofficial Macro API for Twine2/Harlowe.", "main": "build.js", "dependencies": {},