├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build instructions.txt ├── change.log ├── css ├── font-reference.ttf ├── font.css ├── menuStyle.css ├── place to get icons.url └── screenStyle.css ├── data ├── name.txt ├── passages.html ├── production.html └── sketch.html ├── dist ├── 0.2.1 │ └── format.js ├── 0.2.7 │ └── format.js ├── 0.2.8 │ └── format.js ├── 0.2.9 │ └── format.js ├── 0.3.0 │ └── format.js ├── 0.4.0 │ └── format.js ├── 0.4.5 │ └── format.js └── currentRelease │ └── format.js ├── format.html ├── format.json ├── gulpfile.js ├── index.html ├── js ├── init.js ├── makePDF.js ├── makepdf.js.bak ├── menu.js ├── new_passage.js.build ├── passage.js ├── passage.js.bak ├── story.js ├── story.js.bak └── story.js_new.bak ├── lib ├── jquery.js ├── lz-string.js ├── marked.js ├── pdfmake.min.js ├── underscore.js └── vfs_fonts.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## NPM 3 | ################# 4 | node_modules/ 5 | build/ 6 | 7 | ############# 8 | ## OS detritus 9 | ############# 10 | 11 | # Windows image file caches 12 | Thumbs.db 13 | ehthumbs.db 14 | 15 | # Folder config file 16 | Desktop.ini 17 | 18 | # Recycle Bin used on file shares 19 | $RECYCLE.BIN/ 20 | 21 | # Mac horseshit 22 | .DS_Store 23 | 24 | ################### 25 | ## TESTING 26 | ################### 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 seansimonanimation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Screentastic 2 | A Twine 2 Story Format for creating Interactive Screenplays. 3 | 4 | Howdy! 5 | 6 | Here's the basic how-to for using Screentastic 7 | 8 | ## Installation 9 | 10 | -Open Twine 2. 11 | -On the right side, you'll see +story, Import from File, Archive, and Formats. Click on Formats. 12 | -There will be the popup window that shows you all of your currently installed formats. At the top, on the third tab is "Add a New Format". Click that. 13 | -Copy-paste either the folder, or the URL into the box and click "+Add" 14 | -Here's where it gets a little funky. Twine doesn't really know when a Story Format's been added, so just wait for 5-10 seconds and it should be completely loaded. 15 | -X out of the format popup and click on "Formats" again. 16 | -Click on the "Story Formats" tab and Screentastic will show up underneath Sugarcube. There's no icon at this point so it just shows a broken image. 17 | 18 | And that's it! Screentastic is installed in Twine and whenever there is an update, simply overwrite that format.js file and Twine will automatically use the updated format file. 19 | 20 | ## Usage 21 | 22 | Using Twine is pretty simple. There's two main modes: Production mode and Sketch mode. 23 | 24 | In Production mode, Screentastic acts just like a regular screenplay, with links added. It uses its own special kid of markup that looks like so: 25 | #### itm>> content 26 | Itm is the kind of object you need displayed at that point. It could be a character name, some dialogue, a parenthetical... anything. 27 | content is the actual content of the code block. 28 | So for instance, if I have this snippet: 29 | ``` 30 | cha>>Dan the man 31 | dia>>I'm the fucken man! 32 | ``` 33 | It will show up as if you wrote it with Final Draft, Celtx, or any other screenwriting software. 34 | 35 | Sketch mode, on the other hand is a great place for outlining your ideas. simply make the first line of the first passage "sketch>>" or type "window.sketchMode = true" into the story javascript, and Screentastic will turn every single line of the story into an unordered list item. 36 | 37 | I personally use Sketch mode to write down my story beats, then turn them into screenplay format in another story. 38 | -------------------------------------------------------------------------------- /build instructions.txt: -------------------------------------------------------------------------------- 1 | to build screentastic: 2 | 3 | 4 | 5 | install node.js 6 | install GnuWin32 ( allow it to wrap into command prompt) (optional step) 7 | Navigate to the downloaded directory in cmd 8 | 9 | run: "npm install" (may have to be run more than once) 10 | run: "gulp" 11 | run: "gulp release" -------------------------------------------------------------------------------- /change.log: -------------------------------------------------------------------------------- 1 | ************* 2 | CHANGE LOG!!! 3 | ************* 4 | 5 | v0.1.0 6 | ------ 7 | Initial release! 8 | 9 | known bugs: 10 | 1) Links appear all on the same line, for some reason. 11 | 12 | v0.2.1 13 | ------ 14 | changes: 15 | --All links now have exactly one line break after them. 16 | --Added parantheticals. par>> activates. 17 | --general maintainence stuffs 18 | --started adding infrastructure for future features. 19 | 20 | known bugs: 21 | --text starts immediately at the top of the #page element, and looks ugly. DE-UGLIFY! 22 | 23 | 24 | v0.2.7 25 | ------ 26 | Changes: 27 | --Formatted the #page element to look like an 8.5x11 page! 28 | --Added fun little 3 hole punches on the left side! 29 | 30 | known bugs: 31 | --The Three hole punch uses a unicode character, which does not exist in either Courier Prime or Twine. Need to change to something that renders properly or fix it so that it does. 32 | --There's no error catching for when a passage extends beyond the #page element. Need to add either a scroll bar in the page or seperate out pages. Scroll bar is the lazy fix, multiple page fix is the smart option (smart for printing later). 33 | 34 | v0.2.8 35 | ------ 36 | Changes: 37 | --Added a menu! There's nothing in it yet, but it exists! Woo! 38 | --Planned items for menu: Quicksave/load, save to PDF, print. (others, too? There's plenty of room...) 39 | 40 | Known bugs: 41 | --Dropping the menu and simultaneously dropping the #page div destroys the passage data. Gotta find out why... For now, the menu just covers the passage. 42 | --Passage text shows through the window... it should go under (fix with z-index). 43 | --Same bugs from last time. 44 | 45 | v0.2.9/a/b/c 46 | ------------ 47 | Changes: 48 | --Added the icons for the menu. They don't work right now and all they do is throw up an alert, telling you that the functionality's not in yet. 49 | --Fixed the spacing on scene headers. 50 | --Fixed the hole punches. They display correctly in the Twine player now. 51 | 52 | Known bugs: 53 | -- Still no fix for the text breaking the bottom of the page. For now, just make extra passages as a workaround. I hope to have this fixed for 0.3.0. 54 | --I need to do something about those massive menu icons. They hurt to look at right now. 55 | 56 | Thoughts: 57 | --I should put in visual themes. That'd be fun. As long as what's on the page isn't changed, we can do whatever! 58 | --base64 is wonderful. 59 | 60 | v0.3.0 61 | ------ 62 | Changes: 63 | Added a bunch of new features 64 | automatic page parsing: If your story's passage runs beyond a page, don't worry... Screentastic will automatically add pages as needed. 65 | Sketch Mode: An exciting new feature that allows you to quickly get your ideas out there. 66 | The Menu: The menu items are not functional yet, but we've now got a menu that you can open/close at will. 67 | a bunch of changes under the hood: A lot of the passage handling passage.js has been restructured. 68 | Automatic scrolling to the top: Screentastic now drops you off at the top of the page whenever you click a link. 69 | 70 | known issues: 71 | The page parser's spacing for adding new pages is not always consistent. 72 | The links in the menu don't work. Don't worry, they just haven't been programmed yet. They should be done save for the PDF items by 0.4.0. 73 | 74 | v0.4.0 75 | ------ 76 | Changes: 77 | It's been a long time coming, but I've added PDF generation for both Production Mode as well as Sketch Mode! 78 | 79 | known issues: 80 | Generating a PDF doesn't work inside Twine. Only inside browsers. 81 | Sketch mode is pretty spotty on where and when it wants to include unordered list dots. (Might warrant a recode). 82 | Page margins between pages still needs work. 83 | 84 | v0.4.5 85 | ------ 86 | Most of the big stuff is in! At this point it's just filling in the gaps, making fixes, and removing obsolete code. 87 | Additions: 88 | Back button 89 | Forward button 90 | Quicksave 91 | Quickload 92 | We now have hotkeys! 93 | hotkey list: 94 | ctrl+left: Back 95 | ctrl+right: Forward 96 | ctrl+down: Quicksave 97 | ctrl+up: Quickload 98 | ctrl+alt+O: Create and open PDF document 99 | ctrl+alt+P: Create and print PDF Document (experimental) 100 | Darkened the icons on the menu bar (All the buttons work now so no reason not to) 101 | Added labels below the icons on the menu bar. 102 | 103 | Fixes: 104 | Fixed hole punches not showing up on dynamically generated pages. 105 | Made the spacing more consistent on the dynamically generated pages. (I think.) 106 | -------------------------------------------------------------------------------- /css/font-reference.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seansimonanimation/Screentastic/ceae146dac9b2d94327d141bb8c3225a9b66d325/css/font-reference.ttf -------------------------------------------------------------------------------- /css/menuStyle.css: -------------------------------------------------------------------------------- 1 | #menuButton { 2 | position: fixed; 3 | height: 35px; 4 | width: 50px; 5 | right: 20px; 6 | color: #555; 7 | border:solid; 8 | border-width: 3px; 9 | border-color: #999; 10 | background-color: #EEE; 11 | text-align: center; 12 | line-height: 50px; 13 | cursor: pointer; 14 | font-size: 500%; 15 | z-index: 6; 16 | -webkit-touch-callout: none; 17 | -webkit-user-select: none; 18 | -khtml-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | .menuHereButton { 24 | top: 98px; 25 | padding-top: 15px; 26 | } 27 | .menuAwayButton { 28 | top:-1px; 29 | padding-top: 15px; 30 | -webkit-transform:rotate(-180deg); 31 | -moz-transform:rotate(-180deg); 32 | -o-transform:rotate(-180deg); 33 | transform:rotate(-180deg); 34 | ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; 35 | filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 36 | 37 | } 38 | 39 | #menuBar { 40 | position: fixed; 41 | left: -5%; 42 | height: 100px; 43 | width: 110%; 44 | color: #333; 45 | border: solid; 46 | border-width: 3px; 47 | border-color: #999; 48 | background-color: #EEE; 49 | text-align: center; 50 | z-index: 10; 51 | white-space: nowrap; 52 | overflow: hidden; 53 | } 54 | 55 | .menuGone { 56 | top: -110px; 57 | } 58 | .menuHere { 59 | top: -5px; 60 | } 61 | #menuItem { 62 | font-family: 'Screentastic_Icons'; 63 | font-size: 400%; 64 | color: #555; 65 | line-height: 100px; 66 | position: relative; 67 | display: inline-block; 68 | margin:0 auto; 69 | top: -1px; 70 | height: 98%; 71 | width: 100px; 72 | background-color:#EEE; 73 | border: solid; 74 | border-color: #999; 75 | cursor:pointer; 76 | } 77 | /*TODO: find a better way to do the button positioning...*/ 78 | .backButton {left: 25px;} 79 | .forwardButton { left: 12.5px;} 80 | .quickSaveButton { 81 | } 82 | .quickLoadButton { left: -12.5px; } 83 | .makePDFButton { left: -25px; } 84 | .makePrintButton { left: -37.5px; } 85 | 86 | /* page-moving stuffs is broken atm... TODO: find out why. */ 87 | .pageDefaultPos { 88 | } 89 | 90 | .pageMenuPos { 91 | top: 100px; 92 | } 93 | 94 | .buttonlabel { 95 | font-size: small; 96 | font-family: 'CourierPrime'; 97 | line-height: 50%; 98 | margin-top: -12px; 99 | } 100 | -------------------------------------------------------------------------------- /css/place to get icons.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=http://app.fontastic.me/#select/Ldi87sJ7uhLvgSagbYS5db 3 | -------------------------------------------------------------------------------- /css/screenStyle.css: -------------------------------------------------------------------------------- 1 | body { font-family: 'CourierPrime'; background: #AAA; } 2 | 3 | #page { 4 | background: #FFF; 5 | height: 972px; 6 | width: 672px; 7 | border-style: solid; 8 | border-width: 1px; 9 | padding: 72px; 10 | z-index: 5; 11 | position: absolute; 12 | left: 0; 13 | right: 0; 14 | margin-left: auto; 15 | margin-right: auto; 16 | } 17 | 18 | #pageCenter { 19 | width: 100%; 20 | } 21 | 22 | #passageConstruction { 23 | position: absolute; 24 | margin: auto; 25 | display: none; 26 | z-index: 1; 27 | } 28 | 29 | #schCont { 30 | /*character length: 62*/ 31 | position: relative; 32 | display: block; 33 | margin-left: 25px; 34 | width: 635px; 35 | text-transform: uppercase; 36 | margin-top: 20px; 37 | 38 | } 39 | 40 | #traCont { 41 | /*character length: 62*/ 42 | position: relative; 43 | display: block; 44 | margin-left: 25px; 45 | width: 635px; 46 | text-transform: uppercase; 47 | text-align: right; 48 | margin-top: 20px; 49 | 50 | } 51 | 52 | #actCont { 53 | /*character length: 62*/ 54 | position: relative; 55 | display: block; 56 | margin-left: 25px; 57 | margin-top: 20px; 58 | margin-bottom: 10px; 59 | 60 | } 61 | 62 | #diaCont { 63 | /*character length: 34*/ 64 | position: relative; 65 | display: block; 66 | margin-left: 150px; 67 | margin-top: -20px; 68 | 69 | } 70 | 71 | #chaCont { 72 | /*character length: 26*/ 73 | position: relative; 74 | display: block; 75 | margin-left: 250px; 76 | text-transform: uppercase; 77 | margin-top: 20px; 78 | } 79 | 80 | #parCont { 81 | /*character length: 26*/ 82 | position: relative; 83 | display: block; 84 | margin-left: 188px; 85 | 86 | } 87 | 88 | #linkCont { 89 | position: relative; 90 | display: block; 91 | margin-left: 69px; 92 | margin-top: 20px; 93 | 94 | } 95 | #more { 96 | position: relative; 97 | display: block; 98 | margin-left: 300px; 99 | 100 | } 101 | 102 | ul { 103 | word-wrap: break-word; 104 | } 105 | 106 | #pageNumberCont { 107 | /*Future Feature: Page numbers that reflect what'll be seen on the PDF.*/ 108 | } 109 | 110 | /*The things below this line are for funsies and are not actually important to making things industry standard*/ 111 | 112 | #holePunch { 113 | font-family: 'Screentastic_Icons'; 114 | font-size: 125%; 115 | color: #333; 116 | position: absolute; 117 | height: 1122px; 118 | margin-top: -75px; 119 | margin-left: -75px; 120 | width: 100px; 121 | z-index: 6; 122 | text-align: center; 123 | -webkit-touch-callout: none; 124 | -webkit-user-select: none; 125 | -khtml-user-select: none; 126 | -moz-user-select: none; 127 | -ms-user-select: none; 128 | user-select: none; 129 | } 130 | 131 | .tophole { 132 | margin-top: 75px; 133 | } 134 | .midhole { 135 | margin-top: 400px; 136 | } 137 | .bothole { 138 | margin-top: 400px; 139 | } 140 | -------------------------------------------------------------------------------- /data/name.txt: -------------------------------------------------------------------------------- 1 | An Unreasonably Long Title That Tests The Limits of Everyone's Patience 2 | -------------------------------------------------------------------------------- /data/passages.html: -------------------------------------------------------------------------------- 1 | sch>> INT. TIME TRAVEL SCHOOL - MORNING 10 | act>> A classroom full of exhausted College Senior Level TIME CADETS sit in their classroom, staring down their PROFESSOR, Mid-30s, wearing a tweed jacket with elbow patches and a bow tie wrap up her lecture. 11 | act>>One student, CHRIS JENKINS, early twenties, wearing his green hoodie with “DR LORDE’S SCHOOL OF TEMPORAL MECHANICS” printed on the front, sits at his desk with his laptop in front, taking notes. Next to Chris, KATHLEEN MASON, mid-20s, skinny as a rail, hair disheveled, wearing the exact same hoodie as Chris, and black makeup, sits completely disinterested. She shakes her head in an attempt to wake herself up. 12 | act>>The professor points her laser pointer to drawings on a whiteboard, demonstrating what she’s saying. 13 | cha>> PROFESSOR 14 | dia>> So to summarize the Bootstrap Paradox... You go back in time to meet your hero Abraham Lincoln because you love the Ghettysburg Address just so much. 15 | act>> She moves the pointer to a terrible drawing of Abraham Lincoln, lying in a hammock, relaxing. 16 | act>>She moves the pointer to a third drawing of someone handing a paper to Lincoln. 17 | cha>>professor (cont'd) 18 | dia>>so you, the time traveler freak out and hand him the address. He delivers. What is the paradox? Chris. 19 | act>>She points to Chris. Chris stands up. 20 | cha>>Chris 21 | dia>>Who wrote the Ghettysburg address? 22 | act>>The prfessor puts her laser pointer in her jacket pocket. 23 | cha>>Professor 24 | dia>>Exactly. Which came first, the chicken or the egg? The ghettysburg address or the time traveler? 25 | act>>The professor checks her watch. 26 | cha>>Professor (cont'd) 27 | dia>>Class is dismissed. Next class, I want a page on how you can avoid runing all of time and space with this little gem. 28 | act>> The TIME CADETS stand and suffle out of the classroom. Chris and Kathleen follow. 29 | cha>>Professor (cont'd) 30 | par>>(yelling) 31 | dia>>And don't forget, stay out of history! 32 | [[Oppose the man|Oppose1]] 33 | [[Oppose the man|Oppose1]] 34 | act>> The boy opposes the man. Everybody cheers. 35 | act>> The boy steps off the stage 36 | cha>> The Boy 37 | dia>> The play is over! 38 | 39 | -------------------------------------------------------------------------------- /data/production.html: -------------------------------------------------------------------------------- 1 | sch>> INT. TIME TRAVEL SCHOOL - MORNING 10 | act>> A classroom full of exhausted College Senior Level TIME CADETS sit in their classroom, staring down their PROFESSOR, Mid-30s, wearing a tweed jacket with elbow patches and a bow tie wrap up her lecture. 11 | act>>One student, CHRIS JENKINS, early twenties, wearing his green hoodie with “DR LORDE’S SCHOOL OF TEMPORAL MECHANICS” printed on the front, sits at his desk with his laptop in front, taking notes. Next to Chris, KATHLEEN MASON, mid-20s, skinny as a rail, hair disheveled, wearing the exact same hoodie as Chris, and black makeup, sits completely disinterested. She shakes her head in an attempt to wake herself up. 12 | act>>The professor points her laser pointer to drawings on a whiteboard, demonstrating what she’s saying. 13 | cha>> PROFESSOR 14 | dia>> So to summarize the Bootstrap Paradox... You go back in time to meet your hero Abraham Lincoln because you love the Ghettysburg Address just so much. 15 | act>> She moves the pointer to a terrible drawing of Abraham Lincoln, lying in a hammock, relaxing. 16 | act>>She moves the pointer to a third drawing of someone handing a paper to Lincoln. 17 | cha>>professor (cont'd) 18 | dia>>so you, the time traveler freak out and hand him the address. He delivers. What is the paradox? Chris. 19 | act>>She points to Chris. Chris stands up. 20 | cha>>Chris 21 | dia>>Who wrote the Ghettysburg address? 22 | act>>The prfessor puts her laser pointer in her jacket pocket. 23 | cha>>Professor 24 | dia>>Exactly. Which came first, the chicken or the egg? The ghettysburg address or the time traveler? 25 | act>>The professor checks her watch. 26 | cha>>Professor (cont'd) 27 | dia>>Class is dismissed. Next class, I want a page on how you can avoid runing all of time and space with this little gem. 28 | act>> The TIME CADETS stand and suffle out of the classroom. Chris and Kathleen follow. 29 | cha>>Professor (cont'd) 30 | par>>(yelling) 31 | dia>>And don't forget, stay out of history! 32 | [[Oppose the man|Oppose1]] 33 | [[Oppose the man|Oppose1]] 34 | act>> The boy opposes the man. Everybody cheers. 35 | act>> The boy steps off the stage 36 | cha>> The Boy 37 | dia>> The play is over! 38 | -------------------------------------------------------------------------------- /data/sketch.html: -------------------------------------------------------------------------------- 1 | sketch>> 4 | The time, 5pm. We see a rundown house in a bad neighborhood. 5 | Carlos is packing himself a lunch in his backpack in the kitchen. 6 | He walks over to his parents who are playing poker with their friends. 7 | Carlos asks his mom and dad if they can give him a ride to a late night school trip to the museum. 8 | His dad claims that the car is still in the shop and that Carlos could use some exercise (says this with chips in his mouth). 9 | As Carlos ties his shoes at the front door, he can hear his parents talk about him. “Stupid boy is too needy that’s what he is. Don’t know why we even had him.” His mom replies, “We needed someone to take care of the laundry. That’s what adoption is isn’t it? Free manual labor.” They all start to laugh. 10 | Carlos walks out the door. We see his dad’s car in the garage. 11 | Carlos arrives at the museum but arrives too late. 12 | We see everyone from class leaving the building and heading home with their friends and family. 13 | To make matters worse, thunder breaks loose and begins to rain on him. 14 | He heads underneath the museum’s gate entrance to take shelter. 15 | He decides to have the sandwich he packed. 16 | A stray dog walks over him staring hungrily at Carlos’ meal. 17 | Carlos takes pity and shares a small piece of his sandwich. 18 | The dog instead takes a bite into his larger portion and runs off into the museum. 19 | Carlos chases after it. 20 | A pair of hollow eyes watches from the distance. 21 | Carlos looses the dog and decides to leave. 22 | The door gate doesn’t open trapping Carlos inside the museum. 23 | Carlos tries to find security to help let him back outside. 24 | As he walks through the halls past the art displays, playful shadows shaped like wolves begin to follow Carlos from behind. 25 | Carlos comes across a display featuring Native American relics. 26 | Carlos notices one of the pieces missing from the shelf. 27 | He walks up to it and hears a crack sound from his feet. 28 | He looks down and realizes he stepped on the missing display. 29 | The shadows from early lurk in from behind with out Carlos noticing. 30 | Carlos picks up have a piece of a coin with a dragonfly on it. 31 | Two of the shadows give a playful smile to one another and begin to run around in a circle around Carlos while he’s not looking. 32 | Carlos begins to hear security near by. He worries that he’ll be blamed from damaging museum property. 33 | Carlos tries to make a run for it but his feet begin to sink. The floor starts to dissolve into sand engulfing Carlos in. 34 | He manages to pull himself out, but then notices he is no longer in the museum. Instead he is in a small wasteland near the ocean. 35 | Carlos is surrounded by shadows causing him to freak out in fear. 36 | [[go to next page|secondPage]]They then stop and clear a path for their master to step forward. It is coyote spirit towering over Carlos. 37 | Carlos flips out saying “What’s are wolves doing in the city!?” 38 | The coyote snarls and is annoyed. She transforms into a young, girl close to Carlos’ age. She walks up to him and analyzes him. 39 | She asks to herself, “How did a human doing in the Wasteland?”” 40 | She turns her head toward her pack making whistle noises and smirks to act innocent. “Figures.” she says. 41 | Carlos asks, “Excuse me, wolf-lady. Hi, big question. What’s going on?” 42 | “Since you’re new, let me educate. One, I’m a coyote, not a wolf. There’s a difference. And two, my name is Rein. Three, you’re in the Wasteland of the Spirits. And four, you smell like pancakes and tuna-milk.” 43 | Carlos: “What does that last part have to do with-” Rein drops him. 44 | Rein then asks what he has in his hands. 45 | Carlos shows her and sees the broken talisman. Rein asks what he did with the other half. Carlos tells him he found it like this and it’s broken. 46 | She turns to her minions and demands a confession to whoever took the other half to come forward. 47 | Rein explains that her pack can be difficult to tame. And that they enjoy messing around in the human realm at night. They love taking stuff and burring in the woods. (Even though there suppose to give anything they take and give it for her to have.) 48 | The talisman he has is a key that can take anyone between the human and spirit realm. But he needs the other fragment to get back. 49 | Carlos asks for her to take him to the woods. Rein refuses saying it’s not her concern. 50 | Carlos lists everything that’s gone wrong to him today, and points that it was her stupid pets fault that he got into this and that she needs to own up. 51 | Rein roles her eyes and submits to being his guide. 52 | Carlos then asks if he can ride her, but Rein says no. 53 | They walk on foot till they reach a dead tree forest. 54 | Carlos is a bit uncomfortable not being familiar with his new surrounding. 55 | Rein tells him to man up and that life always has new twists and turns that have trouble adjusting. She takes Carlos himself as an example for crashing her turf and had plans on taking a break from taking care of her pets. 56 | Carlos then asks why she puts up with those little shadows that keep trolling her. 57 | Rein explains that they maybe hard to control, but they’re like family to her. 58 | Rein picks up a scent, which leads to a giant hollow tree. 59 | Inside is a load of garbage from the human world. 60 | Carlos tries digging through to find it. Rein tells him that it’s going to take a century at his pace. 61 | Suddenly, a worm-like tale grabs Rein and pulls her in. Emerging from the trash is a giant opossum monster with Rein in coiled in its tail. Carlos stutters in fear. 62 | The opossum accuses Rein of defiling his home. 63 | Carlos steps in and says that they’ll clean the mess up. 64 | The opossum agrees. Rein gives a sour annoyed look at Carlos saying that she hates cleaning. 65 | Rein and Carlos help dispose of all the garbage, but Carlos hasn’t managed to find the other fragment. 66 | The opossum then grabs Carlos and takes away his fragment. He decides that Rein can leave, but not him. 67 | The opossum compliments on Carlos’ service and could use a human servant for tiding up. And when he gets old he’ll eat his remains and use his bones keep on working. 68 | Infuriated, Rein’s aura grows around her taking the form of a giant coyote. She proceeds to bite the opossum by the tail and trash him like a chew toy till it dissolves into moss. 69 | Rein criticizes to Carlos that this is what happens when you don’t say no to others. They take advantage of you. 70 | Carlos apologies saying that he’s never been used to others helping him in return so much. 71 | Rein then hugs Carlos and comforts him. She puts in his hand the other fragment he needs. 72 | Rein then heads back to her turf and wishes him for the best of luck before he leaves. 73 | After Rein vanishes, Carlos takes a deep quiet pause. 74 | [[Go to next page|thirdPage]]He is now back home outside the museum. 75 | Carlos looks into his hand and sees the talisman has been reassembled. 76 | He returns home and finds his foster parents watching TV. They don’t seem to have notice how long he’s been absent. Carlos heads up to his room sitting on his bed. 77 | His mom yells at Carlos and tells him to fix the antenna on the roof. Carlos ignores her. 78 | He pulls out the talisman from the museum and pauses for a moment. 79 | Back in the spirit realm, Rein is disciplining her minions. 80 | Rein picks up on a familiar sent. She turns and finds Carlos standing in front of her. 81 | Rein asks why has he returned? 82 | Carlos tells her that he realizes that he’s found his true home and has decided that he wants remain in the spirit realm. 83 | Rein jokes and comments that what he’s doing is really selfish which Carlos replies with a simple “yup.” 84 | The End. 85 | 86 | -------------------------------------------------------------------------------- /format.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | //= include data/name.txt 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | //= include data/passages.html 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /format.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Screentastic", 3 | "version": "0.4.5", 4 | "description": "A Twine 2 Story Format for creating Interactive Screenplays.", 5 | "author": "Sean Simon", 6 | "image": "icon.svg", 7 | "url": "seansimonanimation.webuda.com", 8 | "license": "MIT License", 9 | "proofing": false 10 | } 11 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var formatJson = require('./format.json'); 3 | var del = require('del'); 4 | var fs = require('fs'); 5 | var jshint = require('gulp-jshint'); 6 | var jshintStylish = require('jshint-stylish'); 7 | var include = require('gulp-include'); 8 | var minifyCss = require('gulp-minify-css'); 9 | var minifyHtml = require('gulp-minify-html'); 10 | var plumber = require('gulp-plumber'); 11 | var rename = require('gulp-rename'); 12 | var replace = require('gulp-replace'); 13 | var uglify = require('gulp-uglify'); 14 | var usemin = require('gulp-usemin'); 15 | var yuidoc = require('gulp-yuidoc'); 16 | 17 | gulp.task('jshint', function() 18 | { 19 | gulp.src('js/*.js') 20 | .pipe(plumber()) 21 | .pipe(jshint({ 22 | globals: 23 | { 24 | jQuery: true, 25 | $: true, 26 | _: true, 27 | marked: true, 28 | LZString: true, 29 | Passage: true, 30 | Story: true, 31 | makePDF: true, 32 | pdfMake: true, 33 | storyData: true, 34 | bfFunc: true, 35 | quickLoadFunc: true, 36 | pdfFunc: true, 37 | quickSaveFunc: true 38 | }, 39 | globalstrict: true, // OK to use 'use strict'; instead of function 40 | "-W032": true, // Unnecessary semicolon 41 | browser: true, 42 | devel: true 43 | })) 44 | .pipe(jshint.reporter('jshint-stylish')); 45 | }); 46 | 47 | gulp.task('doc', function() 48 | { 49 | gulp.src('js/*.js') 50 | .pipe(plumber()) 51 | .pipe(yuidoc()) 52 | .pipe(gulp.dest('doc/')); 53 | }); 54 | 55 | gulp.task('clean', function (cb) 56 | { 57 | del.sync('build/'); 58 | del('dist/', cb); 59 | }); 60 | 61 | // default bake bakes a development version to index.html 62 | 63 | gulp.task('bake', function() 64 | { 65 | gulp.src('format.html') 66 | .pipe(plumber()) 67 | .pipe(include()) 68 | .pipe(rename('index.html')) 69 | .pipe(gulp.dest('./')); 70 | }); 71 | 72 | 73 | // bake for release replaces includes with actual placeholders 74 | // and minifies the whole shebang into a single file in build/, 75 | // which our build staging area 76 | 77 | gulp.task('bake:release', function() 78 | { 79 | return gulp.src('format.html') 80 | .pipe(plumber()) 81 | .pipe(replace('//= include data/name.txt', '{{STORY_NAME}}')) 82 | .pipe(replace('//= include data/passages.html', '{{STORY_DATA}}')) 83 | .pipe(usemin({ 84 | css: [minifyCss()], 85 | html: [minifyHtml()], 86 | js: [uglify()] 87 | })) 88 | .pipe(gulp.dest('build/')); 89 | }); 90 | 91 | gulp.task('copy', function() 92 | { 93 | return gulp.src(formatJson.image) 94 | .pipe(gulp.dest('dist/' + formatJson.name)); 95 | }); 96 | 97 | gulp.task('release', ['bake:release', 'copy'], function (cb) 98 | { 99 | // merge format.html into format.json 100 | 101 | formatJson.source = fs.readFileSync('build/format.html', 'utf-8'); 102 | 103 | // inline CSS and JS 104 | 105 | var css = fs.readFileSync('build/min.css', 'utf-8'); 106 | var js = fs.readFileSync('build/min.js', 'utf-8'); 107 | 108 | formatJson.source = formatJson.source.replace(//i, ''); 109 | formatJson.source = formatJson.source.replace(/'); 110 | 111 | // save it to dist/(version number)/format.js 112 | 113 | var currVerPath = 'dist/' + formatJson.version + '/'; 114 | 115 | if (! fs.existsSync('dist/currentRelease/')) 116 | fs.mkdirSync('dist/currentRelease/'); 117 | if (! fs.existsSync(currVerPath)) 118 | fs.mkdirSync(currVerPath); 119 | fs.writeFileSync('dist/currentRelease/format.js', 'window.storyFormat(' + JSON.stringify(formatJson) + ')'); 120 | fs.writeFileSync(currVerPath + 'format.js', 'window.storyFormat(' + JSON.stringify(formatJson) + ')'); 121 | cb(); 122 | }); 123 | 124 | gulp.task('default', ['jshint', 'bake', 'doc']); 125 | 126 | 127 | 128 | gulp.task('watch', function() 129 | { 130 | gulp.watch(['js/*.js', 'format.html', 'data/*'], ['bake']); 131 | gulp.watch('js/*.js', ['jshint', 'doc']); 132 | }); 133 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | An Unreasonably Long Title That Tests The Limits of Everyone's Patience 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 29 | 30 |
31 |
sch>> INT. TIME TRAVEL SCHOOL - MORNING 40 | act>> A classroom full of exhausted College Senior Level TIME CADETS sit in their classroom, staring down their PROFESSOR, Mid-30s, wearing a tweed jacket with elbow patches and a bow tie wrap up her lecture. 41 | act>>One student, CHRIS JENKINS, early twenties, wearing his green hoodie with “DR LORDE’S SCHOOL OF TEMPORAL MECHANICS” printed on the front, sits at his desk with his laptop in front, taking notes. Next to Chris, KATHLEEN MASON, mid-20s, skinny as a rail, hair disheveled, wearing the exact same hoodie as Chris, and black makeup, sits completely disinterested. She shakes her head in an attempt to wake herself up. 42 | act>>The professor points her laser pointer to drawings on a whiteboard, demonstrating what she’s saying. 43 | cha>> PROFESSOR 44 | dia>> So to summarize the Bootstrap Paradox... You go back in time to meet your hero Abraham Lincoln because you love the Ghettysburg Address just so much. 45 | act>> She moves the pointer to a terrible drawing of Abraham Lincoln, lying in a hammock, relaxing. 46 | act>>She moves the pointer to a third drawing of someone handing a paper to Lincoln. 47 | cha>>professor (cont'd) 48 | dia>>so you, the time traveler freak out and hand him the address. He delivers. What is the paradox? Chris. 49 | act>>She points to Chris. Chris stands up. 50 | cha>>Chris 51 | dia>>Who wrote the Ghettysburg address? 52 | act>>The prfessor puts her laser pointer in her jacket pocket. 53 | cha>>Professor 54 | dia>>Exactly. Which came first, the chicken or the egg? The ghettysburg address or the time traveler? 55 | act>>The professor checks her watch. 56 | cha>>Professor (cont'd) 57 | dia>>Class is dismissed. Next class, I want a page on how you can avoid runing all of time and space with this little gem. 58 | act>> The TIME CADETS stand and suffle out of the classroom. Chris and Kathleen follow. 59 | cha>>Professor (cont'd) 60 | par>>(yelling) 61 | dia>>And don't forget, stay out of history! 62 | [[Oppose the man|Oppose1]] 63 | [[Oppose the man|Oppose1]] 64 | act>> The boy opposes the man. Everybody cheers. 65 | act>> The boy steps off the stage 66 | cha>> The Boy 67 | dia>> The play is over! 68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /js/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | 5 | $(document).ready(function() { 6 | window.bypassError = false; //one of the callbacks in pdfmake activates Screentastic's built-in error handler and destroys #page's content. 7 | pdfMake.fonts = {CourierPrime: { normal: 'CourierPrime.ttf' }}; 8 | window.story = new Story($('tw-storydata')); 9 | $.each(window.story.content, function(pasid, pasobj){ 10 | if (pasobj !== undefined) { 11 | pasobj.parsePassage(); //uses the pdfmake library's built-in functions to auto-parse for lines and pages for us. It's beautiful! 12 | } 13 | }); 14 | window.story.start(); 15 | }); 16 | 17 | 18 | -------------------------------------------------------------------------------- /js/makePDF.js: -------------------------------------------------------------------------------- 1 | //a dumb module that makes PDF documents from existing story stuff. 2 | 3 | 'use strict'; 4 | 5 | //var docDefinition = {content: ['This is a standard paragraph, using default style',{ text: 'This paragraph will have a bigger font', fontSize: 15 },{text: ['This paragraph is defined as an array of elements to make it possible to ',{ text: 'restyle part of it and make it bigger ', fontSize: 15 },'than the rest.']}]}; 6 | 7 | 8 | 9 | 10 | function makePDF(print) { 11 | var allContent = window.storyData[0]; 12 | var a = allContent; 13 | var i = 0; 14 | var c = 0; 15 | var s = 0; 16 | var ss = 0; 17 | var sectionName = ''; 18 | var pasid=0; 19 | var tag=''; 20 | var content = ''; 21 | var sections = []; //an array containing objects containg all sections. 22 | /* 23 | proper format: 24 | [{name:"start",pid:1,tags:"",content:"stuff"},{name:"page2",pid:2,tags:"",content:"stuff"}] and so on and so forth... 25 | */ 26 | for (i=2; i < (a.childElementCount); i++) { 27 | sectionName = a.children[i].attributes.name.value; 28 | pasid = parseInt(a.children[i].attributes.pid.value); 29 | tag = a.children[i].attributes.tags.value; 30 | content = _.unescape(a.children[i].innerHTML); 31 | content = content.replace(/\/\*.*\*\//g, ''); //removes //comments 32 | content = content.replace(/^\/\/.*(\r\n|\n)?/g, ''); // to avoid clashes with URLs, lines must start with these 33 | sections[i-2] = {name:sectionName, pid: pasid, tags: tag, source: content}; 34 | } 35 | var pdfDocDef = { 36 | content: [], 37 | defaultStyle: { font: 'CourierPrime' }, 38 | styles: { 39 | sketch : {margin: [ 0,0,-25,0 ]}, 40 | sch: {margin: [0, 10, 0, 0]}, 41 | tra: { 42 | alignment: "right", 43 | margin: [0,10, 0, 0] 44 | }, 45 | cha: { margin: [190, 10, 144, 0]}, 46 | dia: { margin: [110, 0, 110, 0], width: 324}, 47 | act: { margin: [0, 10, 0, 0]}, 48 | par: { margin: [144,0,110,0]}, 49 | link: { 50 | fontSize: 16, 51 | margin: [0,20,0,0] 52 | }, 53 | }, 54 | pageMargins: [70,70], 55 | pageSize: 'LETTER', 56 | }; 57 | var runningTotal = 0; 58 | for (i=0; i>(.*)(\r\n|\n)?|\[\[(.*?)\]\](\r\n|\n)?)/g; //both the link grabber and the text grabber combined! 184 | var sourceMatches = unes.match(lineRegExp); //returns an array of all line matches where the lines begin with itm>>. 185 | var manipstring = ''; 186 | var lineObj = {}; 187 | var i; 188 | var textExists = true; 189 | var lastItem; 190 | var lineType; 191 | var lineContent; 192 | var target; 193 | var targetBookmark; 194 | try { 195 | textExists = true; 196 | for (i=0; i < sourceMatches.length; i++) { 197 | manipstring = sourceMatches[i]; 198 | if (manipstring.substring(0,2) == "[[") { //It's a link! 199 | lineType = "link"; 200 | var barIndex = manipstring.indexOf('|'); 201 | var endIndex = manipstring.indexOf(']]'); 202 | targetBookmark = manipstring.slice(barIndex + 1,endIndex); 203 | lineContent = manipstring.slice(2, barIndex); 204 | } else { 205 | lineType = manipstring.substring(0,3); 206 | lineContent = manipstring.substring(5); 207 | } 208 | if (lineType == "sch") { 209 | lineContent = lineContent.toUpperCase(); 210 | } else if (lineType == "tra") { 211 | lineContent = lineContent.toUpperCase(); 212 | } else if (lineType == "cha") { 213 | lineContent = lineContent.toUpperCase(); 214 | } else if (lineType == "par") { //This chunk of code is a smart parenthases parser that makes sure that parenthases are always there. 215 | var matches = lineContent.match(/\s?(\()?([^)]*)(\))?$/); 216 | if (matches.length == 1) { 217 | lineContent = "(" + lineContent + ")"; 218 | } else if (matches.length == 2) { 219 | if (matches[0] == "(") { 220 | lineContent = lineContent + ")"; 221 | } else { 222 | lineContent = "(" + lineContent; 223 | } 224 | } 225 | 226 | } else if (lineType == "dia") { 227 | 228 | 229 | } else if (lineType == "act") { 230 | 231 | } else if (lineType == "link") { 232 | lineContent = lineContent + ": Please go to page "; 233 | } else { //error 234 | lineContent = "(There has been an error in your markup descriptor) " + lineType + ">> " + lineContent; 235 | } 236 | 237 | 238 | if (i == (sourceMatches.length - 1)) { 239 | lastItem = true; 240 | } else { 241 | lastItem = false; 242 | } 243 | 244 | 245 | if (lineType == "link") { 246 | if (i === 0) { 247 | lineObj = {text: lineContent, style: lineType, target: targetBookmark, last : lastItem, bookmark: Section.name}; 248 | } else { 249 | lineObj = {text: lineContent, style: lineType, target: targetBookmark, last : lastItem}; 250 | 251 | } 252 | } else { 253 | if (i === 0) { 254 | lineObj = {text: lineContent, style: lineType, last : lastItem, bookmark: Section.name}; 255 | } else { 256 | lineObj = {text: lineContent, style: lineType, last : lastItem}; 257 | } 258 | } 259 | sourceMatches[i] = lineObj; 260 | } 261 | } catch(err) { 262 | textExists = false; 263 | } 264 | if (textExists === false) { 265 | sourceMatches = {text: "This section contains no data."}; //error catching, yay! Also lazy HTML! Yay! 266 | } 267 | return sourceMatches; 268 | } 269 | 270 | 271 | function pdfSketchParser (Section) { 272 | var unes = Section.source; 273 | var i = 0; 274 | var s = 0; 275 | var heightRegEx = /.*(\r\n|\n)?/g; 276 | unes = unes.replace(/sketch>>.*(\n|\r\n)?/g, ''); 277 | var itemArr = unes.match(heightRegEx); 278 | Section.source = itemArr; 279 | return Section; 280 | }; 281 | -------------------------------------------------------------------------------- /js/makepdf.js.bak: -------------------------------------------------------------------------------- 1 | //a dumb module that makes PDF documents from existing story stuff. 2 | 3 | 'use strict'; 4 | 5 | 6 | function makePDF(print) { 7 | 8 | var allContent = window.storyData[0]; 9 | var a = allContent; 10 | var i = 0; 11 | var c = 0; 12 | var s = 0; 13 | var sectionName = ''; 14 | var pasid=0; 15 | var tag=''; 16 | var content = ''; 17 | var sections = []; //an array containing objects containg all sections. 18 | /* 19 | proper format: 20 | [{name:"start",pid:1,tags:"",content:"stuff"},{name:"page2",pid:2,tags:"",content:"stuff"}] and so on and so forth... 21 | */ 22 | for (i=2; i < (a.childElementCount); i++) { 23 | sectionName = a.children[i].attributes.name.value; 24 | pasid = parseInt(a.children[i].attributes.pid.value); 25 | tag = a.children[i].attributes.tags.value; 26 | content = _.unescape(a.children[i].innerHTML); 27 | content = content.replace(/\/\*.*\*\//g, ''); //removes //comments 28 | content = content.replace(/^\/\/.*(\r\n?|\n)/g, ''); // to avoid clashes with URLs, lines must start with these 29 | sections[i-2] = {name:sectionName, pid: pasid, tags: tag, source: content}; 30 | } 31 | 32 | for (i=0; i 672) { 104 | newstring = newstring + "\r\n"; 105 | tempPassage.innerHTML = ""; 106 | } 107 | 108 | } 109 | } 110 | itemArr[i] = newstring; 111 | tempPassage.innerHTML = oldstring + newstring; 112 | oldstring = oldstring + newstring; 113 | tempHeight = tempPassage.clientHeight; 114 | if (tempHeight > 900) { 115 | pageMarker = pageMarker.push(i); 116 | oldstring = ''; 117 | newstring = ''; 118 | } 119 | tempPassage.innerHTML = ''; 120 | //Let's format this properly now. 121 | tempHeight = 0; 122 | for (i=0; i>)(.*)$|\[\[(.*?)\|(.*?)\]\]$)/gm; //both the link grabber and the text grabber combined! 71 | var lineRegExpL = /^((\w{3})(>>)(.*)$|\[\[(.*?)\|(.*?)\]\]$)/; 72 | var sourceMatches = original.match(lineRegExp); //returns an array of all line matches where the lines begin with itm>> or are links. 73 | var i; 74 | for (i=0; i>HAHA! HI!", "dia", ">>", "HAHA! HI!"] 81 | sourceMatches[i] = {text: sourceMatches[i][3], style: sourceMatches[i][1]}; 82 | } else if (sourceMatches[i].length == 3){ 83 | //regular items should have 4 items in their array at this point. Format: ["[[target|display]]","target","display"] 84 | sourceMatches[i] = {text: sourceMatches[i][3], target: sourceMatches[i][1], style: "link"}; 85 | 86 | } else { 87 | sourceMatches[i] = {text: "There is a problem with this item.", style: "err"}; 88 | } 89 | } 90 | console.log(sourceMatches); 91 | return sourceMatches; 92 | }, 93 | _sketch: function(original) { 94 | if (original.slice(0,8) == "sketch>>" ) { 95 | original = original.slice(8); 96 | } 97 | // console.log(original); 98 | var globalLineReg = /(^\[\[(.*?)\|(.*?)\]\]$|^.+$)/gm; 99 | var localLineReg = /(^\[\[(.*?)\|(.*?)\]\]$|^.+$)/; 100 | var sourceMatches = original.match(globalLineReg); //returns an array of all lines and links. 101 | // console.log(sourceMatches); 102 | var i; 103 | for (i=0; i' + display + '' + ''; 126 | } else { 127 | finishedLink = ''; 128 | } 129 | 130 | } catch(err) { 131 | this.linksExist = false; 132 | } 133 | return finishedLink; 134 | }, 135 | 136 | 137 | _pageParser: function(t, c, w) { //t=type, c=content, w=width 138 | //This function automatically figures out how many pages that a passage uses and generates pages accordingly. 139 | var npAtFront = false; 140 | var result = ''; 141 | //console.log(c.length); 142 | 143 | if (t == "cha" || t== "act" ||t == "sch" || t == "tra" || t == "link" || t == "sketch") { 144 | this.pageHeight++; 145 | if (this.pageHeight >=90){ 146 | npAtFront = true; 147 | } else { 148 | npAtFront = false; 149 | } 150 | } 151 | if (npAtFront === true) { 152 | this.pageHeight = 0; 153 | } 154 | var i = 0; 155 | var s = 0; //s is a dummy iterator, so we can build the array as needed without being limited to c(ontent).length 156 | var lineArr = []; //an array holding each line. 157 | for (i=0; i=90) { 169 | if (window.story.sketchMode === false){ 170 | lineArr[s] = "
[MORE]

m

m

m

"; 171 | } else { 172 | console.log(t); 173 | lineArr[s] = "

  • " + t; 174 | } 175 | s++; 176 | this.pageHeight = 0; 177 | } 178 | } 179 | if (npAtFront === true) { 180 | if (window.story.sketchMode === false) { 181 | return ")(

    m

    m

    m

    " + lineArr.join(""); //The )( is so we can easily tell it later. 182 | } else { 183 | return "
    • " + lineArr.join(""); 184 | } 185 | } else { 186 | return lineArr.join(""); 187 | } 188 | } 189 | }); 190 | 191 | 192 | -------------------------------------------------------------------------------- /js/passage.js: -------------------------------------------------------------------------------- 1 | //note to self: always do a whitespace runthrough when code is written. It'll save your sanity. 2 | 'use strict'; 3 | 4 | 5 | function Passage (id, name, tags, source) { 6 | //the numeric id of the passage. Super importante. Number, readonly. 7 | this.id = id; 8 | 9 | //the name of the passage. Not as important. String. 10 | this.name = name; 11 | 12 | //the tags of the passage. Not used by me personally, but eh. Array. 13 | this.tags = tags; 14 | 15 | //The passage's source code. Returns an array that later becomes a string during inital processing. 16 | this.source = source; 17 | this.stack = []; 18 | //a storage place for knowing if text exists in the passage 19 | this.textExists = false; 20 | 21 | //a storage place for knowing if links exist 22 | this.linkExists = false; 23 | 24 | //a storage place for the page height, for knowing if we need to add pages. 25 | this.pageHeight = 0; 26 | 27 | } 28 | 29 | 30 | _.extend(Passage.prototype, { 31 | 32 | 33 | parsePassage: function(){ 34 | //this is the code that actually throws the stuff on screen 35 | if (window.story.sketchMode === false){ 36 | this.stack = this._productionParser(_.unescape(this.source)); 37 | } else { 38 | this.stack = this._sketchParser(_.unescape(this.source)); 39 | } 40 | var passageDef = { 41 | content: this.stack, 42 | defaultStyle: window.story.defaultStyle, 43 | styles: window.story.styles, 44 | pageMargins: window.story.pageMargins, 45 | pageSize: window.story.pageSize 46 | }; 47 | var parsedObj = pdfMake.createPdf(passageDef); 48 | window.bypassError = true; 49 | parsedObj._createDoc(); 50 | 51 | }, 52 | 53 | 54 | screenRender: function(){ 55 | //this is the code that actually formats the stuff that gets thrown up on screen. when testing, it always returns "derp" 56 | var testing = false; 57 | var stack = this.stack; 58 | var i; 59 | var s; 60 | var currentPage = 1; 61 | var fullBring = ''; //The concatenated var containing the full, rendered passage, complete with HTML markup. Also, a Bleach reference :D 62 | for (i=0; i"; 70 | } 71 | 72 | for (s=0; s" + stack[i].ul[s].text + "
    • "; 74 | } 75 | if (!stack[i+1] || stack[i+1].style != "sketch"){ 76 | fullBring = fullBring + "
    "; 77 | } 78 | for (s=0; s currentPage) { 80 | fullBring = fullBring + "

      "; 81 | currentPage = stack[i].positions[s].pageNumber; 82 | } 83 | } 84 | } else if (stack[i].style == "link"){ 85 | var target = stack[i].target; 86 | var display = stack[i].text; 87 | 88 | if (/^\w+:\/\/\/?\w/i.test(target)) { 89 | fullBring = fullBring + '

      ' + display + '' + '

      '; 90 | } else { 91 | fullBring = fullBring + '

      ' + _.escape(display) + '' + '

      '; 92 | } 93 | } else if (stack[i].style == "sch" || stack[i].style == "tra" || stack[i].style == "act" || stack[i].style == "cha"){ 94 | //if (i==2){ 95 | //console.log(stack[i].text, stack[i].positions, stack[i].style, currentPage); 96 | //} 97 | //var result = "derp"; 98 | 99 | var result = this._formatArray(stack[i].text, stack[i].positions, stack[i].style, currentPage); // return [addPageLine + output, addingPages]; 100 | if ( result[1] > currentPage) { 101 | currentPage++; 102 | } 103 | fullBring = fullBring + result[0]; 104 | } else if (stack[i].style == "dia"){ //34 105 | var diaArr = []; 106 | var ss = 0; 107 | if (stack[i].positions.length == 1){ 108 | diaArr[0] = stack[i].text; 109 | } else { 110 | for (s=0; s currentPage){ 128 | fullBring = fullBring.substring(0,fullBring.lastIndexOf("

      ")) + "

    m

    m

    m

    " + fullBring.substring(fullBring.lastIndexOf("

    ")); 129 | currentPage++; 130 | console.log("This one!"); 131 | } else { 132 | fullBring = fullBring + "

    " + diaArr[s] + "
    "; 133 | } 134 | } else if (s !==0 && s == (diaArr.length-1)){ //at the end, multiline 135 | if (stack[i].positions[s-1].pageNumber > currentPage){ 136 | fullBring = fullBring + diaArr[s] + "

    m

    m

    m

    "; 137 | currentPage++; 138 | console.log("This one!"); 139 | 140 | } else { 141 | fullBring = fullBring + diaArr[s] + "

    "; 142 | } 143 | 144 | } else if (s === 0 && diaArr.length == 1) { //single line 145 | if (stack[i].positions[0].pageNumber > currentPage){ 146 | fullBring = fullBring.substring(0,fullBring.lastIndexOf("

    ")) + "

    m

    m

    m

    " + fullBring.substring(fullBring.lastIndexOf("

    ")) + diaArr[0] + "

    "; 147 | currentPage++; 148 | console.log("This one!"); 149 | 150 | } else { 151 | fullBring = fullBring + "

    " + diaArr[s] + "

    "; 152 | } 153 | } else { //somewhere in the middle, multiline. 154 | if (stack[i].positions[s-1].pageNumber > currentPage){ 155 | fullBring = fullBring + "

    [MORE]

    m

    m

    m

    [CONTINUED]

    " + diaArr[s] + "
    "; 156 | console.log("This one!"); 157 | 158 | } else { 159 | fullBring = fullBring + diaArr[s] + "
    "; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | if (window.story.sketchMode === false){ 166 | fullBring = "

    m

    m

    m

    " + fullBring; 167 | } 168 | console.log(fullBring); 169 | return fullBring; 170 | }, 171 | 172 | 173 | 174 | 175 | //Helper functions are here. They are methods of Passage. 176 | /* 177 | A helper function that is connected to passage templates as $. It acts like the jQuery $ function, running a script when the passage is ready in the DOM. The function passed is also bound to div#page for convenience. If this is *not* passed a single function, then this acts as a passthrough to jQuery's native $ function. @method _readyFunc @return jQuery object, as with jQuery() @private 178 | */ 179 | 180 | 181 | _formatArray: function(input, positions, lineType, page) { 182 | //This function formats non-dialogue-driving lines: AKA cha, act, sch, tra. par and dia are handled within the parent function. 183 | var inputArr = []; 184 | var output = ''; 185 | var i=0; 186 | var s=0; 187 | var ss=0; 188 | var lineLength; 189 | var addPageLine = ''; 190 | var addingPages = false; 191 | if (typeof(input) != "string" || typeof(positions) !="object" || typeof(lineType) != "string") { 192 | return "Something is wrong with your inputs."; 193 | } 194 | switch (lineType) { 195 | case "cha": 196 | lineLength = 34; 197 | break; 198 | case "act": 199 | lineLength = 65; 200 | break; 201 | case "sch": 202 | lineLength = 65; 203 | break; 204 | case "tra": 205 | lineLength = 65; 206 | break; 207 | } 208 | 209 | if (lineType == "cha" || lineType == "sch" || lineType == "tra"){ 210 | input = input.toUpperCase(); 211 | } 212 | 213 | if (positions.length === 1){ 214 | inputArr[0] = input; 215 | } else { 216 | ss=0; 217 | for (i=0; ss page){ 246 | //always put it at the beginning... 247 | addPageLine = "

    m

    m

    m

    "; 248 | addingPages = true; 249 | page++; 250 | break; 251 | } else { 252 | addPageLine = ''; 253 | } 254 | } 255 | for (i=0; i" + inputArr[i] + "

    "; 258 | } else { 259 | if (i === 0) { 260 | output = "

    " + inputArr[i] + "
    "; 261 | } else if (i == inputArr.length-1){ 262 | output = output + inputArr[i] + "

    "; 263 | } else { 264 | output = output + inputArr[i] + "
    "; 265 | } 266 | } 267 | } 268 | output = addPageLine + output; 269 | //console.log(output); 270 | return [output, addingPages]; 271 | }, 272 | 273 | 274 | _productionParser: function(original){ 275 | //Takes the original, unescaped string and turns it into an object array. 276 | var lineRegExpG =/^((\w{3})(>>)(.*)$|\[\[(.*?)\|(.*?)\]\]$)/gm; //both the link grabber and the text grabber combined! 277 | var lineRegExpL = /^((\w{3})(>>)(.*)$|\[\[(.*?)\|(.*?)\]\]$)/; 278 | var sourceMatches = original.match(lineRegExpG); //returns an array of all line matches where the lines begin with itm>> or are links. 279 | var i; 280 | for (i=0; i>HAHA! HI!", "dia", ">>", "HAHA! HI!"] 287 | sourceMatches[i] = {text: sourceMatches[i][3], style: sourceMatches[i][1]}; 288 | } else if (sourceMatches[i].length == 3){ 289 | //regular items should have 4 items in their array at this point. Format: ["[[target|display]]","target","display"] 290 | sourceMatches[i] = {text: sourceMatches[i][1], target: sourceMatches[i][2], style: "link"}; 291 | 292 | } else { 293 | sourceMatches[i] = {text: "There is a problem with this item.", style: "err"}; 294 | } 295 | } 296 | return sourceMatches; 297 | }, 298 | 299 | _sketchParser: (function(original) { 300 | if (original.slice(0,8) == "sketch>>" ) { 301 | original = original.slice(8); 302 | } 303 | // console.log(original); 304 | var globalLineReg = /(^\[\[(.*?)\|(.*?)\]\]$|^.+$)/gm; 305 | var localLineReg = /(^\[\[(.*?)\|(.*?)\]\]$|^.+$)/; 306 | var sourceMatches = original.match(globalLineReg); //returns an array of all lines and links. 307 | var i; 308 | 309 | for (i=0; i' + display + '' + '
    '; 332 | } else { 333 | finishedLink = ''; 334 | } 335 | 336 | } catch(err) { 337 | this.linksExist = false; 338 | } 339 | return finishedLink; 340 | }), 341 | }); 342 | 343 | 344 | -------------------------------------------------------------------------------- /js/passage.js.bak: -------------------------------------------------------------------------------- 1 | //note to self: always do a whitespace runthrough when code is written. It'll save your sanity. 2 | 'use strict'; 3 | 4 | 5 | function Passage (id, name, tags, source) { 6 | //the numeric id of the passage. Super importante. Number, readonly. 7 | this.id = id; 8 | 9 | //the name of the passage. Not as important. String. 10 | this.name = name; 11 | 12 | //the tags of the passage. Not used by me personally, but eh. Array. 13 | this.tags = tags; 14 | 15 | //The passage's source code. String. 16 | this.source = _.unescape(source); 17 | 18 | //a storage place for knowing if text exists in the passage 19 | this.textExists = false; 20 | 21 | //a storage place for knowing if links exist 22 | this.linkExists = false; 23 | 24 | //a storage place for the page height, for knowing if we need to add pages. 25 | this.pageHeight = 0; 26 | } 27 | 28 | 29 | _.extend(Passage.prototype, { 30 | /*Stolen from Chris Klimas. 31 | Runs the source through Underscore's template parser then through a Markdown renderer, then converts bracketed links into proper passage links. 32 | Returns the HTML source. This is where the magic happens.*/ 33 | render: function() { 34 | window.bypassError = false; // make sure that the error handler is active. 35 | this.pageHeight = 0; 36 | var result = ''; 37 | var unes = _.unescape(this.source); //unes is the unescaped source. 38 | unes = unes.replace(/\/\*.*\*\//g, ''); 39 | // Remove // comments 40 | // to avoid clashes with URLs, lines must start with these 41 | unes = unes.replace(/^\/\/.*(\r\n?|\n)/g, ''); 42 | if (window.story.sketchMode === false) { 43 | result = this._formatMachine(unes);//Takes the unescaped source and returns the completed text! 44 | } else { 45 | //we're in sketch mode now. Sketchmode is a pretty simple concept. It just takes each line and turns it into an unordered list. 46 | result = this._sketchMachine(unes); 47 | } 48 | return result; 49 | 50 | }, 51 | 52 | 53 | //Helper functions are here. Technically all part of _.extend(Passage.prototype, { which is on line 27 at this time. 54 | /* 55 | (stolen from snowman) A helper function that is connected to passage templates as $. It acts like the jQuery $ function, running a script when the passage is ready in the DOM. The function passed is also bound to div#page for convenience. If this is *not* passed a single function, then this acts as a passthrough to jQuery's native $ function. @method _readyFunc @return jQuery object, as with jQuery() @private 56 | */ 57 | _formatMachine: function(original){ 58 | var lineRegExp =/(\w{3}>>(.*)(\r\n|\n)?|\[\[(.*?)\]\](\r\n|\n)?)/g; //both the link grabber and the text grabber combined! 59 | var sourceMatches = original.match(lineRegExp); //returns an array of all line matches where the lines begin with itm>> or are links. 60 | var i; 61 | this.textExists = false; 62 | try { 63 | this.textExists = true; 64 | for (i=0; i < sourceMatches.length; i++) { 65 | var lineType = sourceMatches[i].substring(0,3); 66 | var lineContent = sourceMatches[i].substring(5); 67 | if (lineType == "if>" || lineType == ">>>" || lineType == "eli" || lineType == "els") { 68 | continue; //We'll let conditional statements pass through... for now. 69 | /*TODO: Conditional statement format: 70 | if>>> condition 71 | itm>>item 72 | itm>> item 73 | >>>>> (end conditional statement) 74 | 75 | if>>> condition 76 | itm>>item 77 | itm>> item 78 | els>> (blank, else statement) 79 | itm>> item 80 | itm>> item 81 | >>>>> (end conditional statement) 82 | 83 | Else If support: (infinte else ifs) 84 | if>>> condition 85 | itm>>item 86 | itm>> item 87 | elif> other condition 88 | itm>> item 89 | itm>> item 90 | elif> other condition 91 | itm>> item 92 | itm>> item 93 | >>>>> (end conditional statement, else optional.) 94 | It basically rejiggers the whole thing into a Javascript if statement, 95 | then brings the whole chunk into one array element. 96 | */ 97 | } else if (lineType == "act" || lineType == "sch" || lineType == "tra") { 98 | lineContent = this.pageParser(lineType, lineContent, 62); 99 | } else if (lineType == "dia"){ 100 | lineContent = this.pageParser(lineType, lineContent, 33); 101 | } else if (lineType == "cha") { 102 | lineContent = this.pageParser(lineType, lineContent, 26); 103 | } else if (lineType == "par") { //Auto-adds parenthases as necessary. 104 | var matches = lineContent.match(/\s?(\()?([^)]*)(\))?$/); 105 | if (matches.length == 1) { 106 | lineContent = "(" + lineContent + ")"; 107 | } else if (matches.length == 2) { 108 | if (matches[0] == "(") { 109 | lineContent = lineContent + ")"; 110 | } else { 111 | lineContent = "(" + lineContent; 112 | } 113 | } 114 | lineContent = this.pageParser(lineContent, 26); 115 | } else if (lineType.substring(0,2) == "[[") { 116 | //link 117 | lineType = "link"; 118 | this.linksExist = true; 119 | } 120 | if (lineType == "act" || lineType == "sch" || lineType == "tra" || lineType == "dia" || lineType == "cha" || lineType == "par"){ 121 | if (sourceMatches[i].substring(0,2) == ")(") { 122 | original = original.replace(sourceMatches[i], lineContent.substring(2) + "
    "); 123 | } else if (lineType == "link") { 124 | original = original.replace(sourceMatches[i], this._linkMachine(sourceMatches[i])); 125 | } else { 126 | original = original.replace(sourceMatches[i], '
    ' + lineContent + "
    "); 127 | } 128 | } 129 | } 130 | } catch(err) { 131 | this.textExists = false; 132 | this.linksExist = false; 133 | console.log(err); 134 | } 135 | if (this.linksExist === false && this.textExists === false) { 136 | original = '

    (This passage has no data or has some other unrecoverable error. Check the Javascript console for your error.)

    '; //lazy error catching, yay! Also lazy HTML! Yay! 137 | } 138 | var holepunch = '

    m

    m

    m

    '; 139 | 140 | 141 | //holepunches only need to be in production mode. 142 | return holepunch + original; 143 | 144 | }, 145 | _sketchMachine: function(original) { 146 | if (original.slice(0,8) == "sketch>>" ) { 147 | original = original.slice(8); 148 | } 149 | // console.log(original); 150 | window.derp = original; 151 | var lineRegExp =/(.+(\r\n|\n)?|\[\[(.*?)\]\](\r\n|\n)?)/g; 152 | var sourceMatches = original.match(lineRegExp); //returns an array of all lines. 153 | // console.log(sourceMatches); 154 | var i; 155 | try { 156 | this.textExists = true; 157 | for (i=0; i < sourceMatches.length; i++) { 158 | if (sourceMatches[i].substring(0,2) == "[["){ 159 | //console.log(sourceMatches[i]); 160 | sourceMatches[i] = "" + this._linkMachine(sourceMatches[i]) + "
      "; 161 | sourceMatches[i] = this._pageParser("sketch",sourceMatches[i],65); 162 | //console.log(sourceMatches[i]); 163 | 164 | } else if (sourceMatches[i].match(/\w/) === null){ //if there's no non-whitespace characters, kill it, let .join() sort it out. 165 | delete sourceMatches[i]; 166 | } else { 167 | //console.log(sourceMatches[i]); 168 | sourceMatches[i] = this._pageParser("sketch",sourceMatches[i],60); 169 | if (sourceMatches[i].substring(0,5) == "
    "){ 170 | sourceMatches[i] = sourceMatches[i] + "
  • "; 171 | } else { 172 | sourceMatches[i] = '
  • ' + sourceMatches[i] + "
  • "; 173 | } 174 | } 175 | 176 | //console.log(this.pageHeight.toString() + sourceMatches[i]); 177 | } 178 | } catch(err) { 179 | console.log("derped!"); 180 | this.textExists = false; 181 | } 182 | //console.log(sourceMatches); 183 | return "
      " + sourceMatches.join("") + "
    "; 184 | }, 185 | 186 | _linkMachine: function(original) { //insert code for links! 187 | var linkHunter = /\[\[(.*?)\]\]((\r\n|\n)*)/; 188 | var liMat = original.match(linkHunter)[1]; //returns a string of "display|target" 189 | var finishedLink; 190 | //[[links]]... Snowman's link code made no goddamn sense... so I rewrote it. HAHA! (notes... add the newline possibility to the regex.) 191 | try { 192 | this.linksExist = true; 193 | var display = liMat.slice(0, liMat.indexOf('|')); //second index to bar 194 | var target = liMat.slice(liMat.indexOf('|') + 1); //bar to last. 195 | if (/^\w+:\/\/\/?\w/i.test(target)) { 196 | finishedLink = ''; 197 | } else { 198 | finishedLink = ''; 199 | } 200 | 201 | } catch(err) { 202 | this.linksExist = false; 203 | } 204 | return finishedLink; 205 | }, 206 | 207 | 208 | _pageParser: function(t, c, w) { //t=type, c=content, w=width 209 | //This function automatically figures out how many pages that a passage uses and generates pages accordingly. 210 | var npAtFront = false; 211 | var result = ''; 212 | //console.log(c.length); 213 | 214 | if (t == "cha" || t== "act" ||t == "sch" || t == "tra" || t == "link" || t == "sketch") { 215 | this.pageHeight++; 216 | if (this.pageHeight >=90){ 217 | npAtFront = true; 218 | } else { 219 | npAtFront = false; 220 | } 221 | } 222 | if (npAtFront === true) { 223 | this.pageHeight = 0; 224 | } 225 | var i = 0; 226 | var s = 0; //s is a dummy iterator, so we can build the array as needed without being limited to c(ontent).length 227 | var lineArr = []; //an array holding each line. 228 | for (i=0; i=90) { 240 | if (window.story.sketchMode === false){ 241 | lineArr[s] = "
    [MORE]

    m

    m

    m

    "; 242 | } else { 243 | console.log(t); 244 | lineArr[s] = "

  • " + t; 245 | } 246 | s++; 247 | this.pageHeight = 0; 248 | } 249 | } 250 | if (npAtFront === true) { 251 | if (window.story.sketchMode === false) { 252 | return ")(

    m

    m

    m

    " + lineArr.join(""); //The )( is so we can easily tell it later. 253 | } else { 254 | return "
    • " + lineArr.join(""); 255 | } 256 | } else { 257 | return lineArr.join(""); 258 | } 259 | 260 | 261 | //Let's save this old, bad chunk of code for now. 262 | /* *****KNOWN ISSUE***** 263 | The page parser can really only guess at what the final result will be, and it seems to find an arbitrary place 264 | to put the pagemaking code each time. I need to figure out an exact way for the parser to guess accurately. 265 | Until then, it works, just the distances between the pages and here the pages actually split seem arbitrary to the 266 | end user. 267 | *\///remove backslash to uncomment 268 | 269 | 270 | var tempPassage = document.getElementById("passageConstruction"); 271 | tempPassage.innerHTML = ""; //Element needs to be cleared out before we can begin. 272 | var tempHeight = 0; 273 | var heightRegEx = /.*(\r\n|\n)?/g; 274 | var wordRegEx = / 275 | var itemArr = original.match(heightRegEx); 276 | var heightSubtractor = 0; 277 | var pageCounter = 1; 278 | var result = ''; 279 | var pageDistance = []; 280 | var newstring = ''; 281 | var changePageArr = []; 282 | var firstPart = ''; 283 | var breakoff = ''; 284 | var secondPart = ''; 285 | var pageChangeArr = []; 286 | var pageBreak = ''; 287 | //We need to build the "#passageConstruction" div character by character (used to be line by line), test the height, and add pages based on that height. To do that, we need to make the tempPassage workable. AKA not display: hidden; 288 | tempPassage.style.position = "absolute"; 289 | tempPassage.style.visibility = "hidden"; 290 | tempPassage.style.display = "block"; 291 | tempPassage.style.width = "672px"; 292 | tempPassage.style.left = "0px"; 293 | tempPassage.style.top = "0px"; 294 | for (i=0; i 900) { 310 | pageDistance = 972 - tempHeight + 82; 311 | if (window.sketchMode === false ) { 312 | //instead of breaking our flow, let's make a new object now and inject everything later. 313 | //What we're doing is inserting the page break at the beginning of the word. 314 | pageBreak = "

    m

    m

    m

    \n"; 315 | pageChangeArr[pageCounter-1] = { index: i, text: pageBreak + itemArr[i]}; 316 | tempPassage.innerHTML = ""; 317 | newstring = ""; 318 | pageCounter = pageCounter + 1; 319 | } else { 320 | pageBreak = "