Your browser lacks required capabilities. Please upgrade it or switch to another to continue.
Please select which kind of issue you'd like to report:
<a target="_blank" href="https://github.com/ChapelR/spellbook-source/issues/new?labels=Spell%20error.&template=spell-error.md">@@.w100-link;<button>Error in spell.</button>@@</a>
<a target="_blank" href="https://github.com/ChapelR/spellbook-source/issues/new?labels=Missing%20spell.&template=missing-spell.md">@@.w100-link;<button>Missing SRD spell.</button>@@</a>
<span class='w100-link'><<button 'Found a bug.'>>
<<set _bugCode to "```\n" + Save.serialize({ 'userReport' : true }) + "\n```">>
<<run $(document).one(':dialogopen', function () {
if (!$('#ui-dialog-body').hasClass('bug-report')) {
return;
}
setTimeout(function () {
$('#bug-report').empty().append(State.temporary.bugCode);
}, Engine.minDomActionDelay || 40)
})>>
<<dialog 'Report Bug' 'bug-report'>>
Please copy this state code and paste it into the issue form.
When you're ready, click the {{{[Report bug.]}}} button.
''State code:''
<textarea id='bug-report' readonly></textarea>
<span class='w100-link'><<button 'Select All'>>
<<run $('#bug-report').select()>>
<</button>>
<<button 'Copy to Clipboard'>>
<<run $('#bug-report').select()>>
<<run document.execCommand('copy')>>
<</button>></span>
<a target="_blank" href="https://github.com/ChapelR/spellbook-source/issues/new?labels=bug&template=bugs.md">@@.w100-link;<button>Report bug.</button>@@</a>
<</dialog>>
<</button>></span><span id='menu-heading'>@@#menu-icon;[img[assets/images/changelog.svg]]@@ @@#menu-text;Patch Notes@@</span>
/%
THINGS TO IMPLEMENT BEFORE 1.0.0:
* PWA support with precaching.
AT 1.0.0, WRAP BETA PATCHES IN <<MESSAGE>>.
%/\
<<include 'Recent'>>
<<include 'Log'>>!!! 0.9.9: Beta patch 8
* Added PWA support. You can now install this web app as a PWA using certain browsers to use the app offline.
** All assets used by the app are now cached the first time the app is loaded, meaning faster load times across the board.
** Users can install the app on their phones and computers by using the {{{Add to Homescreen}}} or {{{Add to Desktop}}} options in their browsers, provided the browser supports it. Doing so gives the app a handful of advantages, particularly with regard to performance and appearance.
** Not all browsers currently support all facets of PWAs, but most modern browsers are working on implementations.
* Added a select / unselect all option and updated the left-side option button layout.
* General performance improvements.
* Added a bug report system in the settings menu for reporting bugs in the app, errors in the spell descriptions, and missing spells.
* Fixed some spell descriptions.!!! 0.9.8: Beta patch 7
* Custom spells are now fully editable.
* Importing custom spells:
** When you import a spellbook that features a custom spell and a spell with the same name is already in your custom spell list, the imported custom spell will be discarded, and the one already saved in your custom spell list will be used instead.
** When you import a spellbook that features a custom spell and you don't have a spell with that name already in your list, that custom spell will be imported along with the spellbook and added to your spell lists.
** To the app, spells are considered to have the same name regardless of case, that is "{{{wish}}}", "{{{Wish}}}", and "{{{wIsH}}}" would all be considered the same name. The app also cares about spacing only so far as it exists between words. This means that "<<= ' '>>{{{Magic Missile }}}", "{{{Magic Missile}}}<<= ' '>>", and "{{{Magic}}}<<= ' '>>{{{Missile}}}" are all considered the same name.
* When spell lists are altered (such as by changing the filters or searching) any selected spells are unselected. This seemed more logical than allowing spells not currently on-screen to preserve their selected status.
* Added additional licensing information.
* Adjusted the placement of some buttons and options.
* Updated load screen.
!!! 0.9.7: Beta patch 6
* Emergency update to get the advanced filters working again.
!!! 0.9.6: Beta patch 5
* Added [[Google Analytics|https://www.google.com/analytics/]].
* Compression for spellbook files has been increased by an almost comical amount (a file containing all spells has gone from 163 KB to 1 KB). Unfortunately, old spellbook files are no longer importable.
* UI changes and improvements.
* Basic support for custom spells has been added. Click the @@.inner-image;[img[assets/images/custom.svg]]@@ icon to get started. This system should not be considered stable, but it's a good starting point. In the future, they'll be editable, but right now, you can only add and delete, and not edit the custom spells once created. Importing/exporting custom spells is also a bit wonky right now, so do that at your own risk.
* Fixed a few minor to moderate bugs and issues, particularly related to the {{{[Remove all.]}}} button, which has always been the bane of my existence.
* Performance improvements.
!!! 0.9.5: Beta patch 4
* Several minor to moderate UI improvements.
* The import/export feature now supports the file system, meaning you can save and load spellbooks to and from your hard disk, rather than copying and pasting all that fiddly gibberish.
!!! 0.9.3: Beta patch 3
* Spellbooks are now automatically ordered based on access: spellbooks you use more will stay at the top of the list and spellbooks you use less will sink to the bottom. Opening a spellbook and adding spells to a spellbook count as accessing it (removing spells doesn't, but you can't remove spells without opening it, so...). Creating a new spellbook also counts as "accessing" it for the purposes of this system. Editing a spellbook's name or tags does not count, however.
* Fixed a bug in the 'priced materials' components filter.
* Fixed a significant number of errors, typos, and formatting issues in the spell descriptions.
!!! 0.9.2: Beta patch 2
* Fixed bug in the search feature where deleting letters would not un-narrow the search.
* Fixed issue where dropdown boxes would flash wildly in night mode, though it was fixed by making these input elements white across both modes.
* Added option to 'slim' the side dock to give the user more screen real-estate. Only has an effect on larger screens.
!!! 0.9.1: Beta patch 1
* Added setting to 'flatten' the appearance of the spell cards, making them look a bit less like buttons. This new option is on by default. Press the @@.inner-image;[img[assets/images/settings.svg]]@@ icon to configure this new setting to your liking.
* Disabled zooming on mobile devices. This should make the app feel much more responsive and prevent the 'sliding around' effect of some UI elements when scrolling on mobile. You can still adjust the size of the apps's text and presentation by adjusting the font-size option in the settings menu.
* Added the Open Gaming License to the license list.
* Added credit to [[vorpalhex|https://github.com/vorpalhex/srd_spells]] in About modal.
* Other minor appearance and UI updates.<<set setup.results to spells.get.sort(spells.list.concat($custom))>>
<<set $listName to 'All Spells'>>
/% filter settings %/
<<set setup.classList to ['any', 'bard', 'cleric', 'druid', 'paladin', 'ranger', 'sorcerer', 'warlock', 'wizard']>>
<<set setup.levelList to ['any', 'cantrip', '1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th']>>
<<set setup.schoolList to ['any', 'abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation']>>
<<set setup.componentList to ['verbal', 'somatic', 'material', 'priced material']>>
<<set setup.actionList to ['any', '1 action', '1 bonus action', '1 reaction', 'minutes', 'hours']>>
<<set setup.rangeList to ['any', 'self', 'touch', 'sight', 'less than 30 feet', '30 feet', '60 feet', '90 feet', '100 feet', '120 feet', '150 feet', 'more than 150 feet']>>
<<set setup.durationList to ['any', 'instantaneous', 'concentration', 'rounds', 'minutes', 'hours', 'other']>><<set $listName to 'All Spells'>>\
<hr />
!!!Your Spellbooks:
<span id='books'></span>\
<span id='list-link'><span class='w100-link load-list'><<button [[See all of your spellbooks.|Lists]]>><</button>></span></span>
<<silently>>
<<timed 40ms>>
<<if $listOfLists.length < 1>>
<<replace '#books'>>...Or not. Why don't you <<link 'create one'>>
<<set _spellToAdd to false, _bookToEdit to false>>
<<popup 'Edit' 'New SpellBook' 'new-book'>>
<</link>>?<</replace>>
<<replace '#list-link'>><</replace>>
<<else>>
<<run SpellList.update('#books', $lists.slice(0, 3))>>
<</if>>
<</timed>>
<</silently>>\
<hr />
!!!Some random spells to keep you company:
<<set _list to spells.list.randomMany(3)>>\
<span id='random-spells'></span>
<<run spells.render.load(false, '#random-spells', function () { return spells.render.listAll(_list); })>>\
<span class='w100-link load-list'><<button [[See all spells.|Results]]>><<run $('#story').attr('data-ctx', '')>><</button>></span><div id='about-main'>\
Programmed by Chapel.
Spells taken from [[this JSON file|https://github.com/vorpalhex/srd_spells]] by vorpalhex. Thanks so much!
</div><div id='about-tools'>
Powered by [[Twine|http://twinery.org/]], [[Tweego|http://www.motoslave.net/tweego/]], and [[SugarCube|http://www.motoslave.net/sugarcube/]].
</div><div id='about-licenses'>
This application and its code are released under the [[Unlicense|http://unlicense.org/]].
For licenses for the supporting software and content, [[click here|https://github.com/ChapelR/spellbook-source/tree/master/licenses]].
</div>
<span class='w100-link'><<button [[Changelog]]>><<run Dialog.close()>><</button>></span>
@@#version;<<= setup.version.string()>>@@<<set $ctx to ''>>\
<<set _classes to []>><<set _components to []>><<set _pricedComp to (_components && _components[3]) ? _components[3] : ''>>\
<span id='menu-heading'>@@#menu-icon;[img[assets/images/custom.svg]]@@ @@#menu-text;Custom Spells@@</span>
<span id='results'></span><<nobr>>
<<if $custom && $custom.length>>
<<run spells.render.load(false, '#results', function () { return spells.render.listAll($custom); })>>
<<else>>
<<timed 40ms>>
<<run setup.loading.dismiss()>>
<<replace '#results'>>You don't seem to have any custom spells. Would you like to <<link 'create one'>>
<<run setup.custom.dialog(true)>>
<</link>>?<</replace>>
<<notify>>No custom spells...<</notify>>
<</timed>>
<</if>>
<<set _filtered to clone($custom)>>
<</nobr>>@@#errors;@@
''Name:''
<<textbox '_name' ''>>
''Classes (check all that apply):''
* <label>Bard: <<checkbox '_bard' false true>></label>
* <label>Cleric: <<checkbox '_cleric' false true>></label>
* <label>Druid: <<checkbox '_druid' false true>></label>
* <label>Paladin: <<checkbox '_paladin' false true>></label>
* <label>Ranger: <<checkbox '_ranger' false true>></label>
* <label>Sorcerer: <<checkbox '_sorcerer' false true>></label>
* <label>Warlock: <<checkbox '_warlock' false true>></label>
* <label>Wizard: <<checkbox '_wizard' false true>></label>
''School:''
<<dropdown '_school' setup.schoolList.slice(1)>>
''Level:''
<<dropdown '_level' setup.levelList.slice(1)>>
''Components:''
* <label>Verbal: <<checkbox '_components[0]' false true>></label>
* <label>Somatic: <<checkbox '_components[1]' false true>></label>
* <label>Material: <<checkbox '_components[2]' false true>></label>
<label>Specific Material(s):
<<textbox '_components[3]' _pricedComp>></label>
<label>''Ritual:'' <<checkbox '_ritual' false true>></label>/% a confirm button for the custom spell edit dialog %/\
''Description:''
<<textarea '_descr' ''>>
<span class='w100-link' id='enter-link'><<button 'Confirm'>>
<<pushClasses 'bard'>>
<<pushClasses 'cleric'>>
<<pushClasses 'druid'>>
<<pushClasses 'paladin'>>
<<pushClasses 'ranger'>>
<<pushClasses 'sorcerer'>>
<<pushClasses 'warlock'>>
<<pushClasses 'wizard'>>
<<pushComponents>>
<<set _spellInfo to {
casting_time: _action,
classes : _classes,
comp : _comp,
description: _descr,
duration: _duration,
level : _level,
name : _name,
range : _range,
ritual : _ritual,
school : _school
}>>
<<checkCustomSpellInfo _spellInfo>>
<<if _errors.length > 0>>
<<addclass '#errors' 'open'>>
<<replace '#errors'>>\
_reportErrors\
<</replace>>
<<else>>
<<if _customEdit>>
<<run setup.custom.replaceSpell(_customEdit, setup.custom.createNoAdd(_spellInfo))>>
<<run notify('Custom spell created.')>>
<<else>>
<<run setup.custom.create(_spellInfo)>>
<<run notify('Custom spell created.')>>
<<run Engine.play('Custom')>>
<</if>>
<<run Dialog.close()>>
<</if>>
<</button>></span><div id='top-bar'>
<div id='ui-buttons'>
<span id='ui-all' class='ui-button load-list'>
<span class='icon'></span>
</span>
<span id='ui-lists' class='ui-button load-list'>
<span class='icon'></span>
</span>
<span id='ui-custom' class='ui-button load-list'>
<span class='icon'></span>
</span>
<span id='ui-about' class='ui-core'>
<span class='icon'></span>
</span>
<span id='ui-settings' class='ui-core'>
<span class='icon'></span>
</span>
<span id='ui-data' class='ui-core'>
<span class='icon'></span>
</span>
<!-- <span id='ui-restart' class='ui-core'>
<span class='icon'></span>
</span> -->
</div>
</div>
<div id='caption' tabindex='0' role='button'>
<div id='caption-text'></div>
<div id='caption-link'></div>
</div>
<div id='story'>
<div id='passages'>
</div>
</div>
<div id='bottom-bar'></div>
<!--<div id='bumper'>
<span id='bumper-body'>
</div>--><<set $ctx to ''>>\
<<resetFilters>>\
<<set setup.results to spells.get.sort(spells.list.concat($custom))>>\
<<set _filtered to null>>\
/% additional info (like book name if applicable)%/\
<span id='menu-heading'>@@#menu-icon;[img[assets/images/all.svg]]@@ @@#menu-text;$listName@@</span>
<span id='results'></span>
<<nobr>>
<<if setup.results.length > 0>>
<<run spells.render.load(false, '#results', function () { return spells.render.listAll(setup.results); })>>
<<else>>
<<timed 40ms>>
<<run setup.loading.dismiss()>>
<<replace '#results'>>No spells match the criteria.<</replace>>
<</timed>>
<</if>>
<</nobr>><<set _list to SpellList.getByName($ctx)>>\
<<resetFilters>>\
<<set _filtered to null>>\
/% additional info (like book name if applicable)%/\
<span id='menu-heading'>@@#menu-icon;[img[assets/images/all.svg]]@@ @@#menu-text;$listName@@</span>
<span id='results'></span>
<<nobr>>
<<if _list.spells && _list.spells.length > 0>>
<<run spells.render.load(false, '#results', function () { return _list.renderList(); })>>
<<else>>
<<timed 40ms>>
<<run setup.loading.dismiss()>>
<<replace '#results'>>No spells match the criteria.<</replace>>
<</timed>>
<</if>>
<</nobr>>''Custom Spells'':
<<dropdown '_customSpells' `['include', 'exclude', 'only']`>>
''Class:''
<<dropdown '_class' setup.classList>>
''Level:''
<<dropdown '_level' setup.levelList>>
''School:''
<<dropdown '_school' setup.schoolList>>
<span class='w100-link-special'><<message 'Advanced Filters' btn>>
''Exclude Components:''
* <label>Verbal: <<checkbox '_components[0]' false true checked>></label>
* <label>Somatic: <<checkbox '_components[1]' false true checked>></label>
* <label>Material: <<checkbox '_components[2]' false true checked>></label>
* <label>Priced Materials: <<checkbox '_components[3]' false true checked>></label>
''Casting Time:''
<<dropdown '_action' setup.actionList>>
''Range:''
<<dropdown '_range' setup.rangeList>>
''Duration:''
<<dropdown '_duration' setup.durationList>>
''Ritual:''
* <label>Only Rituals: <<checkbox '_ritual' false true>></label>
<hr />\
<</message>></span>
<span id='space-link' class='w100-link'><<button 'Confirm'>>
/% reset search term cache %/
<<set _termFiltered to false>>
<<set _selectedSpells to []>>
<<run $(document).trigger(':select-spell')>>
<<if $ctx>>
<<set _filtered to clone(_list.spells)>>
<<else>>
<<set _filtered to clone(setup.results)>>
<</if>>
<<set _level to setup.levelList.indexOf(_level) - 1>>
<<if _level >= 0>>
<<set _filtered to spells.get.byLevel(_level, _filtered)>>
<</if>>
<<if _customSpells && _customSpells !== 'include'>>
<<set _filtered to spells.get.byCustom(_customSpells, _filtered)>>
<</if>>
<<if _class !== 'any'>>
<<set _filtered to spells.get.byTag(_class, _filtered)>>
<</if>>
<<if _school !== 'any'>>
<<set _filtered to spells.get.bySchool(_school, _filtered)>>
<</if>>
<<if _components.includes(false)>>
<<set _filtered to spells.get.byComponent(_components, _filtered)>>
<</if>>
<<if _action !== 'any'>>
<<set _filtered to spells.get.byCastingTime(_action, _filtered)>>
<</if>>
<<if _range !== 'any'>>
<<set _filtered to spells.get.byRange(_range, _filtered)>>
<</if>>
<<if _duration !== 'any'>>
<<set _filtered to spells.get.byDuration(_duration, _filtered)>>
<</if>>
<<if _ritual>>
<<set _filtered to spells.get.byRitual(_filtered)>>
<</if>>
<<if _filtered.length < 1>>
<<notify>>No spells match the criteria.<</notify>>
<<replace '#results'>>No spells match the criteria.<</replace>>
<<else>>
<<if $ctx>>
<<run spells.render.load(true, '#results', function () { return spells.render.listAll(_filtered, _list); })>>
<<else>>
<<run spells.render.load(true, '#results', function () { return spells.render.listAll(_filtered); })>>
<</if>>
<</if>>
<<run Dialog.close()>>
<</button>></span><span id='menu-heading'>@@#menu-icon;[img[assets/images/lists.svg]]@@ @@#menu-text;Spell Books@@</span>
<span id='book-list'></span><<nobr>>
<<if $listOfLists && $listOfLists.length>>
<<run SpellList.render('#book-list', $lists)>>
<<else>>
<<timed 40ms>>
<<run setup.loading.dismiss()>>
<<replace '#book-list'>>You don't seem to have any spellbooks. Would you like to <<link 'create one'>>
<<set _spellToAdd to false, _bookToEdit to false>>
<<popup 'Edit' 'New SpellBook' 'new-book'>>
<</link>>?<</replace>>
<<notify>>No spellbooks...<</notify>>
<</timed>>
<</if>>
<<set _filtered to clone($custom)>>
<</nobr>><<nobr>>
<<set _defaults to { name : '', tags : '' },
_setName to '', _setTags to ''>>
<<if _bookToEdit>>
''Rename Your Spellbook''
<<set _inst to SpellList.getByName(_bookToEdit)>>
<<set _defaults to { name : _inst.name, tags : _inst.tags.join(' ') }>>
<<else>>
''Name Your New Spellbook''
<</if>>
<</nobr>>
Name:
<<textbox '_setName' _defaults.name autofocus>>
Tags:
<<textarea '_setTags' _defaults.tags>>
<span id='errorsBook'></span>
<span id='enter-link' class='confirm-button'><<button 'Confirm'>>
<<set _setName to _setName.trim()>>
<<if !_setName>>
<<replace '#errorsBook'>>//Please enter a name.//<</replace>>
<<elseif _bookToEdit && _setName !== _inst.name && $listOfLists.includes(_setName)>>
<<replace '#errorsBook'>>//That name is already in use.//<</replace>>
<<elseif !_bookToEdit && $listOfLists.includes(_setName)>>
<<replace '#errorsBook'>>//That name is already in use.//<</replace>>
<<else>>
<<if _setTags && _setTags.trim()>>
<<set _setTags to _setTags.trim().split(' ')>>
<<else>>
<<set _setTags to []>>
<</if>>
<<if _bookToEdit>>
<<run _inst.rename(_setName, _setTags)>>
<<else>>
<<if _spellToAdd>>
<<if Array.isArray(_spellToAdd)>>
<<set _spell to _spellToAdd>>
<<else>>
<<set _spell to [_spellToAdd]>>
<</if>>
<<else>>
<<set _spell to []>>
<</if>>
<<run SpellList.add(_setName, _setTags, _spell)>>
<</if>>
<<run Dialog.close()>>
<<if passage() === 'Lists' || passage() === 'Start'>>
/% no need to force refresh in spell lists %/
<<run Engine.play(passage())>>
<<elseif passage() === 'Import'>>
<<run Engine.play('Lists')>>
<</if>>
<</if>>
<</button>></span>
<<if _bookToEdit>>\
<span id='delete-button'><<button `'Delete [' + _defaults.name + ']'`>>
<<dialog 'Are you sure?'>>
Are you sure you want to delete <<= _defaults.name>>?
@@float:left;<<button 'Yes'>>
<<run SpellList.del(_bookToEdit)>>
<<run Dialog.close()>>
<<run Engine.play(passage())>>
<</button>>@@@@float:right;<<button 'No'>>
<<popup 'Edit' 'Edit Spellbook' 'edit-book'>>
<</button>>@@
<</dialog>>
<</button>></span>\
<</if>><span id='menu-heading'>@@#menu-icon;[img[assets/images/search.svg]]@@ @@#menu-text;Filter Spells@@</span>
<<nobr>>
<<set _term to ''>>
<<set _classList to ['any', 'bard', 'cleric', 'druid', 'paladin', 'ranger', 'sorcerer', 'warlock', 'wizard']>>
<<set _class to 'any'>>
<<set _levelList to ['any', 'cantrip', '1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th']>>
<<set _level to 'any'>>
<<set _componentList to ['verbal', 'somatic', 'material']>>
<<set _components to [true, true, true]>>
<<set _schoolList to ['any', 'abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation']>>
<<set _school to 'any'>>
<<set _ritual to false>>
<</nobr>>\
<center>/%
''Spell name:''
<<textbox '_term' ''>>
%/''Class:''
<<dropdown '_class' _classList>>
''Level:''
<<dropdown '_level' _levelList>>
/% ''Exclude Components:''
* <label>Verbal: <<checkbox '_components[0]' false true checked>></label>
* <label>Somatic: <<checkbox '_components[1]' false true checked>></label>
* <label>Material: <<checkbox '_components[2]' false true checked>></label>
%/ ''School:''
<<dropdown '_school' _schoolList>>
''Ritual:''
* <label>Only Rituals: <<checkbox '_ritual' false true>></label>
<span class='big-link' id='space-link'><<button 'Go' 'Results'>>
<<set $results to clone(spells.list)>>
<<set _level to _levelList.indexOf(_level) - 1>>
<<if _term>>
<<set $results to spells.get.byName(_term, $results)>>
<</if>>
<<if _class !== 'any'>>
<<set $results to spells.get.byTag(_class, $results)>>
<</if>>
<<if _level >= 0>>
<<set $results to spells.get.byLevel(_level, $results)>>
<</if>>
<<if _components.includes(false)>>
<<set _comp to []>>
<<for _i to 0; _i < _componentList.length; _i++>>
<<if _components[_i]>>
<<run _comp.push(_componentList[_i])>>
<</if>>
<</for>>
<<if _comp.length > 0>>
<<set $results to spells.get.byComponent(_comp, $results)>>
<</if>>
<</if>>
<<if _school !== 'any'>>
<<set $results to spells.get.bySchool(_school, $results)>>
<</if>>
<<if _ritual>>
<<set $results to spells.get.byRitual($results)>>
<</if>>
<</button>></span>
</center>What would you like to do?
<span class='confirm-button'><<button [[Export]]>><<run Dialog.close()>><</button>>
<<button [[Import]]>><<run Dialog.close()>><</button>></span><span id='menu-heading'>@@#menu-icon;[img[assets/images/data.svg]]@@ @@#menu-text;Export Spellbooks@@</span>
<center><<set _export to ''>>\
<<if $lists.length > 0>>\
''Select a spellbook:''
<<dropdown '_export' $listOfLists>>
<span class='big-link'><<button 'Export To File'>>
<<run setup.share.save(_export, SpellList.getByName(_export).exportList())>>
<</button>></span>
''-OR-''
<span class='big-link'><<button 'Export As Text'>>
<<run $('#share-output').empty().append(SpellList.getByName(_export).exportList())>>
<</button>></span>
''Share text:''
<textarea id='share-output' readonly></textarea>
<span class='big-link'><<button 'Select All'>>
<<run $('#share-output').select()>>
<</button>>
<<button 'Copy to Clipboard'>>
<<run $('#share-output').select()>>
<<run document.execCommand('copy')>>
<</button>></span>
<<else>>\
''You have no spell books to export.''
<</if>></center><span id='menu-heading'>@@#menu-icon;[img[assets/images/data.svg]]@@ @@#menu-text;Import Spellbooks@@</span>
<center>
<label for="file-upload" class="upload-file">\
Import From File\
</label><input id="file-upload" type="file" />
''-OR-''
''Enter a spellbook's share text:''
<textarea id='share-input'></textarea>
<span class='big-link'><<button 'Import From Text'>>
<<script>>
var data = $('#share-input').val(),
list = SpellList.importList(data);
State.temporary.bookToEdit = list.name;
State.temporary.spellToAdd = false;
Dialog.setup('Edit Spellbook', 'edit-book');
Dialog.wiki(Story.get('Edit').text);
Dialog.open();
<</script>>
<</button>></span>
</center><center>
<div class="sk-fading-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</center><<widget 'resetFilters'>>\
<<silently>>
<<set _customSpells to 'include'>>
<<set _class to 'any'>>
<<set _level to 'any'>>
<<set _school to 'any'>>
<<set _components to [true, true, true]>>
<<set _action to 'any'>>
<<set _range to 'any'>>
<<set _duration to 'any'>>
<<set _ritual to false>>
<</silently>>\
<</widget>>
<<widget 'pushClasses'>>\
<<silently>>
<<set _class to $args[0]>>
<<if State.temporary[_class]>>
<<run fast.push(_classes, _class)>>
<</if>>
<</silently>>\
<</widget>>
<<widget 'pushComponents'>>\
<<silently>>
<<set _priced to spells.get.cleanText(_components[3])>>
<<set _comp to { array : [], mat : '' }>>
<<if _components[0]>>
<<run fast.push(_comp.array, 'v')>>
<</if>>
<<if _components[1]>>
<<run fast.push(_comp.array, 's')>>
<</if>>
<<if _components[2]>>
<<run fast.push(_comp.array, 'm')>>
<</if>>
<<if _priced>>
<<set _comp.mat to _priced.toUpperFirst()>>
<</if>>
<</silently>>\
<</widget>>
<<widget 'checkCustomSpellInfo'>>\
<<silently>>
<<set _errors to []>>
<<set _info to $args[0]>>
<<if !_info.name || !spells.get.cleanText(_info.name)>>
<<run fast.push(_errors, 'No name.')>>
<</if>>
<<set _checkName to spells.get.cleanText(_info.name)>>
<<set _check to [
fast.find(spells.list, function (spell) { return spells.get.cleanText(spell.name) === _checkName }),
fast.find($custom, function (spell) { return spells.get.cleanText(spell.name) === _checkName })
]>>
<<run console.log(_check)>>
<<if _check[0]>>
<<run fast.push(_errors, 'Name taken.')>>
<</if>>
<<if _check[1] && (!_customEdit || spells.get.cleanText(_customEdit.name) !== _checkName)>>
<<run fast.push(_errors, 'Name taken.')>>
<</if>>
<<if _info.classes.length < 1>>
<<run fast.push(_errors, 'No classes selected.')>>
<</if>>
<<if !_info.description || !spells.get.cleanText(_info.description)>>
<<run fast.push(_errors, 'No description.')>>
<</if>>
<<if !_info.casting_time || !spells.get.cleanText(_info.casting_time)>>
<<run fast.push(_errors, 'No casting time.')>>
<</if>>
<<if !_info.duration || !spells.get.cleanText(_info.duration)>>
<<run fast.push(_errors, 'No duration.')>>
<</if>>
<<if !_info.range || !spells.get.cleanText(_info.range)>>
<<run fast.push(_errors, 'No range.')>>
<</if>>
<<set _reportErrors to "''Some information was not provided:''\n\n" + _errors.join('\n')>>
<</silently>>\
<</widget>>